Skip to Content

效应(Effect)

效应是「可拦截的副作用」:effect 声明一组操作(op),函数体用 perform 触发,调用方用 handle 拦截并以 resume 续延。

与 fail 通道不同,效应沿调用链强传递:谁调用谁负责,编译期一路检查到入口。

声明与基础环

effect Ask { func Prompt(msg: string) -> string; func Get() -> int { return 42; } } func Greet() uses Ask -> string { return perform Ask.Prompt("名字"); } func Answer() uses Ask -> int { return perform Ask.Get(); }

这个例子的 Ask 中,Prompt 是契约型 op(无方法体,谁触发谁必须被拦截),Get 带默认实现(没人拦就走默认体)。

函数签名用 uses Ask 声明「我会触发哪些效应」,子句顺序与 fail 同位(uses、fail、where、返回类型)。

handle 与 resume

func Caller() uses Ask -> string { return Greet(); } func main() -> int { handle { print(Caller()); print(Answer()); } with { Ask.Prompt(msg) => resume($"你好,{msg}"), Ask.Get() => resume(7), } return 0; }
你好,名字 7

handle { 块 } with { 臂 } 拦截块内触发的效应:Ask.Prompt(msg) 解构 op 实参,resume(值) 把值交回 perform 的位置继续执行。

Answer() 没带 handle 也能跑:Ask.Get 有默认实现,没人拦就走默认返回 42;这里臂拦了它,改返 7。

uses 强传递

调用一个 uses E 的函数,调用者要么自己也 uses E(继续上传),要么处于覆盖全部 op 的 handle 内。两个都没有的则编译期报错:

error MS4011: Function calls a function that uses 'Log'; it must itself declare 'uses Log'.

继承与按写路径派发

effect 单继承(effect Fs : IO),继承链上 virtual op 可被 override。

派发按 perform 的书写路径取最接近的派生实现:有臂时按臂处理,无臂时按书写路径派发:

effect Log { func fire() -> void; virtual func Level() -> int { return 1; } } effect FileLog : Log { override func Level() -> int { return 2; } } func Quiet() -> int => perform FileLog.Level(); func BasePath() -> int => perform Log.Level(); print(Quiet(), BasePath());
2 1

写 FileLog.Level() 走 override 得 2,写 Log.Level() 走基默认得 1。uses IO 盖得住 perform Fs.op(),反之亦然(按 op 面判定)。

effects 并集

effects 把几个 effect 并成一个名字,与 errors 并集同形。成员必须是同域 effect 声明或其它 effects 并集(递归展开),uses App 展开覆盖全部(传递)成员:

effect Log { func fire(msg: string) -> void; } effect Tick { func now() -> int; } effects App = Log | Tick; func Work() uses App -> int { perform Log.fire("起"); return perform Tick.now(); } func main() -> int { handle { print(Work()); } with { Log.fire(msg) => print("日志", msg), Tick.now() => resume(7), } return 0; }
日志 起 7

泛型成员须显式实例化(effects App = Cache<int> | Log; 合法)。裸名 perform / on 经 uses 声明推断到唯一实例化。并集只出现在 uses 子句位,不是值类型。

discard:放弃续延

discard; 表示「不产生值、跳过后续」:handle 块体中 perform 之后的语句全部跳过,控制流直接到 handle 块之后:

handle { print(Middle()); } with { Log.fire() => discard, } print("discard 后控制流到这里");

Middle() 里 perform Log.fire() 一触发就跳出,直接落到块后的 print。

注意

  • 触发顺序:精确臂 → 通配臂 _(仅 void op)→ 默认实现 → 防御性 crash;
  • 默认实现体内禁 perform 契约型 op、禁 resume;override op 的 uses/fail 契约可窄不可宽,与协议实现同律;
  • handle 是语句不是表达式。
最后更新于 2026年10月9日