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
.