在一切的起源
只有线性的指令。
但是我们需要更复杂的跳转,于是出现了 goto
但是 dijkstra 的 炮轰 goto,我的一张大字报 让 goto 消失了。
常见的控制流
break, continue
它们能跳出一个 scope, 但是回不去。
从函数里跳出去
用一个 return 就好了。
多重调用
这里的 return 只能退回 bar 的调用栈。所以我们可以抛异常。
这样有几重都可以跳出来了。
可以用于解释器的函数返回值。
但是上面的这些都只能单次返回,那有没有多次?generator
就可以多次返回了,但是这里只有 foo 往外传数据。
这样就又有 f 往外传,又有 send 往里送,也就是
coroutine
,一个对称的信道。类比 go 就是
yield 2
→ ch <- 2
t = yield
→ t <- ch
generator 在 yield 时顺便塞 queue 里
就是
async
await
下面是重头戏:
怎么实现 yield
我们观察发现不能用抛异常实现,因为需要“保留原始环境”
特别是存在控制流时,比如循环,需要保留整体环境。
于是引入 cps, 每步的下一步都作为 cont
比如 generator, 就是返回 cont, next 就是执行 cont
比如 await, 就是把 cont 和环境放入任务队列
但是 cps 的问题是需要对整个函数作变换,所以一般只有特定的函数用,比如 async, 比如 generator
于是就会遇到函数染色问题,一个 async 需要 await 加在前面,然后里面有 await 说明整个都需要变化,于是一路 async 染上去。
想象一个场景,去窗口排队办事,排了好久的队终于到了,发现忘带了很重要的证件。
如果抛报错,那就是中断,直接回去。
但如果有 effect + handler, 就是时间停止,离开窗口,回家,拿到证件,再继续办事
也就是说作用域可以跨多层,并且可以恢复报错现场。并带着 resume 的数据。
ㅤ | yield | coroutine | await | exception | Algebraic Effect |
can jump multi | n | n | n | y | y |
can recover back | y | y | y | n | y |
carry value out | y | y | y | y | y |
carry value in | n | y | n | n | y |
color | y | ? | y | n | n |
举一个常见的例子
在业务代码里, 某个值不能是全局变量, 但是一层层层层传进内层函数又过于麻烦了,
也就是说我们需要 stop 执行, 用 co 传回去.
附一个简单实现
于是这样, 这里的 hello 就不需要手动一层层传进去了, 用代数效应就能做到分离.
这里的 getVal 可以是任何东西, 比如一个异常.
enum Option<T>{
Some(T)
None
}
‣
return f(l, r, lambda: f(l.next))