Dual APIs
On this page
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.
From a technical perspective, these variants are implemented using two TypeScript overloads.
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:
ts
export 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>}
ts
export 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>}
data-last
In the first overload, the self
argument comes in the last position:
ts
<A, B>(f: (a: A) => B): <E, R>(self: Effect<A, E, R>) => Effect<B, E, R>
ts
<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
:
ts
const mappedEffect = pipe(effect, Effect.andThen(func))
ts
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:
ts
pipe(effect, Effect.andThen(func1), Effect.andThen(func2), ...)
ts
pipe(effect, Effect.andThen(func1), Effect.andThen(func2), ...)
data-first
In the second overload, the self
argument comes in the first position:
ts
<A, E, R, B>(self: Effect<A, E, R>, f: (a: A) => B): Effect<B, E, R>
ts
<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:
ts
const mappedEffect = Effect.andThen(effect, func)
ts
const mappedEffect = Effect.andThen(effect, func)
This variant is convenient when you only need to perform a single operation on the Effect
.
Choosing Between the variants. It's important to note that both overloads achieve the same result. They are simply two different ways of expressing the code. You can choose the overload that best fits your coding style and makes the code more readable for you and your team.