Pattern Matching, Guards and Local Bindings

6.1 Pattern Matching

Enable -Wall (or -Wincomplete-patterns) so GHC warns about non-exhaustive patterns.

factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

isEmpty :: [a] -> Bool
isEmpty [] = True
isEmpty _ = False                 -- _ is a wildcard

firstTwo :: [a] -> Maybe (a, a)
firstTwo (x : y : _) = Just (x, y)
firstTwo _ = Nothing

addPairs :: (Int, Int) -> (Int, Int) -> (Int, Int)
addPairs (a, b) (c, d) = (a + c, b + d)

describeList :: [a] -> String
describeList xs = case xs of
  [] -> "empty"
  [_] -> "singleton"
  _ -> "longer list"

-- As-patterns
duplicateFirst :: [a] -> [a]
duplicateFirst all'@(x : _) = x : all'
duplicateFirst [] = []

6.2 Guards

bmiCategory :: Double -> String
bmiCategory bmi
  | bmi < 18.5 = "underweight"
  | bmi < 25.0 = "normal"
  | bmi < 30.0 = "overweight"
  | otherwise = "obese"           -- otherwise = True

compareAges :: Int -> Int -> String
compareAges a b
  | a > b = "older"
  | a < b = "younger"
  | otherwise = "same age"

6.3 where and let

bmiReport :: Double -> Double -> String
bmiReport weight height
  | bmi < 18.5 = "underweight (" ++ show bmi ++ ")"
  | otherwise = "ok (" ++ show bmi ++ ")"
  where
    bmi = weight / height ^ 2        -- visible to all guards

cylinderArea :: Double -> Double -> Double
cylinderArea r h =
  let sideArea = 2 * pi * r * h
      topArea = pi * r ^ 2
   in sideArea + 2 * topArea         -- let ... in is an expression

6.4 Extensions for Patterns

{-# LANGUAGE LambdaCase, MultiWayIf, ViewPatterns, BangPatterns #-}
classify :: Int -> String
classify = \case                      -- LambdaCase
  0 -> "zero"
  n | n < 0 -> "negative"
    | otherwise -> "positive"

sign :: Int -> Int
sign n = if | n > 0 -> 1 | n < 0 -> -1 | otherwise -> 0   -- MultiWayIf

firstWord :: String -> String
firstWord (words -> (w : _)) = w      -- ViewPatterns
firstWord _ = ""