Теперь озадачился написанием аналога
Effect.gen, чтобы можно было просто делать yield* вычислений и получать более читаемый код, вот искомый результат:
const program = gen(function* () {
const randomService = yield* RandomTag;
const randomNumber = random.random();
const logger = yield* LoggerTag;
logger.log(randomNumber);
return randomNumber;
});
Для реализации понадобится 2 вещи:
1. добавим на
Computation вот такой символ чтобы можно было делать yield* на объекте — в реализации будем просто return yield this — вызов .next() вернет само Computation<V, S>
interface Computation<out Value, in Services extends {} = {}> {
(services: Services): Value;
[Symbol.iterator](): Generator<Computation<Value, Services>, Value, any>;
}
Добавим конструктор из коллбека и обернем все функции, которые возвращают
Computation — sync, andThan, tag и тд
const make = <Value, Services extends {} = {},>(cb: (services: Services) => Value) : Computation<Value, Services> => {
const cb_ = cb as Computation<Value, Services>;
cb_[Symbol.iterator] = function* () {
return yield computation;
};
return cb_;
}
2. И сам
genДобавим типовые хелперы — обратите внимание
Services аккумулируются через &.Недавно узнал интересный ts-прием — чтобы скастовать что-то на уровне типов можно просто сделать
Extract<Some, CastTarget>, чем и воспользовался в Computation.Services:
declare namespace Computation {
export type Any = Computation<any, any>;
export type Value<C extends Any> = ReturnType<C>;
export type Services<C extends Any> = Extract<UnionToIntersection<Parameters<C>[0]>, {}>;
}
const gen = <C extends Computation.Any, A>(
f: () => Generator<C, A, unknown>
): Computation<A, Computation.Services<C>> => {
return make((services: Computation.Services<C>) => {
const iterator = f();
let state = iterator.next();
while (!state.done) {
const computation = state.value;
const value = computation(services);
state = iterator.next(value);
}
return state.value;
});
};
Готово! Можно писать в императивном стиле.
Немного переработал
tag — чтобы возвращался сразу сервис, а не кусок мапкиПесочница https://tsplay.dev/mxrP1w