Chapter 4: Syntax in Functions

Learn You a Haskell for Great Good!

Pattern matching

  • When a function is call, if the function is overloaded with other arguments, it will check from top to bottom until a match is found and return the corresponding value. e.g.
lucky :: (Integral a) => a -> String
lucky 7 = "Lucky number 7!"
lucky x = "Sorry, out of luck."
  • Be sure to have something at the end to catch all or it will failed.
  • Pattern matching can be use with tuple and list
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)
addVectors a b = (fst a + fst b, snd a + snd b)
  • [1,2,3] is the same as 1:2:3:[]. Pattern x:xs will bind head of the list to x and rest to xs.
  • Use error for exception handling with an message follow it
  • Here is a recursive pattern that use for calculating the sum of a numeric list (this pattern is use for a lot of similar recursive function)
sum' :: (Num a) => [a] -> a
sum' [] = 0
sum' (x:xs) = x + sum' xs
  • as pattern is a reference to a pattern. The name of the reference is follow by an @. See the example below uses all as pattern name for the whole string.
capital :: String -> String
capital "" = "empty"
capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]

ghci> capital "Dracula"
"The first letter of Dracula is D"

Guards, guards!

  • Guards are indicated by the pipe | and is like a nested if-then-else statement
  • Here is an example of compare implementation. Notice it can implement using more than one parameters, use of otherwise is similar to catch all and define the function using infix with backticks.
myCompare :: (Ord a) => a -> a -> Ordering
a `myCompare` b
    | a > b     = GT
    | a == b    = EQ
    | otherwise = LT


  • Where binding let you bind to a variables at the end of a function and the whole function can see them, including all the guards. In this case, bmi, skinny, normal and fat are all constant variables.
bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| bmi <= skinny = "You're underweight."
where bmi = weight / height ^ 2
      (skinny, normal, fat) = (18.5, 25.0, 30.0)
  • here is a function defined to calculate a list of weight-height pairs and return a list of bmis.
calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi w h | (w, h) <- xs]
    where bmi weight height = weight / height ^ 2

Let it be

  • Let binding let you bind to variables anywhere and are expressions themselves, but are very local, so they don’t span across guards. The form let <bindings> in <expression> uses the name defined in the “let” part to be use only in the “in” part.
  • Use ; to bind several variable inline
ghci> (let a = 100; b = 200; c = 300 in a*b*c, let foo="Hey "; bar = "there!" in foo ++ bar)
(6000000, "Hey there!")
  • The let binding inside a list comprehension are visible to the output function (the part before the | ), however, it can’t be used in the first predicate (w, h) <- xs because it defines prior to let. We also omit the “in” part when we use let binding inside list comprehension.
calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2]

Case expressions

  • case is like switch statement in other imperative languages but more powerful by using pattern matching. Pattern matching uses the fall through rules defined earlier, thus, a catch all case should be there to avoid error.
describeList :: [a] -> String
describeList xs = "The list is " ++ case xs of []  -> "empty."
                                               [x] -> "a singleton list."
                                               xs  -> "a longer list."
  • Notice the last line is a catch all.