Skip to main content

Data-First vs Data-Last Paradigm

The Two Approaches

// Data-First (Radash, es-toolkit, Pithos standard)
map(users, (user) => user.name);
filter(numbers, (n) => n > 0);

// Data-Last (Ramda, fp-ts, Remeda pipe)
map((user) => user.name)(users);
filter((n) => n > 0)(numbers);

Pithos Choice: Data-First by default

(Except for Zygos which uses chained methods)

Why Data-First?

  1. Superior TypeScript Inference: TypeScript easily deduces the iterator type (user or n) because the data (users, numbers) is provided before. With Data-Last, inference is often broken or requires explicit generics.
  2. JavaScript Standard: All native methods (Array.map, Array.filter) and modern ones (Promise.then) put data in the main context.
  3. Direct Readability: verb(subject, params) reads like a sentence ("Map users to names").
ParadigmExampleTS InferencePithos Usage
Data-Firstmap(items, fn)checkmark Excellent (Natural)Standard (Arkhe)
Data-Lastmap(fn)(items)warning Often complexNot used
Pipingitems.map(fn)checkmark ExcellentZygos (Fluid API)
AutocompletionIDE suggests available methods
FamiliarityClose to native methods (array.map())

But with Piping Support

For functional composition, Pithos also supports the pipe pattern:

import { pipe } from "pithos/arkhe/function/pipe";
import { map, filter, take } from "pithos/arkhe/array";

// Elegant composition
const result = pipe(
users,
(users) => filter(users, (u) => u.active),
(users) => map(users, (u) => u.name),
(names) => take(names, 5)
);
Design Decision

Pithos is data-first only. Unlike Remeda's dual-mode approach, we deliberately chose not to support data-last variants. This keeps the API simple, predictable, and ensures optimal TypeScript inference.

For composition, use pipe() with explicit data-first calls: it's slightly more verbose but crystal clear.


Related