Преследуя желание изучить и глубже понять, как работает fp-ts, за неимением объяснений и хоть какой-то документации я прошел несколько курсов по Haskell - на степике от Дениса Москвина, за что ему благодарен) (https://stepik.org/course/75/syllabus, https://stepik.org/course/693/syllabus)
В Effect cравнительно недавно появилась классная документация. Но, думаю, не лишним будет ознакомиться с ZIO - как раз на днях вышла книга Zionomicon Тем более De Goes супер харизматично и последовательно выдает материал - что способствует его усвоению, думаю и книга огненная.
В Effect cравнительно недавно появилась классная документация. Но, думаю, не лишним будет ознакомиться с ZIO - как раз на днях вышла книга Zionomicon Тем более De Goes супер харизматично и последовательно выдает материал - что способствует его усвоению, думаю и книга огненная.
🔥3🆒1
Advent of Property Based Testing (PBT)
Что такое PBT - это тестирование свойств) Например имея функцию возрастающей сортировки массивов можно обнаружить что она обладать такими:
- длина результата до и после одинаковая
- каждый следующий элемент больше предыдущего
- массив содержит те же элементы что и до сортировки
Но хочется быть уверенным что свойства функции корректны для любых входных данных. Перебирать их все и все их комбинации - достаточно утомительное дело)
Поэтому придумали делать вот что - давайте напишем контракт генерации входных данных - и на конкретном срезе данных будем тестировать функцию - но не один раз а мильены-несчитанные - это конечно не бесконечно, но лучше чем писать руками - а при генерации этих самых срезов давайте еще вначале какие-нибудь специальные краевые случаи поставим - напрмиер для контракта чиселки выдадим
А теперь давайте к делу напишем подобный тест (потыкаться тут https://effect.website/play#f91ec803be7d)
Конечно писать мы будем с использованием Schema из Effect - иначе слишком просто)
Как же так - схема ж для сериализации-десериализации - но нет схема это просто описание контракта которое можно использовать и для генерации так называемых
В настоящее время нет схемы описания имени - по требованиям это любые сочетания англ букв в нижнем регистре. Но у любой схемы можно переопределить аннтацию генерации
Собираем контракт письма воедино
Раз уж затронули тему аннотаций то есть удобная штука
Давайте выведем так - "имя=возраст" - в таком формате принимается решение задачки - просто скопируем из консоли
Собственно тест
Видно, что
в тесте проверяем что функция реально сортирует как нам нужно
Что такое PBT - это тестирование свойств) Например имея функцию возрастающей сортировки массивов можно обнаружить что она обладать такими:
- длина результата до и после одинаковая
- каждый следующий элемент больше предыдущего
- массив содержит те же элементы что и до сортировки
Но хочется быть уверенным что свойства функции корректны для любых входных данных. Перебирать их все и все их комбинации - достаточно утомительное дело)
Поэтому придумали делать вот что - давайте напишем контракт генерации входных данных - и на конкретном срезе данных будем тестировать функцию - но не один раз а мильены-несчитанные - это конечно не бесконечно, но лучше чем писать руками - а при генерации этих самых срезов давайте еще вначале какие-нибудь специальные краевые случаи поставим - напрмиер для контракта чиселки выдадим
NaN +-Infinity - тем более по типам подходит. А когда тест упадет мы нарисуем с какими данными он упал - чтоб было легче воспроизвести)А теперь давайте к делу напишем подобный тест (потыкаться тут https://effect.website/play#f91ec803be7d)
Конечно писать мы будем с использованием Schema из Effect - иначе слишком просто)
Как же так - схема ж для сериализации-десериализации - но нет схема это просто описание контракта которое можно использовать и для генерации так называемых
fc.Arbitrary - тех самых произвольных значений которые удовлетворяют этому контракту с помощью модуля Arbitrary из effectfast-check(https://fast-check.dev/) - инструмент для PBT который и генерит тестовые данные огромными тучамиconst Age = S.Int.pipe(S.between(7, 77)) // хоба - описание возраста из задачи
const AgeArb = Arbitrary.make(Age) // теперь это контракт который понимает fast-check
В настоящее время нет схемы описания имени - по требованиям это любые сочетания англ букв в нижнем регистре. Но у любой схемы можно переопределить аннтацию генерации
fc.Arbitrary.const Name = S.String.annotations({
arbitrary: () => (fc) => fc.string({ unit: fc.constantFrom(..."abcdefghijklmnopqrstuvwxyz") })
}).pipe(
S.minLength(1) // далее накатываем фильтр на непустую строку
)
Собираем контракт письма воедино
const Letter = S.Struct({ name: Name, age: Age }).annotations({
pretty: () => ({ name, age }) => [name, age].join("=")
})
const Letters = S.Array(Letter) // массив писем
const LettersArb = Arbitrary.make(Letters)
Раз уж затронули тему аннотаций то есть удобная штука
Pretty - это для того чтоб указывать как сущность должна превращаться в строку (отладка, логирование и тд) для этого тоже есть аннотация. Давайте выведем так - "имя=возраст" - в таком формате принимается решение задачки - просто скопируем из консоли
Letter.annotations({
pretty: () => ({ name, age }) => [name, age].join("=")
})
const LetterPretty = Pretty.make(Letter) // (value: Letter) => string
Собственно тест
describe("day #1: should properly sort letters", () => {
fc.assert(
fc.property(lettersArb, (unsortedLetters) => {
it(() => {
const letters = sortLetters(unsortedLetters)
for (let i = 1; i < letters.length; ++i) {
const prev = letters[i - 1]
const curr = letters[i]
if (prev.age < curr.age) continue // properly ordered
if (prev.age > curr.age) throw new Error(`Invalid on age ${letterPretty(prev)} ${letterPretty(curr)}`)
if (prev.name > curr.name) throw new Error(`Invalid on name ${letterPretty(prev)} ${letterPretty(curr)}`)
}
})
}),
{ numRuns: 1000 } // количество прогонов
)
})
describe&it - функции для организации тестовfc.property - описание того как проверить свойствоfc.assert - запускатор проверки свойств - принимающий множество настроек (количество прогонов, seed ГСЧ и тд) - он даже пытается минимизировать входные данные котороые приводят к ошибки для более легкой отладкиВидно, что
fast-check можно достаточно бесшовно внедрить и в vitest и в jest - потомучто это просто функции в тесте проверяем что функция реально сортирует как нам нужно
Effect Documentation
Effect Playground
👍3⚡1
Khraks dev
Преследуя желание изучить и глубже понять, как работает fp-ts, за неимением объяснений и хоть какой-то документации я прошел несколько курсов по Haskell - на степике от Дениса Москвина, за что ему благодарен) (https://stepik.org/course/75/syllabus, https:…
Дочитал до 10 главы - в восторге от книги!
Нужно, конечно, держать в уме, что js однопоточный и некоторые примитивы не так полезны, как в многопоточной среде. Но книга отвечает на многие вопросы, которые задают в том же Discord и не освещены в документации. Вводит в темы постепенно, с показательными примерами кода - задает вопросы и отвечает на них, сначала с ошибкой, потом разбирается, что не так и как улучшить. В конце главы даются упражнения - но ответы и их разборы я не искал)
Планирую транслировать все примеры кода на
https://github.com/KhraksMamtsov/effect-zionomicon
Нужно, конечно, держать в уме, что js однопоточный и некоторые примитивы не так полезны, как в многопоточной среде. Но книга отвечает на многие вопросы, которые задают в том же Discord и не освещены в документации. Вводит в темы постепенно, с показательными примерами кода - задает вопросы и отвечает на них, сначала с ошибкой, потом разбирается, что не так и как улучшить. В конце главы даются упражнения - но ответы и их разборы я не искал)
Планирую транслировать все примеры кода на
Effect (благо это сделать достаточно легко) и выложить в репку. Поспособствовать ткскзть популяризации и освоению технологии.https://github.com/KhraksMamtsov/effect-zionomicon
👍5⚡1
Forwarded from Podlodka Podcast – анонсы и новости подкаста про IT
Podlodka #404 – Системы эффектов в языках программирования
Что общего у скорости вычислений, мутабельности, кеширования и исключений? Все это – сайд-эффекты, которые сопровождают результаты вычислений. Виталий Брагилевский показал нам невероятно прекрасную картину, которая открывается, если рассматривать все возможные эффекты как часть одной системы – программирование становится более простым, контролируемым и выразительным!
🎧 Слушать выпуск
👀 Смотреть выпуск
Что общего у скорости вычислений, мутабельности, кеширования и исключений? Все это – сайд-эффекты, которые сопровождают результаты вычислений. Виталий Брагилевский показал нам невероятно прекрасную картину, которая открывается, если рассматривать все возможные эффекты как часть одной системы – программирование становится более простым, контролируемым и выразительным!
🎧 Слушать выпуск
👀 Смотреть выпуск
🔥5 1
# FiberRef
В
При создании
Значения по умолчанию имеют смысл, чтобы не нарушать
Результат
Playground с демонстрацией
FiberRef в Zionomicon
В демо видно что после слияния дочерний
Для чего же может пригодиться
Его можно использовать как контекст — хранить уровень логирования, какие-то аннотации и дефолты
В файбере-обработчике http-запроса — это может быть какая-то контекстная информация об этом запросе, юзер, роль и тд.
Все дефолтные сервисы
Так что можно сказать что
Название
В этом логе видно что файбер один и тот же #0, а значение
В
Zionomicon как-то вскользь прошла тема FiberRef - но, как я обнаружил далее, это очень важная деталь имплементации самого Effect которая не упоминается в документации, поэтому хочу поделиться тем что накопал.FiberRef — Ref который привязан к конкретному файберу, является его локальным состоянием.// FiberRef.ts
declare const make: <A>(
initial: A,
options?: {
readonly fork?: ((a: A) => A) | undefined
readonly join?: ((parent: A, child: A) => A) | undefined
}
) => Effect<FiberRef<A>, never, Scope>
При создании
FiberRef можно указать стратегии копирования и слияния при fork-join файбера.Значения по умолчанию имеют смысл, чтобы не нарушать
fork-join identity закон файберов —Результат
fork и немедленного join файбера, должен быть идентичен результату синхронной работы программы{
fork: identity,
join: (_parent, child) => child
}
Playground с демонстрацией
FiberRef в Zionomicon
В демо видно что после слияния дочерний
FiberRef переопределяет родительский.Для чего же может пригодиться
FiberRef? Его можно использовать как контекст — хранить уровень логирования, какие-то аннотации и дефолты
В файбере-обработчике http-запроса — это может быть какая-то контекстная информация об этом запросе, юзер, роль и тд.
Все дефолтные сервисы
Effect предоставляются через этот механизм Так что можно сказать что
FiberRed — это такой альтернативный неявный способ передать контекст, он не влияет на типовой канал RНазвание
FiberRef отражает основное применение, но механизм гибче и опирается на состояние текущего выполнения — у файбера определен реджистри FiberRefs в который и складываются все FiberRef при создании. При fork-join файберов это реджистри клонируется и мерджатся согласно стратегиям которые задаются при создании рефов. Но можно изменить FiberRefs и текущего файбера — так как это вещь региональная, то есть ее можно переопределить. Например семейство Effect.locally* переопределяет FiberRef в оборачиваемом эффекте. // Effect.ts
export const locally: <A>(
self: FiberRef<A>,
value: A
): <B, E, R>(use: Effect<B, E, R>) => Effect<B, E, R>
В этом логе видно что файбер один и тот же #0, а значение
FiberRef временно переопределяется: const program = Effect.gen(function*() {
const fiberRef = yield* FiberRef.make(42)
// переопределяем 42 -> 46
yield* Effect.locally(fiberRef, 46)(Effect.gen(function*() {
const valueLocally = yield* fiberRef
yield* Effect.log({ valueLocally })
// INFO (#0): { valueLocally: 46 }
}))
const valueAfterLocally = yield* fiberRef
yield* Effect.log({ valueAfterLocally })
// INFO (#0): { valueLocally: 42 }
})## Патчи, Differ
В демо видно, что дочерний
Чтобы решить проблему используется подход как в контролях версий - давайте хранить патчи и накатывать их когда надо. Это знание хранит в себе
Можно сказать что это
Вот наивный пример как сделать
А этот более интересный с применением комбинаторов и конструкторов для примитивов - идея в том что кастомная сущность мапится на более элементарную структуру и обратно, но для этой простой структуры известен
- ZIO Doc — https://zio.dev/reference/state-management/fiberref
- Видео по доке — https://www.youtube.com/watch?v=zpwmsYce8KU
В демо видно, что дочерний
FiberRef переопределяет родительский при join. Но что будет если в FiberRef будет храниться не примитивное значение? Можем получить неожиданное поведение - например как тут потерялось "new A":const defaultSettings = new Settings("default", 0)
let defaultSettingsFiberRef = FiberRef.unsafeMake(defaultSettings)
const runnable = Effect.gen(function*() {
const ref = yield* defaultSettingsFiberRef
yield* Effect.log(ref)
// Settings("default", 0)
yield* Effect.zip(
FiberRef.update(defaultSettingsFiberRef, (x) => x.setA("new A")),
FiberRef.update(defaultSettingsFiberRef, (x) => x.setB(111111)),
{ concurrent: true }
)
const newRef = yield* defaultSettingsFiberRef
yield* Effect.log(newRef)
// Settings("default", 111111)
})
Чтобы решить проблему используется подход как в контролях версий - давайте хранить патчи и накатывать их когда надо. Это знание хранит в себе
Differ:interface Differ<in out Value, in out Patch> {
readonly empty: Patch
diff(oldValue: Value, newValue: Value): Patch
combine(first: Patch, second: Patch): Patch
patch(patch: Patch, oldValue: Value): Value
}
Можно сказать что это
Monoid<Patch>, который умеет находить Patch для Value (diff) и как-то их накатывать (patch). combine — ассоциативная операция (a*(b*c)=(a*b)*c), чтобы при различных порядках Fiber.join результат FiberRef не менялся. Вот наивный пример как сделать
Diff<Settings, ...>А этот более интересный с применением комбинаторов и конструкторов для примитивов - идея в том что кастомная сущность мапится на более элементарную структуру и обратно, но для этой простой структуры известен
Diff. В типе видно, что для рассчетов применяется более примитивный патч.Differ<Settings, readonly [(a: string) => string, (a: number) => number]>
- ZIO Doc — https://zio.dev/reference/state-management/fiberref
- Видео по доке — https://www.youtube.com/watch?v=zpwmsYce8KU
# Schedule ⏲️
В
-
-
-
Решил создать упрощенную модель на промисах, чтобы понять, как работает.
Давайте например напишем
Теперь
давайте теперь напишем
Все остальные комбинаторы похожи структурой, допустим комбинатор
Далее это передается в
Tак как это просто _описание_ расписания - подменив
Самая мощь - это
Люблю разбираться как устроены фичи под капотом - потомучто это знание не только углубляет понимание конкретной технологии, но и расширяет арсенал транслируемых навыков.
такой
Песок с разбором и полным кодом
Schedule — это абстракция для управления повторением эффектов, которая определяет, сколько раз и с какими интервалами операция должна выполняться или перезапускаться. Например в Effect она используется для определения расписаний при обработке ошибок (retry) или при обычном повторении эффектов(repeat)В
Effect они реализованы следующими компонентами:-
Schedule - начальное значение состояния + функция которая по некоторому состоянию и входным данным выдает тройку - [новое состояние, выходное значение, Decision]-
Decision - решение о продолжении выполнения - закончить или продолжать; спустя какое-то время или сразу же -
Driver - по сути стейтфул итератор - менеджит состояние Schedule - выставляя наружу только входные данные => выходное значение Решил создать упрощенную модель на промисах, чтобы понять, как работает.
interface Schedule<out Out, in In = unknown, in out S = any> {
readonly initial: S,
readonly step: (input: In, currentState: S) => [newState: S, Out, Decision],
}
type Decision = { tag: "Continue"; delay: 0 | number } | { tag: "Done" };
interface Driver<Out, in In, in out S> {
private state: S;
constructor(readonly schedule: Schedule<Out, In, S>)
next(input: In): Promise<Out>
}
Давайте например напишем
Schedule который будет стрелять 5 раза с интервалами как последовательность фибоначчи 0-1-1-2-3 секунд. Определим сначала unfold - операцию обратную reduce - из одного значения мы будем получать последовательность.const unfold = <A,>(initial: A, f: (a: A) => A): Schedule<A, unknown, A> =>
new Schedule<A, unknown, A>(initial, (_, state) => [
f(state), // новое состояние
state, // выходное значение
{ tag: "Continue", delay: 0 }, // мгновенное продолжение
]);
Теперь
fibonacci - передаем начальное значение и функцию шагаconst fibonacci = unfold<[number, number]>([0, 1], ([prev, cur]) => [cur,cur + prev])
давайте теперь напишем
map чтобы создавать новый Scheduler с новым выходным значением — дергаем оригинальный и воздействуем на его выходconst map = <Out, Out1>(f: (out: Out) => Out1) =>
<In, S>(schedule: Schedule<Out, In, S>) =>
new Schedule<Out1, In, S>(schedule.initial, (input, state) => {
const [_state, _out, _decision] = schedule.step(input, state);
return [_state, f(_out), _decision];
});
Все остальные комбинаторы похожи структурой, допустим комбинатор
intersect принимает два расписания и возвращает новый new Schedule в кишках которого выясняет, что вернут предыдущие и обрабатывает результат и возвращает свой - если какой-то Schedule решил остановиться то останавливается сам, если оба эмитят то эмитит наименьшее время.Далее это передается в
Driver который сохраняет состояние которое вернул Schedule и передает ему же на вход при следующей итерации, может сброситься до начального значения - потомучто эта информация зашита в сам Scheduler. Функция repeat в цикле запускает driver.next и запускает эффект, до тех пор пока не получит терминальную отмашку.Tак как это просто _описание_ расписания - подменив
Driver мы можем изменить поведение - давайте исполним все, например, игнорируя интервалы. или встроим в какой-нибудь rxjs? Самая мощь - это
Schedule - он композабелен. Да даже в этом примере видно как из простого unfold с различными маппингамми мы создали достаточно сложное расписание. Люблю разбираться как устроены фичи под капотом - потомучто это знание не только углубляет понимание конкретной технологии, но и расширяет арсенал транслируемых навыков.
такой
Schedule я бы не допетрил написать с нуля самостоятельно, a теперь понял смысл и оказалось, что это просто свистопляска вокруг одной функцииdeclare function step(input: In, currentState: S): [newState: S, Out, Decision];
Песок с разбором и полным кодом
🔥4
Написал PR в
https://github.com/standard-schema
Это такой стандарт, чтобы унифицировать интерфейсы различных библиотек для валидации - чтоб не писать тучу адаптеров под каждую. Стандарт еще только первой версии и описывает только процесс превращения "неизвесных" данных в "известные".
Функция, ради которой все затевалось, может возвращать
То есть, условно, моя библиотека для валидации может делать
Effect для использования StandardSchemaV1 в Micro. https://github.com/standard-schema
Это такой стандарт, чтобы унифицировать интерфейсы различных библиотек для валидации - чтоб не писать тучу адаптеров под каждую. Стандарт еще только первой версии и описывает только процесс превращения "неизвесных" данных в "известные".
/** Validates unknown input values. */
declare function validate(value: unknown) => Result<Output> | Promise<Result<Output>>;
Функция, ради которой все затевалось, может возвращать
Promise и не принимает AbortSignal.То есть, условно, моя библиотека для валидации может делать
fetch зачем-то, но при этом не может его прервать.🔥4👍1😱1💩1
# Effect 4.0
Закончились "Effect days".
Хочу немного пройтись по планам на след версию из этого твита (https://x.com/dillon_mulroy/status/1903401848083550429)
## Бетон
- "Smol" — по слухам редьюс бандлсайза 50-80%. Основной проблемой был большой размер рантайма — видимо с этим хорошенько поработали. Надеюсь
- Хотят избавиться от
- Я так понимаю решили избавиться от сабтайпингов и оставить только возможность использовать
- Только я написал [статью на dev.to как устроен
- от
## Обсуждается
-
-
- унификация нейминга функций по всему API
## Примерный план
- Beta через 3 месяца
- Ecosystem Port еще через 3 месяца
- Stable Release
- Effect 4 — первый LTS, поддержка Effect 3 в течение полугода после релиза
Чтож — рад был понаблюдать за ивентом (хоть и только через твиттер), увидеть фотки крутых инженеров, убедиться что gcanti не вымышленный персонаж
Если у кого-то есть комментарии — рад обсудить)
Закончились "Effect days".
Хочу немного пройтись по планам на след версию из этого твита (https://x.com/dillon_mulroy/status/1903401848083550429)
## Бетон
- "Smol" — по слухам редьюс бандлсайза 50-80%. Основной проблемой был большой размер рантайма — видимо с этим хорошенько поработали. Надеюсь
Effect будет более правдоподобным вариантом на фронте, как и Schema (сейчас она импортирует Effect для асинхронных трансформаций)- Хотят избавиться от
FiberRefs. Действительно вводило в заблуждение необходимость FiberRef при наличии механизма опциональных сервисов — так что по совету Max Brown лучше уже сейчас использовать Context.Reference- Я так понимаю решили избавиться от сабтайпингов и оставить только возможность использовать
yield*. Например Either<R, L> это вот такой подтип Effect<R, L>, что позволяло использовать Either в тех местах где ожидался Effect. Теперь будет возможно делать только yield* <Either>. Меньше способов стрельнуть себе в ногу.- Только я написал [статью на dev.to как устроен
Scheduler](https://dev.to/khraks_mamtsov/scheduling-in-effect-understanding-and-implementing-1j70), как его решили переписать полностью, как и многие core модули). Благо непоправимая польза от этого знания не померкнет и останется в копилке "transferable skills" - так что советую)- от
STM вообще решили избавиться в пользу каких-то из-коробочных транзакций???## Обсуждается
-
Either -> Result -
Exit<A, E> -> Result<A, Cause<E>> - унификация нейминга функций по всему API
## Примерный план
- Beta через 3 месяца
- Ecosystem Port еще через 3 месяца
- Stable Release
- Effect 4 — первый LTS, поддержка Effect 3 в течение полугода после релиза
Чтож — рад был понаблюдать за ивентом (хоть и только через твиттер), увидеть фотки крутых инженеров, убедиться что gcanti не вымышленный персонаж
XD. Круто что экосистема развивается и Effect все лучше приспосабливается к реалиям js. Если у кого-то есть комментарии — рад обсудить)
X (formerly Twitter)
Dillon Mulroy λ (@dillon_mulroy) on X
@lyovson @EffectTS_
👍6🔥2🤔1
Хоть
Для этого можно использовать такой прием - давайте помечать
В этом примере использовали брендирование
Более интересным решением будет использовать R-канал для наших целей —
Мораль такова:
если нужно пометить воркфлоу каким-то образом, то стоит использовать
TS-песочница с примером использования: https://tsplay.dev/m0kYaN
Effect и избавляет от проблемы раскраски функций. Все же может быть полезно отслеживать асинхронное выполнение, допустим, чтобы в решающий момент быть уверенным, что Effect.runSync не выкинет ошибку.Для этого можно использовать такой прием - давайте помечать
Effect меткой на тайп-левеле, а runSync ограничим таким образом, чтобы вызов эффекта с такой меткой был невалидным.const AsyncTypeId: unique symbol = Symbol.for("my-project/Async");
type AsyncTypeId = typeof AsyncTypeId;
interface Async {
[AsyncTypeId]: AsyncTypeId;
}
function markAsync<A, E, R>(
effect: Effect.Effect<A, E, R>,
): Effect.Effect<A, E, R> & Async {
return effect as any;
}
function runSync<A, E, Eff extends Effect.Effect<A, E>>(
effect: Eff extends Async ? never : Eff,
) {
return Effect.runSync(effect);
}
В этом примере использовали брендирование
& Async — но это наивный подход, потомучто он не будет работать с большинством встроенных функций Effect, так как они имеют сигнатуру не сохраняющую подтип самого эффекта, а оперируют только над его содержимым:declare const map: {
<A, B>(f: (a: A) => B): <E, R>(self: Effect<A, E, R>) => Effect<B, E, R>
}
Более интересным решением будет использовать R-канал для наших целей —
Effect<A, E, R | Async>. С такой сигнатурой не нужно оборачивать runSync, потомучто он требует Effect<A, E, never>. И можно ввернуть кастомный асинхронный раннер:function runMarkedAsync<A, E, R>(effect:
[Exclude<R, Async>] extends [never]
? Effect.Effect<A, E, R>
: Effect.Effect<A, E>
) { /* ... */ }
Мораль такова:
если нужно пометить воркфлоу каким-то образом, то стоит использовать
R-канал, а не брендированиеTS-песочница с примером использования: https://tsplay.dev/m0kYaN
www.typenoscriptlang.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.
👍2✍1
Khraks dev
# Effect 4.0 Закончились "Effect days". Хочу немного пройтись по планам на след версию из этого твита (https://x.com/dillon_mulroy/status/1903401848083550429) ## Бетон - "Smol" — по слухам редьюс бандлсайза 50-80%. Основной проблемой был большой размер рантайма…
Запись доклада Майкла о будущем Effect 4
https://youtu.be/nyvB6nRe5x0?si=Hh8jAseo6Xz66pDf
TLDR:
https://github.com/Effect-TS/effect-smol
- Уменьшился размер бандла:
-
-
-
-
-
- Многие модули имплементированы по новому - стали более производительными и уменьшились в размере - перешли от initial encoding (интерпретация операций) к final encoding (непосредственное выполнение) - думаю будет ниже порог входа для контрибьюторов, потомучто это более привычный стиль написания кода. Плюс кажется переписали все on top of Effect - раньше в initial encoding помоему для каждого модуля была своя пара интерпретатор-AST
-
- зависшие файберы теперь не прерывают процесс — что более интуитивно
- увеличина производительность
-
-
-
https://youtu.be/nyvB6nRe5x0?si=Hh8jAseo6Xz66pDf
TLDR:
https://github.com/Effect-TS/effect-smol
- Уменьшился размер бандла:
-
5.4Kb — базовый эффект с рантаймом-
17.3Kb — со схемой (было 50Kb)-
6.63Kb — со Stream-
Context.Reference - кажется единственным способом передавать неявные зависимости - RuntimeFlags и FiberRefs не будет -
Queue был прямым портом ZIO.Queue, теперь она будет Mailbox более производительная и фиче-комплит версия очереди- Многие модули имплементированы по новому - стали более производительными и уменьшились в размере - перешли от initial encoding (интерпретация операций) к final encoding (непосредственное выполнение) - думаю будет ниже порог входа для контрибьюторов, потомучто это более привычный стиль написания кода. Плюс кажется переписали все on top of Effect - раньше в initial encoding помоему для каждого модуля была своя пара интерпретатор-AST
Stream.fromIterable(100_000): 3.23s -> 170ms 20x faster - без переписания на go)-
STM встроили в Effect- зависшие файберы теперь не прерывают процесс — что более интуитивно
- увеличина производительность
-
Effect.cron теперь понимает таймзоны и добавляет CronParseError в E-канал-
Layer.effect - теперь предоставляет Scope по дефолту - Layer.scoped не нужен-
Effect.Service - возможно будет теперь единственным способом создавать DI-тегиYouTube
Building Effect 4.0 | Michael Arnaldi (Effect Days 2025)
Get support from the Effect community → https://discord.gg/effect-ts
Michael Arnaldi discusses the upcoming Effect 4.0, highlighting new features, performance improvements, and community feedback integration.
00:00 Intro & the hat story
02:12 Effect’s growth…
Michael Arnaldi discusses the upcoming Effect 4.0, highlighting new features, performance improvements, and community feedback integration.
00:00 Intro & the hat story
02:12 Effect’s growth…
🤓3👍2🔥2
Составил список открытых пропоузалов
• Async Context — R-channel & Context.Reference
• TS throws annotation — E-channel & expected errors
• Record & Tuple — Data module
• Composite — Equal trait
• Async Iterator helpers — Stream
• Explicit Resource Management — Scope
• pipeline operator — pipe function
• do-notation — Effect.Do | Effect.gen
Но более того — имплементация в
TC39 и соответствующих подходов в Effect — как по мне впечатляет. Особенно первый пункт:• Async Context — R-channel & Context.Reference
• TS throws annotation — E-channel & expected errors
• Record & Tuple — Data module
• Composite — Equal trait
• Async Iterator helpers — Stream
• Explicit Resource Management — Scope
• pipeline operator — pipe function
• do-notation — Effect.Do | Effect.gen
Но более того — имплементация в
Effect гораздо более композабельная и типобезопасная.👍6🤯6🔥1💩1
Type Hole
Это типа TODO, но которое НЕ заставляет компилятор гореть красным огнем корректности.
При компиляции GHC скажет
В TS тоже можно реализовать нечто подобное, без автореализации, конечно, но в некоторых местах может быть полезно то, что компилятор не будет подсвечивать код красным и выдавать безумную простыню ошибки.
Мне пригождается в написании
Вот тут и начинается веселуха - если ты написал эти функции неверно (а происходит это на протяжении всего времени ее реализации), то ts подчеркивает красным весь блок
Статья от несравненного Giulio Canti - автора
https://dev.to/gcanti/type-holes-in-typenoscript-2lck
У кого-то были юзкейсы, когда это могло быть полезно?
Это типа TODO, но которое НЕ заставляет компилятор гореть красным огнем корректности.
addTwoNumbers :: Int -> Int -> Int
addTwoNumbers x y = _
При компиляции GHC скажет
Found hole: _ :: Int. В каких-то языках с продвинутой системой типов (возможно сам Haskell, Idris - хз не разбирался) компилятор сам может подставить корректную реализацию! Там даже можно сначала описать типы, а потом "навайбкодить" компилятором реализацию))В TS тоже можно реализовать нечто подобное, без автореализации, конечно, но в некоторых местах может быть полезно то, что компилятор не будет подсвечивать код красным и выдавать безумную простыню ошибки.
const _ = <T>() => T {
throw new Error("Type hole");
}
Мне пригождается в написании
Schema.transform - это когда есть две схемы и их нужно смерджить в одну - AB transform CD = AD - но чтобы это сделать нужны две функции B => C & C => B.Schema.transform<AB, CD, {
encode, // B->C
decode, // C->B
}>
Вот тут и начинается веселуха - если ты написал эти функции неверно (а происходит это на протяжении всего времени ее реализации), то ts подчеркивает красным весь блок
{...} и при попытке навестись на аргументы функци - он сначала показывает всю ошибку (а они очень длинные порой) и только потом тип наводимого аргумента. Сначала я возвращал 123 as any чтобы заткнуть его, но тогда при наведении например на decode: C => B - я не понимал какой тип B должен вернуться. И тут я вспомнил про _() — возвращаем из обоих функций и имеем прелестнейший DX — при ховер на _() виден тип, который должен быть на месте этой дырки, и компилятор не вставляет палки в колеса.Статья от несравненного Giulio Canti - автора
fp-ts и Schemahttps://dev.to/gcanti/type-holes-in-typenoscript-2lck
У кого-то были юзкейсы, когда это могло быть полезно?
👍4❤🔥2🔥1
Недавно открыл для себя
Например есть аналог
Или рефакторнинг — "Переписать с
В README — способ подключения и список возможностей.
В Discord вижу, что идет движуха в проекте.
Кстати — может есть какие-то идеи, какие еще фичи можно туда добавить?
@effect/language-service - плагин для Typenoscript со специфичными для Effect диагностиками и рефакторингами.Например есть аналог
no-floating-promises:Effect.gen(function* () {
//Effect must be yielded or assigned to a variable.effect(3)
Effect.success(1);
})
Или рефакторнинг — "Переписать с
async-await на Effect.gen + yield*"const before = async () => {
const response = await fetch("");
const body = await response.json();
return body;
};
// Rewrite to Effect.gen with failures
const after = () =>
Effect.gen(function* () {
Effect.tryPromise({
try: () => fetch(""),
catch: (error) => ({ _tag: "Error1" as const, error }),
});
const body = yield* Effect.tryPromise({
try: () => response.json(),
catch: (error) => ({ _tag: "Error2" as const, error }),
});
return body;
});
В README — способ подключения и список возможностей.
В Discord вижу, что идет движуха в проекте.
Кстати — может есть какие-то идеи, какие еще фичи можно туда добавить?
🔥4🤩3
Раньше думал, что есть 2 механизма повторов эффектов -
Но после более детального изучения понял что их три - есть еще
Узнал я это пока ковырялся с PR, который решает вот такую вот задачку:
"Полезно было бы узнавать информацию о повторении в повторяемом эффекте!)"
Например, эффект падает после нескольких попыток получить запрос - мы хотим упасть и залогировать сколько времени мы стучались (как на стриншоте в комментах).
Тут у меня в голове все соединилось - я как раз изучил
Так что встречайте — великий и ужасный
PS Прикольно что пригодилось мое изучение
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
Хозяйке на заметку.
1. Эти два способа сужения типа не эквивалентны
2. Tакое API — unsound.
Eсли передать в качестве
PS
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
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
## Заинтересовался вот этой оптимизацией в
Суть в том что трансформеры
Идея оптимизации такова — а давайте, если схема использует только
Для удобства в схеме используется вот такой тип:
Оптимизация состоит из 3х частей:
- нужно сохранять тип трансформаций
- эти хелперы используются главным образом в функции ParseResult.go, которая отвечает за то чтобы обойти все AST-ноды схемы и выдать ParseResult.Parser— искомую функцию энкода-декода
- ParseResult.handleForbidden— функция в которой происходит конечное решение использовать рантайм или нет
Из ee реализации понятно, что чтобы ритуал состоялся необходимо:
- как намерение пользователя — запустить семейство
- так и возможность схемы — использование именно
В противном случае оптимизация перестает работать — и подрубается рантайм эффекта с синхронным планировщиком что, кажется, одно и то же, что и
Но так же важно помнить, что аннотации схемы
Schema:If you want more perf you can useParseResult.flatMapinstead ofEffect.genand 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
Expandable hover
Хорошая фича, интересно как оно будет отбражать интерфейсы и юнионы?
https://x.com/drosenwasser/status/1939775110858383418?t=1rW9WKEh3GdniAtQ7_EGKQ&s=19
Хорошая фича, интересно как оно будет отбражать интерфейсы и юнионы?
https://x.com/drosenwasser/status/1939775110858383418?t=1rW9WKEh3GdniAtQ7_EGKQ&s=19
❤2