Pure, first-class, higher order functions and closures


If you are a computer scientist you will soon or later be confronted with concepts like pure functions or closures. In most cases you won’t even bother and just use those concepts without thinking much about it. With this post I will give you a short explanation what those terms mean and the concept behind them.

Pure functions

Pure functions are the easiest to understand. A function is called pure if it has no internal state. This means for a given set of inputs it will always produce the same output without any side effects.

def add(a: int, b: int) -> int:
  return a + b

Here the add function takes two arguments a and b and returns the sum of both. The function itself doesn’t read or write any internal values. Pure functions don’t need to be methods inside a class and shouldn’t be. In languages like java, where everything needs to be an object, such functions can be marked as static for example.

First-class functions

Calling a function directly can be nice and all, but in some cases it can become handy if a function could be called dynamically or dependent of the current state of execution. First-class functions can be parameters of other functions or stored in variables. This makes them the backbone of every functional language.

def add(a: int, b: int) -> int:
    return a + b


myFunc: add

myResult = myFunc(1, 2)

The add function is assigned to the variable myFunc. Instead of calling add directly myFunc is treated as a function and called with the arguments 1 and 2. The variable myResult will contain the result of the addition 3. With first-class functions it is possible to store functions like other values in complex data structures or pass them deep down to other processes.

An easy example to understand be benefits would be two different operations executed on a list.

def sum(lst: [int]) -> int:
    if len(lst) == 0:
        return 0
    
    result = lst[0]
    for i in range(1, len(lst)):
        result += lst[i]
    return result


def product(lst: [int]) -> int:
    if len(lst) == 0:
        return 0

    result = lst[0]
    for i in range(1, len(lst)):
        result *= lst[i]
    return result

myList = [1, 2, 3]
mySum = sum(myList)
myProduct = product(myList)

We have two pure functions. sum adds every element of the list to the result and returns it. product also takes every element but multiplies it with the result. Both functions look nearly the same, just the operation differs. This can be written much cleaner with first-class functions.

def add(a: int, b: int) -> int:
    return a + b


def mul(a: int, b: int) -> int:
    return a * b


def foreach(lst: [int], op: Callable[[int, int], int]) -> int:
    if len(lst) <= 0:
        return 0

    result = lst[0]
    for i in range(1, len(lst)):
        result = op(result, lst[i])
    return result


myList = [1, 2, 3]
mySum = foreach(myList, add)
myProduct = foreach(myList, mul)

At first glance this doesn’t safe much code, but we got rid of the code duplication. We have a clean separation between the array traversal and the operations. Even more important: if we want to add another operation like the division we just need to add 2 more lines of code.

Higher order functions

Higher order functions are functions that can deal with other functions, either as input arguments or as return values. In our previous example the foreach is a higher order function because the parameter op is expected to be a function.

Closures

Closures are functions that are aware of the scope they where declared in. If they are declared within a function for example they can utilize all of the variables and parameters of this function.

def add(a: int, b: int) -> int:
  return a+b

def add_constant(const: int) -> Callable[[int], int]:

  def closure(x: int):
    return add(x, const)

  return closure

addFive = add_constant(5)
    
ten = addFive(5)
eleven = addFive(6)

In this example we are using all of our concepts combined. First we have the add function which is pure and does nothing more than add two parameters. There is also the add_constant function which creates another function that will add a constant to any number. Because its return value is another function add_constant is a higher order function. addFive is the result of add_constant with the parameter 5 and for this reason a first class function.

Within add_constant we can find our closure. It will use the outer context of add_constant when called and adds its argument to the variable const which is declared in add_constant. Because addFive was created with const being 5, everytime addFive is called, 5 will be added to the input parameter.