Introduction
Haskell's design encourages concise, expressive code through features like higher-order functions, function composition, point-free style, pattern matching, and list comprehensions. Understanding these techniques lets you transform verbose Haskell into compact, idiomatic expressions without sacrificing readability.
Example: Sum of Squares of Even Numbers
Verbose Version
1sumOfSquaresOfEvens :: [Int] -> Int
2sumOfSquaresOfEvens xs =
3 let evens = filter isEven xs
4 squares = map square evens
5 in sum squares
6 where
7 isEven x = x `mod` 2 == 0
8 square x = x * x
Concise Version
sumOfSquaresOfEvens :: [Int] -> Int
sumOfSquaresOfEvens = sum . map (^2) . filter even
This uses function composition (.), the built-in even, and sections ((^2)).
Technique 1: Function Composition
The . operator chains functions right to left:
1-- Verbose
2f x = h (g (k x))
3
4-- Concise
5f = h . g . k
6
7-- Example: get uppercase first letter of a string
8firstUpperChar :: String -> Char
9firstUpperChar = toUpper . head
10
11-- Pipeline: words -> count -> show
12wordCountStr :: String -> String
13wordCountStr = show . length . words
Technique 2: Point-Free Style
Remove the explicit argument when it appears at the end:
1-- With explicit argument (pointful)
2double x = x * 2
3addOne x = x + 1
4isPositive x = x > 0
5
6-- Point-free
7double = (* 2)
8addOne = (+ 1)
9isPositive = (> 0)
10
11-- More complex example
12-- Pointful
13process xs = sort (nub (filter (> 0) xs))
14
15-- Point-free
16process = sort . nub . filter (> 0)
Technique 3: Using Sections
Sections partially apply operators:
1-- Sections for common operations
2map (* 2) [1, 2, 3] -- [2, 4, 6]
3filter (> 3) [1, 2, 3, 4, 5] -- [4, 5]
4map (++ "!") ["hi", "bye"] -- ["hi!", "bye!"]
5
6-- Verbose
7squareAll xs = map (\x -> x * x) xs
8
9-- Concise
10squareAll = map (^2)
Technique 4: List Comprehensions
1-- Verbose
2evenSquares :: [Int] -> [Int]
3evenSquares xs = map (\x -> x * x) (filter even xs)
4
5-- List comprehension
6evenSquares xs = [x * x | x <- xs, even x]
7
8-- Pythagorean triples
9triples n = [(a,b,c) | c <- [1..n], b <- [1..c], a <- [1..b],
10 a*a + b*b == c*c]
Technique 5: Pattern Matching
1-- Verbose with if-else
2describeList :: [a] -> String
3describeList xs =
4 if null xs
5 then "empty"
6 else if length xs == 1
7 then "singleton"
8 else "longer"
9
10-- Pattern matching
11describeList :: [a] -> String
12describeList [] = "empty"
13describeList [_] = "singleton"
14describeList _ = "longer"
Technique 6: Guards
1-- Verbose if-else chain
2bmi :: Double -> String
3bmi x = if x < 18.5
4 then "underweight"
5 else if x < 25.0
6 then "normal"
7 else if x < 30.0
8 then "overweight"
9 else "obese"
10
11-- Guards
12bmi :: Double -> String
13bmi x
14| x < 18.5 = "underweight" | x < 25.0 = "normal" | x < 30.0 = "overweight" | otherwise = "obese" ``` ## Technique 7: Higher-Order Functions Replace explicit recursion with standard library functions: ```haskell -- Explicit recursion sumList :: [Int] -> Int sumList [] = 0 sumList (x:xs) = x + sumList xs -- Using fold sumList :: [Int] -> Int sumList = foldl' (+) 0 -- More examples product' = foldl' (*) 1 maximum' = foldl1 max concat' = foldl' (++) [] any' = foldl' (\acc x -> acc | |
15| --- | --- | --- | --- | --- |
16| `\x -> x + 1` | `(+ 1)` |
17| `\x -> f (g x)` | `f . g` |
18| `map (\x -> x * x) xs` | `map (^2) xs` |
19| `filter (\x -> x > 0) xs` | `filter (> 0) xs` |
20| `foldl (\acc x -> acc + x) 0` | `foldl (+) 0` |
21| `if x then True else False` | `x` |
22| `\x -> not (null x)` | `not . null` |
23
24## Common Pitfalls
25
26* **Over-abstracting**: Point-free style becomes unreadable with complex functions. `f = g . h . k . m . n` is hard to follow. Add type signatures and use pointful style when clarity demands it.
27* **Space leaks with foldl**: Use `foldl'` (strict) from `Data.List` instead of `foldl` (lazy) to avoid stack overflow on large lists.
28* **Operator precedence**: Function composition `.` and application `$` have different precedences. `f . g $ x` means `(f . g) x`, not `f . (g $ x)`.
29* **Readability**: Concise code is not always better. Choose readability over cleverness, especially in team environments.
30* **Type inference**: Overly point-free code can confuse GHC's type inference. Add explicit type signatures to help the compiler and future readers.
31
32## Summary
33
34* Use `.` for function composition to chain transformations
35* Use sections like `(+1)`, `(>0)`, `(^2)` for partial operator application
36* Use point-free style when it improves clarity: `f = sum . map (^2) . filter even`
37* Replace explicit recursion with `map`, `filter`, `foldl'`, and list comprehensions
38* Balance conciseness with readability — add type signatures and avoid overly abstract code