语法全解:从 REPL 快照测试读懂 Range 的完整语义)
Roc 语言 U32 闭区间..语法全解从 REPL 快照测试读懂 Range 的完整语义【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc本篇技术指南以仓库内的 REPL 快照测试 test/snapshots/repl/u32_range_to.md 为骨架系统讲解 Roc 语言闭区间inclusive range语法..的完整行为包括正向迭代、单元素边界、start 大于 end 的空区间语义并结合 src/build/roc/Builtin.roc 中的Range(num)类型实现与 src/cli/ReplSession.zig 中的 REPL 回归测试深入剖析..语法在底层如何被表达为可迭代的Range值。读完本文你将掌握 Roc 区间语法在 REPL 与内置库中的准确语义、边界条件以及Iter.fold、for循环等配套用法并能自行复现与验证这些行为。一、快照文件长什么样REPL 测试的 META / SOURCE / OUTPUT 格式Roc 仓库用「REPL 快照测试」来锁定语言行为的回归基线。每个快照文件是一个 Markdown 文档由四段组成# META用 ini 键值对描述测试意图与类型例如descriptionU32 inclusive range syntax iterates from start to end、typerepl# SOURCE一段交互式 REPL 会话行首»表示用户输入# OUTPUT每条输入对应的输出输入之间用---分隔# PROBLEMS期望的编译诊断结果NIL表示无任何报错。test/snapshots/repl/u32_range_to.md 正是这样一份针对U32 闭区间的规格说明书全文只有 3 条输入、3 个输出却完整刻画了闭区间语法的三个关键行为。二、逐行解读 u32_range_to.md闭区间..的三种行为快照中的三条 REPL 输入均采用相同的模式把区间(start.U32..end.U32)转换为迭代器.iter()再交给Iter.fold配合List.append收集为列表» Iter.fold((1.U32..5.U32).iter(), [], |acc, item| acc.append(item)) » Iter.fold((0.U32..0.U32).iter(), [], |acc, item| acc.append(item)) » Iter.fold((5.U32..3.U32).iter(), [], |acc, item| acc.append(item))对应输出[1, 2, 3, 4, 5] --- [0] --- []这三组用例分别验证了正向闭区间start end1.U32..5.U32产出[1, 2, 3, 4, 5]左闭右闭端点5被包含在内。这是..to与..until开区间最本质的区别单元素区间start end0.U32..0.U32产出[0]即区间退化为恰好一个值端点重合不会导致空集逆向空区间start end5.U32..3.U32产出[]即闭区间语法不自动反向迭代当 start 大于 end 时结果是空区间。# PROBLEMS为NIL说明这三种写法全部是合法代码不存在编译期错误——空区间不是错误而是一种正常、可预期的语义。三、..语法的底层载体Range(num)类型区间语法在 Roc 内置库中并非一次性特判而是构造一个可复用的数值区间描述值。在 src/build/roc/Builtin.roc 中Range(num)被定义为如下记录Range(num) :: { lower : num, upper : num, step : num, upper_bound : [Exclusive, Inclusive], direction : [To, From], len_if_known : [Known(U64), Unknown], }从源码结构看..闭区间与..开区间在底层统一为同一个Range记录二者通过upper_bound字段区分闭区间是Inclusive开区间是Exclusive而direction的To/From则记录区间是「从小到大」还是「从大到小」。len_if_known用于在长度可确定时Known(U64)提供精确的尺寸提示供后续迭代与分配优化使用。正是由于Range是普通的一等值Range.step_by可以直接替换步长而无需重新解析语法Builtin.roc#L6502-L6514Range.iter则把记录转换为真正的Iter(num)流Builtin.roc#L6516-L6520。快照里(1.U32..5.U32).iter()的调用链就是先由语法构造出带Inclusive上界的Range再经iter变为惰性迭代器。四、从快照到实现U32 区间的迭代究竟如何工作iter的签名要求数值类型提供num.range_iter能力iter : Range(num) - Iter(num) where [num.range_iter : num, num, num, [Exclusive, Inclusive], [To, From], [Known(U64), Unknown] - Iter(num)]也就是说每一种支持区间语法的数值类型U32、I64、Dec、F32、F64、I128 等都必须实现range_iter。这解释了仓库中为什么存在一系列几乎同构的快照文件例如 test/snapshots/repl/dec_range_to.md、f32_range_to.md、f64_range_to.md、i128_range_to.md同一套..语义在不同数值类型上被逐一锁定回归。有趣的是把u32_range_to.md与 test/snapshots/repl/dec_range_to.md 对照还能观察到类型相关的步进规则U32 区间1..5步进为 1产出[1, 2, 3, 4, 5]Dec 区间0.5.Dec..2.5产出[0.5, 1.5, 2.5]步进是1.0数值意义上的最小正增量同样包含上界2.5Dec 区间1.25.Dec..1.25产出[1.25]与 U32 的0..0一致端点重合退化为单元素Dec 区间3.5.Dec..2.5产出[]与 U32 的5..3一致start 大于 end 时为空。这证明「闭区间、单元素、空区间」三大语义是跨数值类型的统一约定而步长则委托给具体数值类型决定。五、REPL 中的实战验证用回归测试复现快照快照测试描述的是 REPL 会话而其对应行为在仓库的 Zig 测试套件中也有直接体现。在 src/cli/ReplSession.zig#L3096-L3098 中可以找到同主题回归测试test Repl - range_to { try expectInterpreter(Iter.fold((1..3).iter(), [], |acc, item| acc.append(item)), [1.0, 2.0, 3.0]); }注意此处写法(1..3)未标注类型后缀Roc 会依据Range.iter的类型推导出数值类型输出为[1.0, 2.0, 3.0]。而快照中显式写1.U32则把类型固定为 U32输出为[1, 2, 3, 4, 5]。二者行为完全一致验证了区间类型可以依赖推导也可用U32等类型后缀显式固定Iter.fold(range.iter(), [], |acc, item| acc.append(item))是把区间转成列表的惯用模式acc.append(item)的追加顺序与迭代顺序相同。六、从区间到迭代iter之外的常见用法Range不只能iter()。在 src/build/roc/Builtin.roc 的文档示例中闭区间语法还常见于以下几种场景求和与求积Builtin.roc#L3280、#L3305expect (1..4).iter().sum() 10 expect (1..4).iter().product() Ok(24)注意product返回Ok(24)说明在可空语义下积运算的结果是带包装的Result或类似类型使用时需解包。for 循环直接消费区间Builtin.roc#L3763、#L3795for n in 1..5 { ... } for id in 4..1003 { ... }Range可以直接出现在for ... in之后语言会隐式迭代该区间无需手动调用.iter()。步长调整Range.step_by(range, new_step)以绝对步长替换默认步长Builtin.roc#L6502-L6514步长以区间数值类型本身表达因此自定义数值类型无需做 U64 转换即可参与。反向迭代Range.iter_rev通过数值类型的range_*_to/range_*_from系列能力重建反向区间Builtin.roc#L6522-L6544它并不假设所有类型都严格可逆而是要求类型显式声明支持。长度提示Range.size_hint返回[Known(U64), Unknown]Builtin.roc#L6498-L6500当长度可精确计算时返回Known否则为UnknownRange.custom则允许为内置或第三方数值类型手工构造带精确长度的区间描述Builtin.roc#L6478-L6496。七、小结从一行快照到完整的区间语义模型test/snapshots/repl/u32_range_to.md 虽只有 3 条输入但它完整锁定了 Roc 闭区间..的语义契约start..end是包含上界的闭区间end必然产出start end时区间退化为单元素而非空start end时区间为空不产生任何值且这不是错误该语义对 U32、Dec、F32、F64、I128 等所有实现range_iter能力的数值类型统一成立步长由具体类型决定区间底层是Range(num)一等记录支持.iter()、step_by、iter_rev、size_hint、custom并能直接用于for循环与Iter家族函数。对想要深入探索的读者建议按以下路径继续研究当前仓库区间语义规格test/snapshots/repl/u32_range_to.md 及同目录dec_range_to.md、f32_range_to.md、i128_range_to.md等姊妹快照区间类型实现src/build/roc/Builtin.roc#L6461-L6544 的Num/Range(num)模块REPL 回归测试src/cli/ReplSession.zig#L3096-L3098 的Repl - range_to用例语言总体定位仓库根目录的 README.md 与 Glossary.md 可帮助建立 Roc 语言的整体认知。【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考