compose

Composes two functions.

Signature

compose(f, g)(value)
(('b) => 'c, ('a) => 'b) => (('a) => 'c)

Documentation

Composes two functions.

Example:

const compose = require('folktale/core/lambda/compose');

const inc       = (x) => x + 1;
const double    = (x) => x * 2;
const incDouble = compose(double, inc);

incDouble(3);
// ==> 8

Why Composition Matters?

Composition is a way of creating new functionality by combining existing functionality. It helps managing complexity in software projects, since newer functionality can be defined in terms of existing ones, reducing both the amount of code needed to implement newer features, and the amount of time one needs to spend to understand them.

This technique is used very often in regular JavaScript code, when we feed the results of a function into another function:

const names = [
  'Alissa', 'Max', 'Talib'
];
const upcase = (name) => name.toUpperCase();

names.map(upcase).join(', ');
// ==> 'ALISSA, MAX, TALIB'

Here names is fed into the map function, which returns an array of the same names in capitals. This new array is then fed into the join function, which returns a String where the items are separated by a comma.

If we need to use .map(upcase).join(', ') in a lot of places, it makes sense to create a new function that captures that composition, so we don't have to type it everywhere:

const showNames = (names) =>
  names.map(upcase).join(', ');

Now we can use showNames instead of .map(upcase).join(', '), and we get the same functionality:

showNames(names);
// ==> 'ALISSA, MAX, TALIB'

The Problem With Methods in JavaScript

JavaScript is a hybrid language, where you can express programs by using object oriented techniques, functional programming techniques, and some other paradigms. It's not uncommon to see parts of a program expressed in OOP, as in the examples from the previous section.

However, objects in JavaScript can't be safely extended. Ideally, we'd like to write:

names.show();

Instead of:

showNames(names);

Which would be more consistent with the rest of the program. Because this involves mutating the array, all sorts of problems could happen so it's better to keep the new functionality as a separate function.

Expressing things in functional programming doesn't have this problem because functions exist on their own, rather than being part of an object.

Composition in Functional Programming

In functional programming, functions are the basic building block, and we compose functions to create bigger things. To do this, we often have to rephrase the common method calls in terms of regular functions:

const names = [
  'Alissa', 'Max', 'Talib'
];

const upcase = (name) => name.toUpperCase();
const map    = (transform, items) => items.map(transform);
const join   = (separator, items) => items.join(separator);

With this we can express the composition in terms of regular function calls:

const showNames = (names) =>
  join(', ', map(upcase, names));

showNames(names);
// ==> 'ALISSA, MAX, TALIB'

Now, while the way this program expresses its functions is consistent, we still have to move the whole expression into the function, and our code is not as easy to follow as before. Rather than reading from left to right, we have to first read the inside of a function application before we read what's outside of it.

These issues are not a problem with simple expressions such as these, and most of the expressions in functional programming tend to be very simple. But we can improve this by abstracting over the idea of composition. That is, instead of requiring syntactic composition, by having people write out the whole expression, we can ask the program to combine these functions for us. This is what the compose function does:

const compose = require('folktale/core/lambda/compose');

const showNames2 = compose(
  capitals => join(', ', capitals),
  names    => map(upcase, names)
);

Instead of reading from inside of a complex expression to the outside, now you can read each expression on its own, and visualise the flow of data linearly.

CONVENIENCE
compose does right-to-left composition, so the last function is applied first. You can use the infix version of compose to avoid this.

Currying and Composition

Composing unary functions is trivial, as seen on the first example of this documentation:

const compose = require('folktale/core/lambda/compose');

const inc       = (x) => x + 1;
const double    = (x) => x * 2;
const incDouble = compose(double, inc);

But composing functions that take more than one argument doesn't read as naturally:

const upcase = (name) => name.toUpperCase();
const map    = (transform, items) => items.map(transform);
const join   = (separator, items) => items.join(separator);

const showNames = compose(
  capitals => join(', ', capitals),
  names    => map(upcase, names)
);

The problem is that compose can only safely compose unary functions, so you have to do some additional work to place the values in the "right places" when your function takes more than one argument.

One way to solve this problem is to curry the functions that will be composed. That is, turn a function of arity N, into N functions of arity 1:

const upcase2 = (name) => name.toUpperCase();
const map2    = (transform) => (items) => items.map(transform);
const join2   = (separator) => (items) => items.join(separator);

const showNames2 = compose(join2(', '), map2(upcase2));

Note that with a small change to how map and join are defined, we can make a much better use of the compose function. This is not without its drawbacks, however, as now join has to be called as join(',')(names).

CONVENIECE
Folktale offers a curry function as a convenience for automatically creating curried versions of existing functions, which also works around the 1-argument limitation.

See folktale/core/lambda/curry for details.

Another way to work around this problem is by using the partialize function provided by Folktale. This function creates a new function that specifies only parts of the argument for the original function:

const partialize = require('folktale/core/lambda/partialize');
const _          = partialize.hole;

const upcase3 = (name) => name.toUpperCase();
const map3    = partialize(2, (transform, items) => items.map(transform));
const join3   = partialize(2, (separator, items) => items.join(separator));

const showNames3 = compose(
  join3(', ', _),
  map3(upcase, _)
);

See folktale/core/lambda/partialize for more information on how the partialize function works.

Composing More Than Two Functions

The compose operation is limited to composing only two functions. This might sound limiting, but it ensures that function composition is well-defined.

Because a lot of functions in JavaScript are expected to be variadic, and just ignore the additional parameters, you'd get some very surprising behaviour when trying to pass compose to them. For example, the following would throw a type error:

const compose = require('folktale/core/lambda/compose');

const inc    = (x) => x + 1;
const double = (x) => x * 2;
const square = (x) => x * x;

[inc, double, square].reduce(compose)(3);
// ==> throws TypeError: '1 is not a function'

Because it's equivalent to:

const fns = [inc, double, square];
compose(
  compose(
    square,
    double,
    1,
    fns
  ),
  inc
  0,
  fns
)(3);

If you need to compose more than two functions, you can use the infix syntax, or the all convenience function.

Composition With The Infix Syntax

With the This-Binding syntax proposed for JavaScript, it's possible to compose multiple functions in an, arguably, more natural way:

 const compose = require('folktale/core/lambda/compose');

 const then  = compose.infix;
 const inc   = (x) => x + 1;
 const plus4 = inc::then(inc)::then(inc)::then(inc);

 plus4(2);
 // ==> 6

Composition With The all Function

If you need to compose more than two functions, you might consider using the all convenience function instead, which is variadic:

const compose = require('folktale/core/lambda/compose');

const inc   = (x) => x + 1;
const plus4 = compose.all(inc, inc, inc, inc);

plus4(2);
// ==> 6

Composition with the all convenience still happens from right to left.

Properties

Convenience

all(...fns)

Conveniently composes multiple functions.

infix(that)

Conveniently composes function with the This-Binding syntax.

Source Code

Defined in source/core/lambda/compose.js at line 19, column 0
(f, g) => (value) => f(g(value))
Stability
stable
Licence
MIT
Module
folktale/core/lambda/compose
Authors
Copyright
(c) 2013-2017 Quildreen Motta, and CONTRIBUTORS
Authors
  • Quildreen Motta
Maintainers
  • Quildreen Motta <queen@robotlolita.me> (http://robotlolita.me/)