Functions
Functions are Haskell’s central abstraction. Every function takes one argument and returns a value (which may itself be a function — currying); function application is left-associative and uses no parentheses for the arguments; functions are first-class values that may be passed, returned, stored, and composed. Higher-order functions and function composition together admit a substantial fraction of Haskell’s expressive power. Lambda abstractions, sections of operators, and local definitions through let and where round out the function-related surface.
This page covers function definitions, currying, partial application, lambdas, sections, composition, and the principal combinators. The deeper applicative and monadic surface — <$>, <*>, >>= — is in Functors and applicatives and Monads.
Function definitions
A function is defined by an equation:
square :: Int -> Int
square x = x * x
add :: Int -> Int -> Int
add x y = x + y
greet :: String -> String
greet name = "Hello, " ++ name ++ "."
The form: <name> <patterns> = <expression>. The name is the function; the patterns are the parameters; the expression is the body. The type signature (the :: line) is optional; the compiler infers types if absent, though the conventional discipline is to write signatures for top-level functions.
A function may have multiple equations, each with its own patterns:
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)
length :: [a] -> Int
length [] = 0
length (_:xs) = 1 + length xs
The equations are tried top-to-bottom; the first matching equation produces the result. The mechanism is pattern matching at the function level; the full treatment is in Pattern matching.
Currying
Every Haskell function takes one argument. A function that appears to take multiple arguments is actually a function that takes one argument and returns a function:
add :: Int -> Int -> Int
-- read as: Int -> (Int -> Int)
-- "a function from Int to (a function from Int to Int)"
add x y = x + y
-- definition admits the apparent two-argument form
addOne :: Int -> Int
addOne = add 1 -- partial application; addOne is a one-argument function
The mechanism is currying (after Haskell B. Curry, after whom the language is named). The compiler treats add 1 2 and (add 1) 2 as identical; the application is left-associative.
Function application is whitespace: f x y is (f x) y. The parentheses around (add 1) are unnecessary; the same applies to ordinary application: (map (* 2)) [1, 2, 3] is the same as map (* 2) [1, 2, 3].
The function-type arrow -> is right-associative: Int -> Int -> Int is Int -> (Int -> Int). The two right-associative conventions — application left, type arrow right — admit the apparent multi-argument form to read naturally:
add :: Int -> Int -> Int
add x y = x + y
result = add 1 2 -- 3
Partial application
Currying admits partial application — supplying some arguments to produce a function expecting the rest:
add :: Int -> Int -> Int
add x y = x + y
addOne :: Int -> Int
addOne = add 1 -- = \y -> add 1 y
multiplyBy :: Int -> Int -> Int
multiplyBy n x = n * x
doubleEm :: [Int] -> [Int]
doubleEm = map (multiplyBy 2)
Partial application is the conventional Haskell mechanism for what other languages express through closures or factory functions. The pattern is pervasive in idiomatic code.
Sections of operators
A section is a partial application of an infix operator:
(+ 1) -- \x -> x + 1
(1 +) -- \x -> 1 + x
(* 2) -- \x -> x * 2
(/ 2) -- \x -> x / 2
(> 0) -- \x -> x > 0
(`div` 2) -- \x -> x `div` 2
Sections admit compact higher-order programming:
filter (> 0) [-2, -1, 0, 1, 2] -- [1, 2]
map (* 2) [1, 2, 3, 4] -- [2, 4, 6, 8]
takeWhile (/= ' ') "hello world" -- "hello"
The exception is - (subtraction), which is also unary negation; the section (- 1) is parsed as the literal -1, not as \x -> x - 1. Use subtract 1 or (\x -> x - 1) for the partially-applied subtraction.
Lambda expressions
A lambda is an anonymous function:
square = \x -> x * x
addPair = \(x, y) -> x + y
apply f x = f x
mapAndSum f xs = sum (map f xs)
result = mapAndSum (\x -> x * x) [1, 2, 3, 4] -- 30
The form: \<patterns> -> <expression>. The \ is meant to resemble the Greek letter λ.
Lambdas are first-class values; they may be passed, returned, and stored. The conventional discipline is to use lambdas for short, one-off callbacks; for substantial bodies, a named local function (in a where or let) is conventionally clearer.
The LambdaCase extension admits a single-argument pattern-matching lambda:
{-# LANGUAGE LambdaCase #-}
describe = \case
Nothing -> "no value"
Just x -> "value: " ++ show x
Higher-order functions
A higher-order function takes one or more functions as arguments or returns a function:
map :: (a -> b) -> [a] -> [b]
filter :: (a -> Bool) -> [a] -> [a]
foldr :: (a -> b -> b) -> b -> [a] -> b
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
twice :: (a -> a) -> a -> a
twice f x = f (f x)
result = twice (+ 1) 5 -- 7
The principal higher-order patterns:
- Map: apply a function to every element of a structure.
- Filter: keep elements satisfying a predicate.
- Reduce / Fold: combine elements using a binary function.
- Compose: build a function from smaller pieces.
- Apply: take a function and apply it.
The combination is the foundation of functional programming. The full treatment of map, filter, fold is in Loops and Functors and applicatives.
Function composition (.)
The . operator composes functions:
(.) :: (b -> c) -> (a -> b) -> a -> c
(f . g) x = f (g x)
The composition f . g means “apply g, then apply f”. The mechanism is the conventional Haskell pipeline:
sumOfSquares :: [Int] -> Int
sumOfSquares = sum . map (^ 2)
evenCount :: [Int] -> Int
evenCount = length . filter even
reversedUpper :: String -> String
reversedUpper = reverse . map toUpper
The composition reverse . map toUpper reads “map to upper, then reverse” — the operator’s right-to-left composition matches mathematical notation but reverses the visual order from a Java-style pipeline.
The (.) is right-associative; f . g . h is f . (g . h). The reading: apply h, then g, then f.
Function application ($)
The $ operator is right-associative function application with very low precedence:
($) :: (a -> b) -> a -> b
f $ x = f x
The principal use is to avoid parentheses around an argument:
print (map (* 2) (filter even [1..10])) -- with parens
print $ map (* 2) $ filter even [1..10] -- with $
print . map (* 2) . filter even $ [1..10] -- combination
The $ admits writing a pipeline as a top-level expression. The very-low precedence (0) means $ evaluates only after every other operator.
Point-free style
Point-free (or tacit) style is writing functions without naming their parameters explicitly:
-- Pointed:
sumOfSquares xs = sum (map (^ 2) xs)
-- Point-free:
sumOfSquares = sum . map (^ 2)
-- Pointed:
double x = x * 2
-- Point-free:
double = (* 2)
-- Pointed:
isPositive x = x > 0
-- Point-free:
isPositive = (> 0)
The point-free form is shorter and emphasises the function rather than its application. The trade-off:
- Pros: more abstract, more compositional, easier to refactor.
- Cons: can be cryptic, especially for non-trivial compositions.
The conventional advice is to use point-free for simple compositions and to fall back to the explicit form when the result would be confusing. Tools like Hlint suggest point-free transformations; the conventional discipline is to apply them where they help.
Local definitions: let and where
Two forms admit local bindings:
let (an expression)
average :: [Double] -> Double
average xs =
let total = sum xs
count = length xs
in total / fromIntegral count
The let expression introduces bindings visible in the in expression. Bindings can refer to each other (mutually recursive).
where (a clause)
quadratic :: Double -> Double -> Double -> Double -> Double
quadratic a b c x = result
where
result = a * x * x + b * x + c
The where clause attaches to a top-level definition or a guard, with bindings visible throughout the right-hand side. where bindings can refer to the function’s parameters.
The conventional choice between let and where:
wherefor definitions that conceptually belong with the function.letfor bindings local to a specific expression.
Both forms support pattern bindings:
classify :: (Int, Int) -> String
classify (x, y)
| sumIsZero = "diagonal-balanced"
| otherwise = "elsewhere"
where
sumIsZero = x + y == 0
Standard combinators
The standard library provides a substantial set of combinators — small reusable function-shaping helpers:
id :: a -> a
id x = x
const :: a -> b -> a
const x _ = x
flip :: (a -> b -> c) -> b -> a -> c
flip f x y = f y x
curry :: ((a, b) -> c) -> a -> b -> c
curry f x y = f (x, y)
uncurry :: (a -> b -> c) -> (a, b) -> c
uncurry f (x, y) = f x y
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
on f g x y = g x `f` g y
(&) :: a -> (a -> b) -> b -- reverse application; from Data.Function
x & f = f x
The combinators admit compact code:
sortBy (compare `on` length) ["abc", "a", "ab", "abcd"]
-- sort by length
map (uncurry (+)) [(1, 2), (3, 4), (5, 6)]
-- [3, 7, 11]
(5 &) (+ 1) (* 2)
-- equivalent to (5 + 1) * 2 = 12; the & is reverse application
Recursion as the iteration mechanism
Functions in Haskell are typically recursive; the language has no for/while loops:
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)
fibonacci :: Int -> Int
fibonacci 0 = 0
fibonacci 1 = 1
fibonacci n = fibonacci (n - 1) + fibonacci (n - 2)
The treatment of recursion and the higher-order functions that subsume most loops is in Loops.
Common patterns
Pipeline
processItems :: [Item] -> [Result]
processItems = map process . filter valid . sortBy priority
The composition map process . filter valid . sortBy priority admits reading the pipeline as a sequence of stages.
Parametric helpers
counterUpdate :: (Int -> Int) -> Counter -> Counter
counterUpdate f (Counter n) = Counter (f n)
increment :: Counter -> Counter
increment = counterUpdate (+ 1)
double :: Counter -> Counter
double = counterUpdate (* 2)
The pattern admits derivation: the parametric helper covers the common case; specific cases supply the appropriate function.
Combinator-based config
defaultConfig :: Config
defaultConfig = Config { ... }
withPort :: Int -> Config -> Config
withPort p c = c { port = p }
withTimeout :: Int -> Config -> Config
withTimeout t c = c { timeout = t }
myConfig :: Config
myConfig = withTimeout 30 . withPort 9090 $ defaultConfig
The pattern admits composable configuration through partial application and composition.
Higher-order callback with closure
makeCounter :: IO (IO Int)
makeCounter = do
ref <- newIORef 0
return $ do
n <- readIORef ref
writeIORef ref (n + 1)
return n
Functions returning functions admit closures over local state — the conventional Haskell idiom for stateful actions.
flip for argument reordering
mapResult :: (a -> b) -> [a] -> [b]
mapResult = flip map -- map with arguments flipped
result = mapResult [1, 2, 3] (* 2) -- [2, 4, 6]
The flip admits reordering arguments without an explicit lambda. Useful when the convention of the existing function does not match the call site’s needs.
A note on the absence of imperative control structures
Haskell does not have return-as-statement, break, continue, or any imperative control structure. The conventional substitutes:
returnfrom a function: every function’s value is its single expression’s value; “early return” is achieved through pattern matching with multiple equations or through guarded expressions.break/continue: in higher-order operations,takeWhile,dropWhile, and the lazy nature of operations admit early termination. For state-machine-style loops, the recursive form admits arbitrary control.- Mutable counters: stateful computation uses the
Statemonad orIORefin IO.
The conventional discipline is to express the algorithm as a function from input to output, with recursion handling the “iteration” when needed and higher-order functions handling the conventional patterns. The combination is more declarative than imperative loops; the cost is the steep learning curve for programmers coming from imperative languages.