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.

Here’s an illustration of these two variants using 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 last:

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

This version is commonly used with the pipe function. You start by passing the Effect as the initial argument to pipe and then chain transformations like Effect.map:

Example (Using data-last with pipe)

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

This style is helpful when chaining multiple transformations, making the code easier to follow in a pipeline format:

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

In the second overload, the self argument comes first:

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

This form doesn’t require pipe. Instead, you provide the Effect directly as the first argument:

Example (Using data-first without pipe)

const mappedEffect = Effect.map(effect, func)

This version works well when you only need to perform a single operation on the Effect.