Effect-TS
(関係ないけど)Nix使ってるやんmrsekut.icon
関連?
ちょっと見てみる
@effectの種類
data
io
schema
諸々のデータ型の提供
zodとかio-tsみたいなやつ
tree shake考慮されてる
Concurrency
JSでここの改善できるものなのか?mrsekut.icon
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.icon
2023/8/30現在ほぼ何も書かれてない
Guides
Effect Essentials
Effectの基本
だが、ずっと第1引数がneverだからそこまで面白くないmrsekut.icon
安全な除算関数
code: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.icon
いや後からエイリアス作るのかな
これだけだとまだ旨味がわからないな
合成性とか、never以外のeffect typeを使った例を見たい
Effect-tsで提供されている関数群は、ただのEffectのコンストラクタ
Effect.succeed
code:ts
const succeed: <A>(value: A) => Effect<never, never, A> = core.succeed
Efect..fail
code:ts
const fail: <E>(error: E) => Effect<never, E, never> = core.fail
Effect<Requirements, Error, Value>
このイメージ
code:ts
type Effect<Requirements, Error, Value> = (
context: Context<Requirements>
) => Error | Value
Contextに積んでいく
Requirements
Contextに積んでいく
Error
失敗しないならnever
Value
neverだと無限ループ
sync effect
thunkを作る関数
Effect.sync
同期的な副作用
失敗しないものに対して使う
code:ts
const sync: <A>(evaluate: LazyArg<A>) => Effect<never, never, A> = core.sync
code:ts
const program = Effect.sync(() => {
console.log("Hello, World!") // side effect
return 42 // return value
})
↑これ自体は値
宣言時にはconsole.logが呼ばれない
thunkっぽい
Effect.try
throwする可能性があるthunk
code:ts
// Effect<never, unknown, any>
const program = Effect.try(
() => JSON.parse("") // JSON.parse may throw for bad input
)
catch節もかける
code: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
code: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
code: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.icon
組み上げたEffectsを実行するためにrunXXX関数を使う
runSync
runSyncExit
Exitを返す
runPromise
runPromiseExit
using genertors
generorを使って手続き的に書ける
do式っぽいmrsekut.icon
code: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.icon
これのすごさは、
sync関数もasync関数も全く同じ見た目で書けているという点
この文脈においては関数に色がついてないとみなせるmrsekut.icon
上のgenを使ったコードを関数合成だけで書くと以下のようになる
code:ts
const program2 = pipe(
Effect.flatMap((a, b) => divide(a, b)), Effect.map(increment),
Effect.map((n2) => Result is: ${n2})
);
do式使わずに、>>=使ってるイメージmrsekut.icon
method chainでも書ける
code:ts
Effect.flatMap((a, b) => divide(a, b)), Effect.map((n1) => increment(n1)),
Effect.map((n2) => Result is: ${n2})
)
pipe
上記のgenの引数の_がpipeになってる
code: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!"))
}
})
code:ts
import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)
普通に
code: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
code: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
code:ts
import { pipe, Effect } from "effect"
const flatMappedEffect = pipe(effect, Effect.flatMap(func))
tap
計算フローに影響を与えずに副作用を実行する
Effect.all(effects)
effectのリストを渡すと結合できる
2つのerror
予期されるerror
自作する
code: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を作る
code:ts
import { Effect, Context } from "effect"
export interface Random {
readonly next: Effect.Effect<never, never, number>
}
export const Random = Context.Tag<Random>()
やっとhandlerの話が出てきたmrsekut.icon
code: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と呼んでるっぽい
code: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
途中まで読んだ
ここからよんでない