← На главную

Gleam continuations объединяют синхрон и асинхрон в одной task

16.07.2026 10:41 · hackernews

В разработке на Gleam одна и та же бизнес-логика часто повторяется под разные способы получения данных: синхронный, с ошибками, асинхронный. Допустим, есть функция fetch, которая по строковому ключу возвращает строку. Логика превращает список ключей в верхний регистр и считает длину каждого значения.

Поначалу всё просто — simple_func работает с fn(String) -> String. Потом бизнес просит обработку отсутствующих значений: fetch возвращает Result(String, Nil), и пишется fallible_func. Затем запрос на асинхронность — async_func с Promise(String).

Код каждый раз почти одинаковый — меняется только тип fetch и обёртка вокруг результата. А когда нужно одновременно и ошибки, и асинхронность (Promise(Result(...))), придётся писать четвёртую версию. Дублирование растёт.

Решение — continuations. В Gleam это тип Continuation(t, a) = fn(fn(a) -> t) -> t. Он представляет собой «я не знаю, как именно ты получишь значение, но когда получишь — сделай вот это». Обобщённый параметр t скрывает детали эффекта (синхронность, ошибку, асинхронность).

Пишется одна функция task, которая принимает fetch возвращающий Continuation(t, String). Логика внутри та же самая: преобразование ключа, вызов fetch, вычисление длины. Выход — Continuation(t, List(Int)). Всё, код бизнес-логики определён один раз.

Теперь нужны «исполнители» (runners). Каждый runner конкретизирует t и задаёт, как именно работает fetch.

Так одна и та же task работает с любым эффектом — нужно только подставить свой runner. Это и есть абстракция эффектов с помощью continuations. Разделение логики и эффектов. Автор параллельно строит проект EYG — эксперимент по улучшению языков и инструментов (новости о нём в рассылке).

Читать оригинал →