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 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
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.
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
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 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 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 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 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.
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
addFive was created with
const being 5, everytime
addFive is called, 5 will be added to the input parameter.