Roc 语言 Try.map_both 实战:从 REPL 快照测试看懂 Ok/Err 双分支映射语义

发布时间:2026/11/5 2:23:06

Roc 语言 Try.map_both 实战:从 REPL 快照测试看懂 Ok/Err 双分支映射语义 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),仅供参考
延伸阅读

更多相关文章

2026/11/6 0:16:12

凸极同步发电机电磁计算:非线性磁路建模与饱和耦合求解

简介:本资源是一份面向电机设计初学者与电气工程专业学生的凸极同步发电机电磁计算教学文档,聚焦中小型同步发电机(75kW/400V/1500r/min/50Hz)的完整电磁设计流程。文档系统覆盖额定参数选定、磁路几何尺寸计算(含定子…

2026/11/5 3:53:36

核心银行系统架构与存款业务实现:从客户信息到账务核对

简介:一份聚焦银行业核心系统基础知识的完整资料,面向银行新入职员工、核心系统运维与开发人员及金融IT项目人员,帮助快速建立从银行IT系统分类到核心业务系统模块的整体认知。资源为1个DOC文档,大小约1.54MB,内容覆盖…

2026/11/5 8:37:59

UE5摄像机抖动指南:Perlin噪点原理与Sequence实战调优

做游戏的人都知道,镜头是玩家和游戏世界之间的那层玻璃。镜头稍微抖一下,爆炸就有了重量;镜头处理得太平,再猛的场面也像在放幻灯片。UE5里实现摄像机抖动的方式不少,但最常用、最可控的路线,还是Perlin噪点…

2026/11/5 16:51:33

EasyU优启通PE维护盘实战指南:制作、启动与核心操作全解析

1. 为什么一个PE维护盘值得单独拿出来聊装系统、修引导、救数据、清病毒,这些活儿但凡干过几次的人都知道,真正决定效率的往往不是你手速多快,而是手边那套PE环境靠不靠谱。EasyU(圈内习惯叫优启通)就是在这个场景下被…

2026/11/5 16:26:13

Rust原来很简单二:所有权系统

Part 2 核心篇 —— 所有权系统 本篇覆盖 第 6 ~ 10 章:所有权、借用与引用、生命周期、字符串与切片、内存模型。 ⚠️ 这是全书最重要的一篇。 把它吃透,Rust 从此“很简单”;跳过它,你会处处碰壁。请慢下来,动手敲每一段代码。 第 6 章 所有权:Rust 的第一性原理 6…

2026/11/5 15:00:49

基于Hadoop+Spark的民宿智能推荐系统设计与实现

1. 项目概述:大数据时代的民宿智能推荐这个毕业设计项目融合了当前大数据领域最主流的技术栈,构建了一个完整的民宿推荐系统解决方案。作为一名长期从事大数据系统开发的工程师,我认为这个选题非常贴合实际应用场景——随着旅游业的复苏和共享…

2026/11/5 14:10:30

ADS122U04IPWR+STM32F427ZI工业级温度测量信号链设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配RS485,解决远距离温度监控难题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配PIC18F46K22的RTD与热电偶测温实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑