Creating a typed "compose" function in TypeScript
Tags: typescript, functional
Published: 2019-01-02 10:08:53

---
title: Creating a typed "compose" function in TypeScript
published: true
description: 
tags: typescript, fp, functional
---

I recently wrote a couple of small utility functions that turned into a deep exploration of TypeScript generics, as well as the new typed tuples in TypeScript 3. This post assumes you know at least a bit about generics in TypeScript. If you don't, I highly recommend [reading up on them](https://www.typescriptlang.org/docs/handbook/generics.html), as they're one of the most powerful features in the language. It assumes no prior knowledge of functional programming.

One of the core concepts in functional programming is composition, which is where several functions are combined into one function that performs all of the tasks. The output of one function is passed to the input of another. Lots of libraries include a `compose` function to help with this, including Lodash and Ramda, and it also a pattern used a lot in React. These helpers allow you to turn this:

```js
const output = fn1(fn2(fn3(fn4(input))));
```
into this:

```js
const composed = compose(fn1, fn2, fn3, fn4);

const output = composed(input);
```

The new `composed` function can then be reused as needed. The partner of `compose` is `pipe`, which is just compose with the arguments reversed. In this case the arguments are in the order they are executed, rather than the order they appear when nested. In the compose example above, it is the inner `fn4` function that is called first, the output of which is then passed out to `fn3`, then `fn2` and `fn1`. In a lot of situations it is more intuitive to think of the functions like a Unix pipe, when the value is passed in to the left, then piped from each function to the next. As a contrived example, the shell command to find the largest files in a directory would be:

```bash
du -s * | sort -n | tail
```

Imagine this in an imaginary JavaScript environment:

```typescript
const largest = tail(sort(du("*")));
```

You could implement it with pipe as:

```typescript
const findLargest = pipe(du, sort, tail);
const largest = findLargest("*");
```

In a more realistic example, imagine loading a JSON file, performing several operations on it, then saving it.

Best of all, a real world example: I implemented this because I was working on an Alexa quiz skill, where I was using a functional approach to handling requests. I passed an object that contained the request and response data through a series of handlers that checked if the user had answered the question, then asked the next question, then completed the game if appropriate.

```typescript
const handler = pipe(handleAnswer, askQuestion, completeGame);
const params = handler(initialParams);
```

For this I had defined the interface for params, and wanted to be able to type the arguments. I wanted the signature of `handler` to match the signature of the functions passed-in. I also wanted TypeScript to ensure that all of the functions passed-in were all of the same type. I also wanted it to be able to infer this, as it's annoying to have to specify types unnecessarily. I wasn't able to find any TypeScript library that supported this for arbitrary numbers of arguments, so let's go ahead and write one.

First, let's write the actual functions, and then work out how to type them. This is quite simple using the built-in [`Array.reduce`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce). We'll start with `pipe`, as that doesn't require any changes to the argument order. Let's remind ourselves of the signature of the simplest form of `reduce`:

```typescript
function reduce(callbackfn: (previousValue: T, currentValue: T) => T): T;
```

`reduce` is passed a callback function that is called for each element in the array. The first argument passed to the the callback is the return value from the previous callback. The second argument is the next value from the array. For the short version of reduce, the first call to callback actually passes the first element in the array as `previousValue`, and the second as `currentValue`. There is a longer version that lets you pass an initial value, which is what you need to do if the return value will be different from the type of the array elements. We'll start with the simpler version:

```typescript
export const pipe = (...fns) =>
  fns.reduce((prevFn, nextFn) => value => nextFn(prevFn(value)));
```

This gradually builds up the composed function step-by-step, adding the next function in the array as we iterate through it. The callback returns a new function that in turn calls `prevFn` (which is the function composed from the previous functions in the array), and then wraps that in a call to `nextFn`. On each call, it wraps the function in the next one, until finally we have one function that calls all of the elements in the array.

```typescript
export const pipe = <R>(...fns: Array<(a: R) => R>) =>
  fns.reduce((prevFn, nextFn) => value => nextFn(prevFn(value)));
```

This looks quite confusing (as generic types often do) but it's not as bad as it looks. The `<R>` is a placeholder for the return type of the function. While this is a generic function, the neat thing is that TypeScript can infer this type from the type of the arguments that are passed to it: if you pass it a string, it knows that it will return a string. The signature says that `pipe` accepts any number of arguments, which are all functions that accept one argument and return a value of the same type as that argument. This isn't quite right though: `pipe` needs at least one argument. We need to change the signature to show the first argument is required. We do this by adding an initial parameter with the same type, and then passing that as the second argument to `reduce`, which means it's used as the starting value.

```typescript
export const pipe = <R>(fn1: (a: R) => R, ...fns: Array<(a: R) => R>) =>
  fns.reduce((prevFn, nextFn) => value => nextFn(prevFn(value)), fn1);
```

Now we have defined `pipe`, defining `compose` is as simple as switching the order of `nextFn` and `prevFn`:

```typescript
export const compose = <R>(fn1: (a: R) => R, ...fns: Array<(a: R) => R>) =>
  fns.reduce((prevFn, nextFn) => value => prevFn(nextFn(value)), fn1);
```

Before we go any further, we need to test that it's all working as expected. I like to use [Jest](https://jestjs.io/) for testing, so let's define some tests to see how it should be working:

```typescript
import { compose, pipe } from "./utils";

describe("Functional utils", () => {
  it("composes functions", () => {
    const fn1 = (val: string) => `fn1(${val})`;
    const fn2 = (val: string) => `fn2(${val})`;
    const fn3 = (val: string) => `fn3(${val})`;
    const composedFunction = compose(fn1, fn2, fn3);
    expect(composedFunction("inner")).toBe("fn1(fn2(fn3(inner)))");
  });
  it("pipes functions", () => {
    const fn1 = (val: string) => `fn1(${val})`;
    const fn2 = (val: string) => `fn2(${val})`;
    const fn3 = (val: string) => `fn3(${val})`;

    const pipedFunction = pipe(fn1, fn2, fn3);
    expect(pipedFunction("inner")).toBe("fn3(fn2(fn1(inner)))");
  });
});
```

These functions just return strings showing that they were called. They're using template literals, if you're not aware of the backtick syntax. Take a look at the typings of the composed functions to see how they're doing, and try changing the signatures of the functions to see that type-checking works.

![inferred function signature](https://thepracticaldev.s3.amazonaws.com/i/qcfqsxrw8lmzu1laxwrp.png)

Here you can see that the type of `pipedFunction` has been inferred from the types of the functions passed to `pipe`.

![bad function type](https://thepracticaldev.s3.amazonaws.com/i/jdtoy69r64f5kx7fvqoy.png)

Here we can see that changing `fn2` to expect a number causes a type error: the functions should all have the same signature. 

We'd get a similar error if we passed a function that accepted more than one argument. However this doesn't need to be a limitation of a `compose` or `pipe` function. Strictly speaking, the first function could accept anything, as long as it returns the same type, and all of the other functions take a single argument. We should be able to get the following working:

```typescript
it("pipes functions with different initial type", () => {
  const fn1 = (val: string, num: number) => `fn1(${val}-${num})`;
  const fn2 = (val: string) => `fn2(${val})`;
  const fn3 = (val: string) => `fn3(${val})`;

  const pipedFunction = pipe(fn1, fn2, fn3);
  expect(pipedFunction("inner", 2)).toBe("fn3(fn2(fn1(inner-2)))");
});
```

The composed or piped function should have the same signature as the first function. So how should we type this? We can change the type of `fn1` to allow different arguments, but then we lose our type safety of those arguments:

```typescript
export const pipe = <R>(
  fn1: (...args: any[]) => R,
  ...fns: Array<(a: R) => R>
) => fns.reduce((prevFn, nextFn) => value => nextFn(prevFn(value)), fn1);
```

We need another generic type to represent the args of `fn1`. Before TypeScript 3, we'd be reduced to adding loads of overloads:

```typescript
export function pipe<T1, R>(
  fn1: (arg1: T1) => R,
  ...fns: Array<(a: R) => R>
): (arg1: T1) => R;

export function pipe<T1, T2, R>(
  fn1: (arg1: T1, arg2: T2) => R,
  ...fns: Array<(a: R) => R>
): (arg1: T1, arg2: T2) => R;

export function pipe<T1, T2, T3, R>(
  fn1: (arg1: T1, arg2: T2, arg3: T3) => R,
  ...fns: Array<(a: R) => R>
): (arg1: T1, arg2: T2, arg3: T3) => R;

export function pipe<T1, T2, T3, T4, R>(
  fn1: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => R,
  ...fns: Array<(a: R) => R>
): (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => R;

export function pipe<R>(
  fn1: (...args: any[]) => R,
  ...fns: Array<(a: R) => R>
): (a: R) => R {
  return fns.reduce((prevFn, nextFn) => value => nextFn(prevFn(value)), fn1);
}
```

This is clearly ridiculous. Luckily TypeScript 3 introduces [typed `...rest` parameters](https://blogs.msdn.microsoft.com/typescript/2018/07/30/announcing-typescript-3-0/#tuples-and-parameters). This lets us do this, which works with any number of arguments:

```typescript
export const pipe = <T extends any[], R>(
  fn1: (...args: T) => R,
  ...fns: Array<(a: R) => R>
) => {
  const piped = fns.reduce(
    (prevFn, nextFn) => (value: R) => nextFn(prevFn(value)),
    value => value
  );
  return (...args: T) => piped(fn1(...args));
};
```

We've added a new generic type `T`, which represents the arguments of the first function. Before TypeScript 3 this code gave an error, but now by stating that it `extends any[]`, the compiler accepts it as a typed tuple parameter list. We can't pass `fn1` directly to reduce, as we were doing before, as it is now a different type. Instead we pass the identity function `value => value` as the second value - a function that just returns its argument, unchanged. We then wrap the reduced function in another function with the correct type and return that.

This gives us a piped function with the same type as its first argument:

![inferred type](https://thepracticaldev.s3.amazonaws.com/i/l2hqqdsysr3wr5siibj1.png)

It still type-checks the other arguments too: they must all be functions that accept one argument of the same type returned by the first function, and they must all return the same type.

So where does this leave `compose`? Unfortunately we can't type it in the same way. While `pipe` takes its type from the first function passed to it, `compose` uses the type of the last function. Typing that would require `...rest` arguments at the beginning of the argument list, which aren't supported yet: 
{% github https://github.com/Microsoft/TypeScript/issues/1360 %} 
Until then you'll need to stick with composing functions that accept just one argument.

The final library is here:

```typescript
export const pipe = <T extends any[], R>(
  fn1: (...args: T) => R,
  ...fns: Array<(a: R) => R>
) => {
  const piped = fns.reduce(
    (prevFn, nextFn) => (value: R) => nextFn(prevFn(value)),
    value => value
  );
  return (...args: T) => piped(fn1(...args));
};

export const compose = <R>(fn1: (a: R) => R, ...fns: Array<(a: R) => R>) =>
  fns.reduce((prevFn, nextFn) => value => prevFn(nextFn(value)), fn1);

```

The tests for it, which can show usage:

```typescript
import { compose, pipe } from "./utils";

describe("Functional helpers", () => {
  it("composes functions", () => {
    const fn1 = (val: string) => `fn1(${val})`;
    const fn2 = (val: string) => `fn2(${val})`;
    const fn3 = (val: string) => `fn3(${val})`;
    const composedFunction = compose(fn1, fn2, fn3);
    expect(composedFunction("inner")).toBe("fn1(fn2(fn3(inner)))");
  });

  it("pipes functions", () => {
    const fn1 = (val: string) => `fn1(${val})`;
    const fn2 = (val: string) => `fn2(${val})`;
    const fn3 = (val: string) => `fn3(${val})`;

    const pipedFunction = pipe(fn1, fn2, fn3);

    expect(pipedFunction("inner")).toBe("fn3(fn2(fn1(inner)))");
  });

  it("pipes functions with different initial type", () => {
    const fn1 = (val: string, num: number) => `fn1(${val}-${num})`;
    const fn2 = (val: string) => `fn2(${val})`;
    const fn3 = (val: string) => `fn3(${val})`;
    const pipedFunction = pipe(fn1, fn2, fn3);

    expect(pipedFunction("inner", 2)).toBe("fn3(fn2(fn1(inner-2)))");
  });
});

```