效应(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;
}你好,名字
7handle { 块 } 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是语句不是表达式。
