FP in C++: Function composition




Learn about function composition

Function composition is one of the central concepts of functional programming

Combining functions enables creating complex functionality

Output becomes input

Output of the inner function becomes the input of the outer function.

Type match is essential

Composition requires output-to-input type match.

        g:: double -> double
        f::           double -> bool

Not all functions can be composed

Function f expects a single value, however g returns an array.

          g:: int -> [int]
          f::         int -> bool

This problem can be solved with monads and Kleisli composition.

Q: What is the result of the composition f(g(x))?

f(x) = x2 - x - 1
g(x) = x + 1

Substitute x in f with the definition of g.

Use nested lambda to express composition

auto compose = [](auto f, auto g) {
  return [=](auto x) {
    return f(g(x));
auto op = compose(f2, f1);

Unary function composition only.

Let's look at a concrete example

Assume, you have temperature expressed in ℉. Does this value fall within the room temperature range 20 - 25 ℃ ?

We have the following functions at hand

double fahrenheitToCelsius(double fahr){
    return (fahr - 32) * 5.0 / 9.0;
bool isRoomTemperature(double t){
    return (t >= 20.0 && t < 25.0) ? true : false;

All we need to do is compose functions

Notice the order, we compose from right to left.

auto op = compose(isRoomTemperature, fahrenheitToCelsius);
auto val1 = op(71.9); // returns 1, i.e. room temperature
auto val2 = op(50.3); // returns 0, i.e. not room temperature

We could have created a distinct function for this purpose

auto opComposed(double fahr){
    return isRoomTemperature(fahreheitToCelsius(fahr));
int main() {
   auto val = opComposed(71.9); // room temperature

However, composition gives us elegance and generality

auto vecBool = map(vecTemp, 
               compose(isRoomTemperature, fahrenheitToCelsius));

The composed function can be directly fed into higher-order functions, such as map and filter.

An operation is commutative if changing the order of the operands does not change the result.

Q: Compose f(g(x)), then g(f(x))

f(x) = 0.5x - 5
g(x) = 2x + 10

Try a different function pair.

Did you get tricked by the previous question?

Composition is not commutative in general

f • g ≠ g • f

The result happened to be the same, because the functions were mutual inverses.

Let's create a variation of the temperature example. We are interested in calculating BMI of a person and determining whether he is overweight.

We have the following functions at hand

double bmi(double weight, double height){
    return weight / height / height;
bool isOverweight(double bmi){
    return (bmi >= 25.0) ? true : false;

We need to supply 2 values to calculate BMI

Multi-argument composition requires parameter pack

auto compose = [](auto f, auto g) { 
    return [=](auto&& ... x) { 
        return f(g(x...)); 

Three dots ... in C++ denotes the parameter pack.

Passing multiple parameters becomes simple

auto op = compose(isOverweight, bmi);
auto val1 = op(59.8, 1.7); // returns 0, i.e. false
auto val2 = op(73.0, 1.52); // returns 1, i.e. true

Certain binary operations (e.g. +) exhibit associative property. Rearranging parentheses won't change the value of an expression.

Composition is always associative

(f • g) • h = f • (g • h) compose(compose(f,g),h)=compose(f,compose(g,h))

Q: What is the result of the composition

(f • g) • h and f • (g • h)?

f(x) = x - 2, g(x) = x2, h(x) = 2x,

Distinguish between function composition and application

Composition produces another function, concrete values are not supplied.

Function application requires that we supply concrete values for one or more arguments.

Full application produces a value, partial application produces a function that "remembers" values.

Q: Mark all cases of full application

Were concrete values supplied?

Let's sum it all up

Function composition