Reading — step 1 of 7
Learn
Three small but pervasive bits of Haskell syntax that polish your code: let, where, and $. LYAH chapter 5 covers these as foundational.
let — local bindings
areaCircle :: Double -> Double
areaCircle r =
let pi' = 3.14159
squared = r * r
in pi' * squared
let X in Y— define bindings X, use them in Y- Multiple bindings indented to the same column
inis required for let-as-expression
In do blocks (next lesson on IO), let doesn't need in:
main = do
let name = "Ada"
let age = 36
putStrLn (name ++ " is " ++ show age)
where — bottom-up bindings
areaCircle :: Double -> Double
areaCircle r = pi' * squared
where
pi' = 3.14159
squared = r * r
Same meaning, different style:
let— top-down: define then usewhere— bottom-up: use then define
Idiomatic Haskell prefers where for definition-style code. The main expression reads at the top; helpers below.
bmi :: Double -> Double -> String
bmi weight height
| bmi < 18.5 = "underweight"
| bmi < 25.0 = "normal"
| bmi < 30.0 = "overweight"
| otherwise = "obese"
where
bmi = weight / (height * height)
Where clauses can be shared across guards. let bindings can't be (each let is per-equation).
$ — function application without parens
The dollar sign is just regular function application — but with the LOWEST precedence:
putStrLn (show (1 + 2)) -- noisy parens
putStrLn $ show $ 1 + 2 -- cleaner
$ is right-associative, low-precedence — basically: "evaluate everything to my right as one big expression, then apply the function on the left."
Without $:
f (g (h x))
With $:
f $ g $ h x
Slightly cleaner for chains of single-arg applications.
. — function composition
f . g = \x -> f (g x)
-- Use:
length . filter even . map (* 2) $ [1, 2, 3, 4, 5]
-- map *2: [2,4,6,8,10]; filter even: [2,4,6,8,10]; length: 5
. is composition — (f . g) x = f (g x). Builds pipelines from right to left.
Compare to $:
$— apply function to argument.— compose functions
Common idiom:
result = function . step1 . step2 . step3 $ input
Reads: "input through step3, then step2, then step1, then function." The . sequence is the pipeline; $ applies it to the input.
let vs where — choosing
let— when the binding is local to ONE expression, especially indoblocks and lambdaswhere— when the binding belongs to a top-level function, especially with guards
-- let — fine for short expressions:
f x = let y = expensive x in y * y + y
-- where — better for multi-line functions:
f x = result
where
intermediate = step1 x
moreWork = step2 intermediate
result = combine moreWork
Common mistakes
- Forgetting
inafter a let expression (outside of do) — syntax error. - Indenting let/where bindings inconsistently — Haskell is whitespace-sensitive; bindings must align.
- Confusing
$with.—$applies;.composes.f $ xisf x.f . xonly works if x is also a function. $chains that are hard to read —f $ g $ h $ i $ xworks but parens or.are sometimes clearer.- Using
whereinside a lambda — not allowed. Useletor extract a top-level function.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…