Skip to content
AgileLogics
4 min read

let and where

Prerequisites: Pattern matching

Both let and where name intermediate values so expressions stay readable.

where: definitions after the expression

cylinderArea :: Double -> Double -> Double
cylinderArea r h = sides + 2 * top
  where
    sides = 2 * pi * r * h
    top = pi * r ^ 2

main :: IO ()
main = print (cylinderArea 1 2)
18.84955592153876

where reads like a mathematical definition: "the area is the sides plus two tops, where sides is ... and top is ...". Its definitions can see the function's arguments and are shared by all the guards of that equation.

let ... in: definitions before the expression

cylinderArea :: Double -> Double -> Double
cylinderArea r h =
  let sides = 2 * pi * r * h
      top = pi * r ^ 2
   in sides + 2 * top

main :: IO ()
main = do
  print (cylinderArea 1 2)
  print (let x = 3; y = 4 in x * y)
18.84955592153876
12

let ... in is an expression, so it can go anywhere a value can. Inside a do block, you write let without in, as you've seen already.

Helper functions in where

where can define functions too, and pattern match:

initials :: String -> String -> String
initials first last' = [f] ++ ". " ++ [l] ++ "."
  where
    (f : _) = first
    (l : _) = last'

report :: [Int] -> String
report xs = "count " ++ fmt (length xs) ++ ", total " ++ fmt (sum xs)
  where
    fmt n = "<" ++ show n ++ ">"

main :: IO ()
main = do
  putStrLn (initials "Grace" "Hopper")
  putStrLn (report [4, 5, 6])
G. H.
count <3>, total <15>
  • A trailing apostrophe is a legal character in names: last' avoids clashing with the Prelude's last. (x' is read "x prime".)
  • Indentation matters: everything in a where block must line up.

Which to use?

It's mostly style. where is more common for function helpers and works across guards; let is handy for a quick local value inside an expression and inside do blocks.