Skip to content

Dual APIs

When you’re working with APIs in the Effect ecosystem, you may come across two different ways to use the same API. These two ways are called the “data-last” and “data-first” variants.

When an API supports both variants, we call them “dual” APIs.

Let’s explore these two variants using a concrete example of a dual API: Effect.map.

The Effect.map function is defined with two TypeScript overloads. The terms “data-last” and “data-first” refer to the position of the self argument (also known as the “data”) in the signatures of the two overloads:

declare const map: {
// data-last
<A, B>(f: (a: A) => B): <E, R>(self: Effect<A, E, R>) => Effect<B, E, R>
// data-first
<A, E, R, B>(self: Effect<A, E, R>, f: (a: A) => B): Effect<B, E, R>
}

In the first overload, the self argument comes in the last position:

declare const map: <A, B>(
f: (a: A) => B
) => <E, R>(self: Effect<A, E, R>) => Effect<B, E, R>

This is the variant we have been using with pipe. You pass the Effect as the first argument to the pipe function, followed by a call to Effect.andThen:

const mappedEffect = pipe(effect, Effect.andThen(func))

This variant is useful when you need to chain multiple computations in a long pipeline. You can continue the pipeline by adding more computations after the initial transformation:

pipe(effect, Effect.andThen(func1), Effect.andThen(func2), ...)

In the second overload, the self argument comes in the first position:

declare const map: <A, E, R, B>(
self: Effect<A, E, R>,
f: (a: A) => B
) => Effect<B, E, R>

This variant doesn’t require the pipe function. Instead, you can directly pass the Effect as the first argument to the Effect.andThen function:

const mappedEffect = Effect.andThen(effect, func)

This variant is convenient when you only need to perform a single operation on the Effect.