generated at
Effect-TS
fp-tsio-tsなどを内部で使っている
ZIO inspired
Giulio Cantiがチームに入った ref

(関係ないけど)Nix使ってるやんmrsekut






名前と型からしてAlgebraic Effects and Handlersぽいことをしたいのだろうなmrsekut
ちょっと見てみる




@effectの種類

data
io
schema

諸々のデータ型の提供
zodとかio-tsみたいなやつ


tree shake考慮されてる


Concurrency
JSでここの改善できるものなのか?mrsekut
Resource Safety
Safely manage acquisition and release of resources, even when your program fails.
Error Handling
Handle errors in a structured and reliable manner using Effect's built-in error handling capabilities.
Asynchronicity
Write code that looks the same, whether it is synchronous or asynchronous.
Observability
debugの文脈でこの単語使ってるんだmrsekut

2023/8/30現在ほぼ何も書かれてない


Guides
Effect Essentials
Effectの基本
だが、ずっと第1引数がneverだからそこまで面白くないmrsekut




安全な除算関数
ts
const divide = (a: number, b: number): Effect.Effect<never, Error, number> => b === 0 ? Effect.fail(new Error("Cannot divide by zero")) : Effect.succeed(a / b);
これ、単純な Effect 型使うんだmrsekut
いや後からエイリアス作るのかな
たぶん第1引数がEffect Typeなのだろう
ここではcontextと呼ばれてる
これだけだとまだ旨味がわからないな
合成性とか、never以外のeffect typeを使った例を見たい

Effect-tsで提供されている関数群は、ただのEffectのコンストラクタ

Effect.succeed
ts
const succeed: <A>(value: A) => Effect<never, never, A> = core.succeed
Efect..fail
ts
const fail: <E>(error: E) => Effect<never, E, never> = core.fail


Effect<Requirements, Error, Value>
このイメージ
ts
type Effect<Requirements, Error, Value> = ( context: Context<Requirements> ) => Error | Value
Context に積んでいく
Requirements
Effect Typeのイメージ
Contextに積んでいく
Error
失敗しないならnever
Value
neverだと無限ループ
Kokaでいうdiv型みたいな?

sync effect
thunkを作る関数
Effect.sync
同期的な副作用
失敗しないものに対して使う
ts
const sync: <A>(evaluate: LazyArg<A>) => Effect<never, never, A> = core.sync
ts
const program = Effect.sync(() => { console.log("Hello, World!") // side effect return 42 // return value })
↑これ自体は値
宣言時にはconsole.logが呼ばれない
thunkっぽい
Effect.try
throwする可能性があるthunk
ts
// Effect<never, unknown, any> const program = Effect.try( () => JSON.parse("") // JSON.parse may throw for bad input )
catch節もかける
ts
const program = Effect.try({ try: () => JSON.parse(""), // JSON.parse may throw for bad input catch: (unknown) => new Error(`something went wrong ${unknown}`) // remap the error })


async effects
Effect.promise
ts
// $ExpectType Effect<never, never, string> const program = Effect.promise<string>( () => new Promise((resolve) => { setTimeout(() => { resolve("Async operation completed successfully!") }, 2000) }) )
失敗しないasync関数
Effect.tryPromise
Effect.tryの非同期版
Effect.async
ts
import { Effect } from "effect" import * as NodeFS from "node:fs" // $ExpectType Effect<never, Error, Buffer> const program = Effect.async<never, Error, Buffer>((resume) => { NodeFS.readFile("todos.txt", (error, data) => { if (error) { resume(Effect.fail(error)) } else { resume(Effect.succeed(data)) } }) })
callback関数から作る


Run
hsっぽいmrsekut
組み上げたEffectsを実行するためにrunXXX関数を使う
runSync
runSyncExit
Exitを返す
runPromise
runPromiseExit


using genertors
generorを使って手続き的に書ける
do式っぽいmrsekut
ts
import { Effect } from "effect" const increment = (x: number) => x + 1 const divide = (a: number, b: number): Effect.Effect<never, Error, number> => b === 0 ? Effect.fail(new Error("Cannot divide by zero")) : Effect.succeed(a / b) // $ExpectType Effect<never, never, number> const task1 = Effect.promise(() => Promise.resolve(10)) // $ExpectType Effect<never, never, number> const task2 = Effect.promise(() => Promise.resolve(2)) // $ExpectType Effect<never, Error, string> export const program = Effect.gen(function* (_) { // Effect.Effect<never, never, number>からnumberを取り出してる const a = yield* _(task1) const b = yield* _(task2) const n1 = yield* _(divide(a, b)) const n2 = increment(n1) return `Result is: ${n2}` }) Effect.runPromise(program).then(console.log) // Output: "Result is: 6"
通常のTypeScriptのコードからかなり逸脱するが、すごいmrsekut
これのすごさは、
sync関数もasync関数も全く同じ見た目で書けているという点
この文脈においては関数に色がついてないとみなせるmrsekut
上のgenを使ったコードを関数合成だけで書くと以下のようになる
ts
const program2 = pipe( Effect.all([task1, task2]), Effect.flatMap(([a, b]) => divide(a, b)), Effect.map(increment), Effect.map((n2) => `Result is: ${n2}`) );
do式使わずに、>>=使ってるイメージmrsekut
method chainでも書ける
ts
const program = Effect.all([task1, task2]).pipe( Effect.flatMap(([a, b]) => divide(a, b)), Effect.map((n1) => increment(n1)), Effect.map((n2) => `Result is: ${n2}`) )


pipe
上記のgenの引数の _ がpipeになってる
ts
import { Effect, Random } from "effect" const program = Effect.gen(function* (_) { const n = yield* _( Random.next, Effect.map((n) => n * 2) ) if (n > 0.5) { return yield* _(Effect.succeed("yay!")) } else { return yield* _(Effect.fail("oh no!")) } })
ts
import { pipe } from "effect" const result = pipe(input, func1, func2, ..., funcN)
普通に
ts
import { pipe } from "effect" const increment = (x: number) => x + 1 const double = (x: number) => x * 2 const subtractTen = (x: number) => x - 10 const result = pipe(5, increment, double, subtractTen) console.log(result) // Output: 2
map
ts
import { pipe, Effect } from "effect" const increment = (x: number) => x + 1 // $ExpectType Effect<never, never, number> const mappedEffect = pipe( Effect.succeed(5), Effect.map((x) => increment(x)) ) console.log(Effect.runSync(mappedEffect)) // Output: 6
flatMap
ts
import { pipe, Effect } from "effect" const flatMappedEffect = pipe(effect, Effect.flatMap(func))
tap
計算フローに影響を与えずに副作用を実行する
Effect.all(effects)
effectのリストを渡すと結合できる



2つのerror
予期されるerror
自作する
ts
import { Effect } from "effect" class HttpError { readonly _tag = "HttpError" } // $ExpectType Effect<never, HttpError, never> const program = Effect.fail(new HttpError())
こういうプログラムを書いた時に、ちゃんと全体の式が
Effect<never, FooError | BarError, string> になる
errorがちゃんと積まれて言ってる
途中まで読んだ




Context Management
effect typeの作成
Random Effectを作る
ndet型的なmrsekut
ts
import { Effect, Context } from "effect" export interface Random { readonly next: Effect.Effect<never, never, number> } export const Random = Context.Tag<Random>()
やっとhandlerの話が出てきたmrsekut
ts
import { Effect, Console } from "effect" import { Random } from "./service" // $ExpectType Effect<Random, never, void> const program = Effect.gen(function* (_) { const random = yield* _(Random) const randomNumber = yield* _(random.next) return yield* _(Console.log(`random number: ${randomNumber}`)) })
この時点では next() がまだ実装されていない
Effect.provideServiceというのを使うらしい
kokaでのhandlerのことをserviceと呼んでるっぽい
ts
// $ExpectType Effect<never, never, void> ←handleしてるのでRはnever!! const runnable = Effect.provideService( program, Random, Random.of({ next: Effect.sync(() => Math.random()) }) ) Effect.runSync(runnable) // Output: random number: 0.8241872233134417
途中まで読んだ


ここからよんでない