## DEV Community is a community of 662,276 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

# Practical Guide to Fp-ts P5: Apply, Sequences, and Traversals Ryan Lee
Engineering @AWS. Formerly @Interac. Writing about Typescript, Functional Reactive Programming, Blockchain/Crypto
Originally published at rlee.dev Updated on ・6 min read

## Introduction

Welcome to part 5 of this series on learning fp-ts the practical way.

By now you've been introduced to the operators `of`, `map`, `chain`, `flatten`, but there's one operator we haven talked about yet: `ap` or apply. The `ap` operator is a greater part of what is called an Applicative. And applicatives form the basis for sequences and traversals.

In this post, I will explain the rationale for `ap`, its usecases, and how we don't actually need it because we have sequences and traversals.

## Apply

What is the mysterious `ap` operator, otherwise known as Apply?

In many ways, it is like the reverse of `map`. Rather than piping a value into a function, you pipe a function into a value.

To demonstrate this, lets learn about currying. Currying is taking a function with multiple parameters and converting it into a higher order function such that it takes a single argument repeatedly.

For example, we can have a `write` function that takes 3 parameters.

``````declare function write(key: string, value: string, flush: boolean): unknown
``````

And we can convert it into a curried function like so:

``````const writeC = (key: string) => (value: string) => (flush: boolean) =>
write(key, value, flush)
``````

Trivially we can call the function like this:

``````writeC('key')('value')(true)
``````

And, if we wanted to do the same with our pipe syntax we could try something like this.

``````// ❌ Wrong
pipe(true, 'value', 'key', writeC)
``````

But unfortunately this doesn't work because pipeline is evaluated from left-to-right; the compiler will complain that `true` cannot be piped into `value` and `value` cannot be piped into `key`. To make this work, we will need to enforce the order of operations (just like in math), with more pipes!

``````// ✅ Correct
pipe(true, pipe('value', pipe('key', writeC)))
``````

Now the compiler understands because we force the right side to evaluate first. However, this syntax isn't ideal because its annoying to add additional pipes for the sake of ordering.

The solution to this is `ap`.

``````import { ap } from 'fp-ts/lib/Identity'

pipe(writeC, ap('key'), ap('value'), ap(true))
``````

Remember when I said `ap` is just piping a function into a value? This is exactly what you see here.

`writeC` is piped into `key` which forms the function `(value: string) => (flush: boolean) => write(key, value, flush)`. This function is piped into `value` which forms the function `(flush: boolean) => write(key, value, flush)`. And finally, this last function is piped into `true` which calls our 3 parameter write function: `write(key, value, flush)`.

In essence, `ap` just makes it easier to curry function values while keeping the correct order of operations.

Another use case for `ap` is when you have functions and values that don't play well together because one of them is trapped inside an `Option` or an `Either`, etc... `ap` is useful in this scenario because it can lift values or functions into a particular category.

To demonstrate, lets look at an example.

``````import * as O from 'fp-ts/lib/Option'
import { Option } from 'fp-ts/lib/Option'

declare const a: Option<number>
declare const b: Option<string>
declare function foo(a: number, b: string): boolean
``````

As you can see, we want to call `foo` using our variables `a` and `b`, but the problem is: `a` and `b` are in the Option category while `foo`
takes plain values.

A naive way of executing `foo` is to use `chain` and `map`.

``````// Option<boolean>
O.option.chain(a, (a1) => O.option.map(b, (b1) => foo(a1, b1)))
``````

But this is terrible because:

1. We have to awkwardly name our variables with a number suffix because we don't want to shadow the outer variable.
2. It doesn't scale if we have more parameters.
3. Its ugly and confusing.

Lets try again.

First we need to convert `foo` into a curried function `fooC`.

``````const fooC = (a: number) => (b: string) => foo(a, b)
``````

Then it is just the same thing as we did before, BUT we need to lift `fooC` into the `Option` category using `of`, because the `Option` version of `ap` must operate on two options.

``````// Option<boolean>
pipe(O.of(fooC), O.ap(a), O.ap(b))
``````

Lets extend the example a bit further. Let say we had another function `bar` that takes a boolean (the return value of `foo`) and returns an `object`. Naturally, we want to call `foo` and subsequently `bar` with the return value of `foo`.

We have already computed `foo` as an `Option<boolean>`, so this is nothing more than a simple lift into `ap`

``````declare function bar(a: boolean): object

const fooOption = pipe(O.of(fooC), O.ap(a), O.ap(b))

// Option<object>
pipe(O.of(bar), O.ap(fooOption))
``````

Cool, `ap` is clearly powerful. But what are the problems with `ap`?

First, its boring to have to curried every function in existence just to use fp.

Second, reversing the order of the input value of a function inside of a pipe from left-to-right to right-to-left breaks the natural `flow` of operations.

In the real world, there's hardly a usecase for `ap` because we can leverage sequences instead.1

## Sequences

So what is a sequence?

In math, we think of a sequence as a sequence of numbers. Similarly, we can apply this to a sequence of Options, a sequence of Eithers, etc...

The most common usecase for a sequence is convert an array of say Options into an Option of an array.

``````// How?
Array<Option<A>> => Option<A[]>
``````

To do this, you need to provide sequence an instance of `Applicative`. An applicative has 3 methods: `of`, `map`, and `ap`. This applicative defines the type of the objects inside of the collection. For a list of `Options`, we would provide it with `O.option`.

``````import * as A from 'fp-ts/lib/Array'
import * as O from 'fp-ts/lib/Option'

const arr = [1, 2, 3].map(O.of)
A.array.sequence(O.option)(arr) // Option<number[]>
``````

Now we lets go back to the problem: how do we use sequence such that we don't have to write a curried function and use `ap`?

Enter `sequenceT`.

### SequenceT

`sequenceT` is the same as a regular `sequence` except you pass it a rest parameter (vararg). The return value is the provided applicative with a tuple as the type parameter.

For example:

``````//  Option<[number, string]>
sequenceT(O.option)(O.of(123), O.of('asdf'))
``````

Now you see where this is going. We can just pipe this into our original `foo` and `bar` functions.

``````declare function foo(a: number, b: string): boolean
declare function bar(a: boolean): object

// Option<object>
pipe(
sequenceT(O.option)(O.of(123), O.of('asdf')),
O.map((args) => foo(...args)),
O.map(bar),
)
``````

Note, I had to use the `...` spread syntax to convert the tuple into parameter form.

### SequenceS

Sometime our function takes a single object parameter rather than multiple arguments. To solve this problem we can leverage `sequenceS`.

``````import * as E from 'fp-ts/lib/Either'

type RegisterInput = {
email: string
}

declare function validateEmail(email: string): E.Either<Error, string>
declare function register(input: RegisterInput): unknown

declare const input: RegisterInput

pipe(
input,
sequenceS(E.either)({
email: validateEmail(email),
}),
E.map(register),
)
``````

## Traversals

Sometimes your inputs will not line up nicely and you need to perform some additional computations before applying `sequence`. Traversal is the answer to this. It performs the same thing sequence but lets us transform the intermediate value.

A good example network request to retrieve parts of a file. You either want all the parts or you want none of them.

``````import * as TE from 'fp-ts/lib/TaskEither'
import * as A from 'fp-ts/lib/Array'

declare const getPartIds: () => TaskEither<Error, string[]>
declare const getPart: (partId: string) => TaskEither<Error, Blob>

1. Sequences and traversals use `ap` internally.