Forever Functional: Higher-order functions with TypeScript
Tags: typescript, functional, programming
Published: 2022-11-22 00:16:09

by [Federico Kereki](https://blog.openreplay.com/forever-functional-higher-order-functions-with-typescript/)

In a [previous article in this series](https://blog.openreplay.com/forever-functional-higher-order-functions-functions-to-rule-functions/), I discussed several types of higher-order functions (HOF) whose arguments and results may be functions themselves. All the examples there, were done in vanilla JavaScript, but nowadays it's more common to work with TypeScript, so we need to add appropriate data typing to our code. However, this is more than just trivial! 

In this article, I'll review some examples from my previous article, focusing instead on the necessary data types and additional requirements that we'll also have to consider. And, as a bonus, we'll consider an extra method with several typing quirks of its own!

## Wrapping functions

This type of HOF takes a function as an argument and returns a new one with the same functionality but some added features like logging or timing. Let's work with the former because solutions for both cases are practically identical.

Suppose we have a function and want to automatically add some logging, so we'll see what arguments it got and what value it returned. In plain JavaScript, we'd write something like this.

```javascript
function addLogging(fn) {
  return (...args) => {
    console.log(`entering ${fn.name}(${args})`);
    try {
      const valueToReturn = fn(...args);
      console.log(`exiting  ${fn.name}=>${valueToReturn}`);
      return valueToReturn;
    } catch (thrownError) {
      console.log(`exiting  ${fn.name}=>threw ${thrownError}`);
      throw thrownError;
    }
  };
}
```

Upon entering, we log whatever arguments we get. Then, we try to call the original function, store the returned value, log it, and return it. If the function throws an exception, we log that and throw the error up for processing. How do we write this in TypeScript?

```javascript
function addLogging<T extends (...args: any[]) => any>(
  fn: T
): (...args: Parameters<T>) => ReturnType<T> {
  return (...args: Parameters<T>): ReturnType<T> => {
    .
    . everything as above
    .
  };
}
```

Our `addLogging()` HOF will need a generic type definition because it can deal with all kinds of functions with different data types involved. The first line defines a generic type, `T`, that represents a function with an unknown number of arguments, of any type, and returns a value, also of any type. In the second line, we specify that the input function `fn` is of type `T`. So far, so good!

How do we specify the format of the returned function? The key here is that the output type must fully match the input function. We cannot just say that the returned function will have any arguments; we have to say that they match the types of the parameters of `T`, which is written as `Parameters<T>` using a [utility type](https://www.typescriptlang.org/docs/handbook/utility-types.html) in TypeScript.

Similarly, we cannot just say that the output function's result is of any type. We want to say it will be the same type as the result of our function of type T. For this, TypeScript provides another utility type, `ReturnType<T>`.

Now we can understand more clearly the first four lines: we defined a generic type of function `T`, we said that the input function was of that type, and we said that our HOF would return a new function whose parameters and result would have the same types as those of `T`; neat!

Let's now consider a different kind of problem, where the output function need not match the typing for the input one.

## Altering functions

These HOFs take a function as input and produce a new function with changed functionality. In the previous article, we considered examples such as negating conditions (to simplify filtering operations) or changing the number of parameters (arity) of functions. Let's review the first one for our analysis, which will provide a reasonably simple example of the needed data typing.

Suppose we have a function that tests for a condition; with it we can write code as `someData.filter(testCondition)`, and the resulting array will only include the array elements that satisfy the test. But what would we do if we wanted the elements that did *not* satisfy the condition? If we had a `not()` HOF to negate a predicate (that is, to produce the opposite result), we could just write `someData.filter(not(testCondition))`, and it would be clear enough.

A simple implementation of not() could be the following -- and let's go with an arrow function for variety, so we'll see how TypeScript works with those.
```javascript
const not = (fn) => (...args) => !fn(...args);
```

Given a function, we create a new function that returns the opposite of whatever the original function would have returned. How do we write data types for this?

```javascript
const not =
  <T extends (...args: any[]) => boolean>(fn: T) =>
  (...args: Parameters<T>): boolean =>
    !fn(...args);
```

We'll write data types similar to what we had in the previous section. The second line shows how a generic function is specified; the generic type T goes before the definition of the function, after the assignment operator. In this case, since we want to apply `not()` to boolean functions, we will be clear and say that's the expected type of the input `fn` function, also in the second line. 

What will be returning? We'll produce a new function whose parameters must match those of `fn` (in the same way as with `logging())`, and the result type will be `boolean`. If you prefer, write `ReturnType<T>` instead; whatever you think is clearer.

The experience gained with the `logging()` HOF was helpful with  `not()`; let's see the third example, with an extra complication.



<h2>Open Source Session Replay</h2> 

_<a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a> is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data._

![OpenReplay](https://blog.openreplay.com/banner-blog_1oYPGT.png)

_Start enjoying your debugging experience - <a href="https://github.com/openreplay/openreplay" >start using OpenReplay for free</a>._




## Creating new functions

The last type of HOFs that we considered in [the previous article](https://blog.openreplay.com/forever-functional-higher-order-functions-functions-to-rule-functions/) were able to produce new functions of their own. We discussed converting a method into a function (demethodizing) or transforming a callback-based async function into a promise. Let's see how we would demethodize functions, which provides an excellent example of the problems we need to solve.

In the [previous article](https://blog.openreplay.com/forever-functional-higher-order-functions-functions-to-rule-functions/), we showed how to "demethodize" any method and transform it into a function. We saw a way to do this with `bind()`:

```javascript
const demethodize = (fn) => (...args) => fn.bind(...args)();
```

How do we convert this to TypeScript? We could think of using the same kind of solution by defining a type `T` and writing:

```javascript
const demethodize =
  <T extends (...args: any[]) => any>(fn: T) =>
  (...args: Parameters<T>): ReturnType<T> =>
    fn.bind(...args)();
```

But this won't work! The last line will be marked as an error: "*A spread argument must either have a tuple type or be passed to a rest parameter. ts(2556)*" What's going on? The issue is that we cannot use the spread syntax (which allows for any number of arguments) with functions that expect a fixed number of parameters. In this case, `bind()` requires at least one first argument: the object that will assume the value of `this`. 

How do we specify that the variable number of arguments includes at least one? The simplest way is to separate the initial argument as follows:

```javascript
const demethodize =
  <T extends (arg0: any, ...args: any[]) => any>(fn: T) =>
  (arg0: any, ...args: Parameters<T>): ReturnType<T> =>
    fn.bind(arg0, ...args)();
```

We are telling TypeScript that the list of arguments to our "demethodized" method will always include at least an initial `arg0`; problem solved!

## Creating new methods

Let's have an extra HOF for additional challenges! Instead of "demethodizing" a method to convert it into a function, what about "methodizing" a function to add it to some object's prototype?

For instance, we could want to add a `reverse()` method to strings, so we could write something like `"URUGUAY".reverse()` and get `"YAUGURU"`. (Arrays already have a `reverse()` method, but strings do not.) First, we should write the needed function.

```javascript
function reverse(x: string): string {
  return x.split("").reverse().join("");
}
```

The first parameter for any function to be "methodized" must always be whatever object the method will work on. Working in JavaScript, to add a new method to a prototype, we would require a `methodize()` function like this:

```javascript
function methodize(obj, fn) {
  obj.prototype[fn.name] = function (...args) {
    return fn(this, ...args);
  };
}
```

With it, we could write `methodize(String, reverse)`, and from now on all strings will have gained a brand new `reverse()` method; good! But how do we write this in TypeScript? The answer is not too easy!

```javascript
function methodize<
  T extends any[],
  F extends (arg0: any, ...args: T) => any,
  O extends { prototype: { [key: string]: any } }
>(obj: O, fn: F) {
  obj.prototype[fn.name] = function (
    this: Parameters<F>[0],
    ...args: T
  ): ReturnType<F> {
    return fn(this, ...args);
  };
}
```

Wow! The first two generic types look familiar. `T` represents a varying number of parameters, and `F` is a function with a mandatory first `arg0` parameter (as we saw in the previous section). The seventh line, the one starting with `this:`, is interesting too. You cannot have an argument or variable called `this`, and here we're saying that the value of `this` will be the same type as `arg0`. The actual function won't have an extra argument; this is just a [trick used by TypeScript](https://www.typescriptlang.org/docs/handbook/2/functions.html#declaring-this-in-a-function).

But what about type `O`? The issue is that TypeScript objects to our assignment to `obj.prototype`, because it cannot tell if `obj` actually has a `prototype` attribute. Type `O` says that the object to which we'll attach a new method does actually have a `prototype`, so everything is OK.

Are we done? Unhappily, no. If we write the following `"URUGUAY" .reverse()` as we wanted, we'll get an error: "*Property' reverse' does not exist on type '"MONTEVIDEO"'. ts(2339)*". The problem is that TypeScript works with static typing and cannot tell that the `reverse()` will eventually exist. So, we need to add a global declaration like this:

```javascript
declare global {
  interface String {
    reverse(): string;
  }
}
```

Everything will finally be OK with this definition (that could also appear in a `.d.ts` file); we added a new method to all strings and let TypeScript know about it -- great!

## Conclusion

Working with higher-order functions in TypeScript and writing correct data typing for them can sometimes be a frustrating puzzle, requiring several attempts, back and forth, until the right solution is achieved. I hope this article will help you on your way to modern, typed Functional Programming!

> A TIP FROM THE EDITOR: It will help if you read the original [Higher Order Functions -- Functions To Rule Functions](https://blog.openreplay.com/forever-functional-higher-order-functions-functions-to-rule-functions/) article, in which all these functions are explained.




[![newsletter](https://blog.openreplay.com/newsletter_Z1P0UIP.png)](https://newsletter.openreplay.com/)