在 EYG 中编写有状态的效应处理器

Lobsters Hottest 工具

摘要

一份关于在 EYG 编程语言中编写有状态效应处理器的指南,涵盖单效应计数器、通用 Update/State 效应、用于测试的效应转发,以及从效应中返回函数时因递归类型错误而遇到的陷阱。

<p><a href="https://lobste.rs/s/shs3xv/writing_stateful_effect_handlers_eyg">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/10/16 10:42

# 高级效应 来源:https://eyg.run/guides/advanced-effects ## 有状态处理器 ### 入门 处理效应时,一个常见的需求是依赖于引发效应的计算之外的某个状态。例如,`Increment`效应需要跟踪当前计数。又如,在测试中,`StandardOut`的处理器可能会构建一个由所有已写入消息组成的状态。 下面是单个有状态`Increment`效应的实现。 `` let with_counter = (initial, computation) -> { handle Increment( (_unit, resume) -> { (state) -> { resume(state)(!int_add(state, 1)) } }, (_) -> { let value = computation({}) let finish = (count) -> { {count, value} } finish } )(initial) } with_counter(0, (_) -> { let _ = perform Increment({}) let _ = perform Increment({}) let _ = perform Increment({}) "完成" }) `` 每个 handle 表达式只作用于一个效应标签。可以嵌套处理器以处理多个效应,但需要谨慎管理多个处理器之间共享的同一个状态。 这是为了保持类型系统简单而做出的设计,不过将来可能会放宽这一限制。如果需要多个带效应的操作,你有两种选择。 ### 通用的有状态效应 `` let with_counter = (initial, computation) -> { handle Update( (transform, resume) -> { (state) -> { let state = transform(state) resume(state)(state) } }, (_) -> { let value = computation({}) let finish = (count) -> { {count, value} } finish } )(initial) } let increase = (amount) -> { let _ = perform Update((state) -> { !int_add(state, amount) }) {} } let get = (_) -> { perform Update((state) -> { state }) } with_counter(0, (_) -> { let _ = increase(3) let value = get({}) let _ = perform StandardOut(!int_to_string(value)) let _ = increase(2) !int_to_string(value) }) `` 这个计数器的值可以增加,也可以读取。函数`get`和`increase`是这一有状态操作的公共接口。通用的`Update`效应接收一个将当前状态转换为新状态的变换函数。 只有在你能控制效应的设计时,才能使用这种通用方法,而这并非总是可行。例如,我们可能想测试一个使用`Get`和`Increase`效应的库。 在这种情况下,可以将这些效应转发给一个你能够控制的新效应。 `` let with_state = (initial, f) -> { // 通用的 State 效应持有共享状态。 let run = handle State((update, resume, state) -> { let state = update(state) resume(state)(state) }, (_) -> { // 这些处理器转换操作;它们不持有独立的状态。 let value = handle Increase((amount, resume) -> { let _ = perform State((state) -> { !int_add(state, amount) }) resume({}) }, (_) -> { handle Get((_, resume) -> { let state = perform State((state) -> { state }) resume(state) }, f) }) let finish = (state) -> { {state, value} } finish }) run(initial) } with_state(0, (_) -> { let before = perform Get({}) let _ = perform Increase(2) let middle = perform Get({}) let _ = perform Increase(-1) let after = perform Get({}) [before, middle, after] }) // {state: 1, value: [0, 2, 1]} `` ## 返回函数 对以下代码进行类型检查时,会失败并报错`此表达式具有递归类型`。 `` let main = (_) -> { let f = perform Get({}) f("") } main({}) `` 失败的原因并非一目了然。`f`的类型出现在`main`函数的`Get`效应行的返回类型中。调用`f`时,它的效应会与当前函数块(即`main`块)中的效应进行统一。这会导致系统尝试构造一个递归类型。 因此,建议不要从效应中返回函数。

相似文章

在 Erlang 程序中嵌入 EYG

Lobsters Hottest

一份关于将 EYG 脚本语言嵌入 Erlang 应用程序的指南,涵盖依赖项配置、运行纯程序、错误处理以及添加代数效应支持。

EYG 迎来一次异常实用的更新

Lobsters Hottest

EYG 编程语言迎来了实用性更新,包括已文档化的文本语法、CLI 中新增的文件系统操作,以及改进的解析器错误提示,便于 Shell 脚本编写。

EYG:大量CLI改进、新指南和新效果

Lobsters Hottest

EYG CLI 新增了命令(eval、check、script、shell)、标志、通过 glam 库实现的漂亮打印、改进的错误输出、更简便的安装,以及新的效果,如 DeleteFile、Env、Hash、Now、Random、Sleep。同时发布了新的指南。

Effectful 递归方案

Lobsters Hottest

《Effekt》编程语言博客文章演示了如何利用效应和处理器实现递归方案(特别是 catamorphisms),以此取代传统的基于函子的方法,从而避免了对无限递归类型的依赖。

代数效应:给普通人的解释

Hacker News Top

这是一篇教育性博客文章,通过类比 try/catch 和 async/await 来解释编程中的代数效应概念,并讨论了它们与 React 及未来编程范式的潜在关联。