
Bend 语言 Dups 与 Sups 完全指南手工复制与超级位置叠加的原理与实践【免费下载链接】BendA massively parallel, high-level programming language项目地址: https://gitcode.com/GitHub_Trending/be/Bend导读dupduplication复制与supsuperposition叠加/超级位置是 Bend 这一大规模并行高级语言中一对互为镜像的核心机制dup负责把一个项按需复制成多份sup则把一个可能值叠加起来参与后续所有计算。本文以 docs/dups-and-sups.md 为主干结合 src/fun/parser.rs、src/fun/transform/linearize_vars.rs 等源码系统讲解let手工复制语法、{...}叠加语法、二者配对时的构造/析构行为以及两个dup相互干扰导致结果错误这一关键限制帮助读者在编写高阶函数与并行程序时规避这一类陷阱。一、术语背景Bend 中的复制与叠加Bend 建立在交互组合子Interaction Combinators模型之上程序会被编译成由节点与端口构成的交互网络interaction net这一转换由 src/fun/term_to_net.rs 负责。在 HVMHigher-order Virtual Machine层面dup与sup最终都体现为带标签的扇出节点fan node即 src/fun/mod.rs 中定义的Term::Fan/// Either a tuple or a superposition Fan { fan: FanKind, // FanKind::Dup 或 FanKind::Tup tag: Tag, els: VecTerm, },理解这一底层表示有助于解释本文后面涉及的种种行为dup复制把某个值拆成多份副本分别供多个使用者使用。使用次数超过一次的变量编译器会自动复制同时语言也提供了let语法进行手工复制。sup叠加把两个或多个值叠加成一个不确定项是所有可能值的并行替身。任何操作作用在叠加态上都会在每种可能性上分别执行天然映射为并行分支。dup与sup的对偶性一次复制正好对应一次叠加的消耗。当一个dup遇到一个sup时二者发生交互等价于把叠加的各个分支分别分配给复制的各个变量即构造与析构一对值。二、手工复制用let编写dup2.1 基本语法当一个变量在函数体中被使用多次时Bend 会自动完成复制无需任何特殊标记。但也可以使用let语句手动把一项复制成多份。语法上dup表现为let {a1 a2} value; ...的形式花括号内给出要绑定到的多个新变量# 使用 let 手工复制丘奇编码的 2 ch2 λf λx let {f1 f2} f; (f1 (f2 x)) # 丘奇编码的 3需要连续两次复制 ch3 λf λx let {f0 f1} f; let {f2 f3} f0; (f1 (f2 (f3 x)))上面的ch2把函数f复制为f1、f2两份再依次应用两次得到丘奇数 2ch3则先复制出f0、f1再对f0做第二次复制得到f2、f3从而获得三次应用。这正体现了dup的语义一个值多个副本互不共享。2.2 编译器如何实现自动复制即使不写任何let只要某个变量在作用域内被引用多次编译器也会自动生成对应的dup。这一逻辑位于 src/fun/transform/linearize_vars.rs其核心是按引用次数线性化若某个绑定被引用0次该绑定被擦除Erase binding若被引用1次保持原样Keep as-is若被引用超过1次则自动插入Term::Let其模式为duplicate_pat(nam, uses)——即构造一个Pattern::Fan(FanKind::Dup, Tag::Auto, ...)把原变量复制成与使用次数相等的若干份。fn duplicate_pat(nam: Name, uses: u64) - BoxPattern { Box::new(Pattern::Fan( FanKind::Dup, Tag::Auto, (1..uses 1).map(|i| Pattern::Var(Some(dup_name(nam, i)))).collect(), )) }可见手写let {f1 f2} f与编译器自动插入的复制在语义上完全等价都是往 AST 里注入一个FanKind::Dup模式。这也是为什么文档示例中List/map里对参数f的两次使用会被编译器展开成显式的{f1 f2} f。三、叠加态用{}定义sup3.1 基本语法sup使用花括号写出花括号内包含两个或多个可能值sup {3 7}{3 7}表示一个同时等于 3 或 7的叠加值。从解析器实现看{开头的项在 src/fun/parser.rs 中被解析为// Sup if self.starts_with({) { let els self.list_like(|p| p.parse_term(), {, }, ,, false, 2)?; return Ok(Term::Fan { fan: FanKind::Dup, tag: tag.unwrap_or(Tag::Auto), els }); }即sup在语法树里同样被表示为Term::Fan与dup共用同一节点类型只是它出现在值的位置上而let {x1 x2} ...形式的dup出现在模式pattern的位置上见 src/fun/parser.rs。二者一个构造、一个析构恰好构成一对。3.2 叠加参与计算的传播规则叠加态可以出现在任何期望普通值的位置。任何运算与叠加态交互时都会对每个可能值分别执行一次结果仍然是叠加态mul λa λb (* a b) result (mul 2 5) # 返回 10 result_sup (mul 2 {5 7}) # 返回 {10 14} multi_sup (mul {2 3} {5 7}) # 返回 {{10 14} {15 21}}单个叠加参数{5 7}乘法分别在5、7上执行得到{10 14}两个叠加参数{2 3}与{5 7}结果成为两层的叠加{{10 14} {15 21}}覆盖全部四种组合。这一笛卡尔积式的传播就是叠加并行性的来源在一次计算中同时探索所有可能性而不是串行遍历。对应的实际用例可见测试 tests/golden_tests/run_file/sup_app.bendmain ({(λx x) (λx x)} 3)这里把两个恒等函数叠加后应用于3测试验证了叠加应用于函数时的行为。3.3 叠加态的读取在交互网络上运行结束后结果中的sup需要被读回readback为语法层面的项。这一过程由 src/fun/net_to_term.rs 的read_fan完成如果叠加节点在读取路径上遇到配对的dup就按dup的各个分支把叠加值拆开FanKind::Dup分支如果没有配对的dup则原样保留为叠加项Term::Fan。也就是说未配对的sup会作为一等值存活在结果中。四、dup与sup配对等价于元组的构造与析构当把一个叠加值与一个复制配对时二者恰好构成构造/析构的逆运算# 每个 dup 变量现在都各自拿到 {1 2} 叠加中的一份 let {x1 x2} {1 2}语义上{1 2}构造了一个同时是 1 和 2的值而let {x1 x2} ...将其析构x1拿到1x2拿到2。二者合起来等价于一个元组(1, 2)的打包与解包。从交互组合子的角度解释sup生成一个扇出fan-out节点dup生成一个扇入fan-in节点二者相遇即发生交互annihilation/commutation 规则把两端的值一一配对。这正是 Bend 中实现一个函数返回多个值复制与叠加互相抵消等模式的底层机制。五、关键限制两个dup相互干扰会破坏结果5.1 破坏性干涉的产生由于复制在编译后的交互网络中是以特定方式排列的当两个dup相遇时它们会互相产生破坏性干涉destructive interference。此时的结果虽然在 HVM 层是良定义的well defined但在 λ 演算语义层面却是错误的因此正确的 Bend 程序必须满足一条强约束一个变量不应复制另一个本身也在复制变量的变量。换句话说同一段计算路径上只能存在单一来源的复制。若出现嵌套/重叠的双重复制各dup的端口配对会被打乱导致变量绑定错位。5.2 高阶函数中的典型踩坑示例下面这段来自原文档的示例展示了在使用高阶函数时双重复制如何导致错误def List/map(xs: List(A), f: A - B) - List(B): fold xs: case List/Nil: return List/Nil case List/Cons: # f 在这里被复制 return List/Cons(f(xs.head), List/map(xs.tail, f)) # 上面这行会被编译器转换为对 f 的显式复制 # {f1 f2} f # return List/Cons(f1(xs.head), List/map(xs.tail, f2)) def main() - _: # 这个 lambda 复制了 x同时又因为 List/map 而自身被复制。 # 这会导致错误行为。 # 在当前这个具体例子中运行时能捕获到并报错 # 但目前并非总是如此。 return List/map([1, 2, 3], lambda x: ( x x))逐步拆解这里的双重复制来源List/map的case List/Cons分支中参数f被使用两次f(xs.head)与递归调用List/map(xs.tail, f)因此编译器自动把f复制为f1、f2对应 src/fun/transform/linearize_vars.rs 中的duplicate_pat逻辑这就是第一层dup。传入的 lambdaλx ( x x)内部复制了变量xx被使用两次这构成第二层dup。当List/map复制f时被复制的对象本身还含有一个内部dupx的复制。两个dup叠加在一起产生破坏性干涉最终得到错误结果。在文档给出的这个具体例子中运行时恰好能够检测到这种异常并报错但文档明确强调并非所有情况都能被运行时捕获因此不能依赖运行时兜底而应在编写时就避免双重复制。5.3 解决方案只保留一个复制来源要修复这类程序必须保证复制来源唯一二选一即可让List/map保持线性f在List/map内部不被复制例如改为对f的引用只出现一次或者使用use等机制避免复制让传入的函数保持线性传入的 lambda 内部不复制任何变量即λx ( x x)改为不重复使用x的函数。只要满足其中一条复制来源就只有一个dup与dup不会相遇结果保持正确。这是编写高阶、递归函数时必须牢记的 Bend 特有约束。六、实践要点与使用建议综合上述原理与限制在实际编写 Bend 程序时可遵循以下要点场景推荐写法说明手动复制一个项let {a b} value; ...等价于编译器对多引用变量的自动复制表示多个可能值并行计算{v1 v2}运算结果自动成为各可能值的叠加叠加参与多次运算(f {a b} {c d})结果按笛卡尔积展开为多层叠加构造/析构配对let {x1 x2} {1 2}等价于元组的打包与解包高阶函数传参传入线性函数或保证高阶函数不复制参数避免两个dup相遇产生破坏性干涉几点补充说明叠加与类型检查sup属于未类型化untyped特性的范畴。在类型检查开启时函数体内的Term::Fan { fan: FanKind::Dup, .. }会触发错误 Superposition term in type-checked function见 src/fun/check/check_untyped.rs。也就是说叠加主要用于未类型化的快速原型与并行探索场景。显式与隐式复制并存手写let {f1 f2} f与依赖编译器自动复制src/fun/transform/linearize_vars.rs在语义上一致二者可以混用。结果读取运行结果中的叠加在读取回语法树时会保留src/fun/net_to_term.rs因此可以直接在终端观察叠加分支展开后的完整结构。测试佐证仓库的 golden 测试tests/golden_tests/run_file/sup_app.bend 与快照run_file__sup_app.bend.snap覆盖了叠加应用于函数的运行行为可作为验证叠加语义的最小样例。总结dup与sup是 Bend 并行模型在语言层最直接的体现dup用let {a b} x手工复制项sup用{a b}叠加可能值二者配对时等价于元组的构造与析构叠加在参与任何运算时都会按分支并行展开。与此同时Bend 对复制来源有严格限制——一个变量不能复制另一个自身也在复制的变量否则两个dup的破坏性干涉会产生 λ 层语义错误。理解并遵守这一约束是写出正确高阶函数与并行程序的前提。【免费下载链接】BendA massively parallel, high-level programming language项目地址: https://gitcode.com/GitHub_Trending/be/Bend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考