news 2026/9/19 17:32:34

Roc 语言 List.keep_if 空列表行为深入解析:从 REPL 快照测试看过滤器语义

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Roc 语言 List.keep_if 空列表行为深入解析:从 REPL 快照测试看过滤器语义

Roc 语言 List.keep_if 空列表行为深入解析:从 REPL 快照测试看过滤器语义

【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc

List.keep_if是 Roc 语言标准库中用于按谓词(predicate)过滤列表的核心函数。本文以仓库中一个专门的 REPL 快照测试 list_keep_if_empty.md 为骨架,结合标准库实现源码与同族测试用例,系统讲解List.keep_if的语义、空列表与全不匹配场景下的行为、底层实现原理、性能特性以及易用变体,帮助读者在实战中准确使用该函数。

快照测试是什么:一个用例的完整解剖

在 Roc 编译器的测试体系中,test/snapshots/repl/目录存放的是REPL 类型的快照测试type=repl)。这类测试模拟用户在 REPL 中输入表达式,然后断言解释器求值后打印出的输出。本文关联的文档 list_keep_if_empty.md 内容如下:

# META description=List.keep_if on empty list returns empty list type=repl # SOURCE » List.keep_if([1, 2, 3], |_| Bool.False) # OUTPUT [] # PROBLEMS NIL

按 snapshots 目录说明 的格式约定,一个 REPL 快照由四部分组成:

  • META:以 INI 格式描述用例,description是一句话语义说明,type=repl声明这是 REPL 求值测试;
  • SOURCE»前缀的行是用户在 REPL 提示符后输入的表达式,本例为对三元素列表[1, 2, 3]调用List.keep_if,谓词是忽略参数、恒为Bool.False的匿名函数|_| Bool.False
  • OUTPUT:求值器输出的真实结果,这里是空列表[]
  • PROBLEMS:编译器诊断报告序列化结果,NIL表示本次求值零警告、零错误,类型检查完全通过。

用例名称中的 "empty" 指的是结果为空:即使输入列表非空,只要谓词对每个元素都返回Bool.Falsekeep_if就会返回一个空列表[]。测试文件 list_keep_if_none.md 用更贴近业务的谓词|x| x > 10验证了同一结论,输出同样是[];而 list_keep_if_empty.md 采用的恒假谓词则是边界行为最直接、最不留歧义的表达。

keep_if 的核心语义:保留谓词为 True 的元素

List.keep_if的语义在标准库源码 src/build/roc/Builtin.roc 中有权威定义:

Run the given function on each item of a list, and return all the items for which the function returnedBool.True.

即:对列表中的每个元素运行给定函数,收集并返回所有函数返回Bool.True的元素。其类型签名与实现如下:

keep_if : List(a), (a -> Bool) -> List(a) keep_if = |list, predicate| List.fold( list, [], |acc, item| if predicate(item) { List.concat(acc, [item]) } else { acc }, )

要点拆解:

  • 类型是泛型的List(a)对元素类型a完全多态,谓词a -> Bool只读不修改元素,返回仍是List(a),元素类型不变;
  • 实现基于List.fold:以空列表[]为累加器从左向右遍历,谓词为真时把当前元素List.concat(acc, [item])追加到结果,为假时原样保留累加器——这正是"过滤"的经典折叠实现;
  • 保持原始顺序:由于是顺序折叠 + 尾部追加,结果列表中元素的相对顺序与输入列表完全一致;
  • 空输入自然得到空输出:当输入是[]时,List.fold不执行任何一次折叠步骤,直接返回初始累加器[]。快照 list_keep_if_empty.md 验证的[1, 2, 3]+ 恒假谓词,本质上与空输入殊途同归——List.fold的零次或多次执行都收敛到同一个空结果。

同目录下的对照用例 list_keep_if.md 展示了"部分保留"情形:List.keep_if([1, 2, 3, 4, 5], |x| x > 2)输出[3.0, 4.0, 5.0],注意 REPL 中数字默认按F64打印,所以整数字面量显示为带.0的形式。

空结果的内存与性能特性(从源码文档求证)

List.keep_if返回的空列表并非"临时拼凑"的结果,其内存行为在 Builtin.roc 的文档注释中有明确的性能说明,这是官方文档事实:

  1. 结果容量与原列表等大keep_if总是返回一个与原列表占用完全相同内存量的列表,即使长度变小。原因在于实现无法预先知道结果需要多少空间,如果猜测的长度过小,就必须重新分配(re-allocate);
  2. 原地可变优化:如果传入的是唯一引用(unique)的列表,keep_if会**原地修改(mutate in place)**来组装结果列表,此时不会在堆上分配任何新内存;
  3. 全保留短路:如果所有元素最终都被保留,Roc 会原样返回原始列表,不做任何拷贝。

这条特性直接解释了本文主题:当谓词恒为假、结果为[]时,若传入的是唯一列表,函数会在原地把列表"清空",不产生额外堆分配;同时PROBLEMS: NIL表明这一过程完全静默、无诊断。文档还指出,若希望过滤后收缩内存占用,可以改用两次List.fold遍历的方案:第一次精确计算新列表长度,第二次再填充新列表(见源码注释 Builtin.roc)。

同族函数对照:drop_if、keep_if_try 与效果化变体

围绕keep_if,标准库提供了一组语义互补的函数,在 REPL 快照中均有对应用例,实战中可按需选择:

List.drop_if:保留谓词为 False 的元素

实现位于 Builtin.roc,与keep_if结构完全对称,只是if predicate(item)为真时跳过、为假时追加。快照 list_drop_if.md 验证:List.drop_if([1, 2, 3, 4, 5], |x| x > 2)输出[1.0, 2.0]——即"去掉大于 2 的元素"。源码文档明确其性能特性与keep_if完全相同。

List.keep_if_try:谓词可失败的过滤

类型签名为List(a), (a -> Try(Bool, err)) -> Try(List(a), err)(见 Builtin.roc):谓词返回Try,元素映射为Ok(Bool.True)时保留,遇到第一个Err立即停止并返回该错误。快照 list_keep_if_try.md 给出了四条关键证据:

» [1, 2, 3, 4].keep_if_try(|n| Ok(n > 2)) Ok([3.0, 4.0]) » [1, 2, 3].keep_if_try(|n| if n < 3 { Ok(n.is_even()) } else { Err(Stop) }) Err(Stop) » [1.I64, 2, 3, 4].keep_if_try!(|n| Ok(n > 2)) Ok([3, 4]) » List.keep_if_try([], |_| Ok(Bool.True)) Ok([])

其中List.keep_if_try([], |_| Ok(Bool.True))输出Ok([])证明:空输入或谓词恒真时,keep_if_try返回的是包在Ok里的空列表,而非裸[]——这正是Try版本与普通版本在"空结果"形态上的关键差异。第 5、6 行还用堆分配长字符串验证了keep_if_try对非内联存储元素也能正确保序返回。

List.keep_if_try!:效果化的过滤

当谓词本身需要执行 I/O(如数据库查询)时,使用keep_if_try!(见 Builtin.roc),其类型签名带能力(capability)约束:List(a), (a => Try(Bool, err)) => Try(List(a), err)。与keep_if_try相同,它逐元素执行谓词直到某个谓词返回Err才停止。源码文档给出了典型场景示例:users.keep_if_try!(|user| SQL.query_bool!("SELECT is_active FROM users WHERE id = ?", [user.id]))——即过滤出所有活跃用户,任何一次查询失败即整体返回Err

如何在本地复现与验证这些行为

快照测试既是回归测试,也是可交互的"求值说明文档"。按 test/snapshots/README.md 的用法说明,可以本地重现本文全部示例:

# 生成/校验全部快照 zig build run-snapshot-tool # 只运行单个 REPL 快照(调试构建默认开启追踪) zig build run-snapshot-tool -- test/snapshots/repl/list_keep_if_empty.md # 更新该快照的期望输出(PROBLEMS 部分使用 --update-expected) zig build run-snapshot-tool -- test/snapshots/repl/list_keep_if_empty.md --update-expected # 开启求值器逐步追踪,观察 keep_if 的折叠过程 zig build run-snapshot-tool -- test/snapshots/repl/list_keep_if_empty.md --trace-eval

注意:--trace-eval仅适用于type=repl快照且一次只能处理单个文件;调试构建默认启用追踪,发布构建需用-Dtrace-eval=true显式开启。

若想在真实 REPL 中交互式验证,仓库根目录 README.md 描述了构建与运行roc的方式,构建完成后直接执行:

roc repl

然后在提示符后输入List.keep_if([1, 2, 3], |_| Bool.False),即可看到输出[]

小结:从快照用例到实战要点

list_keep_if_empty.md这一快照用例,用最简洁的形式锁定了List.keep_if的一个关键语义:过滤结果可以为空,且类型检查完全静默通过(PROBLEMS: NIL。将其与 Builtin.roc 的实现和文档、以及 list_keep_if.md、list_keep_if_none.md、list_keep_if_try.md、list_drop_if.md 等快照对照,可以得到四条可直接用于编码的结论:

  1. keep_if保留谓词为Bool.True的元素,结果有序、类型不变;输入为空或谓词恒假时返回[]
  2. 结果列表与输入等容量,传入唯一列表时可原地过滤、零堆分配;全部保留时直接返回原列表;
  3. 需要谓词可失败(如解析、校验)时用keep_if_try,首个Err即短路,空结果形态为Ok([]);谓词含 I/O 效果时用keep_if_try!
  4. 语义互补的drop_ifkeep_if共享完全相同的性能特征,按"保留什么"还是"丢弃什么"的直觉选择即可。

理解快照测试的 META / SOURCE / OUTPUT / PROBLEMS 四段结构,不仅能读懂上述行为,还能在修改标准库后通过zig build run-snapshot-tool快速回归验证——这正是 Roc 编译器团队用快照守护语义边界的工程实践。

【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 17:32:31

FigmaCN使用指南:6个实用技巧,让设计师彻底告别英文界面

FigmaCN使用指南&#xff1a;6个实用技巧&#xff0c;让设计师彻底告别英文界面 【免费下载链接】figmaCN 中文 Figma 插件&#xff0c;设计师人工翻译校验 项目地址: https://gitcode.com/gh_mirrors/fi/figmaCN FigmaCN 是一款免费的中文 Figma 插件&#xff0c;能把 …

作者头像 李华
网站建设 2026/9/19 17:30:19

济南樱花燃气灶故障维修电话|燃气灶漏气检测|欧米到家咨询电话

燃气灶是济南家庭日常烹饪中使用频率很高的设备&#xff0c;涉及点火、燃烧、熄火保护、阀体和燃气连接等多个安全环节。遇到燃气灶打不着火、有火花却点不燃、一松手就熄火、火焰发黄发红、火力变小、锅底熏黑、旋钮拧不动、关火后持续打火&#xff0c;或闻到燃气异味等情况时…

作者头像 李华
网站建设 2026/9/19 17:28:46

uni-app 多客户多平台自动发布:HBuilderX 工程改造 CLI 实战

有一段时间&#xff0c;我的工作状态基本是&#xff1a;打开 HBuilderX&#xff0c;同时打开六七个 uni-app&#xff08;Vue2&#xff09;项目&#xff0c;挨个点“发行”&#xff0c;选微信小程序&#xff0c;等编译完再切到 H5&#xff0c;等到新客户上线那几天&#xff0c;一…

作者头像 李华
网站建设 2026/9/19 17:28:19

遥感旋转框转YOLO格式实战:DOTA数据集坐标转换与训练避坑指南

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

作者头像 李华
网站建设 2026/9/19 17:24:43

卡方分布的前世今生:从测量误差到假设检验的基石

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

作者头像 李华