TGViewer
Channel Public Channel
Khraks dev

Khraks dev

@khraks_dev

Subscribers
108
Photos
6
Videos
1
Links
40
Recent Posts 20 shown
Post #57 84
Добавил еще PR с Context.Mixin, чтобы подмешивать к классу поведение Context.Service

Теперь можно даже вот так:


class Test extends Context.Mixin<Test>("Test")(
Schema.Class<Test>("Test")({
id: Schema.String
})
) {
test.method() { this.id }
}

Effect.gen(function*() {
const test = yield* Test
return test.method()
}) // Effect<string, never, Test>
.pipe(
Effect.provide(Test, Schema.decodeSync(Test)({ id: 'asd' }))
)


Теперь Test — это и схема, и ключ в Context, и эффект по доставанию инстанса из R-канала.

Не то чтобы это было целевая фича, но интересно получилось.

А фича в быстром поднятии какой-то имеющейся базы в экосистему эффекта.

Был класс — хоба и это уже Context.Service

Discord & PR
Discord Discord - Group Chat That’s All Fun & Games Discord is great for playing games and chilling with friends, or even building a worldwide community. Customize your own space to talk, play, and hang out.
Post #56 100

This post (sticker, poll or similar) has no web preview. Open in Telegram

  • 🔥 3
  • ❤ 1
Post #54 114

This post (sticker, poll or similar) has no web preview. Open in Telegram

  • 👍 2
  • 🔥 2
Post #52 104
Итак HKT на основе TypeLambda

interface TypeLambda {
readonly Arg1: unknown;
readonly Arg2: unknown;
readonly Arg3: unknown;
readonly Return: unknown;
}

type Call<TL extends TypeLambda, A1, A2, A3> = (TL & {
readonly Arg1: A1;
readonly Arg2: A2;
readonly Arg3: A3;
})["Return"];

interface MyEffect<A, E, R> {
(r: R): Promise<A | E>;
}

interface MyEffectLambda extends TypeLambda {
Return: MyEffect<this["Arg1"], this["Arg2"], this["Arg3"]>;
}

type Result = Call<MyEffectLambda, 1, 2, 3>;
// ^? MyEffect<1, 2, 3>

Реализация очень простая:
Основной трюк - это полиморфный this
Благодаря ему MyLambda и выглядит как самостоятельная функция - взять переданные аргументы и как-то их заколбасить. В Call<...> видно, что мы передаем лямбду и типо-аргументы, далее пересекаем TL & {...Args} (это и есть момент передачи аргументов) - после чего polymorphic this начинает видеть конкретные типы. Наконец, просто забираем результат через ['Return'] - очень просто и изящно.
Так же в отличие от предыдущего примера это легче масштабируется - нужен еще один параметр? просто добавьте Arg4 в TypeLambda и существующие лямбды не надо исправлять - Arg4 в них просто игнорируется
Что на счет разной разрядности типов - метод Call<> выглядит одинаково как для Array<T> так и для Either<A, E>

interface ArrayLambda extends TypeLambda {
Return: Array<this['Arg1']>
}
interface EitherLambda extends TypeLambda {
Return: Either<this['Arg1'], this['Arg2']>
}

type ArrayResult = Call<ArrayLambda, number, void, void>
// ^? Array<number>
type EitherResult = Call<EitherLambda, number, string, void>
// ^? Either<number, string>

Ещё есть такое понятие, как натуральная трансформация. Это когда мы хотим заменить один функтор на другой, не изменяя его содержимое. Например, преобразовать Either<A, E> в Option<A>, сохранив тот же A.

interface OptionLambda extends TypeLambda {
Return: Option<this["Arg1"]>;
}

type NaturalTransformation<F extends TypeLambda, G extends TypeLambda> = <A1, A2, A3>(fa: Call<F, A1, A2, A3>) => Call<G, A1, A2, A3>;

type nt = NaturalTransformation<MyEffectLambda, OptionLambda>;
// ^? <A1, A2, A3>(fa: MyEffect<A1, A2, A3>) => Option<A1>

Вот так она просто выражается в энкодинге на TypeLambda - в случае с дефункционализацией пришлось бы городить все возможные перегрузки чтобы сочетать (`URI2<A, B>` и URI<A> и URI3<A, B, C>`) собсна вот в `fp-ts https://gcanti.github.io/fp-ts/modules/NaturalTransformation.ts.html

https://tsplay.dev/Nl4PrN
fp-ts NaturalTransformation.ts Functional programming in TypeScript
  • 👍 2
  • 🔥 2
Post #51 133
# HKT Дефункционализация

В TypeScript нет настоящих Higher-Kinded Types (HKT).
А они применяются в FP - вот и ищут люди способы эмулировать.
На данный момент мне известно о двух способах
Первый называется дефункционализация и вот пример его реализации

// lib
interface URIToValue<out A> {
// Id: Container<A>
}
type URIS = keyof URIToValue<any>;
type Apply<URI extends URIS, A> = URIToValue<A>[URI];

interface Functor<URI extends URIS> {
map<A, B>(fa: Apply<URI, A>, f: (a: A) => B): Apply<URI, B>;
}

// userland Option<T>
type Option<A> = { _tag: "Some"; value: A } | { _tag: "None" };

declare module 'lib' {
interface URIToValue<out A> {
Option: Option<A>;
}
}

const optionFunctor: Functor<"Option"> = {
map(fa, f) {
//^? Option<A>
return fa._tag === "Some" ? { _tag: "Some", value: f(fa.value) } : fa;
},
};

// userland Array<T>
declare module 'lib' {
interface URIToValue<out A> {
Array: Array<A>;
}
}

const arrayFunctor: Functor<"Array"> = {
map(fa, f) {
//^? Array<A>
return fa.map(f);
},
};

Тут идея такова что мы каждый тип данных регистрируем с помощью аугментации модулей и слияния интерфейсов в реестре URIToValue и добываем настоящий тип с помощью Apply<Id, Value>

Вроде бы окей но сложности начинаются при попытке написать универсальную композицию функторов:

// Option + Array = Option<Array<T>>
// Array + Option = Array<Option<T>>

function compose<URI_outer extends URIS, URI_inner extends URIS>(
outer: Functor<URI_outer>,
inner: Functor<URI_inner>,
): Functor<URI_outer + URI_inner> {
return {
map(f_outer_inner, f) {
return outer.map(f_outer_inner, (f_inner) => inner.map(f_inner, f));

}
}
}

Чтобы сделать Apply в функции Functor.map - нам нужно ЗАРАНЕЕ зарегистрировать тип данных в реестре URIToValue - на лету не получится (`Functor<URI_outer + URI_inner>`)

И еще одна задачка появляется при композиции функторов разной арности
например хочу сделать Either + Array / Array + Either — возникнет проблема с тем как хранить и передавать E. И нужно будет добавить перегрузку compose21 (`Either<Array<A>, E>`), `compose12`(`Array<Either<A, E>>`) и т.д.

Дальше посмотрим на второй способ реализации HKT с помощью TypeLambda подхода и как этот способ решает проблемы универсальной композиции
  • 👍 2
Post #50 309
Combiner & Reducer

В v4 добавили новые модули которые являются адаптированными Semigroup & Monoid из fp-ts.
Semigroup (Полугруппа) — это некий тип A и операция combine которая каким-то образом делает из двух экземпляров типа один. Как отражено в названии - комбинирует)

interface Combiner<A> {
combine: (self: A, that: A) => A
}

Самое интересное: подразумевается, что эта штука удовлетворяет неким законам.

// ассоциативность
(a⋅b)⋅c = a⋅(b⋅c)

Тут смысл такой что не важно в каком порядке выполняется операция комбинирования — результат будет тем же. Еще говорят, что полугруппа — это основа параллельности. Ну верно же: получается всю работу (выполнение всех операций combine`) можно выполнить на разных исполнителях параллельно (в примере ниже `ab и `cd`)

((a⋅b)⋅c)⋅d = (a⋅b)⋅(c⋅d)

Еще одним свойством является замкнутость — то есть комбинируя значения одного типа мы не получим значение другого.
Опять же удобство в композуемости — в других модулях предоставляются функции для построения более сложных комбайнеров)))

// Option.ts
declare function makeCombinerFailFast<A>(
combiner: Combiner<A>
): Combiner<Option<A>>

const sumNum: Combiner<number> = Combiner.make((a, b) => a + b)
const sumOpt: Combiner<Option<number>> = makeCombinerFailFast(sumNum)
// sumOpt(None, None) // None
// sumOpt(None, Option(2)) // None
// sumOpt(Option(2), None) // None
// sumOpt(Option(2), Option(1)) // Option(3)

Например тут мы поднимаем более простой комбайнер в Option и получаем новый — который объединяет непустые значения и оставляет пустые как есть.
Помимо "мерджа" значений можно выбрать один из 2х элементов:
например брать только первый или только второй элемент (`Combiner.first` & `Combiner.second`) .
А если скрестить силы с модулем Order то можно выбирать только максимальный или минимальный элементы.
В общем инкапсулируя некую логику по комбинированию в такую абстракцию мы получаем доступ к богатым возможностям которые предоставляет экосистема. А соблюдение законов дает гарантии поведения.
Пока вижу мало использования внутри v4 экосистемы напрямую — возможно предполагается использовать только саму combine функцию.

Использовали ли когда-то полугруппы? Были какие-то необычные примеры использования? Как вам нейминг?)
  • 👍 7
Post #49 307
Khraks dev ## Effect 4.0 Добавили модуль Filter — похож на Predicate. Но Predicate больше про нативную обертку предикатов и тайпгардов. interface Filter<in Input, out Output = Input> { (input: Input): Output | absent } const absent: unique symbol = Symbol.for("ef…
Апдейт по модулю Filter github

В итоге Filter прадставляет собой вот такую функцию:

export interface Filter {
(input: Input): Result<Pass, Fail>
}


По-сути это filterMap: остается то что в succeed() плюс можно сразу мапнуть результат

Array.filter(
[1,2,3],
x => isOdd(x)
? succeed(x.toString())
: fail(x)
) // ["1", "3"]


Но дополнительный трюк в том, что были добавлены перегрузки которые одновременно принимают и обычный предикат — то есть одновременно работает и обычная фильтрация без изменения типа.

Array.filter(isOdd) // [1, 2]


Сначала я подумал, что есть какой-то unsound случай, но нет все чистенько-ладно)
Таким образом удалось избавиться от семейства функций filterMap во всей экосистеме, объединив две функции в одну.
GitHub effect-smol/packages/effect/src/Filter.ts at main · Effect-TS/effect-smol Core libraries and experimental work for Effect v4 - Effect-TS/effect-smol
  • ❤ 1
  • 👍 1
Post #47 364
#DI как в Effect Pt3
Теперь озадачился написанием аналога 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
www.typescriptlang.org TS Playground - An online editor for exploring TypeScript and JavaScript The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
  • 🔥 4
Post #46 227
#DI как в Effect Pt2
Получаем вот что:

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 используете?
www.typescriptlang.org TS Playground - An online editor for exploring TypeScript and JavaScript The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
  • 👍 4
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
Post #44 409
## Effect 4.0
Вмерджили моe предложение об улучшении DX при работе с Redacted.
Теперь можно задать label при конструировании и при сериализации будет выведен он.


const redacted = Redacted.make('password')
const redactedWithLabel = Redacted.make('password', { label: 'MY_PASS' })
console.log(redacted) // <redacted>
console.log(redactedWithLabel) // <MY_PASS>


Пропихнуть в Config не получилось, а хотелось бы чтобы label и имя переменной совпадали — Тим предположил, что это потенциальная утечка имени переменной окружения. Я думал об этом, но полагал, что opt in поведение более прагматично и прокатит, но нет — придется обходиться более явным вариантом.


Config.redacted('MY_PASS_ENV', { label: 'MY_PASS_NAME' })
// вместо
Config.redacted('MY_PASS')



MY_PASS_ENV — название имени переменной окружения
MY_PASS_NAME — label от Redacted

https://github.com/Effect-TS/effect-smol/pull/269
  • 🔥 4
Post #43 402
## Effect 4.0
Добавили модуль Filter — похож на Predicate. Но Predicate больше про нативную обертку предикатов и тайпгардов.

interface Filter<in Input, out Output = Input> {
(input: Input): Output | absent
}
const absent: unique symbol = Symbol.for("effect/Filter/absent")

Более удобный, как по мне, потомучто является по факту filterMap — вместо Option.none() теперь Filter.absent и не нужен Option.some

declare function filterMap<A, B>(f: (value: A) => Option<B>): (arr: Array<A>) => Array<B>

declare function newFilter<A, B>(f: Filter<A, B>): (arr: Array<A>) => Array<B>

PS У меня не однозначное мнение пока, пушто такой подход не дает тотальности (работает не одинаково на области определения), но нужно поюзать.

newFilter(x => x)([1, 2])
// [1, 2]
newFilter(x => x)([Filter.absent, Filter.absent])
// []

Так что Filter — прагматичный, Predicate — академичный и посмотрим что из этого получится

PS возможно реально лучше назвать FilterMap
PSS кстати, вроде такая реализация лечит какое-то чудачество тайпскрипта — как вспомню отпишу
  • 👍 2
  • ❤ 1
Post #41

This post (sticker, poll or similar) has no web preview. Open in Telegram

  • 🤔 4
  • ❤ 2
Post #40 461
## Заинтересовался вот этой оптимизацией в Schema:

If you want more perf you can use ParseResult.flatMap instead of Effect.gen and get eager optimization skipping the Effect runtime.
Michael Arnaldi

Суть в том что трансформеры Schema.transformOrFail должны возвращать Effect<unknown, ParseResult.ParseIssue, unknown>. Но например Either — это его подтип, который не требует вовлечения рантайма.

Идея оптимизации такова — а давайте, если схема использует только Either, то ее декодеры-энкодеры будем запускать без использования рантайма.

Для удобства в схеме используется вот такой тип:
type ParseResult<A> = Either<A, ParseResult.ParseIssue>

Оптимизация состоит из 3х частей:

- нужно сохранять тип трансформаций Either и не превращать его в Effect — за это отвечают функции в ParseResult с категорией optimisation: ParseResult.flatMap
- эти хелперы используются главным образом в функции ParseResult.go, которая отвечает за то чтобы обойти все AST-ноды схемы и выдать ParseResult.Parser— искомую функцию энкода-декода
- ParseResult.handleForbidden— функция в которой происходит конечное решение использовать рантайм или нет

Из ee реализации понятно, что чтобы ритуал состоялся необходимо:

- как намерение пользователя — запустить семейство validate/decode/encode c суффиксом Either/Option/Sync
- так и возможность схемы — использование именно ParseResult<A> при трансформациях

В противном случае оптимизация перестает работать — и подрубается рантайм эффекта с синхронным планировщиком что, кажется, одно и то же, что и Effect.runSync

Но так же важно помнить, что аннотации схемы concurrent и batching НЕ будут применены для синхронных трансформаций, то есть тут трансформации первичны.
X (formerly Twitter) Michael Arnaldi (@MichaelArnaldi) on X @dillon_mulroy if you want more perf you can use ParseResult.flatMap instead of Effect.gen and get eager optimization skipping the Effect runtime. Not that you'd need it
  • 🔥 3
Post #39 366
ExecutionPlan — появился в свежем релизе Effect@3.16
Интересно было посмотреть что за штучка. Кристализовался модуль в процессе разработки @effect/ai
Суть в том, что можно описать в плоском виде (просто массив) порядок сервисов, которые будет использовать эффект при неудаче.

То есть семантика буквально:
- попробовать сначала с этим сервисом:
- если успех, то идем дальше
- если нет - то другой сервис
- и тд
- пока не пройдемся по всему плану или не выйдем при первом успехе

Под капотом обычный советский Effect.while, который по порядку провайдит сервисы и выставляет ретраи по расписанию. https://github.com/Effect-TS/effect/blob/main/packages/effect/src/internal/executionPlan.ts#L52

Признаться, я ожидал нечто непонятное в реализации, но парни просто нашли достаточно запутанный кусок юзер-лэнд кода и запрятали за наглядную абстракцию
  • 👍 3
  • 🥱 3
Post #38 372
Хозяйке на заметку.

1. Эти два способа сужения типа не эквивалентны
function test<T>(testFn: T | (() => T)) {
if (testFn instanceof Function) {
testFn();
// ^? () => T
}

if (typeof testFn === "function") {
// @ts-expect-error: This expression is not callable.
testFn();
// ^? (() => T) | (T & Function)
}
}


2. Tакое API — unsound.
Eсли передать в качестве T — функцию, то в рантайме невозможно будет отличить T от () => T

PS
function useState<S>(initialState: S | (() => S)): ...
  • ✍ 10
  • 🔥 1
Post #37 357
Раньше думал, что есть 2 механизма повторов эффектов - retry и repeat. Первый перезапускает упавшие эффекты, второй повторяет эффект переданный эффект после(!) инициального его запуска. Поэтому Effect.log(123).pipe(Effect.repeatN(2)) выполнится 3 раза - инициально + 2 повторения.

Но после более детального изучения понял что их три - есть еще scheduling - это когда мы запускаем эффект строго по расписанию (Effect.schedule).

Узнал я это пока ковырялся с PR, который решает вот такую вот задачку:
"Полезно было бы узнавать информацию о повторении в повторяемом эффекте!)"

Например, эффект падает после нескольких попыток получить запрос - мы хотим упасть и залогировать сколько времени мы стучались (как на стриншоте в комментах).

Тут у меня в голове все соединилось - я как раз изучил Schedule и FiberRef. Решение - просто вытаскивать из драйвера расписаний инфу о попытке и провайдить ее в обернутый эффект через Context.Reference. Но там где последняя попытка - там и все предыдушие - можно же сохранять инфу о всех вызовах! Но, как правильно заметил Тим Смарт, это привело бы к утечке памяти в случае бесконечных расписаний.

Так что встречайте — великий и ужасный Schedule.CurrentIterationMetadata (PR)
Effect.gen(function* () {
const currentIterationMetadata = yield* Schedule.CurrentIterationMetadata
// ^? Schedule.IterationMetadata
// elapsed: Duration.zero,
// elapsedSincePrevious: Duration.zero,
// input: undefined,
// now: 0,
// recurrence: 0,
// start: 0

console.log(currentIterationMetadata)
}).pipe(Effect.repeat(Schedule.recurs(2)))


PS Прикольно что пригодилось мое изучение FiberRef и Schedule.)
  • ✍ 3
  • ❤ 2
Older posts →

About this channel

How can I read @khraks_dev without a Telegram account?
TGViewer shows the public web preview Telegram publishes for Khraks dev: recent posts, photos, videos and the subscriber count, with no app, login or account.
How many subscribers does Khraks dev have?
Khraks dev (@khraks_dev) has 108 subscribers on Telegram, refreshed roughly every 30 minutes.
Does Khraks dev know I viewed it here?
No. Public channel previews carry no viewer identity, and TGViewer has no accounts or tracking of what you look up.
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 →