Functions: First Class
Last time, we covered some of the basics of functional programming. We saw how code could be comprised of and evaluated using entirely mathematical functions. There was much emphasis defining functions as an entity that operates purely on its inputs and outputs. Let’s further expound on that by thinking about what those inputs and outputs can be.
I like to imagine a typical imperative program as a story with nouns and verbs. In this story, you have objects, and actions that can be performed on or by objects. In Java, you may have a class to represent a Dog (noun), and that dog can perform an action like dog.eat(food) (verb). In Ruby, where everything is an object, you can invoke a method even on a number, e.g. 2.+(3). The point remains: you have objects, and operations you can do with them.
In functional programming, functions are not just verbs - they’re nouns too. A function can be stored in a variable, passed as an argument to another function, and returned as the result of a function. When a language allows this, we say functions are first-class values. This is one of those ideas that sounds simple but has surprisingly far-reaching consequences for how you write code.
Storing Functions in Variables
In Haskell, there’s no ceremony around this. A function is just a value like any other:
double :: Int -> Int
double x = x * 2
applyTwice :: (Int -> Int) -> Int -> Int
applyTwice f x = f (f x)
applyTwice takes a function f and a value x, and applies f to x twice. Notice the type signature: (Int -> Int) is the type of a function that takes an Int and returns an Int. Functions that take other functions as arguments (or return them) are called higher-order functions, and they’re everywhere in Haskell.
We can use applyTwice with double:
applyTwice double 3 -- evaluates to 12
Or we can pass in any other compatible function on the fly. This is a big deal! In Java, you’d typically have to create a whole interface and an anonymous class to pass behavior around. In Haskell (and in modern JavaScript, Python, etc.), you just pass the function.
Map, Filter, and Fold
The three workhorses of functional programming are map, filter, and fold (sometimes called reduce). Each of them is a higher-order function that takes a function as one of its arguments.
map applies a function to every element of a list and returns the resulting list:
map :: (a -> b) -> [a] -> [b]
map double [1, 2, 3, 4] -- [2, 4, 6, 8]
filter keeps only the elements of a list for which a predicate function returns True:
filter :: (a -> Bool) -> [a] -> [a]
filter even [1, 2, 3, 4, 5, 6] -- [2, 4, 6]
foldr collapses a list down to a single value by repeatedly applying a function:
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr (+) 0 [1, 2, 3, 4] -- 10
With these three functions, you can express a huge range of list operations without writing a single loop. And because they’re so well-understood, code written with map and filter tends to be immediately readable to anyone familiar with the style - you don’t have to trace through loop logic, you just see the intent!
Lambda Expressions
Often you want to pass a small, one-off function to map or filter without giving it a name. For this, Haskell has lambda expressions (also called anonymous functions). The syntax uses a backslash, which is meant to evoke the Greek letter lambda (λ):
map (\x -> x * x) [1, 2, 3, 4, 5] -- [1, 4, 9, 16, 25]
filter (\x -> x `mod` 3 == 0) [1..20] -- [3, 6, 9, 12, 15, 18]
You define the parameter(s) after the \, put ->, and then write the body. No ceremony, no name. The lambda is just a value you pass inline.
Partial Application
Here’s one that tends to surprise people coming from other languages. In Haskell, every function technically takes exactly one argument. When you write add x y, what’s actually happening is that add x returns a new function that’s waiting for y. This is called currying (named after the logician Haskell Curry - yes, the language is named after him too!).
The practical upshot is partial application: you can call a multi-argument function with fewer arguments than it expects, and you’ll get a new function back:
add :: Int -> Int -> Int
add x y = x + y
addFive :: Int -> Int
addFive = add 5
addFive 3 -- 8
addFive 10 -- 15
addFive is just add with the first argument already filled in. This makes it trivially easy to create specialized functions from general ones, and it pairs beautifully with map:
map (add 10) [1, 2, 3] -- [11, 12, 13]
Composing Functions
One last idea: in math, you can compose two functions f and g to get a new function f ∘ g, where (f ∘ g)(x) = f(g(x)). Haskell has an operator for this, the dot (.):
transform :: [Int] -> [Int]
transform = filter even . map (* 3)
transform [1, 2, 3, 4, 5] -- [6, 12]
transform is defined as the composition of two functions: first multiply each element by 3, then keep only the even results. Reading right to left, as function composition is traditionally written, you can trace exactly what happens. No intermediate variables, no loops - just a pipeline of transformations!
First-class functions unlock a fundamentally different way of building software. Instead of writing explicit instructions for how to do something, you define small, composable pieces of logic and plug them together. The resulting code tends to be shorter, more declarative, and - once you get comfortable with the style - surprisingly readable. It’s a shift in perspective that’s worth the effort.