Higher Order Guards (Functions)
Tags: beginners, programming, tutorial, typescript
Published: 2022-06-14 12:14:59

TypeScript Narrowing #6

{% embed https://www.youtube.com/embed/0kmllUg1-48 %}
_See this and many other articles at [lucaspaganini.com](lucaspaganini.com)_

Oofff, it’s part 6 already! I wonder how many of you are reading this since the beginning.

Today we'll grab a pattern from the functional programming world known as "Higher Order Functions" and use it to create functions that receive functions and return new functions.

```ts
function f1() {}
```

```ts
function f1(f2: Function) {}
```

But we won't stop there. We won't just return _any_ new functions, more precisely, we will return new custom type guards! So, I'm calling those guard creation functions "Higher Order Guards".

```ts
const makeIsNot = fn => ✨magic✨
```

As you'll soon find out, that will open the door for new possibilities of reusing our code.

```ts
const makeIsNot = fn => ✨magic✨

const isNotString = makeIsNot(isString)

let aaa = 'abc' as  string | number | boolean
if (isNotString(aaa)) {
  aaa // <- aaa: number | boolean
} else {
  aaa // <- aaa: string
}
```

<p>I'm Lucas Paganini, and in this blog, we release web development tutorials. <app-newsletter-modal-trigger>Subscribe</app-newsletter-modal-trigger> if you're interested in that.</p>

## Higher Order Functions

The theoretical description of a higher order function is a tongue twister: a function that receives a function and returns another function. So let me show it to you in practice, and you'll see that it's not as complex as it sounds.

Let's say we have a lot of custom type guards, and now we want inverted versions of them.

- We already have `isString`, now we want `isNotString`.
- We already have `isNumber`, now we want `isNotNumber`.
- You get the idea...

We did something very similar in the end of our third article, when we wrote a guard for truthy values that works by _excluding_ falsy values.

```ts
type Truthy<T> = Exclude<T, Falsy>;

const isTruthy = <T extends unknown>(value: T): value is Truthy<T> =>
  value == true;

// Test
let x: null | string | 0;
if (isTruthy(x)) {
  x.trim(); // <- x: string
}
```

We can apply the same technique to create our inverted type guards. That's how they would look like:

```ts
const isNotString = <V extends unknown>(
  value: V
): value is Exclude<V, string> => isString(value) === false;

const isNotNumber = <V extends unknown>(
  value: V
): value is Exclude<V, number> => isNumber(value) === false;
```

But writing those inverted guards manually is tedious and repetitive. I bet you can see a pattern in them: all we need to create an inverted guard, is the custom type guard that will be inverted.

In other words: the only difference between `isNotString` and `isNotNumber`, is that while one uses the `isString` guard, the other uses the `isNumber` guard.

## Higher Order Guards

Could we stop repeating ourselves and create a function that accepts a type guard as an argument and returns the inverted version of the given type guard?

```ts
const makeIsNot = (fn) => (v) => !fn(v);

const isNotString = makeIsNot(isString);
```

Hell yeah we can! Let's create it now!

I have a personal convention of prefixing functions with the word `make` when they return new functions. So, it _makes_ sense to me to call our function `makeIsNot`, since it _makes_ the _is not_ version of a type guard.

## `makeIsNot` Implementation

The function implementation alone, is already a bit tricky, so I'll navigate it with you before we get into the TypeScript signature.

Let's use `isNotString` as an example.

```ts
const makeIsNot = (fn) => (v) => !fn(v);

const isNotString = makeIsNot(isString);
```

Calling `makeIsNot` with `isString`, returns a function that receives one argument (called `v`), and returns the inverted return of calling `isString` with `v`.

```ts
const makeIsNot = (fn) => (v) => !fn(v);

const isNotString = makeIsNot(isString);
```

```ts
const makeIsNot = (fn) => (v) => !fn(v);

const isNotString = (
  (fn) => (v) =>
    !fn(v)
)(isString);
```

The same works for `isNotNumber`. Calling `makeIsNot` with `isNumber`, returns a function that receives one argument (called `v`), and returns the inverted return of calling `isNumber` with `v`.

```ts
const makeIsNot = (fn) => (v) => !fn(v);

const isNotNumber = makeIsNot(isNumber);
```

```ts
const makeIsNot = (fn) => (v) => !fn(v);

const isNotNumber = (
  (fn) => (v) =>
    !fn(v)
)(isNumber);
```

## `makeIsNot` Signature

All is good and well with the implementation, now, to the type signature of `makeIsNot`.

```ts
type MakeIsNot = <F extends (v: unknown) => v is any>(
  fn: F
) => <V extends Parameters<F>[0] = Parameters<F>[0]>(
  v: V
) => v is Exclude<V, F extends (v: unknown) => v is infer T ? T : never>;

const makeIsNot: MakeIsNot = (fn) => (v) => !fn(v);
```

Let's break it down and see what can be simplified.

## Type Guard Function Type

First, there are two places where we're referring to a function that returns a type predicate (in other words, a custom type guard).

Let's create a type, called `TypeGuardFunction`, to isolate that type definition and simplify our code a little.

```ts
type TypeGuardFunction<T = any> = (v: unknown) => v is T;

type MakeIsNot = <F extends TypeGuardFunction>(
  fn: F
) => <V extends Parameters<F>[0] = Parameters<F>[0]>(
  v: V
) => v is Exclude<V, F extends TypeGuardFunction<infer T> ? T : never>;

const makeIsNot: MakeIsNot = (fn) => (v) => !fn(v);
```

A little better, right?

## Predicate Function Type

Also, even though the naming `TypeGuardFunction` makes a lot of sense, since it is indeed a type guard function, this name is very specific to TypeScript. And it turns out that functions that receive an argument and return a `boolean` already had a name before TypeScript even existed. Those functions are known as "Predicate Functions".

So, let's use the name `PredicateFunction` instead.

```ts
type PredicateFunction<T = any> = (v: unknown) => v is T;

type MakeIsNot = <F extends PredicateFunction>(
  fn: F
) => <V extends Parameters<F>[0] = Parameters<F>[0]>(
  v: V
) => v is Exclude<V, F extends PredicateFunction<infer T> ? T : never>;

const makeIsNot: MakeIsNot = (fn) => (v) => !fn(v);
```

## Unpack Predicate Function Type

Also, not that it repeats, but that part where we're inferring the type of the `PredicateFunction` is kinda ugly to look at. Let's isolate that in a type.

👉 If you're at a lost with the ternary operator and the `infer` keyword, I have two videos for you. Both are one minute long. One explains conditional types in TypeScript (the ternary operator), and the other explains the `infer` operator. Their links are in the description.

I have another personal convention which is to use the prefix `Unpack` when I'm creating a type that infers something. So I'll call it `UnpackPredicateFunction`.

```ts
type PredicateFunction<T = any> = (v: unknown) => v is T;

type UnpackPredicateFunction<F extends PredicateFunction> =
  F extends PredicateFunction<infer T> ? T : never;

type MakeIsNot = <F extends PredicateFunction>(
  fn: F
) => <V extends Parameters<F>[0] = Parameters<F>[0]>(
  v: V
) => v is Exclude<V, UnpackPredicateFunction<F>>;

const makeIsNot: MakeIsNot = (fn) => (v) => !fn(v);
```

## Back to the Signature

Ok... it's not _simple_. But it is _simpler_. Let's try to analyze the signature now.

1. First, we receive an argument, a `PredicateFunction` called `fn`;
2. Then, we return a new function;
3. This new function receives an argument, called `v`. Which has the same type of the first parameter of `fn`;
4. And that, returns a type predicate saying that `v` is _not_ of the type guarded by our `fn` function.

```ts
type PredicateFunction<T = any> = (v: unknown) => v is T;

type UnpackPredicateFunction<F extends PredicateFunction> =
  F extends PredicateFunction<infer T> ? T : never;

type MakeIsNot = <F extends PredicateFunction>(
  fn: F
) => <V extends Parameters<F>[0] = Parameters<F>[0]>(
  v: V
) => v is Exclude<V, UnpackPredicateFunction<F>>;

const makeIsNot: MakeIsNot = (fn) => (v) => !fn(v);
```

## Library

There are some limitations to our function. For example, right now, it only works with guards that receive a single argument. Also, it's not tested.

```ts
type PredicateFunction<T = any> = (v: unknown) => v is T;

type UnpackPredicateFunction<F extends PredicateFunction> =
  F extends PredicateFunction<infer T> ? T : never;

type MakeIsNot = <F extends PredicateFunction>(
  fn: F
) => <V extends Parameters<F>[0] = Parameters<F>[0]>(
  v: V
) => v is Exclude<V, UnpackPredicateFunction<F>>;

const makeIsNot: MakeIsNot = (fn) => (v) => !fn(v);

const isNotString = makeIsNot(isString);
```

If you're interested in having `makeIsNot` in your codebase (and also `makeIsInstance`, `makeIsIncluded`, and a lot more), instead of copying the code from this article, a better way is to just install my TypeScript utilities library.

```ts
import { makeIsNot } from '@lucaspaganini/ts';

const isNotString = makeIsNot(isString);
```

```ts
import { makeIsNot, makeIsInstance } from '@lucaspaganini/ts';

const isNotString = makeIsNot(isString);
const isArray = makeIsInstance(Array);
```

```ts
import { makeIsNot, makeIsInstance, makeIsIncluded } from '@lucaspaganini/ts';

const isNotString = makeIsNot(isString);
const isArray = makeIsInstance(Array);
const isFamousCat = makeIsIncluded(['Garfield', 'Tom']);
```

- It's open source
- Has tests
- Documentation
- Works on Node and Browsers
- It's MIT
- And you can easily install it with `npm install @lucaspaganini/ts`.

```bash
npm install @lucaspaganini/ts
```

We'll talk more about that library in the next article.

## Conclusion

Today's content was pretty advanced. I remember how hard it was for me to learn functional programming and advanced TypeScript notations, so we really did our best with the examples and animations to hopefully, give you an easier learning experience than the one I had.

I would love to have a feedback from you. So, please [send me a tweet](https://twitter.com/LucasPaganini) and let us know if you could understand everything, and your questions, if you have any.

References are below. If you enjoyed the content, you know what to do.

And if your company is looking for remote web developers, consider contacting me and my team on [lucaspaganini.com](https://lucaspaganini.com/).

In the next article, we will use our newly found knowledge to create a workaround for a highly requested feature in TypeScript: asynchronous type guards. Subscribe if you don't want to miss it.

Until then, have a great day, and I’ll see you soon.

## References

1. [Higher Order Functions _Clojure Documentation_ ](https://clojure.org/guides/higher_order_functions#_higher_order_functions)
2. [Functional Programming - Predicate Functions _Stanford Education_](https://dcl-prog.stanford.edu/function-predicate.html)
3. [TypeScript Utilities Library - `@lucaspaganini/ts` _GitHub Repository_](https://github.com/LucasPaganini/ts)