9.6 The Composition Function
One of the most useful higher-order functions is the one which allows the composition of functions. Given two functions f and g, the composition of f with g is written \((f \circ g)\) and is defined by the function:
\[(f \circ g)(t)=f(g(t))\]
This form of defining the composition of functions shows that the function f has to be applied to the result of the function g. However, it does not show that what is actually being defined is the function \(\circ\). To make this definition more evident, it is useful to use the more formal notation \(\circ(f,g)\). In this form, it becomes more visible that \(\circ\) is a function that takes other functions as arguments. Thus, \(\circ\) is a higher-order function, as it not only receives two functions as arguments, but also produces a new function as result. Given that the \(\circ\) function has no name, the best way of producing it is with a lambda expression. The mathematical definition of the composition function is:
\[\circ(f,g)=\lambda t.f(g(t))\]
We can now define it in Julia:
composition(f, g) = t -> f(g(t))
Using the function composition we can now create other functions more easily. For example, we know that the Euler’s number \(e=2.718281828459045\) can be defined in terms of the summation function:
\[e=\sum_{n=0}^{\infty}\frac{1}{n!}\]
Since the aforementioned definition uses the composition of the functions inverse and factorial, defined in previous sections, we can get an approximate value of e using the following expression:
> summation(composition(inverse, factorial), 0.0, 20.0)
2.718281828459045
Obviously, for combinations of more than two functions, the combination expression may become long, as in composition(composition(f, g), h). In these cases, it may be useful to employ a more compact expression where we can group the functions we wish to compose in an array, allowing us to use instead compositions([f, g, h]). Since we need to process an array of functions, we can define the function compositions using recursion, where we compose the first element of the array with the composition of the remaining elements. Here is a first sketch of this function:
compositions(fs) =
if fs == []
???
else
composition(fs[1], compositions(fs[2:end]))
end
What we still need to find out is what should come in place of ???. If we collapse the computational process for compositions([f, g, h]), we have:
composition(f, composition(g, composition(h, ???)))
It is logical to think that instead of composition(h, ???) we should have just h, which means that instead of ??? we should have the neutral element of the composition of functions. That element is the identity function, an thus we have:
compositions(fs) =
if fs == []
identity
else
composition(fs[1], compositions(fs[2:end]))
end
Given the usefulness of the composition and compositions functions it not surprising to verify that they are already predefined in Julia under the name of \(\circ\) (written, in Julia, as \circ followed by Tab), which takes any number of (function) arguments and returns their composition as result.
9.6.1 Exercises 45
9.6.1.1 Question 171
Define the function fourth_power in terms of the functions square and composition.
9.6.1.2 Question 172
Define the function identity in terms of the function composition.
9.6.1.3 Question 173
Using the functions restriction and composition, define the function symmetric_function that, given the function \(f(x)\) produces the symmetric function \(-f(x)\). Its evaluation must produce the following interaction:
> symmetric_sqrt = symmetric_function(sqrt)
#35 (generic function with 1 method)
> symmetric_sqrt(2)
-1.4142135623730951