
Roc 语言 for 循环中 var 变量逐次迭代重赋值基于 REPL 快照测试的完整解析【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc导读本文以 Roc 编译器仓库中的 REPL 快照测试 test/snapshots/repl/for_loop_var_every_iteration.md 为骨架深入讲解 Roc 语言中var可变绑定与for循环的组合用法如何在每次迭代中重赋值变量、如何利用循环外的累计状态完成统计类计算以及这种写法在编译器各阶段词法、解析、规范化、类型检查中对应的真实实现。读完本文你将掌握在 Roc 的 REPL 与测试快照体系中编写逐次迭代重赋值代码的方法并理解$前缀命名约定背后的编译原理。一、关联文档是什么一份 REPL 快照测试Roc 编译器使用快照测试snapshot test来验证编译管线各阶段的输出。每个快照文件就是一个独立的技术用例它把一段 Roc 源码喂给编译器然后把分词、解析、规范化、类型检查等每个阶段的产物固化为期望输出任何行为回归都会在快照 diff 中暴露出来。本仓库的快照体系说明见 test/snapshots/README.mdSnapshot tests provide comprehensive validation of the compilation pipeline by showing how source code is transformed through each stage: tokenization, parsing, canonicalization, and type checking etc.本文聚焦的 REPL 快照 属于typerepl类型它的SOURCE段以»提示符模拟用户在 REPL 中输入一段表达式OUTPUT段记录 REPL 的求值结果PROBLEMS段记录诊断报告NIL表示零诊断。# META ~~~ini descriptionFor loop with var reassignment on every iteration typerepl ~~~ # SOURCE ~~~roc » result { var prev_ 0 var count_ 0 for n in [10, 20, 30, 40, 50] { count_ count_ 1 prev_ n } prev_ count_ } ~~~ # OUTPUT assigned result # PROBLEMS NIL这段快照精准地刻画了一个典型场景在for循环的每次迭代中对循环体外部声明的var变量进行重赋值并在循环结束后读取这些变量计算最终结果。二、逐行拆解这段代码到底做了什么先看代码逻辑本身。用户在 REPL 中把一个块表达式{ ... }赋值给resultvar prev_ 0声明一个可变绑定prev_初值为0用于记录列表中的最后一个元素var count_ 0声明一个可变绑定count_初值为0用于统计迭代次数for n in [10, 20, 30, 40, 50] { ... }遍历一个包含 5 个U64元素的列表每次迭代count_ count_ 1计数器自增每次迭代prev_ n把当前元素写入prev_因此循环结束后prev_必然是最后一个元素50prev_ count_块表达式的最后一个表达式即块的返回值即50 5 55。REPL 求值完成后输出assigned result表示绑定成功PROBLEMS段为NIL表示该输入在 REPL 求值流程中没有产生任何诊断。值得注意的是同目录下还有一份等价的statement 快照test/snapshots/statement/for_loop_var_every_iteration.md它把同样的逻辑写成带类型标注与expect断言的模块代码result : U64 result { var prev_ 0 var count_ 0 for n in [10, 20, 30, 40, 50] { count_ count_ 1 prev_ n } prev_ count_ } expect result 55这份变体用expect result 55显式断言了我们的推导prev_末元素 50count_迭代 5 次 55。同时它的# TYPES段显示result被推断为U64与类型标注一致。三、语法基础var可变绑定与for循环3.1var声明可变绑定的唯一来源在 Roc 中普通绑定foo ...是不可变的想要可重新赋值必须显式使用var关键字。这在编译器的 CIRCanonical Intermediate Representation规范化中间表示中对应三种不同的语句变体见 src/canonicalize/Statement.zigs_var带初始化器的可变声明如var prev_ 0s_var_uninitialized不带初始化器的可变声明且只有在所有控制流路径都赋值后才能被读取见 Statement.zig 源码注释s_reassign对先前声明的var的重赋值如prev_ n见 Statement.zig。从源码结构看这三类语句都有明确的注释约束Not valid at the top level of a module——var声明与重赋值只能出现在块表达式内部函数体、{ }块中不能出现在模块顶层。这也是本快照把全部逻辑包在{ ... }块里的原因。3.2for循环遍历列表的语句for循环同样是一种块内语句CIR 中对应s_for变体定义在 src/canonicalize/Statement.zig/// A block of code that will be ran multiple times for each item in a list. /// /// Not valid at the top level of a module /// /// for item in [1,2,3] { /// print!(item.toStr()) /// }for与var都是 Roc 的关键字词法层面由 src/parse/tokenize.zig 中的.KwFor与.KwVar标记定义。在 statement 快照的# TOKENS段可以看到它们被正确分词为KwVar,LowerIdent,OpAssign,Int, # var prev_ 0 KwVar,LowerIdent,OpAssign,Int, # var count_ 0 KwFor,LowerIdent,KwIn,OpenSquare,Int,... ,CloseSquare,OpenCurly, # for n in [...]3.3$前缀只是命名约定不是语法要求一个容易困惑的细节statement 变体中对prev_、count_各报告了一条Var Name Missing$警告# EXPECTED与# PROBLEMS段提示开发者把它们改名为$prev_、$count_。这条警告的完整文本来自编译器的诊断实现 src/canonicalize/ModuleEnv.zig其措辞非常明确The name is only a convention; mutability comes from thevardeclaration.即$前缀只是社区命名惯例让可变绑定在代码中一眼可辨可变性本身完全来自var关键字。反向场景不加var却用$前缀则报告 Dollar Prefix Withoutvar 警告。这一点在仓库测试中也有印证src/check/test/issue_10875_test.zig 的测试名即 dollar spelling does not make an immutable binding mutable而 src/lsp/test/handler_integration_tests.zig 也注明 only a naming convention; the unprefixed mutable binding is valid too。因此在本 REPL 快照中不带$的prev_/count_是完全合法的写法快照的PROBLEMS段为NIL正是这一点的体现。四、编译器视角一次重赋值如何走完整个管线statement 变体快照为我们提供了这条代码从源码到类型推断的完整中间表示是理解逐次迭代重赋值底层原理的最佳教材。4.1 解析树PARSEs-vars-fors-decl解析阶段把源码组织成结构化语法树。var prev_ 0、var count_ 0被解析为(s-var ...)节点for循环被解析为(s-for (p-ident n) (e-list ...) (e-block ...))循环体内的两行重赋值被解析为普通声明(s-decl (p-ident count_) (e-binop (op ) ...))与(s-decl (p-ident prev_) (e-ident n))块的最后一行prev_ count_是(e-binop (op ) ...)。这验证了 Roc 词法→语法两阶段的结构先有for循环的语法骨架可变性语义在下一阶段才真正落地。4.2 规范化CANONICALIZE重赋值降级为s-reassign关键变化发生在规范化阶段。从 src/canonicalize/Can.zig 附近的源码注释可以看到reassignments becomes_reassigninstead of declarations——对var的重赋值在规范化时被识别为s_reassign而不是普通声明。statement 快照的# CANONICALIZE段给出了完整证据(s-for (p-assign (ident n)) (e-list (elems (e-num (value 10)) ... (e-num (value 50)))) (e-block (s-reassign (p-var-assign (ident count_)) (e-dispatch-call (method plus) (constraint-fn-var 339) (receiver (e-lookup-local (p-var-assign (ident count_)))) (args (e-num (value 1))))) (s-reassign (p-var-assign (ident prev_)) (e-lookup-local (p-assign (ident n)))) (e-empty_record)))这里有两个值得注意的实现细节count_ count_ 1被规范化为方法派发(method plus)说明在 Roc 中是约束方法constraint methodcount_ 1本质是对count_调用plus方法方法的实现由U64这一具体类型在单态化/类型检查阶段解析循环体块的最后一个语句是(e-empty_record)for循环体本身在每次迭代结束后返回空记录{}对应快照源码里if/else分支的{}惯例真正的返回值来自循环体外块表达式的最后一个表达式prev_ count_同样是(method plus)派发。4.3 类型推断TYPES一切收敛为U64# TYPES段显示result的类型是U64prev_、count_、列表元素10..50、1以及最终结果全部统一为U64。类型标注result : U64与expect result 55在类型检查中互相印证保证逐次迭代重赋值的代码在编译期就是类型安全的。五、实战变体来自兄弟快照的三种进阶写法每次迭代都重赋值var是 Roc 中实现循环内累计状态的基本模式。仓库里还有多份同主题的 REPL 快照可以作为该模式的进阶练习5.1 跨迭代累计 极值跟踪test/snapshots/repl/for_loop_var_reassign_tracking.mdvar reassignment tracking across iterations同时维护sum_累计和与max_最大值并在循环体内使用if/else条件更新» result { var sum_ 0 var max_ 0 for n in [3, 7, 2, 9, 1] { sum_ sum_ n if n max_ { max_ n } else { {} } } sum_ max_ }注意else分支的{}if是表达式两个分支必须类型一致而for循环体内不需要任何值所以用空记录{}保持类型统一。5.2 条件持久化只更新满足条件的迭代test/snapshots/repl/for_loop_var_conditional_persist.mdvar that persists across iterations with conditional updates只在n % 2 0时更新lastEven_与evenCount_» result { var lastEven_ 0 var evenCount_ 0 for n in [1, 2, 3, 4, 5, 6, 7, 8] { if n % 2 0 { lastEven_ n evenCount_ evenCount_ 1 } else { {} } } lastEven_ * evenCount_ }这展示了var重赋值与if条件分支结合时的行为未被条件命中的迭代不会改写变量循环结束后的读取拿到的是最后一次成功更新的值。5.3 嵌套 for 循环test/snapshots/repl/for_loop_nested.md 展示了嵌套for内层循环的多次迭代共享外层同一个result_可变绑定实现两层列表的累计运算» product { var result_ 0 for i in [1, 2, 3] { for j in [10, 20] { result_ result_ (i * j) } } result_ }这些快照的OUTPUT段全部是assigned 名字、PROBLEMS段全部为NIL与本文核心文档形成一组完整的forvar重赋值测试矩阵。六、如何复现与调试这些快照快照工具的使用方法记录在 test/snapshots/README.md你可以在本仓库中复现本文涉及的 REPL 快照# 生成/刷新所有快照 zig build run-snapshot-tool # 只更新指定快照文件例如本文关联文档 zig build run-snapshot-tool -- test/snapshots/repl/for_loop_var_every_iteration.md # 根据新的期望输出更新快照 zig build run-snapshot-tool -- file_path --update-expected # 调试 REPL 求值过程打开解释器追踪 zig build run-snapshot-tool -- test/snapshots/repl/for_loop_var_every_iteration.md --trace-eval其中--trace-eval是 REPL 快照专用的调试利器它只能在单个typerepl快照上使用调试构建默认开启追踪输出发布构建需要额外传入-Dtrace-evaltrue编译选项。当你想亲眼看到prev_ n在 5 次迭代中如何逐步从10变成50时这个追踪开关就是最直接的观察窗口。七、小结模式、约定与边界围绕这份 REPL 快照可以提炼出 Roc 语言中逐次迭代重赋值的完整知识闭环模式在块内用var声明累计状态for n in 列表 { ... }遍历时逐次重赋值循环结束后把块的最后表达式作为结果返回约定var变量建议以$开头如$prev_但$只是命名惯例——可变性完全由var关键字决定源码依据ModuleEnv.zig边界var声明与重赋值不能出现在模块顶层只能存在于块表达式内源码依据Statement.zig原理重赋值在规范化阶段从普通声明降级为s_reassign、*等运算符是约束方法派发最终所有参与运算的数值统一收敛为U64IR 依据statement 变体的# CANONICALIZE与# TYPES段验证expect result 55与 REPL 的assigned result从模块与交互两个角度证实了求值正确性。如果需要继续深入可以在 test/snapshots/repl 目录下找到更多for相关快照如for_loop_complex_mutation.md、for_loop_empty_list.md、for_loop_list_u64.md、range_for_loop.md并结合 src/canonicalize/Statement.zig 中的语句定义逐一对照即可系统掌握 Roc 的循环与可变状态语义。【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考