Получаем вот что:
const program: Computation<void, { logger: Logger, random: Random }> = andThan(getRandom)(logNumber)
const runnable = provideServices(program)({ logger: ConsoleLogger, random: MathRandom })
run(runnable) // OK
Можно заметить что мы заранее декларировали сервисы функкий в типах. Можно сделать более интересно — как
yield* MyServiceTag в Effect. Чтобы мы его просто читали и он сам проставлялся в зависимости функции! Получается мы хотим сделать такую функицю которая бы на вход принимала какой-то сервис и тут же бы возвращала его обратно в качестве результата вычисления — для дальнейшего использования
const tag = <Service extends {}>(): Reader<Service, Service> => {
return (service) => service;
};
Соберем теперь все вместе
const RandomTag = tag<{ random: Random }>();
const LoggerTag = tag<{ logger: ConsoleLogger }>();
const getRandom = flatMap(RandomTag)(
(services) => sync(() => services.random.random() ** 2),
);
const logNumber = (num: number) =>
flatMap(LoggerTag)((services) => sync(() => services.logger.log(num)));
const program = flatMap(getRnd)(logRnd);
const withConsole = provide(program)({
logger: ConsoleLogger,
});
const runnable = provide(withConsole)({
random: MathRandom,
});
run(program); // error
run(withConsole); // error
run(runnable); // ok
Вообще это подход к организации DI например в fp-ts. Там и в Haskell этот тип
Computation<> наызвается Reader. Так что идея не нова. В том же Effect под капотом Context та же мапка но на уровне типов зависимости трекаются через тип сумму |, а не произведение &. В начальных версиях было через & но от этого ушли из-за проблем с ts. Так что если нужен Effect-like DI — посмотрите на семейство модулей
Reader fp-ts.Песочница https://tsplay.dev/NBOazm
А вы какой DI используете?