
Функциональное программирование для Dart,
с ленивостью и конкурентностью из коробки.
FxDart — это порт FxTS на Dart: библиотека для композиции ленивых конвейеров над синхронными и асинхронными данными, где превращение шести последовательных запросов по секунде в конкурентную пачку за две секунды — это один вызов метода.
📒 Посмотрите в деле: Daily Ledger — полноценное приложение на FxDart →
⚖️ Dart vs FxDart — 53 реальные задачи, бок о бок →
⚡ RxDart vs FxDart — push vs pull, 50 честных вердиктов →
▲ Это живой пример — измените код и нажмите Запустить. Он компилируется настоящим компилятором Dart и выполняется в вашем браузере.
Что такое FxDart?
FxDart переносит модель программирования FxTS в Dart: набор из ~120 небольших композируемых функций для работы с коллекциями и асинхронными данными. В его основе три идеи:
- Ленивые вычисления — операторы вроде
map,filterиtakeлишь строят конвейер и не делают работы, пока терминальный оператор (toList,each,reduce…) не протянет через него значения. Обработка диапазона из миллиона элементов с.take(3)вычислит ровно 3 результата. - Единая модель для sync и async — одни и те же имена операторов
работают и с обычными
Iterable, и сFxAsyncIterable, pull-основанной асинхронной последовательностью FxDart (с мостами кStreamв обе стороны). - Декларативная конкурентность —
concurrent(n)просит вышестоящий конвейер вычислять поnэлементов за раз, сохраняя порядок результатов. Это фирменная возможность FxTS, точно перенесённая в Dart.
События во времени и отказы как значения живут на двух других слоях. Какой слой? — это решение; эта страница остаётся маркетинговой.
Зачем это нужно?
В Dart уже есть Iterable и Stream. FxDart оправдывает
себя там, где их возможностей не хватает:
| Задача | Чистый Dart | FxDart |
|---|---|---|
| Ограничить число одновременных вызовов API до n, сохранив порядок | Ручные очереди, Completer, кропотливый учёт |
.map(fetch).concurrent(3) |
| Асинхронные конвейеры преобразований | Stream работает по push-модели; смешивать await, backpressure и ленивость становится непросто |
Pull-цепочка — каждое значение вычисляется только по запросу |
| Обработка данных (группировка, индексация, подсчёт, разбиение, zip, chunk…) | Каждый раз самописные циклы | По одной проверенной именованной функции на каждое понятие |
| Читаемые многошаговые преобразования | Вложенные вызовы или промежуточные переменные | Цепочки fx() слева направо, полностью типизированные |
Плюсы & минусы
✓ Плюсы
- Ленивость бесплатно — конвейеры прерываются досрочно; вычисляются только запрошенные значения.
- Конкурентность с сохранением порядка одним оператором:
concurrent(n)/concurrentPool(n)в порядке завершения. - Полностью типизированные цепочки —
fx()сохраняет вывод типов от начала до конца; синхронные операторы — обычные функции над нативнымиIterable, поэтому всё совместимо с обычным Dart. - Небольшие узкоспециализированные функции — ~120 операторов: transform / filter / slice / combine / aggregate / object / util.
- Проверенная в бою семантика — поведение перенесено из FxTS вместе с 850+ его тестов.
- Ноль зависимостей — чистый Dart.
✗ Минусы
- Нет каррирования с данными в конце — в Dart нет перегрузки функций, поэтому каррированный стиль
pipeиз FxTS превращается в цепочки; динамическийpipe()теряет статические типы. - Вторая асинхронная абстракция —
FxAsyncIterableсуществует потому, чтоStreamне умеет выражать обратный канал для конкурентности; мосты просты, но это ещё одно понятие для изучения. - Порог входа — мышление ленивыми конвейерами отличается от императивных циклов.
- Не всегда самый быстрый вариант — на маленьких горячих циклах написанный вручную
forможет обогнать композицию операторов; FxDart оптимизирован под ясность и работу с I/O. - Некоторые API из TS не переносятся буквально — вместо этого они получают нативные для Dart формы:
curryстал типизированным геттером-расширением.curried, а старые имена оставлены как deprecated-заглушки для миграции.
Конкурентность на вкус
Шесть фиктивных запросов по 300 мс — последовательно ~1,8 с, с
concurrent(3) около 0,6 с. Попробуйте изменить число: