Big bang
This commit is contained in:
commit
cc5a1154be
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.idea*
|
80
03-starting-out.hs
Normal file
80
03-starting-out.hs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
|
||||||
|
doubleMe x = x + x
|
||||||
|
|
||||||
|
doubleUs x y = (doubleMe x) + (doubleMe y)
|
||||||
|
|
||||||
|
doubleSmallNumber x = if x > 100
|
||||||
|
then x
|
||||||
|
else (x * 2)
|
||||||
|
|
||||||
|
doubleSmallNumber' x = (doubleSmallNumber x) + 1
|
||||||
|
|
||||||
|
listConcatNums = [1,2,3,4] ++ [9,10,11,12]
|
||||||
|
|
||||||
|
-- Strings are lists of chars
|
||||||
|
listConcatChars = "hello" ++ " " ++ "world"
|
||||||
|
|
||||||
|
-- Cons prepends a value to a list:
|
||||||
|
listPrependNum xs = 5:xs
|
||||||
|
|
||||||
|
listPrependA xs = 'A':xs
|
||||||
|
|
||||||
|
-- Access item at list index:
|
||||||
|
listFirstValue xs = xs !! 0
|
||||||
|
|
||||||
|
-- head, tail, last, init are useful built-ins for list operations
|
||||||
|
-- head - get first element
|
||||||
|
-- tail - get everything except first element
|
||||||
|
-- last - get last element
|
||||||
|
-- init - get everything except last element
|
||||||
|
-- length - get length of list
|
||||||
|
-- null - check if list is empty
|
||||||
|
-- reverse - reverses a list
|
||||||
|
-- take - get the first n elements of a list, if possible
|
||||||
|
-- drop - drop the first n elements of a list, if possible
|
||||||
|
-- maximum - get largest element in list
|
||||||
|
-- minimum - get smallest element in list
|
||||||
|
-- sum - add up all numbers in list
|
||||||
|
-- product - product of all numbers in list
|
||||||
|
-- elem - check if an item appears in a list: 4 `elem` [3,4,5,6] == True
|
||||||
|
|
||||||
|
-- Texas ranges:
|
||||||
|
getRangeThru n = [1..n]
|
||||||
|
|
||||||
|
-- getRangeThru 4 == [1,2,3,4]
|
||||||
|
|
||||||
|
getEvensThru n = [2,4..n]
|
||||||
|
|
||||||
|
-- getEvensThru 8 == [2,4,6,8]
|
||||||
|
|
||||||
|
-- Can use infinite ranges. e.g., get first 10 multiples of 13:
|
||||||
|
first10of13 = take 10 [13,26,..]
|
||||||
|
|
||||||
|
-- cycle - cycles a list infinitely - e.g. cycle [1,2,3] == [1,2,3,1,2,3,..]
|
||||||
|
-- repeat - takes an element and produces an infinite list - e.g. repeat 5 == [5,5..]
|
||||||
|
-- replicate - get a list of some item n times - e.g. replicate 3 10 == [10,10,10]
|
||||||
|
|
||||||
|
-- List comprehension, like set comprehension in math:
|
||||||
|
first10Evens = [x*2 | x <- [1..10]] -- == [2,4,6,8,10,12,14,16,18,20]
|
||||||
|
|
||||||
|
-- We can constrain x further using predicates:
|
||||||
|
first10EvensWhoseDoublesAreGreaterThan12 = [x*2 | x <- [1..10], x*2 >= 12] -- == [12,14,16,18,20]
|
||||||
|
|
||||||
|
-- Say, e.g., we want to filter a list for odd numbers only, replacing those less than 10 with BOOM, else BANG
|
||||||
|
boomBangs xs = [ if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x ]
|
||||||
|
|
||||||
|
-- Can have multiple predicates, and multiple domains:
|
||||||
|
multiRange = [ x*y | x <- [1..3], y <- [2..4] ] -- result will have length 9
|
||||||
|
|
||||||
|
-- Since strings are lists, we can use list comprehension to work on them, e.g.
|
||||||
|
removeNonUppercase st = [ c | c <- st, c `elem` ['A'..'Z']]
|
||||||
|
|
||||||
|
-- Tuples
|
||||||
|
-- fst - get the first item in a 2-tuple
|
||||||
|
-- snd - get the second item in a 2-tuple
|
||||||
|
|
||||||
|
-- zip - create pairs from 2 lists:
|
||||||
|
zipTest1 = zip [1..3] ["one", "two", "three"] -- == [(1,"one"),(2,"two"),(3,"three")]
|
||||||
|
|
||||||
|
-- zip clips to the shortest list:
|
||||||
|
zipTest2 = zip [1..] ["one", "two"] -- == [(1,"one"),(2,"two")]
|
86
04-types-and-typeclasses.hs
Normal file
86
04-types-and-typeclasses.hs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
-- in GHCi, we can use :t to get the type of something
|
||||||
|
|
||||||
|
-- :t "Hello!"
|
||||||
|
"Hello!" :: [Char]
|
||||||
|
|
||||||
|
-- It's good practice to give functions type declarations
|
||||||
|
|
||||||
|
removeNonUppercase :: String -> String
|
||||||
|
removeNonUppercase st = [c | c <- st, c `elem` ['A'..'Z']]
|
||||||
|
|
||||||
|
addThree :: Int -> Int -> Int -> Int
|
||||||
|
addThree x y z = x + y + z
|
||||||
|
|
||||||
|
-- Common types:
|
||||||
|
-- Int - normal integer
|
||||||
|
-- Integer - big integer, less efficient
|
||||||
|
-- Float - real floating point, single precision
|
||||||
|
-- Double - real floating point, double precision
|
||||||
|
-- Bool - True or False
|
||||||
|
-- Char - single character
|
||||||
|
|
||||||
|
-- Type variables - write polymorphic functions that don't use the type-specific
|
||||||
|
-- features of the values passed in:
|
||||||
|
|
||||||
|
-- :t fst
|
||||||
|
fst :: (a,b) -> a
|
||||||
|
|
||||||
|
-- :t head
|
||||||
|
head :: [a] -> a
|
||||||
|
|
||||||
|
-- `a` is the type-variable here
|
||||||
|
|
||||||
|
-- => denotes a class constraint. e.g. :t (==)
|
||||||
|
(==) :: (Eq a) => a -> a -> Bool
|
||||||
|
|
||||||
|
-- This says (==) takes a and a to Bool, given that the a's are members of the Eq class
|
||||||
|
-- Another example:
|
||||||
|
|
||||||
|
-- :t elem
|
||||||
|
elem :: (Eq a) => a -> [a] -> Bool
|
||||||
|
|
||||||
|
-- Eq - classes support equality testing
|
||||||
|
-- Ord - types have an ordering
|
||||||
|
|
||||||
|
-- e.g. :t (>)
|
||||||
|
(>) :: (Ord a) => a -> a -> Bool
|
||||||
|
|
||||||
|
-- To be a member of Ord, you must be a member of Eq and have defined behavior for the `compare` function.
|
||||||
|
|
||||||
|
-- Show - members can be presented as strings
|
||||||
|
-- Read - opposite of show - takes a string and returns a type that is a member of Read
|
||||||
|
|
||||||
|
-- e.g.
|
||||||
|
restTest1 = read "[1,2,3,4]" ++ [3] -- == [1,2,3,4,3]
|
||||||
|
|
||||||
|
-- The way the result of `read` is used determines what typeclass the value is instanced in
|
||||||
|
-- :t read
|
||||||
|
read :: (Read a) => String -> a
|
||||||
|
|
||||||
|
-- the `a` variable is determined by the usage. We can be explicit:
|
||||||
|
readTest2 = read "5" :: Int -- a is Int
|
||||||
|
|
||||||
|
-- Enum - sequential types that can be enumerated - Enum types can be used in list ranges
|
||||||
|
-- Bounded - have upper and lower bounds
|
||||||
|
|
||||||
|
-- e.g.
|
||||||
|
minBoundTest1 = minBound :: Int
|
||||||
|
maxBoundTest1 = maxBound :: Char
|
||||||
|
|
||||||
|
-- these have type (Bounded a) => a - essentially polymorphic constants
|
||||||
|
-- the value changes depending on the typecast
|
||||||
|
|
||||||
|
-- Num - members can act like numbers, e.g.
|
||||||
|
|
||||||
|
-- :t 20
|
||||||
|
20 :: (Num t) => t
|
||||||
|
|
||||||
|
-- :t (*)
|
||||||
|
(*) :: (Num a) => a -> a -> a
|
||||||
|
|
||||||
|
-- Integral - only whole numbers - superset including Int and Integer
|
||||||
|
-- Floating - only floating point numbers - Float and Double
|
||||||
|
|
||||||
|
-- Useful note: fromIntegral takes an Integral to a generic Num type
|
||||||
|
-- :t fromIntegral
|
||||||
|
fromIntegral :: (Num b, Integral a) => a -> b
|
124
05-syntax-in-functions.hs
Normal file
124
05-syntax-in-functions.hs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
-- Pattern matching - different function bodies for different destructured type matches
|
||||||
|
|
||||||
|
lucky :: (Integral a) => a -> String
|
||||||
|
lucky 7 = "Lucky number seven!"
|
||||||
|
lucky _ = "Sorry, out of luck..."
|
||||||
|
|
||||||
|
-- This also makes recursion pretty nice
|
||||||
|
|
||||||
|
factorial :: (Integral a) => a -> a
|
||||||
|
factorial 0 = 1
|
||||||
|
factorial x => x * (factorial (x-1))
|
||||||
|
|
||||||
|
-- When making functions, always include a catch-all pattern match
|
||||||
|
|
||||||
|
-- We can use pattern patching to do destructuring. e.g.
|
||||||
|
|
||||||
|
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)
|
||||||
|
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
|
||||||
|
|
||||||
|
-- By the way, can also pattern match in list comprehensions
|
||||||
|
|
||||||
|
listComprehensionTest1 = [ a+b | (a,b) <- [(1,3),(2,4)] ]
|
||||||
|
|
||||||
|
-- Here, we note that if a pattern match fails, it will be skipped and move on to the next element!
|
||||||
|
|
||||||
|
-- Implement head ourselves:
|
||||||
|
head :: [a] -> a
|
||||||
|
head [] = error "Empty list has no head"
|
||||||
|
head (x:_) = x
|
||||||
|
|
||||||
|
-- We can use cons to destructure multiple elements:
|
||||||
|
firstSum :: (Num a) -> [a] -> a
|
||||||
|
firstSum [] = 0
|
||||||
|
firstSum (x:[]) = x
|
||||||
|
firstSum (x:y:[]) = x + y
|
||||||
|
firstSum (x:y:z:_) = x + y + z
|
||||||
|
|
||||||
|
-- What about length?
|
||||||
|
length :: (Num b) => [a] -> b
|
||||||
|
length [] = 0
|
||||||
|
length (x:xs) = 1 + (length xs)
|
||||||
|
|
||||||
|
-- Now, sum:
|
||||||
|
sum :: (Num a) => [a] -> a
|
||||||
|
sum [] = 0
|
||||||
|
sum (x:xs) = 1 + (sum xs)
|
||||||
|
|
||||||
|
-- We can also name patterns:
|
||||||
|
emptyUnless2OrMore :: [a] -> [a]
|
||||||
|
emptyUnless2OrMore [] = []
|
||||||
|
emptyUnless2OrMore (x:[]) = []
|
||||||
|
emptyUnless2OrMore list@(x:y:_) = list
|
||||||
|
|
||||||
|
-- Guards are a flow-through boolean alternation
|
||||||
|
bmiTell :: (Floating a) => a -> String
|
||||||
|
bmiTell bmi
|
||||||
|
| bmi <= 18.5 = "underweight"
|
||||||
|
| bmi <= 25.0 = "normal"
|
||||||
|
| bmi <= 30.0 = "overweight"
|
||||||
|
| otherwise = "obese"
|
||||||
|
|
||||||
|
-- Interesting: implementation of max
|
||||||
|
max :: (Ord a) => a -> a -> a
|
||||||
|
max a b
|
||||||
|
| a > b = a
|
||||||
|
| otherwise = b
|
||||||
|
|
||||||
|
-- Implementation of compare
|
||||||
|
myCompare :: (Ord a) -> a -> a -> Ordering
|
||||||
|
a `myCompare` b -- functions can also be defined using infix-syntax
|
||||||
|
| a < b = LT
|
||||||
|
| a == b = EQ
|
||||||
|
| otherwise = GT
|
||||||
|
|
||||||
|
-- We can use `where` to define local variables for a function block:
|
||||||
|
bmiTell :: (Floating a) => a -> a -> String
|
||||||
|
bmiTell weight height
|
||||||
|
| bmi <= skinny = "underweight"
|
||||||
|
| bmi <= normal = "normal"
|
||||||
|
| bmi <= fat = "overweight"
|
||||||
|
| otherwise = "obese"
|
||||||
|
where bmi = weight / height ^ 2
|
||||||
|
skinny = 18.5
|
||||||
|
normal = 25.0
|
||||||
|
fat = 30.0
|
||||||
|
|
||||||
|
-- the variables in `where` need to be indented at the same level so Haskell knows how to scope them correctly
|
||||||
|
-- we can also pattern-match in `where`:
|
||||||
|
|
||||||
|
initials :: String -> String -> String
|
||||||
|
initials first last = [f] ++ ". " ++ [l] ++ "."
|
||||||
|
where (f:_) = first
|
||||||
|
(l:_) = last
|
||||||
|
|
||||||
|
-- We can also define local functions in the where-body
|
||||||
|
calcBmis :: (Floating a) => [(a,a)] -> [a]
|
||||||
|
calcBmis xs = [bmi w h | (w,h) <- xs]
|
||||||
|
where bmi weight height = weight / height ^ 2
|
||||||
|
|
||||||
|
-- We can also use `let`, which is an expression, not a syntactic construct:
|
||||||
|
cylinder :: (Floating a) => a -> a -> a
|
||||||
|
cylinder r h = let sideArea = 2 * pi * r * h in
|
||||||
|
let topArea = pi * r ^ 2 in
|
||||||
|
sideArea + 2 * topArea
|
||||||
|
|
||||||
|
-- Because it's an expression, it can be used to shorten inline code:
|
||||||
|
squaresTest1 = [let square x = x * x in (square 5, square 3, square 2)]
|
||||||
|
|
||||||
|
-- let is good for quickly destructuring a type structure inline:
|
||||||
|
tupleTest1 = (let (a,b,c) = (1,2,3) in a+b+c) * 100 -- == 600
|
||||||
|
|
||||||
|
-- Case statements
|
||||||
|
-- Turns out, pattern matching function bodies is just syntactic sugar for case statements!
|
||||||
|
|
||||||
|
head :: [a] -> a
|
||||||
|
head [] = error "No head of empty list"
|
||||||
|
head (x:_) = x
|
||||||
|
|
||||||
|
-- Is equivalent to:
|
||||||
|
head :: [a] -> a
|
||||||
|
head xs = case xs of
|
||||||
|
[] -> error "No head of empty list"
|
||||||
|
(x:_) -> x
|
||||||
|
|
42
06-recursion.hs
Normal file
42
06-recursion.hs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
-- Example: maximum in list
|
||||||
|
maximum :: (Ord a) -> [a] -> a
|
||||||
|
maximum [] = error "maximum of empty list"
|
||||||
|
maximum [x] = x
|
||||||
|
maximum (x:xs) = max x (maximum xs)
|
||||||
|
|
||||||
|
-- Another example, replicate
|
||||||
|
replicate (Num i, Ord i) => i -> a -> [a]
|
||||||
|
replicate n x
|
||||||
|
| n <= 0 = []
|
||||||
|
| otherwise = x:(replicate (n-1) x)
|
||||||
|
|
||||||
|
-- Now, take
|
||||||
|
take :: (Num i, Ord i) => i -> [a] -> [a]
|
||||||
|
take n _
|
||||||
|
| n <= 0 = [] -- if n is 0 or less, empty list
|
||||||
|
-- if guard is non-exhaustive, matching falls through to next pattern
|
||||||
|
take _ [] = []
|
||||||
|
take n (x:xs) = x:(take (n-1) xs)
|
||||||
|
|
||||||
|
reverse :: [a] -> [a]
|
||||||
|
reverse [] = []
|
||||||
|
reverse (x:xs) = (reverse xs) ++ [x]
|
||||||
|
|
||||||
|
zip :: [a] -> [b] -> [(a,b)]
|
||||||
|
zip _ [] = []
|
||||||
|
zip [] _ = []
|
||||||
|
zip (x:xs) (y:ys) = (x,y):(zip xs, ys)
|
||||||
|
|
||||||
|
elem :: (Eq a) => a -> [a] -> Bool
|
||||||
|
elem _ [] = False
|
||||||
|
elem a (x:xs)
|
||||||
|
| a == x = True
|
||||||
|
| otherwise = a `elem` xs
|
||||||
|
|
||||||
|
-- Double recursion!
|
||||||
|
quicksort :: (Ord a) => [a] -> [a]
|
||||||
|
quicksort [] = []
|
||||||
|
quicksort (x:xs) = let smallerSorted = quicksort [ a | a <- xs, a <= x ] in
|
||||||
|
let biggerSorted = quicksort [ a | a <- xs, a > x ] in
|
||||||
|
smallerSorted ++ [x] ++ biggerSorted
|
120
07-higher-order-functions.hs
Normal file
120
07-higher-order-functions.hs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
-- Partial application is awesome, since haskell functions are curried:
|
||||||
|
compareWithHundred :: (Num a, Ord a) => a -> Ordering
|
||||||
|
compareWithHundred x = compare 100 x
|
||||||
|
|
||||||
|
-- this is equivalent to:
|
||||||
|
compareWithHundred :: (Num a, Ord a) => a -> Ordering
|
||||||
|
compareWithHundred = compare 100
|
||||||
|
|
||||||
|
-- Infix functions can also be partially-applied:
|
||||||
|
divideByTen :: (Floating a) => a -> a
|
||||||
|
divideByTen = (/10)
|
||||||
|
|
||||||
|
-- (/10) 200 == 200 / 10
|
||||||
|
|
||||||
|
isUpperAlphanum :: Char -> Bool
|
||||||
|
isUpperAlphanum = (`elem` ['A'..'Z'])
|
||||||
|
|
||||||
|
-- Functions can take functions as params:
|
||||||
|
applyTwice :: (a -> a) -> a -> a
|
||||||
|
applyTwice f x = f (f x)
|
||||||
|
|
||||||
|
-- Example implementation of zipWith:
|
||||||
|
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
|
||||||
|
zipWith _ [] _ = []
|
||||||
|
zipWith _ _ [] = []
|
||||||
|
zipWith f (x:xs) (y:ys) = (f x y):(zipWith f xs ys)
|
||||||
|
|
||||||
|
flip :: (a -> b -> c) -> b -> a -> c
|
||||||
|
flip f x y = f y x
|
||||||
|
|
||||||
|
-- map built-in:
|
||||||
|
map (a -> b) -> [a] -> [b]
|
||||||
|
map _ [] = []
|
||||||
|
map f (x:xs) = (f x):(map f xs)
|
||||||
|
|
||||||
|
-- filter built-in:
|
||||||
|
filter (a -> Bool) -> [a] -> [a]
|
||||||
|
filter _ [] = []
|
||||||
|
filter f (x:xs)
|
||||||
|
| f x = x:(filter xs)
|
||||||
|
| otherwise = filter xs
|
||||||
|
|
||||||
|
-- Largest # under 100000 divisible by 3829:
|
||||||
|
-- (this stops after finding the first matching value, because of lazy evaluation)
|
||||||
|
largestDivisible :: (Integral a) => a
|
||||||
|
largestDivisible = head (filter p [100000,99999,..])
|
||||||
|
where p x = x `mod` 3829 == 0
|
||||||
|
|
||||||
|
takeWhile :: (a -> Bool) -> [a] -> [a]
|
||||||
|
takeWhile _ [] = []
|
||||||
|
takeWhile p (x:xs)
|
||||||
|
| p x = x:(takeWhile p xs)
|
||||||
|
| otherwise = []
|
||||||
|
|
||||||
|
-- Collatz sequence builder
|
||||||
|
chain :: (Integral a) => a -> [a]
|
||||||
|
chain 1 = [1]
|
||||||
|
chain n
|
||||||
|
| even n = n:(chain (n `div` 2))
|
||||||
|
| odd n = n:(chain (n*3 + 1))
|
||||||
|
|
||||||
|
numLongChains :: Int
|
||||||
|
numLongChains = length (filter isLong (map chain [1..100]))
|
||||||
|
where isLong xs = length xs > 15
|
||||||
|
|
||||||
|
-- Instead, with lambdas!
|
||||||
|
numLongChains :: Int
|
||||||
|
numLongChains = length (filter (\xs -> length xs > 15) (map chain [1..100]))
|
||||||
|
|
||||||
|
-- You can pattern-match in lambdas, but only on one case. If a value doesn't match the pattern,
|
||||||
|
-- you get a runtime error. So, make sure your pattern is exhaustive.
|
||||||
|
|
||||||
|
-- Folds - foldl - left fold
|
||||||
|
sum :: (Num a) => [a] -> a
|
||||||
|
sum xs = foldl (\acc x -> acc + x) 0 xs
|
||||||
|
|
||||||
|
-- Or, more succinctly with currying:
|
||||||
|
sum xs = foldl (+) 0 xs
|
||||||
|
|
||||||
|
-- Generally, because of currying, a function `foo a = bar b a` can be written as `foo = bar b`
|
||||||
|
-- Another fold example:
|
||||||
|
elem :: (Eq a) => a -> [a] -> Bool
|
||||||
|
elem y ys = foldl (\acc x -> if x == y then True else acc) False ys
|
||||||
|
|
||||||
|
-- Right folds `foldr` work similarly, but from the right side, and the acc x are reversed. e.g.
|
||||||
|
map :: (a -> b) -> [a] -> [b]
|
||||||
|
map f xs = foldr (\x acc -> (f x):acc) [] xs
|
||||||
|
|
||||||
|
-- We could have done this with `foldl` and `++`, but cons is much cheaper than `++`, so we generally
|
||||||
|
-- use `foldr` when building lists from lists.
|
||||||
|
|
||||||
|
-- `foldl1` and `foldr1` work the same as `foldl` and `foldr`, but they use the starting element in
|
||||||
|
-- the list as the initial accumulator (the left-most or right-most, respectively)
|
||||||
|
|
||||||
|
-- `scanl` and `scanr` are like their fold* counterparts, but return an array of all the states of the accumulator:
|
||||||
|
scanl1Test = scanl1 (\acc x -> if x > acc then x else acc) [3,4,5,3] -- == [3,4,5,5]
|
||||||
|
|
||||||
|
-- These have a `scanl1` and `scanr1` equivalent. The final result of `scanl*` is the last element, and the head
|
||||||
|
-- element for `scanr1`
|
||||||
|
|
||||||
|
-- Function application
|
||||||
|
-- Take:
|
||||||
|
($) :: (a -> b) -> a -> b
|
||||||
|
f $ x = f x
|
||||||
|
|
||||||
|
-- Right-associative function application!
|
||||||
|
sum (map sqrt (1 + 2 + 3)) == sum $ map $ sqrt $ 1 + 2 + 3
|
||||||
|
|
||||||
|
-- This also means we can use function application... as a function:
|
||||||
|
mapTest1 = map ($ 3) [(4+), (^2)] -- == [4 + 3, 3 ^ 2]
|
||||||
|
|
||||||
|
-- Also, function composition:
|
||||||
|
(.) :: (b -> c) -> (a -> b) -> a -> c
|
||||||
|
f . g = \x -> f (g x)
|
||||||
|
|
||||||
|
-- This means more concise code! TFAE
|
||||||
|
mapTest2 = map (\x -> negate $ abs x) [1..3]
|
||||||
|
mapTest3 = map (negate . abs) [1..3]
|
||||||
|
|
||||||
|
-- If we want to compose functions with multiple parameters, we need to partially apply them first.
|
26
08-modules.hs
Normal file
26
08-modules.hs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
-- Splat into global namespace
|
||||||
|
import Data.List
|
||||||
|
|
||||||
|
-- Splat specific items into global namespace
|
||||||
|
import Data.List (nub, sort)
|
||||||
|
|
||||||
|
-- Splat everything but specific items
|
||||||
|
import Data.List hiding (nub)
|
||||||
|
|
||||||
|
-- Import, but not in global namespace
|
||||||
|
import qualified Data.List
|
||||||
|
|
||||||
|
-- Import, but not in global namespace, but with alias
|
||||||
|
import qualified Data.List as L
|
||||||
|
|
||||||
|
-- In Geometry.hs
|
||||||
|
module Geometry (
|
||||||
|
sphereVolume,
|
||||||
|
cubeArea -- exported functions
|
||||||
|
) where
|
||||||
|
|
||||||
|
sphereVolume :: Float -> Float
|
||||||
|
sphereVolume radius = (4.0 / 3.0) * pi * (radius ^ 3)
|
||||||
|
|
||||||
|
cubeVolume :: Float -> Float
|
||||||
|
cubeVolume side = side * side * side
|
179
09-types-and-typeclasses-redux.hs
Normal file
179
09-types-and-typeclasses-redux.hs
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
-- data keyword defines a new data type. e.g. from prelude:
|
||||||
|
|
||||||
|
data Bool = False | True
|
||||||
|
|
||||||
|
data Shape =
|
||||||
|
Circle Float Float Float
|
||||||
|
| Rectangle Float Float Float Float
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
-- Value constructors are actually functions returning the data type
|
||||||
|
-- :t Circle
|
||||||
|
Circle :: Float -> Float -> Float -> Shape
|
||||||
|
|
||||||
|
surface :: Shape -> Float
|
||||||
|
surface (Circle _ _ r) = pi * r ^ 2
|
||||||
|
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)
|
||||||
|
|
||||||
|
-- Note the signature. We can't do `surface :: Circle -> Float` because `Circle` is NOT a type, `Shape` is.
|
||||||
|
-- Because value constructors are functions, we can partially apply them!
|
||||||
|
|
||||||
|
map (Circle 10 20) [4,5] -- == [Circle 10 20 4, Circle 10 20 5]
|
||||||
|
|
||||||
|
-- Let's make the data type more clear
|
||||||
|
data Point =
|
||||||
|
Point Float Float
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data Shape =
|
||||||
|
Circle Point Float
|
||||||
|
| Rectangle Point Point
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
-- now, we pattern match:
|
||||||
|
surface :: Shape -> Float
|
||||||
|
surface (Circle _ r) = pi * r ^ 2
|
||||||
|
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
|
||||||
|
|
||||||
|
-- To export a data type from a module, export Shape(..) for all constructors, or, e.g., Shape(Circle,Rectangle)
|
||||||
|
|
||||||
|
-- We can use the record syntax to get named fields with automatic lookup functions:
|
||||||
|
|
||||||
|
data Person =
|
||||||
|
Person {
|
||||||
|
firstName :: String,
|
||||||
|
lastName :: String,
|
||||||
|
age :: Int
|
||||||
|
} deriving (Show)
|
||||||
|
|
||||||
|
personTest1 = Person {firstName="Garrett", lastName="Mills", age=21}
|
||||||
|
|
||||||
|
-- This also creates automatic attribute access functions:
|
||||||
|
personTest2 = firstName personTest1 -- == "Garrett"
|
||||||
|
|
||||||
|
-- Types can have zero or more parameters:
|
||||||
|
data Maybe a = Nothing | Just a
|
||||||
|
|
||||||
|
-- We can pattern match on the record type:
|
||||||
|
sayHi :: Person -> String
|
||||||
|
sayHi (Person {firstName=f, lastName=l}) = "Hello, " ++ [f, ' ', l]
|
||||||
|
|
||||||
|
-- 3D vector type example:
|
||||||
|
data Vector a =
|
||||||
|
Vector a a a
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
vplus :: (Num t) => Vector t -> Vector t -> Vector t
|
||||||
|
(Vector i j k) `plus` (Vector l m n) = Vector (i+l) (j+m) (k+n)
|
||||||
|
|
||||||
|
-- In this case, it is best to put the typeclass restriction on the functions
|
||||||
|
-- where it matters, instead of the data declaration itself, to avoid unnecessary restrictions.
|
||||||
|
|
||||||
|
-- Example of a nullary, enumerable typeclass:
|
||||||
|
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
|
||||||
|
deriving (Eq, Ord, Show, Read, Bounded, Enum)
|
||||||
|
|
||||||
|
-- The `type` keyword can be used to alias types. e.g.
|
||||||
|
type String = [Char]
|
||||||
|
|
||||||
|
-- Aliases can be parameterized:
|
||||||
|
type AssocList k v = [(k,v)]
|
||||||
|
|
||||||
|
-- We can also partially-apply type constructors as aliases. TFAE:
|
||||||
|
type IntMap v = Map Int v
|
||||||
|
type IntMap = Map Int
|
||||||
|
|
||||||
|
-- Data types can be recursive. e.g. implementing our own cons-list:
|
||||||
|
data List a =
|
||||||
|
Empty
|
||||||
|
| Cons a (List a)
|
||||||
|
deriving (Show, Read, Eq, Ord)
|
||||||
|
|
||||||
|
-- We can make an op infix by using only special chars. We also define the fixity to determine the tightness of the bind:
|
||||||
|
infixr 5 :-:
|
||||||
|
data List a =
|
||||||
|
Empty
|
||||||
|
| a :-: (List a)
|
||||||
|
deriving (Show, Read, Eq, Ord)
|
||||||
|
|
||||||
|
-- Let's implement a binary search tree:
|
||||||
|
data Tree =
|
||||||
|
Empty
|
||||||
|
| Node a (Tree a) (Tree a)
|
||||||
|
deriving (Show, Read, Eq)
|
||||||
|
|
||||||
|
singleton :: a -> Tree a
|
||||||
|
singleton x = (Node x Empty Empty)
|
||||||
|
|
||||||
|
treeInsert :: (Ord a) => a -> Tree a -> Tree a
|
||||||
|
treeInsert x Empty = singleton x
|
||||||
|
treeInsert (Node a left right)
|
||||||
|
| x == a = Node x left right
|
||||||
|
| x < a = Node a (treeInsert x left) right
|
||||||
|
| x > a = Node a left (treeInsert x right)
|
||||||
|
|
||||||
|
treeElem :: (Ord a) => a -> Tree a -> Bool
|
||||||
|
treeElem _ Empty = False
|
||||||
|
treeElem x (Tree a left right)
|
||||||
|
| x == a = True
|
||||||
|
| x < a = (treeElem x left)
|
||||||
|
| otherwise = (treeElem x right)
|
||||||
|
|
||||||
|
-- Defining typeclasses by hand
|
||||||
|
-- Here's the typeclass Eq:
|
||||||
|
|
||||||
|
class Eq a where
|
||||||
|
(==) :: a -> a -> Bool
|
||||||
|
(/=) :: a -> a -> Bool
|
||||||
|
x == y = not (x /= y)
|
||||||
|
x /= y = not (x == y)
|
||||||
|
|
||||||
|
-- Now, we can make our custom data types instances of a typeclass by hand:
|
||||||
|
|
||||||
|
data TrafficLight = Red | Yellow | Green
|
||||||
|
|
||||||
|
instance Eq TrafficLight where
|
||||||
|
Red == Red = True
|
||||||
|
Green == Green = True
|
||||||
|
Yellow == Yellow = True
|
||||||
|
_ == _ = False
|
||||||
|
|
||||||
|
-- Defining (==) and (/=) in terms of mutual recursion means that we only need to
|
||||||
|
-- specify one of them in our instance declarations.
|
||||||
|
|
||||||
|
-- Similarly, we can do:
|
||||||
|
instance Show TrafficLight where
|
||||||
|
show Red = "Red light"
|
||||||
|
show Yellow = "Yellow light"
|
||||||
|
show Green = "Green light"
|
||||||
|
|
||||||
|
-- We can force typeclasses to be subclasses of others. Partial example:
|
||||||
|
class (Eq a) => Num a where
|
||||||
|
-- ...
|
||||||
|
|
||||||
|
-- In this case, Num instances must satisfy Num AND Eq.
|
||||||
|
-- :info TypeClass in GHCi will show the functions for a particular typeclass or constructor
|
||||||
|
|
||||||
|
-- Functors
|
||||||
|
-- Consider:
|
||||||
|
class Functor f where
|
||||||
|
fmap :: (a -> b) -> f a -> f b
|
||||||
|
|
||||||
|
-- e.g. how map is a functor
|
||||||
|
instance Functor [] where
|
||||||
|
fmap = map
|
||||||
|
|
||||||
|
-- Notably, we need f to be a type constructor, not a concrete type. [a] is concrete, [] is a constructor
|
||||||
|
-- Similarly, Maybe:
|
||||||
|
|
||||||
|
instance Functor Maybe where
|
||||||
|
fmap f (Just x) = Just (f x)
|
||||||
|
fmap f Nothing = Nothing
|
||||||
|
|
||||||
|
-- What about Either? It takes 2 parameters. Partially apply it!
|
||||||
|
data Either a b = Left a | Right b
|
||||||
|
instance Functor (Either a) where
|
||||||
|
fmap f (Right x) = Right (f x)
|
||||||
|
fmap f (Left x) = Left x
|
||||||
|
|
||||||
|
-- Use :k Type to get the kind of a type or type constructor in GHCi
|
Loading…
Reference in New Issue
Block a user