Effect
Еще один виток развития программиста и коммьюнити - понимание преимуществ описаний (конфигов) vs непосредственным исполнением.
Так появились
Какие преимущества это дает?
Ну, например, мы можем иметь несколько интерпретаторов-исполнителей этих самых описаний:
- давайте рендерить компоненты на сервере, в браузере и нативно в телефонах
- давайте исполним
Давайте сделаем наше "описание" объектом высшего порядка? Будем передавать в его в функции и другие "описания" и модифицировать поведение?
-
- и тут
Тайпскрипт дал строгую типизацию, но он не сохраняет ниформацию о выбрасываемых ошибках
Фронтенд компоненты не сохранают информацию о требуемых контекстах (зависимостях) - об этом, обычно, узнают из ошибок в рантайме
Эфекты можно комбинировать различными способами при этом "каналы" ошибок и зависимостей будут расширяться все новыми вариантами - в примере видно что исполнение эфектов последовательно конструирует новый эфект с суммой зависимостей и суммой возможных ошибок.
Результирующая программа также является эффектом - удобство в том, что интерпретатор (runtime) этой программы требует чтобы "канал" зависимостей был типа never - нашей программе должны быть предоставлены все зависимости перед исполнением.
В настоящий момент экосистема
-
-
-
-
-
-
Вышеописанное не полность покрывает фичи предоставлямые
Надеюсь
https://effect.website/
Матералы:
- https://www.youtube.com/@effect-ts
- https://github.com/antoine-coulon/effect-introduction
- https://github.com/ethanniser/effect-workshop
Еще один виток развития программиста и коммьюнити - понимание преимуществ описаний (конфигов) vs непосредственным исполнением.
Так появились
JVM, .Net(CLR) для которых можно писать на Java/Scala/Kotlin, C#/F#/Q# и пр. Так произошло с фронтедом когда появился React - теперь мы пишем компоненты - описание разметки вместо jQuery лапши. Так, надеюсь произойдет и с js/ts с популяризацией Effect - экосистемы вокруг примитива-вычисления.Effect - является описанием некоего вычисления, так же как фронтенд-компонен - описанием некой разметки.Какие преимущества это дает?
Ну, например, мы можем иметь несколько интерпретаторов-исполнителей этих самых описаний:
- давайте рендерить компоненты на сервере, в браузере и нативно в телефонах
- давайте исполним
Effect в различных js окруженияхДавайте сделаем наше "описание" объектом высшего порядка? Будем передавать в его в функции и другие "описания" и модифицировать поведение?
-
slots, renderProps, HOC- и тут
Effect предлагает огромное множество возможностей - функции-кобинаторы для повторений (`repeat`) различных видов, ретраев (`retry`) со стратегиями, контролируемого параллельного исполнения, последовательного исполненияТайпскрипт дал строгую типизацию, но он не сохраняет ниформацию о выбрасываемых ошибках
try {
throw 'Error'
} catch (x: unknown) {}
Фронтенд компоненты не сохранают информацию о требуемых контекстах (зависимостях) - об этом, обычно, узнают из ошибок в рантайме
Effect в свою очередь позиционирует себя как missing ts standard library, better ts - его модель решает проблему трекинга возможных ошибок и требуемых зависимостей
type Result<A, E> =
| { type: 'ok' , ok : A }
| { type: 'err', err: E };
type Effect<A, E, R> = (requirements: R) => Promise<Result<A, E>>
Эфекты можно комбинировать различными способами при этом "каналы" ошибок и зависимостей будут расширяться все новыми вариантами - в примере видно что исполнение эфектов последовательно конструирует новый эфект с суммой зависимостей и суммой возможных ошибок.
const andThan = (
from: Effect<A1, E1, R1>,
next: (prev: A1) => Effect<A2, E2, R2>
): Effect<A2, E1 | E2, R1 | R2>
Результирующая программа также является эффектом - удобство в том, что интерпретатор (runtime) этой программы требует чтобы "канал" зависимостей был типа never - нашей программе должны быть предоставлены все зависимости перед исполнением.
В настоящий момент экосистема
Effect достаточно богата и влючает:-
@effect/schema - более универсальный старший брат zod для декодирования за авторством gcanti-
@effect/cli - фреймворк для CLI-
effect-http - фреймворк для описания rest-api с последующей генерацией OpenAPI схемы, и конструирования типобезопасного клиента и сервера-
@effect/rpc @effect/rpc-http-
@effect/opentelemetry-
@effect/platform @effect/platform-node @effect/platform-bun @effect/platform-browserВышеописанное не полность покрывает фичи предоставлямые
Effect, но это уже делает его многообещающим подарком комьюнити. Кроме того авторы проекта - крутейшая команда высококлассных специалистов.Надеюсь
Effect скоро станет новым стандартом разработки и призываю всех поставить на эту лошадку, пока он ней никто не знает. Это как повысит вышу конкурентноспособность в будущем, так и сделает ваш настоящий не-effect код лучше - потомучто вы просто не сможете писАть по старому.https://effect.website/
Матералы:
- https://www.youtube.com/@effect-ts
- https://github.com/antoine-coulon/effect-introduction
- https://github.com/ethanniser/effect-workshop
effect.website
Effect – The best way to build robust apps in TypeScript
Effect is a powerful TypeScript library designed to help developers easily create complex, synchronous, and asynchronous programs.
👍5🎉1🗿1
🚀 Effect 3.0 🚀
Начиная с 3 версии
Следующими задачами станут обеспечение стабильности библиотек экосистемы, а также добавление большого количества документации и примеров.
Приятно, что пара моих PR тоже там есть)
https://effect.website/blog/effect-3.0
Начиная с 3 версии
effect замедляет ломающие изменения и становится стабильным. (В отличие от остальных экосистемных пакетов 😢).Следующими задачами станут обеспечение стабильности библиотек экосистемы, а также добавление большого количества документации и примеров.
Приятно, что пара моих PR тоже там есть)
https://effect.website/blog/effect-3.0
Effect Documentation
Effect 3.0 (Release)
🔥5
Effect 3.0.4
За 30 минут решилась 3 летняя задача по избавлению
- изменением порядка типо-параметров
-
- добавлением контекста для схем
- Effect.Tag
За 30 минут решилась 3 летняя задача по избавлению
Effect.gen (аналог do-нотации) от функции-адаптера которая служила прослойкой чтобы не "fuck of"(M Arnaldi) вывод типов. Теперь можно просто yield* MyEffect. Очередное улучшение DX можно поставить на первое место с:- изменением порядка типо-параметров
-
Pipeablе- добавлением контекста для схем
- Effect.Tag
Работаю над PR по добавлению в
Сначала я наивно полагал, что
Почему две функции мапятся на один
Если кто-то знает нюансы работы с
Работая над этим PR - убил сразу всех зайцев:
- соприкоснулся с командой
- узнал интересные детали работы
- узнал о наличии
- всплакнул, работая с непромифицированным апи - какого года релиз?
https://github.com/Effect-TS/effect/pull/2691
Effect обертки над Geolocation API. getCurrentPosition - нехитрыми преобразованиями мапится на Effect - такой ваншот получения координат устройстваwatchPosition и clearWatch - напятся на Stream - но тут прояснилась интересная деталь.Сначала я наивно полагал, что
watchPosition(successCb, errorCb) имеет семантику - хоть одна ошибка и автоотписка. Но после ресерча понял, что эта функция будет стрелять в оба канала ошибки и координаты независимо. А отписываться нужно самостоятельно только если прилетит ошибка о нехватке прав.Почему две функции мапятся на один
Stream? Потому что в Effect есть свой механизм прерываний - который и использует конструктор Stream.async. То есть чтоб отписаться от слежки (`clearWatch`) достаточно прервать Fiber в котором исполняется стрим.Если кто-то знает нюансы работы с
Geolocation API - конструктивная критика приветствуется)Работая над этим PR - убил сразу всех зайцев:
- соприкоснулся с командой
Effect- узнал интересные детали работы
Geolocation API- узнал о наличии
EmitOps - удобных хелперах для жизненного цикла стрима- всплакнул, работая с непромифицированным апи - какого года релиз?
https://github.com/Effect-TS/effect/pull/2691
GitHub
Browser Geolocation API by KhraksMamtsov · Pull Request #2691 · Effect-TS/effect
Type
Refactor
Feature
Bug Fix
Optimization
Documentation Update
Denoscription
Wrap Geolocation API with Effect
https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API
But I have som...
Refactor
Feature
Bug Fix
Optimization
Documentation Update
Denoscription
Wrap Geolocation API with Effect
https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API
But I have som...
🔥4
Do-simulation
Часто мы оперируем не одним значением в программе - а комбинируем несколько.
Но что делать, если значения завернуты "коробоки"?
Давайте порассуждаем на примере типа данных
На ум приходит что-то такое:
То есть мы получаем доступ к завернутому значению через замыкание, которое играет роль некоего "контекста" в котором лежат известные развернутые значения.
А давайте мы опустим этот контекст с уровня "так работает язык", на уровень объектов самогО языка - будем использовать для контекста (
Вырисовался паттерн который можно положить в функцию с понятным названием и переиспользовать.
Действительно - для работы такого подхода нам достаточно передать каким будет название нового поля в контексте и вычислить его значение, исходя из предыдущего контекста. Функция называется
Начальное значение контекста так же положим в переменную -
Подобный код на Haskell выглядел бы следующим образом:
Логика обрамленa в блок
Кроме использования в
Какой способ записи предпочитаете вы -
Было ли понятным объяснение? Стоит ли что-то расписать подробнее?
Часто мы оперируем не одним значением в программе - а комбинируем несколько.
Но что делать, если значения завернуты "коробоки"?
Давайте порассуждаем на примере типа данных
Either<R, L>На ум приходит что-то такое:
const Ea, Eb, Ec // Either<'a', ErrorA> ...
Either.flatMap(Ea, a =>
Either.flatMap(Eb, b =>
Either.flatMap(Ec, c =>
Either.some(a + b + c)
))) // Either<'abc', ErrorA | ErrorB | ErrorC>
То есть мы получаем доступ к завернутому значению через замыкание, которое играет роль некоего "контекста" в котором лежат известные развернутые значения.
А давайте мы опустим этот контекст с уровня "так работает язык", на уровень объектов самогО языка - будем использовать для контекста (
x) и складывать в его поля fieldName (аналог названия переменной в контексте языка) развернутые значения переменных. Изначально контекст пуст - Right<{}>. const Ea, Eb, Ec // Either<'a', ErrorA> ...
Either.right({}).pipe(
Either.flatMap(x => Either.map(Ea, a => ({...x, fieldA: a}))),
Either.flatMap(x => Either.map(Eb, b => ({...x, fieldB: b}))),
Either.flatMap(x => Either.map(Ec, c => ({...x, fieldC: c}))),
Either.map(x => x.fieldA + x.fieldB + x.fieldC)
)
Вырисовался паттерн который можно положить в функцию с понятным названием и переиспользовать.
const bind = (
fieldName: string,
workWithContext: (x) => Either<fieldValue, ...>
) => Either.flatMap(
(x) => Either.map(workWithContext(x),
fieldValue => ({...x, [fieldName]: fieldValue}))
)
Действительно - для работы такого подхода нам достаточно передать каким будет название нового поля в контексте и вычислить его значение, исходя из предыдущего контекста. Функция называется
bind потому что мы привязываем новое значение (fieldValue) к его имени (fieldName) в контексте.Начальное значение контекста так же положим в переменную -
const Do = Either.right({}).Eigher.Do.pipe(
Either.bind('fieldA'), x => Ea), // Either<{fieldA: 'a'}, ErrorA>
Either.bind('fieldB'), x => Eb), // Either<{fieldA: 'a', fieldB: 'b'}, ErrorA | ErrorB>
Either.bind('fieldC'), x => Ec),
Either.map(x => x.fieldA + x.fieldB + x.fieldC)
)
Подобный код на Haskell выглядел бы следующим образом:
do {
fieldA <- Ea
fieldB <- Eb
fieldC <- Ec
return fieldA + fieldB + fieldC
}Логика обрамленa в блок
do { ... }, отчего и название - Do-notation (Ду-нотация)Кроме использования в
pipe можно использовать и gen версию:Either.gen(function* () {
const fieldA = yield* Ea
const fieldB = yield* Eb
const fieldC = yield* Ec
return fieldA + fieldB + fieldC
})Do-simulation определена для модулей Option, Either, Effect, Stream, Array.Какой способ записи предпочитаете вы -
pipe vs gen?Было ли понятным объяснение? Стоит ли что-то расписать подробнее?
👍3👏2🔥1
Нарисовал схему иерархии типов данных в
Показалось интересным что
Почему?
Вычисления запускаются движком эфф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
