Functional Basics: First Class Functions
In functional programming, the concept of first-class functions is fundamental. A function is considered to be a first-class citizen if it can be treated like any other value. This means that, similar to other values, functions can be assigned to variables, stored in data structures, passed as arguments to other functions, and returned as values from other functions.
The ability to use functions as values leads to the concept of higher-order functions. A higher-order function is one that can take other functions as arguments, or return functions as results. This capability introduces a new level of abstraction, where not only values, but actions can be encapsulated and manipulated.
Consider, for instance, a function that accepts another function as an argument:
In the example above, apply
is a higher-order function because it takes a function f
as a parameter. This function f
is then applied to x
.
First class functions can also lead to functions being stored in structures. Take for instance this example in Dart of an list of functions:
A function can also return another function as a result. Consider this example where a function generates another function:
In this case, addX
returns a function that takes a parameter y
and adds x
to it. Here is the same in TypeScript, but with an application of the function following it's assignment:
And the exact same example again in TypeScript, but with lambda (arrow) syntax:
Since the idea of first class functions - specifically ones that return other functions - and their syntax can be a bit overwhelming at first if you are not used to it, now let's look at addX
in a handful a languages:
// F#
let addX x = (fun y -> x + y)
// F# without lambda
let addX x y = x + y
// Dart
Function addX(x) {
return (y) => x + y;
}
// TypeScript
const addX = (x: number) => (y: number) => x + y;
// Python
def addX(x):
return lambda y: x + y
// C# (Using delegates)
Func<int, Func<int, int>> addX = x => y => x + y;
The concept of first-class functions gives way to other powerful functional programming techniques such as currying, function composition, and monads, all of which rely on the ability to manipulate functions as values. Eventually we will get to these concepts but on this journey we're being sure to take in all of the sights along the way.
Like expressions, utilizing functions as first-class citizens is something that's easy to start doing now, without any additional overhead and it's subtleties will slowly help you grok other concepts. Are you making a function that takes two parameters? Why not make it a function that takes one parameter that returns a function that takes the other parameter? A habit as simple as this is a great step! We can write functions and store them to named variables, pass them as arguments, and return them from functions. Depending on the language we can do this succinctly (in a language with first class functions) or verbosely (in a language like C# that fills the gap with concepts like delegates), but either way we're paving the road to composable, function-first software.