Нарисовал схему иерархии типов данных в
Показалось интересным что
Почему?
Вычисления запускаются движком эффeкта - он то и будет интерпретировать
Интересным является и реализация этой иерархии:
- Каждый вышестоящий модуль в иерархии аугментирует непосредственно предыдущий - на уровне типов
- В реализации типов ниже
Effect. Показалось интересным что
Option и Either являются подтипом Effect. Но при этом Option не является подтипом Either. Почему?
Option и Either являются описанием данных, a Effect - вычисления.Вычисления запускаются движком эффeкта - он то и будет интерпретировать
Option и Either, как Effect - константу (вычисления которые всегда возвращают одно и то же).Exit - тот же Either, но имеет семантику "результат вычисления Effect " - к нему применяется вышеописанная логика.Tag - описание связи ( Id<->Value ) зависимости, интерпретируется как Effect который вернет реализацию зависимости Value, если предоставить ее IdSTM - Effect который выполнится с гарантией транзакционности.Stream - вычисление которое может испустить несколько значений - Effect уже представляется как single-shot StreamSink - приемник значений испускаемых Stream - Effect работает как "константный" - Sink Интересным является и реализация этой иерархии:
- Каждый вышестоящий модуль в иерархии аугментирует непосредственно предыдущий - на уровне типов
- В реализации типов ниже
Effect в иерархии добавлено дополнительное поле _op - на которое опирается движок👍2🔥1
Khraks dev
Do-simulation Часто мы оперируем не одним значением в программе - а комбинируем несколько. Но что делать, если значения завернуты "коробоки"? Давайте порассуждаем на примере типа данных Either<R, L> На ум приходит что-то такое: const Ea, Eb, Ec // Either<'a'…
В продолжение заметки о
Для меня интересным было узнать, о спецэфектах которые может вытворять монада для массивов. Часто использую её для генерации всех возможных пропсов, которые использую в тестах UI компонентов.
https://ruhaskell.org/posts/theory/2018/01/18/effects-haskell.html
Do-notation хочу поделиться отличной статьёй о видах коробок(контекстах), в которых могут происходить вычисления. Для меня интересным было узнать, о спецэфектах которые может вытворять монада для массивов. Часто использую её для генерации всех возможных пропсов, которые использую в тестах UI компонентов.
https://ruhaskell.org/posts/theory/2018/01/18/effects-haskell.html
Не смотря на минусы
Брендирование строк, например, теряет это свойство(
Помню был PR по добавлению специального синтаксиса для брендов, но он так и не взлетел.
Playground link
enum в typenoscript - это до сих пор единственный способ представить номинативный юнион и сохранить механизм "исчерпывания" (exhaustion) - как то проверка в Record и сужение в switch-case. Брендирование строк, например, теряет это свойство(
Помню был PR по добавлению специального синтаксиса для брендов, но он так и не взлетел.
Playground link
enum Fruit { orange = 'orange', apple = 'apple'}
enum Color { orange = 'orange', green = 'green'}
type Fruit_union =
| 'orange' & {__tag: 'Fruit'}
| 'apple' & {__tag: 'Fruit'}
type Color_union =
| 'orange' & {__tag: 'Color'}
| 'green' & {__tag: 'Color'}
// exhaustiveness example
const testExUnion: Record<Fruit_union, number> = {} // doesn't work
// @ts-expect-error Type '{}' is missing the following properties from type 'Record<Fruit, number>': orange, apple
const testExEnum: Record<Fruit, number> = {} // works fine
// nominative example
const testNomColor: Color = Color.orange
// @ts-expect-error Type 'Fruit.orange' is not assignable to type 'Color'.
const testNomColor1: Color = Fruit.orange
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
Бесплатный курс по
Ссылка
effect от Sandro MaglioneLearning to use effect will soon become an insane unfair advantage
Ссылка
typeonce.dev
Effect: Beginners Complete Getting Started | Typeonce
Build production and type-safe Typenoscript apps with Effect. In this course we go from no knowledge of effect to implementing an API request with Runtime, Layer, Config, and more.
👍5
Как говорил Архимед:
На самом деле продолжение фразы другое: "... то поймешь, как не надо называть функции "
Если долго смотреть на эту демку, то поймешь контрвариантность функции по аргументам
function test(cb: (x: 4 | 2) => void) {
cb(4)
}
test(x => x)
// ^? (parameter) x: 4 | 2
function num(x: number) {}
function twoOrFour(x: 2 | 4) {}
function two(x: 2) {}
function four(x: 4) {}
function str(x: string) {}
test(num)
test(twoOrFour)
test(two) // Type '4 | 2' is not assignable to type '2'
test(four) // Type '4 | 2' is not assignable to type '4'
test(str) //Type 'number' is not assignable to type 'string'На самом деле продолжение фразы другое: "
👍2😁2❤1
В 3.8 для многих структур данных я реализовал интерфейс
Теперь, например,
Это дало возможность работать с ними в контексте
С одной стороны стало более единообразно с совсем уж не
C другой - не так самодокументировано - при чтении глазами нужно помнить, с каким типом идет работа, ведь, допустим, тех же
В поиске пути реализации этой фичи я изучил рантайм представление эффекта и набрел на модуль Effectable. Этот модуль как раз нужен для облегчения внедрения своих примитивов в
Он представляет из себя набор прототипов и базовых классов.
Примерно так можно интегрировать свои структуры данных в экосистему
Как по мне - фичи крутые - как
Effect https://effect.website/blog/effect-3.8#more-types-implement-effect. Теперь, например,
Ref, Fiber, Deferred - стали подтипами Effect.Это дало возможность работать с ними в контексте
Effect.gen бесшовно:
Effect.gen(function*() {
const ref = yield* Ref.make('val')
const refOldWay = yield* Ref.get(ref) // было
const newWay = yield* ref // хоба
})
С одной стороны стало более единообразно с совсем уж не
Effect данными - Option, Either и тд.C другой - не так самодокументировано - при чтении глазами нужно помнить, с каким типом идет работа, ведь, допустим, тех же
Ref ов существует туева хуча - ScopedRef , SyncronizedRef , SubnoscriptionRef.В поиске пути реализации этой фичи я изучил рантайм представление эффекта и набрел на модуль Effectable. Этот модуль как раз нужен для облегчения внедрения своих примитивов в
Effect мир. С его помощью и была имплементирована фича.Он представляет из себя набор прототипов и базовых классов.
Effect`-примитив в рантайме представляется объектом с полем _op и другими полями необходимыми для "запуска" соответствующей _op ерации - их существует некоторое количество. В качестве точки расширения выступает операция OP_COMMIT которая подразумевает наличие метода commit(): Effect.
export class MyVal
extends Effectable.Class<"val">
implements Effect.Effect<"val">
{
#val = "val" as const;
commit() {
return Effect.sync(() => this.#val);
}
}
Примерно так можно интегрировать свои структуры данных в экосистему
Как по мне - фичи крутые - как
Effectable так и экосистемная эффективизация) Что думаете об этом? Будете использовать?)Effect Documentation
Effect 3.8 (Release)
👍2👏1
Захотелось поупражняться с effect и решил сделать CLI
Постановку взял тут https://news.1rj.ru/str/we_use_js/3891
Подсчет статистики наивный
Gist - https://gist.github.com/KhraksMamtsov/c29c112a5e1bf01e21e309646528445e
Из крутого:
-
-
-
- типобезопасность (не знаю нужно ли упоминать — это effect)
Постановку взял тут https://news.1rj.ru/str/we_use_js/3891
Подсчет статистики наивный
⋮)Gist - https://gist.github.com/KhraksMamtsov/c29c112a5e1bf01e21e309646528445e
Из крутого:
-
--wizard из коробки — возможность интерактивно составить какую-либо конкретную команду для cli-
--help коробочный -
--completions всякого рода — sh, bash, fish, zsh (сам не пользуюсь)- типобезопасность (не знаю нужно ли упоминать — это effect)
import { Args, Command } from "@effect/cli";
import { NodeContext, NodeRuntime } from "@effect/platform-node";
import { Path, FileSystem as FS } from "@effect/platform";
import { Effect, Stream } from "effect";
import * as Schema from "@effect/schema/Schema";
// Схема статистики файла
const StatsContract = Schema.Struct({
chars: Schema.Number,
lines: Schema.Number,
words: Schema.Number,
});
// Функция энкода статистики в JSON
const encodeStatsContractToJSON = Schema.encodeEither(
Schema.parseJson(StatsContract)
);
// Подсчет статистики файла
const calculateFileStats = (path: string) =>
Effect.gen(function* () {
// импорт сервиса файловой системы
const fs = yield* FS.FileSystem;
return yield* fs.stream(path).pipe(
Stream.decodeText("utf8"),
Stream.flatMap((x) => Stream.fromIterable(x.split(""))),
Stream.runFold({ lines: 0, words: 0, chars: 0 }, (acc, cur) => ({
chars: acc.chars + 1,
lines: acc.lines + (cur === "\n" ? 1 : 0),
words: acc.words + (cur === " " ? 1 : 0),
}))
);
});
// Описание аргумента команды
const pathArg = Args.file({ exists: "yes" }).pipe(
Args.withDenoscription("Path to file")
);
// Описание подкоманды с обработчиком
const reportSubCommand = Command.make("report", { pathArg }, (x) =>
Effect.gen(function* () {
const fs = yield* FS.FileSystem;
const path = yield* Path.Path;
const stats = yield* calculateFileStats(x.pathArg);
const cwd = path.resolve();
const statsPath = path.join(cwd, "stats.json");
const data = yield* encodeStatsContractToJSON(stats);
yield* fs.writeFileString(statsPath, data);
})
);
const printSubCommand = Command.make("print", { pathArg }, (x) =>
Effect.gen(function* () {
const stats = yield* calculateFileStats(x.pathArg);
yield* Effect.log(stats);
})
);
// Собираем приложение
const appCommand = Command.make("app").pipe(
Command.withDenoscription("Node Kata solution"),
Command.withSubcommands([reportSubCommand, printSubCommand])
);
// Конвертим в effect
const cli = Command.run(appCommand, {
name: "Node Kata",
version: "0.0.1",
});
// Предоставляем зависимости
const program = cli(process.argv).pipe(Effect.provide(NodeContext.layer));
// Запускаем
NodeRuntime.runMain(program);
Telegram
Node.JS [ru] | Серверный JavaScript
👩💻 Задачка по NodeJS
Создайте приложение на Node.js, которое принимает путь к текстовому файлу в качестве аргумента командной строки, подсчитывает количество строк, слов и символов в файле, а затем сохраняет результат в stats.json. Программа также должна…
Создайте приложение на Node.js, которое принимает путь к текстовому файлу в качестве аргумента командной строки, подсчитывает количество строк, слов и символов в файле, а затем сохраняет результат в stats.json. Программа также должна…
🔥6
Khraks dev
Захотелось поупражняться с effect и решил сделать CLI Постановку взял тут https://news.1rj.ru/str/we_use_js/3891 Подсчет статистики наивный ⋮) Gist - https://gist.github.com/KhraksMamtsov/c29c112a5e1bf01e21e309646528445e Из крутого: - --wizard из коробки — возможность…
This media is not supported in your browser
VIEW IN TELEGRAM
Запись работы
wizard - там под каждый тип аргументов вылезает свой промпт - выбор файла, выбор из вариантов и тд👍1
В мире ts достаточно темных зон и иногда выручает только знание нюансов.
Задачка: получить в типо-параметре дженерик функции кортеж его аргумента вот так
Решаем наивным подходом: Приходит понимание что трактовать аргумент можно двояко — и как гомогенный(одного типа) массив и как кортеж
Попытка номер 2 (
Вспоминаю про
Натыкаюсь в исходниках какой-то библиотеки для type-level ts на такой трюк (
Оказывается если в ограничениях типо-аргумента есть хоть один кортеж - то он будет трактоваться как кортеж 🤯
https://github.com/microsoft/TypeScript/issues/27179#issuecomment-422911925
Задачка: получить в типо-параметре дженерик функции кортеж его аргумента вот так
const test1 = tuple([1, '2', false]) // [number, string, boolean]
Решаем наивным подходом: Приходит понимание что трактовать аргумент можно двояко — и как гомогенный(одного типа) массив и как кортеж
declare function tupleNaive<T extends ReadonlyArray<any>>(elements: T): T;
tupleWrong([1,'2',false]) // (string | number | boolean)[]
tupleWrong([1, 2]) // number[]
tupleWrong([]) // never[]
Попытка номер 2 (
...elements: T) — сигнатура функции поменяласьdeclare function tupleDisctruction<T extends ReadonlyArray<any>>(...elements: T): T;
tupleDisctruction(1, '2', false) // [number, string, boolean]
tupleDisctruction(1, 2) // [number, number]
tupleDisctruction() // []
Вспоминаю про
const для вывода литералов (<const T ...): Почти, но не тоdeclare function tupleConst<const T extends ReadonlyArray<any>>(elements: T): T;
tupleConst([1, '2', false]) // readonly [1, '2', false]
tupleConst([1, 2]) // readonly [1, 2]
tupleConst([]) // readonly []
Натыкаюсь в исходниках какой-то библиотеки для type-level ts на такой трюк (
<T extends ReadonlyArray<any> | readonly [any]>)declare function tuple<T extends ReadonlyArray<any> | readonly [any]>(elements: T): T;
tuple([1, '2', false]) // [number, string, boolean]
tuple([1, 2]) // [number, number]
tuple([]) // []
Оказывается если в ограничениях типо-аргумента есть хоть один кортеж - то он будет трактоваться как кортеж 🤯
https://github.com/microsoft/TypeScript/issues/27179#issuecomment-422911925
GitHub
Add a way to prefer narrowing array to tuple if possible · Issue #27179 · microsoft/TypeScript
Search Terms tuple narrow, prefer tuple Suggestion This is a meta-suggestion over the top of some of the cases in #16896 and #26113, basically at current it's quite repetitive to specify overlo...
🔥4🤯2😱1
Хочется быть уверенным, что чувствительная информация случайно не утечет в сторонние системы при во время логирования и сериализации.
Изначально в
Я же хочу поделиться "домашним" вариантом этой фичи, которая работает на том же принципе.
Идея — оборачивать чувствительную информацию в объект
Когда чувствительные данные пересекают порог - оборачиваем их в
Обратить внимание, что
Может возникнуть вопрос — зачем? Я могу настроить фильтры в сторонних системах и добиться того, что информация будет отфильтрована. Но в этом подходе нет гарантий, что, допустим, при переименовании чувствительного поля или изменении формата данных система сделает все правильно. В то время как при использовании
Интересно пользуетесь ли вы чем-то похожим? Если еще нет - то можно затащить в проект)
Может кто-то знает — есть ли в других языках подобная утилита? (Хочу порекомендовать коллегам питонистам)
Изначально в
Effect модуль Secret служил для этой цели, но ограничен только работой со строками. Тогда я предложил обобщить идею до любого типа данных и сделал PR с модулем Redacted<T>. Имплементация там специфична для экосистемы. Я же хочу поделиться "домашним" вариантом этой фичи, которая работает на том же принципе.
Идея — оборачивать чувствительную информацию в объект
Redacted, который под капотом сохраняет ee в WeakMap за ссылкой на свой же инстанс.const globalRedactedRegistry = new WeakMap<Redacted<any>, any>();
export class Redacted<T = string> {
public readonly label: string = "redacted";
constructor(value: T, label?: string) {
if (label) { this.label = label; }
globalRedactedRegistry.set(this, value);
}
static value<T>(redacted: Redacted<T>): T {
return globalRedactedRegistry.get(redacted);
}
toString() { return `<${this.label}>`; }
toJSON() { return `<${this.label}>`; }
inspect() { return `<${this.label}>`; }
[Symbol.for("nodejs.util.inspect.custom")]() {
return `<${this.label}>`;
}
[Symbol.for("Deno.customInspect")]() {
return `<${this.label}>`;
}
[Bun.inspect.custom]() { return `<${this.label}>`; }
}
Когда чувствительные данные пересекают порог - оборачиваем их в
Redacted и спим спокойно - разворачиваем только когда нужно передать их дальше, за пределы приложения.const password = new Redacted(process.env.PASSWORD, 'PASSWORD');
const login = new Redacted(process.env.LOGIN, 'LOGIN');
// ...
console.log({ password, login })
/*
{ "password": "<PASSWORD>",
"login": "<LOGIN>" }
*/
// ...
const client = new SomeClient({
password: Redacted.value(password),
login: Redacted.value(login),
})
Обратить внимание, что
.value - статический метод, а не геттер - это сделано нарочито-нарочно, чтобы можно было искать по коду все места, где раскрывается информация.Может возникнуть вопрос — зачем? Я могу настроить фильтры в сторонних системах и добиться того, что информация будет отфильтрована. Но в этом подходе нет гарантий, что, допустим, при переименовании чувствительного поля или изменении формата данных система сделает все правильно. В то время как при использовании
Redacted эта гарантия лежит в коде, рядом с данными.Интересно пользуетесь ли вы чем-то похожим? Если еще нет - то можно затащить в проект)
Может кто-то знает — есть ли в других языках подобная утилита? (Хочу порекомендовать коллегам питонистам)
👍2
Преследуя желание изучить и глубже понять, как работает 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