-- 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