fxdart 0.2.1 copy "fxdart: ^0.2.1" to clipboard
fxdart: ^0.2.1 copied to clipboard

A functional programming library for Dart, ported from FxTS. Lazy evaluation, concurrent async iteration, and pipeline-style composition.

FxDart

fxdart #

A functional programming library for Dart, ported from FxTS. Lazy evaluation, concurrent async iteration, and pipeline-style composition — the FxTS programming model, rebuilt on Dart's type system.

Version codecov

Launch FxDart 101 — Interactive Docs

👆 Click above for a guided course with a live in-browser playground for every function.

Why fxdart? #

  • Lazy evaluation — operators build a pipeline and do no work until a terminal operator runs, so fx(hugeList).map(f).filter(g).take(3) only computes 3 results.
  • Concurrency you can dialconcurrent(n) evaluates the upstream chain n items at a time while preserving order, turning six 1-second requests into a ~2-second batch with one method call.
  • Type-safe pipelines — the fx() chain keeps full static typing end to end; sync operators are plain functions over native Iterables, so everything interops with ordinary Dart code.
  • One mental model for sync and async — the same operator names work on Iterable (sync) and FxAsyncIterable (async), with Stream bridges in both directions.

Install #

See the installation guide on pub.dev for the latest version.

Usage #

Sync pipelines #

Sync operators are data-first functions over lazy Iterables; the fx() chain composes them with full type inference:

import 'package:fxdart/fxdart.dart';

fx([1, 2, 3, 4, 5])
    .map((a) => a + 10)
    .filter((a) => a % 2 == 0)
    .toArray(); // [12, 14]

// Equivalent with top-level functions:
toArray(filter((a) => a % 2 == 0, map((a) => a + 10, [1, 2, 3, 4, 5])));

// Laziness: only 3 squares are ever computed.
fx(range(1, 1000000)).map((a) => a * a).take(3).toArray(); // [1, 4, 9]

Async pipelines #

Async operators work on FxAsyncIterable<T> — a pull-based protocol ported from FxTS's AsyncIterable handling. Lift values in with toAsync / fromStream (or .toAsync() on a chain), and out with .toArray() / .toStream():

await fx([1, 2, 3, 4])
    .toAsync()
    .map((a) async => a + 10) // callbacks may be async
    .filter((a) => a % 2 == 0)
    .toArray(); // [12, 14]

// Streams bridge both ways.
await fxStream(Stream.fromIterable([1, 2, 3])).map((a) => a * 2).toArray();

Concurrency #

concurrent(n) is FxTS's signature feature, ported faithfully: a concurrency marker travels backwards through the pipeline's iterator protocol, so the upstream chain evaluates n items at once while results stay in order.

// 6 requests of 1s complete in ~2s instead of ~6s.
await fx([1, 2, 3, 4, 5, 6])
    .toAsync()
    .map((id) => fetchUser(id))
    .concurrent(3)
    .toArray();

concurrentPool(n) is the completion-order variant (faster first results, no ordering guarantee). This back-channel protocol is why fxdart has its own FxAsyncIterable instead of building on push-based Streams, which cannot express it.

API overview #

Category Functions
Generate range, repeat, cycle, entries, keys, values
Transform (lazy) map, mapEffect, flatMap, flat, scan, scan1, peek, pluck
Filter (lazy) filter, reject, compact, uniq, uniqBy, difference(By), intersection(By), compress
Slice (lazy) take, takeRight, takeWhile, takeUntilInclusive, drop, dropRight, dropWhile, dropUntil, slice, chunk, split
Combine (lazy) append, prepend, concat, zip, zip3, zipWith, zipWithIndex, transpose, reverse, fork
Aggregate reduce, fold, reduceLazy, toArray, sum, average, min, max, size, join, groupBy, indexBy, countBy, sort, sortBy, toSorted, partition, each, consume
Access head, last, nth, find, findIndex, includes, isEmpty, every, some
Object (Map) omit, pick, omitBy, pickBy, prop, props, evolve, fromEntries, compactObject, resolveProps, isMatch, matches
Function pipe, pipe1, pipeLazy, identity, always, tap, apply, juxt, memoize, negate, not, when, unless, throwError, throwIf, cases, add, gt, gte, lt, lte, delay, sleep, unicodeToArray, .curried/.uncurried (extension getters, arity 2–5)
Predicates isNull, isNotNull, isNil, isBoolean, isNumber, isString, isDate, isList, isMap
Async every lazy/aggregate operator has an *Async twin (mapAsync, toArrayAsync, ...), plus toAsync, fromStream, concurrentAsync, concurrentPoolAsync, asyncEmpty
Util debounce, throttle, shuffle, createSeededRandom
Chains fx() (sync, extends Iterable), fxAsync(), fxStream(); Fx<num>/FxAsync<num> gain sum/average/min/max

Differences from FxTS #

Dart has no function overloads, variadic generics, or conditional types, so some APIs deliberately deviate:

FxTS fxdart
curried data-last (map(f) inside pipe) fx() chain (typed) or dynamic pipe(value, [closures])
one map dispatching sync/async map (Iterable) / mapAsync (FxAsyncIterable); chains use plain names
reduce(f, seed, iter) overload fold(seed, f, iter) (unseeded reduce(f, iter) unchanged)
tuples (zip, entries, partition) Dart records: (A, B)
TS objects (omit, pick, evolve, ...) Map-based equivalents
undefined null (head/find/nth return T?)
AsyncIterable / for await FxAsyncIterable + toStream() / fromStream() bridges
variadic zip/juxt/cases fixed arities (zip/zip3) or list/record parameters
curry(f) .curried / .uncurried extension getters — see WHY_CURRIED.md

FxTS's curry needs arity reflection and recursive conditional types, which Dart lacks — so fxdart curries through per-arity extensions instead, resolved statically and fully typed:

int add(int a, int b) => a + b;
final addOne = add.curried(1); // int Function(int)
fx([1, 2, 3]).map(addOne).toArray(); // [2, 3, 4]

WHY_CURRIED.md tells the full design story: why the direct port is impossible, how static extension resolution stands in for overloading, why the getter is named curried, and how the same port-the-meaning philosophy resolves the other unportable APIs. Those keep @Deprecated stubs (curry, isUndefined, isArray, isObject, takeUntil) so migrating code gets analyzer guidance instead of silent breakage.

Testing #

The FxTS spec suite has been ported alongside the library: 850+ tests covering sync/async behavior, error propagation, laziness, and concurrency timing across every operator.

dart test

Coverage is measured on every push and pull request and reported to Codecov. To reproduce locally:

dart run coverage:test_with_coverage   # writes coverage/lcov.info

Acknowledgments #

Great thanks to Indong Yoo, CTO of Marpple, the creator of FxTS (and FxJS before it), whose functional programming model — lazy iteration with first-class, order-preserving concurrency — this library ports to Dart. All core ideas, operator semantics, and the original test suite come from the marpple/FxTS repository.

Author #

👤 Bansook Nam

🤝 Contributing #

Contributions, issues and feature requests are welcome! Feel free to check the issues page.

📝 License #

Copyright © 2023 Bansook Nam.

This project is MIT licensed.

3
likes
0
points
1.26k
downloads

Publisher

verified publisherbansook.xyz

Weekly Downloads

A functional programming library for Dart, ported from FxTS. Lazy evaluation, concurrent async iteration, and pipeline-style composition.

Repository (GitHub)
View/report issues

License

unknown (license)

More

Packages that depend on fxdart