What Comes Next to Higher Order Functions
In a previous post we talked about Higher Order Functions, what they are, and how we use them. However, most of the time we are unaware of the advantages they offer.
Higher Order Functions come in different flavors and we will review them here.
We will use F# for convenience as the language in our examples, but the same can be achieve in Scala or any other functional programming language.
Storing functions into variables
We saw how we can store a function into a variable to be used as any other value that we could pass to another function or to abstract functions from some implementation details that are executed at runtime, yet without loosing the type safety some languages offer.
The following example shows what we just talked about.
let succ x = x + 1
let f = succ
[1..100] |> List.map f // will return [(1+1), (2+1), ... , 101]
Even though this is very useful sometimes, there is much more we can do with higher order functions.
Partial Function Application
Let’s suppose we have the following function that sums up three numbers
let sum x y z = x + y + z
we can partially apply this function by doing:
let s1 = sum 1 3 // partially evaluated to sum (1,3, z)
let s2 = sum 1 // partially evaluated to sum (1, y, z)
Now we could do:
let total = s 3 // evals to 7
let anotherTotal = s2 1 1 // evals to 3
In our example, s1 and s2 are partial functions that have been bind to some value and then partially apply. Then we can evaluate them to their final results.
Some functional languages, such as F#, only have one argument functions. However, sometimes, we don’t notice this at first look.
Currying is the process of splitting a function into a nested chain of one argument functions.
let sum x y z = x + y + z
will be transformed to:
let sum x = function y -> function z -> x + y + z
Some people will argue that it is basically the same; however, languages such as F# take big advantages from this since all functions are defined with only one argument which gives a standard way to define functions across the entire language.
Please note that currying differs from partially applied functions. In our previous example in order to get the full application of sum we need to do:
let value = sum 1 2 3 // evals to 6
but if we do:
let partialSum = sum 1 2
here partialSum is a function that takes one argument and returns an Int.
Partial application creates a new function while reducing the number of arguments from the original function.
Currying makes every function in the chain a function of exactly one argument that returns another function with the same characteristic (a function of one argument).
Closure is the process of capturing a function and data together as a single unit. A function closure can take 0 to infinite number of arguments, but it’s also aware of data not passed to it.
In other words, we are also storing the environment along with the function definition into a variable. Let’s see an example:
let add x = fun y z -> x + y + z
Here the function add returns a fun y z -> x + y + z which has access to x. The x variable is not part of it, yet it is aware of the x. Closures are aware of the environment where they are defined!
If we do:
let add x = fun y z -> x * (y + z)
let closure = add 2
closure 1 1
closure is a function f: y z -> Int but when executing the last statement (closure 1 1) it does have access to the x variable stored in its environment causing evaluation to be equals to 4.
A more realistic example will be:
let mapSum s = fun x -> x + s
let mappedList = [1..10] |> List.map (mapSum 5)
Here, mapSum is returning a function f(x) which is aware of the value passed to mapSum. Then it is used to map each element of the list.
Higher order functions let us use functions as variables while passing them to other functions. In addition, knowing the different types of higher order functions enables us to take advantages of them and understand better certain patterns we see in functional languages.
Functional programming is a new trend many of us have already noticed. Partially applied functions, currying functions and closures are some the building blocks of functional languages and we all should have an idea of how they work so we can do more in the new world trending nowadays.