TGViewer
Khraks dev Khraks dev @khraks_dev · 108 subscribers
Post #45 184
#DI как в Effect Pt1

Мааам можно мне: "Effect-like-DI"
Нет! У нас "Effect-like-DI" есть дома
Дома: Reader

После прочтения заметки вы будете понимать шутку.
Часто вижу, что хвалят DI в Effect.
Хочу поделиться дедовским рецептиком наколенной версии.

Во первых каждое вычисление должно трекать необходимую зависимость на уровне типов — в Effect это третий типопараметр R.

interface Computation<out Value, in Services extends {} = {}> {
(services: Services) => Value
}

и так давайте вычислением будет функция которая чтобы посчитать Value ожидает на вход некие сервисы Services — их будем передавать прям мапой { serviceName: serviceImplementation }
Давайте сразу аналог Effect.sync сделаем — вычисление которое не требует никаких Services — Computation<Value>

const sync = <Value,>(cb: () => Value): Computation<Value> => () => cb()

А теперь запускатор этих самых вычислений:

const run = <A, R extends {}>(reader: {} extends R ? Reader<A, R> : never) =>
reader({} as R);

{} extends R — это условие как раз гарантирует что для корректного запуска функции ненужны никакие зависимости, собственно можно запустить функции просто вызовом, который потребует передать необходимые аргументы-сервисы или {} если ничего не требуется
Чтож бахнем хеллоу-ворлд:

const helloDI: Computation<void> = sync(() => console.log("Hello, DI!"))
const result = run(helloDI) // => void

А теперь давайте-ка вынесем наш логгер в сервис и доработаем функцию

interface Logger {
log: (...args: ReadonlyArray<unknown>) => void
}
const ConsoleLogger: Logger = globalThis.console

const helloDI: Computation<void, { logger: Logger }> = (services) => services.logger.log("Hello, DI!")
run(helloDI) // Error

Получаем ошибку — функция требует чтобы мы предоставили сервис-логгер
Давайте напишем функцию которая провайдит зависимость! Нужно держать в уме, что Services это по сути Key-Value мапка, а пробрасывать зависимости будем прям подмножеством этой мапки Partial<Services>, при этом из первоначальной мапки будем омитить то что пробросили Omit<Services, keyof Provided>

const provideServices =
<Value, Services extends {}>(computation: Reader<Value, Services>) =>
<Provided extends Partial<Services>>(provided: Provided): Reader<Value, Omit<Services, keyof Provided>> =>
(services) => {
const value = computation({ ...req, ...provided } as {} as Services);
return value;
};

Погнали пробросим зависимость:

const runnable = provideServices(helloDI)({ logger: ConsoleLogger })
run(runnable) // OK

А теперь давайте также будем выводить в консоль рандомные числа!

interface Random {
random: () => number;
}
const MathRandom: RandomService = globalThis.Math
const getRandom: Computation<void, { random: Random }> = (services) => services.random()

const logNumber = (num: number): Computation<void, { logger: Logger }> = (services) => services.logger.log(`Random: ${num}`)

Теперь нужно связать эти две функции, чтобы вернулась новая. Понятно чтоб мы сначала получим рандомное чисто и потом его залогируем, но для этого нам понадобятся оба сервиса — рандом & логгер

const andThen =
<Serivces1 extends {}, Value1>(computation1: Computation<Value1, Serivces1>) =>
<Value2, Serivces2 extends {}>(
fn: (a: Value1) => Computation<Value2, Services2>,
): Computation<Value2, Services1 & Services2> => {
return (r: Services1 & Services2) => {
const value1 = computation(r); // тут из общих сервисов возьмем только Services1
const computation2 = fn(value1);
const value2 = computation2(r) // тут из общих сервисов возьмем только Services2
return value2;
};
};
  • 👍 3
More from @khraks_dev
  1. Sep 10, 2026Добавил еще PR с Context.Mixin, чтобы подмешивать к классу поведение Context.Service Тепер…
  2. Sep 10, 2026Post #56
  3. Sep 7, 2026Post #55
  4. Sep 3, 2026Post #54
  5. Sep 3, 2026Итак HKT на основе TypeLambda interface TypeLambda { readonly Arg1: unknown; readonly Arg2…
  6. Aug 30, 2026# HKT Дефункционализация В TypeScript нет настоящих Higher-Kinded Types (HKT). А они приме…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →