effect

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Effect Basics

Effect基础

The Effect Type

Effect类型

ts
Effect<Success, Error, Requirements>
  • Lazy: describes a workflow, doesn't execute until run
  • Immutable: every operation returns a new Effect
  • Aliases:
    A
    (success),
    E
    (error),
    R
    (requirements)
ts
Effect<Success, Error, Requirements>
  • 惰性执行:描述一个工作流,直到调用时才会执行
  • 不可变:每个操作都会返回一个新的Effect
  • 别名:
    A
    (成功值)、
    E
    (错误值)、
    R
    (依赖项)

Creating Effects

创建Effect

CombinatorInputOutput
succeed
A
Effect<A>
fail
E
Effect<never, E>
sync
() => A
Effect<A>
try
() => A
Effect<A, UnknownException>
try
{ try, catch }
Effect<A, E>
promise
() => Promise<A>
Effect<A>
tryPromise
() => Promise<A>
Effect<A, UnknownException>
tryPromise
{ try, catch }
Effect<A, E>
async
(resume) => void
Effect<A, E>
suspend
() => Effect<A, E, R>
Effect<A, E, R>
ts
import { Effect } from "effect"

const ok = Effect.succeed(42)
const err = Effect.fail(new Error("oops"))
const sync = Effect.sync(() => console.log("hi"))
const trySync = Effect.try(() => JSON.parse(str))
const tryAsync = Effect.tryPromise(() => fetch(url))
Use
suspend
for:
  • Lazy evaluation with side effects
  • Recursive effects (prevents stack overflow)
  • Unifying return types
组合器输入输出
succeed
A
Effect<A>
fail
E
Effect<never, E>
sync
() => A
Effect<A>
try
() => A
Effect<A, UnknownException>
try
{ try, catch }
Effect<A, E>
promise
() => Promise<A>
Effect<A>
tryPromise
() => Promise<A>
Effect<A, UnknownException>
tryPromise
{ try, catch }
Effect<A, E>
async
(resume) => void
Effect<A, E>
suspend
() => Effect<A, E, R>
Effect<A, E, R>
ts
import { Effect } from "effect"

const ok = Effect.succeed(42)
const err = Effect.fail(new Error("oops"))
const sync = Effect.sync(() => console.log("hi"))
const trySync = Effect.try(() => JSON.parse(str))
const tryAsync = Effect.tryPromise(() => fetch(url))
suspend
的适用场景:
  • 带有副作用的惰性求值
  • 递归Effect(防止栈溢出)
  • 统一返回类型

Running Effects

运行Effect

RunnerOutput
runSync
A
(throws on async)
runPromise
Promise<A>
runFork
RuntimeFiber<A, E>
ts
Effect.runSync(program)           // sync only
Effect.runPromise(program)        // async
Effect.runFork(program)           // background fiber
运行器输出
runSync
A
(异步场景会抛出错误)
runPromise
Promise<A>
runFork
RuntimeFiber<A, E>
ts
Effect.runSync(program)           // 仅同步场景可用
Effect.runPromise(program)        // 异步场景
Effect.runFork(program)           // 后台纤程

When to Run Effects

何时运行Effect

Run effects ONLY at program edges:
  • Entry point (
    main
    )
  • Controller/route handlers
  • Event handlers
Anti-pattern - wrapping effects in async functions:
ts
// BAD: runs effect mid-chain
async function getUser(id: string) {
  return Effect.runPromise(fetchUser(id))
}

// GOOD: return the effect, run at edge
const getUser = (id: string): Effect.Effect<User, Error> =>
  fetchUser(id)
仅在程序边界处运行Effect:
  • 入口点(
    main
  • 控制器/路由处理器
  • 事件处理器
反模式 - 在异步函数中包装Effect:
ts
// 错误示例:在链式调用中途运行Effect
async function getUser(id: string) {
  return Effect.runPromise(fetchUser(id))
}

// 正确示例:返回Effect,在边界处运行
const getUser = (id: string): Effect.Effect<User, Error> =>
  fetchUser(id)

Pipelines

管道操作

OperatorPurpose
map
Transform success value:
A => B
flatMap
Chain effects:
A => Effect<B, E, R>
andThen
Flexible chain (value, fn, Effect, Promise)
tap
Side effect, keeps original value
all
Combine effects into tuple/struct
ts
import { Effect, pipe } from "effect"

const program = pipe(
  fetchAmount,
  Effect.map((n) => n * 2),
  Effect.flatMap((n) => applyDiscount(n)),
  Effect.tap((n) => Console.log(`Result: ${n}`))
)

// or with .pipe method
const program2 = fetchAmount.pipe(
  Effect.andThen((n) => n * 2),
  Effect.andThen((n) => applyDiscount(n))
)

// combine multiple effects
const both = Effect.all([effectA, effectB])
const struct = Effect.all({ a: effectA, b: effectB })
操作符用途
map
转换成功值:
A => B
flatMap
链式调用Effect:
A => Effect<B, E, R>
andThen
灵活链式调用(支持值、函数、Effect、Promise)
tap
执行副作用,保留原始值
all
将多个Effect组合为元组/结构体
ts
import { Effect, pipe } from "effect"

const program = pipe(
  fetchAmount,
  Effect.map((n) => n * 2),
  Effect.flatMap((n) => applyDiscount(n)),
  Effect.tap((n) => Console.log(`Result: ${n}`))
)

// 或者使用.pipe方法
const program2 = fetchAmount.pipe(
  Effect.andThen((n) => n * 2),
  Effect.andThen((n) => applyDiscount(n))
)

// 组合多个Effect
const both = Effect.all([effectA, effectB])
const struct = Effect.all({ a: effectA, b: effectB })

Generators

生成器

ts
const program = Effect.gen(function* () {
  const a = yield* effectA
  const b = yield* effectB
  return a + b
})
Supports standard control flow:
ts
Effect.gen(function* () {
  const user = yield* getUser(id)
  if (!user) {
    return yield* Effect.fail("not found")
  }
  return user.name
})
ts
const program = Effect.gen(function* () {
  const a = yield* effectA
  const b = yield* effectB
  return a + b
})
支持标准控制流:
ts
Effect.gen(function* () {
  const user = yield* getUser(id)
  if (!user) {
    return yield* Effect.fail("not found")
  }
  return user.name
})

Gen vs Pipe: When to Use

Gen与Pipe:如何选择

Use
Effect.gen
when:
  • Control flow needed (
    if
    /
    else
    ,
    for
    ,
    while
    , early returns)
  • Multiple dependent sequential steps
Use
pipe
when:
  • Linear transformations
  • Simple chains without branching
使用
Effect.gen
的场景:
  • 需要控制流(
    if
    /
    else
    for
    while
    、提前返回)
  • 多个依赖的连续步骤
使用
pipe
的场景:
  • 线性转换
  • 无分支的简单链式调用

Tagged Errors

标记式错误

ts
import { Effect, Data } from "effect"

class NotFound extends Data.TaggedError("NotFound")<{
  id: string
}> {}

class Unauthorized extends Data.TaggedError("Unauthorized")<{}> {}

const program: Effect.Effect<User, NotFound | Unauthorized> = 
  Effect.gen(function* () {
    // ...
    yield* Effect.fail(new NotFound({ id }))
  })

// Handle specific errors
program.pipe(
  Effect.catchTag("NotFound", (e) => Effect.succeed(null)),
  Effect.catchTag("Unauthorized", () => Effect.fail("denied"))
)
ts
import { Effect, Data } from "effect"

class NotFound extends Data.TaggedError("NotFound")<{
  id: string
}> {}

class Unauthorized extends Data.TaggedError("Unauthorized")<{}> {}

const program: Effect.Effect<User, NotFound | Unauthorized> = 
  Effect.gen(function* () {
    // ...
    yield* Effect.fail(new NotFound({ id }))
  })

// 处理特定错误
program.pipe(
  Effect.catchTag("NotFound", (e) => Effect.succeed(null)),
  Effect.catchTag("Unauthorized", () => Effect.fail("denied"))
)

Short-Circuiting

短路特性

Effects stop at first error:
ts
Effect.gen(function* () {
  yield* task1          // runs
  yield* Effect.fail(e) // fails here
  yield* task2          // never runs
})
Use
Effect.all
with
{ mode: "either" }
to collect all results regardless of failures.
Effect会在第一个错误处停止执行:
ts
Effect.gen(function* () {
  yield* task1          // 会执行
  yield* Effect.fail(e) // 在此处失败
  yield* task2          // 永远不会执行
})
使用
Effect.all
并配置
{ mode: "either" }
可以收集所有结果,无论是否有失败。

Additional Resources

更多资源

For pattern matching For working with streams Advanced concurrency (when basics aren't enough) One-time signaling between fibers Producer-consumer with back-pressure Broadcasting messages to multiple subscribers Limiting concurrent access to a resource Blocking fibers until a condition/event Concurrent-safe LRU caching with TTL
模式匹配相关 流处理相关 高级并发(基础功能无法满足时) 纤程间一次性信号传递 带背压的生产者-消费者模式 向多个订阅者广播消息 限制资源的并发访问 阻塞纤程直到条件/事件触发 支持TTL的并发安全LRU缓存