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