
Roc 语言 Try.map_both 实战从 REPL 快照测试看懂 Ok/Err 双分支映射语义【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc本篇技术指南以 Roc 仓库中的 REPL 快照测试 test/snapshots/repl/try_map_both.md 为核心结合 Builtin.roc 中Try类型的源码实现系统讲解Try.map_both与Try.map_both!的语义、参数与返回值规则并演示如何通过roc snapshot在本地复现与验证。读完本文你将掌握 Roc 结果类型双分支映射的完整用法以及 REPL 快照文件的结构化格式能够独立阅读、编写和运行同类测试。一、背景Roc 的Try结果类型在 Roc 语言中Try(ok, err)是一个内置的结果类型用于表示可能成功也可能失败的计算。它在 Builtin.roc 中被定义为Try(ok, err) : [Ok(ok), Err(err)]即Try恰好包含两个变体携带成功值ok的Ok(ok)以及携带失败信息err的Err(err)。这种结构与许多语言中的Result/Either类型类似是 Roc 中显式错误处理的基石函数可以返回Try调用方通过match或Try模块提供的组合子如map_ok、map_err、map_both、catch等安全地处理两种分支。Try.map_both与它的 effectful 版本Try.map_both!正是同时对 Ok 和 Err 两个分支做转换的组合子也是本快照测试文件的核心验证对象。二、快照文件的结构一个可执行的 REPL 会话记录test/snapshots/repl/try_map_both.md是 Roc 仓库中 REPL 快照测试的一种它以 Markdown 形式记录一次 REPL 会话的输入与期望输出由roc snapshot工具读取并执行输出与文件中记录不符时即判定测试失败。这类文件统一由四段组成METAini 格式的元信息description描述测试意图typerepl声明这是 REPL 快照区别于eval、build等其他类型SOURCE以»为提示符的 REPL 输入序列每行是一条独立语句OUTPUT与输入一一对应的期望输出相邻输出之间以---分隔PROBLEMS期望的编译/运行问题列表NIL表示全部通过、无任何问题。本文件的完整内容如下摘录自 try_map_both.md# META ~~~ini descriptionTry.map_both with Ok and Err variants typerepl ~~~» Try.map_both(Try.Err(failed), |val| val 1, |_| Failed) » Try.map_both!(Try.Err(failed), |val| val 1, |_| Failed) » Try.map_both(Try.Ok(100), |val| val - 50, |_| Failed) » Try.map_both!(Try.Ok(100), |val| val - 50, |_| Failed) » t Try.Ok(a string long enough to be heap-allocated instead of stored inline) » Try.map_both(t, |s| s, |e| e) » t对应输出Err(Failed) --- Err(Failed) --- Ok(50.0) --- Ok(50.0) --- assigned t --- Ok(a string long enough to be heap-allocated instead of stored inline) --- Ok(a string long enough to be heap-allocated instead of stored inline)从输出可以看出该快照在 src/snapshot_tool/main.zig 的驱动下逐行执行---是每次求值结果之间的标准分隔符assignedt 是 REPL 对绑定语句的固定反馈其生成逻辑位于 src/cli/ReplSession.zig 附近绑定成功时输出assigned关键字。下面逐行拆解语义。三、逐行实验map_both 在 Ok 与 Err 上的行为3.1 Err 分支只有 err_transform 被调用» Try.map_both(Try.Err(failed), |val| val 1, |_| Failed)结果为Err(Failed)。观察要点输入是Try.Err(failed)携带字符串错误failed第一个参数|val| val 1是ok_transform本应作用于Ok携带的值第二个参数|_| Failed是err_transform把原错误failed整体丢弃、替换为标签Failed由于输入是Err只有 err_transform 运行ok_transform 完全未被调用即使它会对val 1也不会产生任何影响返回值仍被包裹在Err变体中类型从Try(_, Str)变为Try(_, Failed)。这验证了map_both的只运行与当前变体匹配的那个函数的核心语义。3.2 Ok 分支只有 ok_transform 被调用» Try.map_both(Try.Ok(100), |val| val - 50, |_| Failed)结果为Ok(50.0)。观察要点输入是Try.Ok(100)携带数值 100ok_transform|val| val - 50执行计算得到 50err_transform|_| Failed未被执行结果以Ok变体返回。注意输出中的50.0Roc 的 REPL 在展示数值字面量时使用浮点书写形式可对照 repl_unbound_numbers_hide_type.md 中42→42.0、1 2→3.0的输出惯例这是 REPL 的显示约定并不代表运算本身涉及浮点精度问题。3.3 effectful 版本 map_both!同样的分支语义支持副作用» Try.map_both!(Try.Err(failed), |val| val 1, |_| Failed) » Try.map_both!(Try.Ok(100), |val| val - 50, |_| Failed)输出与纯函数版本完全一致Err(Failed)与Ok(50.0)。区别在于签名中的map_both!接受两个 effectful 变换函数其类型为map_both! : Try(a, b), (a c), (b d) Try(c, d)这意味着 ok_transform 与 err_transform 内部可以执行日志、数据库写入、HTTP 请求等有副作用的操作例如文档中给出的request.map_both!(|ok| Log.info!(...), |e| Log.warn!(...))场景并且只有与当前变体匹配的那个 effect 会被真正运行另一个则完全不会触发——这在副作用场景下尤为关键能避免在错误分支上误执行成功路径的逻辑。3.4 不变式验证map_both 保持包装结构» t Try.Ok(a string long enough to be heap-allocated instead of stored inline) » Try.map_both(t, |s| s, |e| e) » t这里做了两件事将一个大字符串装入Try.Ok并绑定到tREPL 输出assignedt用两个恒等函数|s| s与|e| e调用map_both随后再次求值t。两行输出都是Ok(a string long enough to be heap-allocated instead of stored inline)证明传入的字符串足够长会被堆分配而不是内联存储这正是测试 description 隐含的意图之一——验证 REPL 对堆分配字符串的显示与引用语义map_both使用恒等变换时是纯透传的Ok保持Ok内容原样保留t本身未被修改Roc 值默认不可变。四、源码级剖析map_both 与 map_both! 的实现在 Builtin.roc 中两个函数的完整实现如下map_both : Try(a, b), (a - c), (b - d) - Try(c, d) map_both |try, ok_transform, err_transform| match try { Err(b) Err(err_transform(b)) Ok(a) Ok(ok_transform(a)) }map_both! : Try(a, b), (a c), (b d) Try(c, d) map_both! |try, ok_transform!, err_transform!| match try { Err(b) Err(err_transform!(b)) Ok(a) Ok(ok_transform!(a)) }几个值得注意的源码事实分支选择由match try决定只有命中的分支对应的变换函数被求值。对Err(b)执行err_transform(b)并包回Err对Ok(a)执行ok_transform(a)并包回Ok。变换函数是尾随的二元参数调用顺序为map_both(result, ok_transform, err_transform)注意 ok_transform 在前、err_transform 在后与match分支书写顺序一致阅读源码时不易混淆。类型允许 Ok 与 Err 变换为不同目标类型(a - c)与(b - d)中的c、d相互独立因此map_both可以把Try(I64, Str)一步变成Try(I64, Failed)如本节实验也可以把两个分支分别变换成完全不同的类型。!后缀是 Roc 的 effect 约定带!的函数可运行副作用其类型箭头从-变为map_both!内部同样遵循只跑匹配分支的规则从源码可见Err分支只调用err_transform!。从实现角度map_both可以看作 map_ok 与 map_err 的合并形态——后两者分别只变换单个分支未命中的分支原样返回而map_both一次性覆盖两个分支。五、与相邻组合子的选型对比围绕TryBuiltin.roc 提供了多组相似函数选型时可按下表区分函数签名要点语义适用场景map_bothTry(a,b), (a-c), (b-d) - Try(c,d)分别变换 Ok/Err仍返回Try两个分支都要改、且保持结果类型map_both!Try(a,b), (ac), (bd) Try(c,d)同上但变换函数可有副作用在分支内执行日志、IO 等 effectmap_ok/map_ok!Try(a,err), (a-b) - Try(b,err)只变换 OkErr 原样返回仅成功路径需要转换map_err/map_err!Try(ok,a), (a-b) - Try(ok,b)只变换 ErrOk 原样返回仅错误路径需要转换、不恢复catch/catch!Try(ok,err), (err-a), (ok-a) - a两个分支收敛为同一普通类型需要把Try折叠成普通值如错误处理兜底on_err/on_err!Try(ok,a), (a-Try(ok,b)) - Try(ok,b)仅对 Err 运行恢复函数可返回新的Try从错误中恢复且恢复本身可能失败map2/map2!Try(a,err), Try(b,err), (a,b-c) - Try(c,err)两个结果都 Ok 才运行合并函数组合多个可能失败的计算关键判别标准有二是否两个分支都要变换map_both是map_ok/map_err否以及结果是否保持Try包装保持则用map_both收敛成普通值则用 catch。快照测试中|_| Failed这类丢弃原错误、换成统一错误标签的写法正是map_both最常见的实战形态之一。六、在本地运行与验证该快照仓库根目录下即可执行快照测试。roc snapshot是快照工具 src/snapshot_tool/main.zig 暴露的命令它支持直接运行单个 REPL 快照文件该文件会给出--trace-eval等调试用法并提示--trace-eval只能用于typerepl的快照# 在仓库根目录执行运行指定的 REPL 快照测试 roc snapshot test/snapshots/repl/try_map_both.md执行过程中工具会启动一个真实 REPL 会话复用 src/cli/ReplSession.zig 中的会话逻辑逐条执行 SOURCE 段中的语句将实际输出与 OUTPUT 段比对PROBLEMS为NIL意味着该文件当前无需记录任何诊断问题若 REPL 行为发生变化导致输出不一致快照测试即会失败从而充当map_both语义的回归防线。若想手动体验同样行为可以直接在 REPL 中粘贴 SOURCE 段的语句» Try.map_both(Try.Err(failed), |val| val 1, |_| Failed) » Try.map_both!(Try.Err(failed), |val| val 1, |_| Failed) » Try.map_both(Try.Ok(100), |val| val - 50, |_| Failed) » Try.map_both!(Try.Ok(100), |val| val - 50, |_| Failed)预期会得到与快照 OUTPUT 完全一致的Err(Failed)、Err(Failed)、Ok(50.0)、Ok(50.0)。七、总结该快照验证了什么try_map_both.md虽只有 31 行却系统覆盖了Try.map_both的全部关键行为双分支映射Ok与Err各自绑定专属变换函数互不干扰惰性分支求值只运行与当前变体匹配的变换未命中分支完全不执行纯函数与 effectful 版本语义一致map_both与map_both!在分支选择、包装结构上行为相同差异仅在是否允许副作用类型变换自由度Ok/Err 可分别变换为不同的目标类型错误标签可整体替换不可变性与透传对已绑定值施加恒等map_both不会改变原值。这些结论既有快照输出的直接证据又与 Builtin.roc 中match驱动的实现一一对应。对想要深入 Roc 错误处理模型的开发者而言从这样一个最小可复现的 REPL 快照出发配合源码阅读是理解Try组合子语义最直接、最不易出错的路径。【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考