# Dual APIs

Most combinators in Effect — `Effect.map`, `Effect.flatMap`, `Stream.filter`,
`Stream.map`, and many more — are **dual**: they can be called in two
equivalent ways. You can pass the data first and call the function directly, or
pass the data last and use the function as a step inside `pipe`. Understanding
both forms lets you read any Effect code fluently and pick the most readable form
for each situation.

```ts
import { Effect, pipe } from "effect"

const double = (n: number) => n * 2
const effect = Effect.succeed(21)

// data-first: the Effect is the first argument — no pipe needed.
const a = Effect.map(effect, double)

// data-last: the Effect is supplied last, by pipe.
const b = pipe(effect, Effect.map(double))

// `.pipe` on the Effect itself is the idiomatic form of data-last.
const c = effect.pipe(Effect.map(double))
```

All three produce the same `Effect<number>`. The difference is purely
ergonomic.

## How a dual signature is shaped

The terms "data-first" and "data-last" describe where the `self` argument (the
data the function operates on) sits. A dual function has both overloads:

```ts
import type { Effect } from "effect"

declare const map: {
  //                               ┌─── data-last (used by pipe)
  //                               ▼
  <A, B>(f: (a: A) => B): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B, E, R>
  //             ┌─── data-first (called directly)
  //             ▼
  <A, E, R, B>(self: Effect.Effect<A, E, R>, f: (a: A) => B): Effect.Effect<B, E, R>
}
```

- The **data-last** overload takes only the transformation and returns a function
  awaiting the data. That returned function is exactly what `pipe` feeds the data
  into.
- The **data-first** overload takes the data and the transformation together and
  returns the result immediately.

At runtime a single implementation handles both, dispatching on the number of
arguments it receives.

## Choosing a form

Use the form that reads best for the situation.

When you apply several transformations in sequence, data-last with `.pipe` keeps
each step on its own line and reads top to bottom:

```ts
import { Effect } from "effect"

const program = Effect.succeed(1).pipe(
  Effect.map((n) => n + 1),
  Effect.map((n) => n * 10),
  Effect.tap((n) => Effect.log(`value is ${n}`))
)
```

  For a single operation, data-first avoids the `pipe` ceremony:

```ts
import { Effect } from "effect"

const program = Effect.map(Effect.succeed(1), (n) => n + 1)
```
**Rule of thumb:** Reach for `.pipe(...)` (data-last) whenever you have **two or more** steps; use
the direct (data-first) call for a lone transformation. Within a single
`Effect.gen`, you will often prefer `yield*` over either form — duals matter most
when building pipelines outside of generators.

## Inside `Effect.gen`

Generators give you a third option that frequently beats both: bind intermediate
results with `yield*` and write ordinary expressions. There is no `pipe` and no
nesting.

```ts
import { Effect } from "effect"

const program = Effect.gen(function*() {
  const n = yield* Effect.succeed(1)
  const doubled = n * 2
  yield* Effect.log(`value is ${doubled}`)
  return doubled
})
```

Use `yield*` for the linear flow; drop into `.pipe` only when a single value needs
a chain of combinators applied to it.

## Writing your own dual functions

When you build a reusable combinator over your own data type, make it dual with
`Function.dual` so callers get both forms for free. The first argument tells
`dual` how to recognize a data-first call — either the data-first arity, or a
predicate over the arguments.

```ts
import { Function, pipe } from "effect"

// Arity 2 = the data-first overload takes two arguments.
const sum = Function.dual<
  (that: number) => (self: number) => number, // data-last
  (self: number, that: number) => number // data-first
>(2, (self, that) => self + that)

sum(2, 3) // data-first → 5
pipe(2, sum(3)) // data-last → 5
```
**Note:** You only need `Function.dual` when authoring new combinators. The built-in Effect,
Stream, and Schema APIs are already dual, so you simply choose the calling form
that reads best.