Raku re-binding(重绑定)规则及相关问题技术问询
[注意:本问题基于旧版本Rakudo提出,此前令人困惑的输出是Rakudo的bug导致,该bug已通过PR 4536修复。下方保留原始问题内容,供历史参考。]
问题背景
Raku有时会禁止重绑定操作,以下两行代码:
sub f($a) { $a := 42 } my \var = 'foo'; var := 'not-foo';
会触发编译时报错:
===SORRY!=== Error while compiling Cannot use bind operator with this left-hand side
但Raku在非常多场景下允许重绑定,其中不少场景的表现出乎我的意料。以下所有代码均可成功完成重绑定,所有say语句的输出均为not-foo:
my Any \a = 'foo'; say a := 'not-foo'; my Any $b := 'foo'; say $b := 'not-foo'; my @c := ('foo', 'foo'); say @c := ('not-foo', 'not-foo'); my @d is List = ('foo', 'foo'); say @d := ('not-foo', 'not-foo'); my %e := (:foo<foo>); say %e := (:not-foo<not-foo>); sub fn1(Any \a) { a := 'not-foo'; say a } fn1 'foo'; sub fn2(Any $b) { $b := 'not-foo'; say $b } fn2 'foo'; sub fn3(@c) { @c := ('not-foo', 'not-foo'); say @c } fn3 ('foo', 'foo'); sub fn4(+@d) { @d := ('not-foo', 'not-foo'); say @d } fn4 ('foo', 'foo'); sub fn5(@d is raw) { @d := ('not-foo', 'not-foo'); say @d } fn5 ('foo', 'foo'); my ($one-foo, $two-foo) := ('foo', 'foo'); $one-foo := 'not-foo'; say $one-foo; my \foo = 'foo'; say MY::<foo> := 'not-foo'; sub foo-fn { 'foo' } MY::<&foo-fn> := { 'not-foo' } say foo-fn; my $absolutely-foo = 'foo'; sub fn6 { CALLER::<$absolutely-foo> := 'not-foo';} fn6; say $absolutely-foo;
基于上述测试,我初步归纳当前只要满足以下任一条件,无论名称是否携带sigil(类型标识符),都允许执行重绑定:
- 该名称存在任意显式类型约束(包括
Any类型,以及@或%sigil自带的类型约束); - 重绑定操作使用限定名。
当前重绑定操作对已声明变量、参数均生效,甚至包含未用rw或copy修饰的参数。正如最后一个示例所示,重绑定甚至可以(看似)突破词法作用域的限制。(该示例基于Roast测试用例,该用例标注了注释-- legal?,说明不止我一人对该行为感到惊讶;该测试原本是针对is dynamic修饰的变量执行重绑定,上述示例的表现更令人意外)。
据我目前测试,只有声明为constant的名称无法通过上述方式执行重绑定。
提出的问题
现正式提出以下4个技术问题:
- 我对当前Raku重绑定行为的描述是否准确?即上述两条规则是否可以完整覆盖现有行为,还是需要补充其他额外规则?
- 该行为是否正确、符合设计预期、对齐语言规范?尽管存在S03-binding相关测试,我极少找到关于重绑定的官方规范说明。
- 如果该行为不符合设计预期,那么重绑定的官方规则应该是什么?
- 是否有方法可以告知Raku“严格禁止将该名称重绑定到新值”?
回答
问题1:你归纳的规则准不准确?
你观察到的所有反常的重绑定行为,都是2022年之前版本Rakudo的bug,后来已经修复了,所以你基于有问题的版本总结的那两条规则,只适用于旧的bug版本,对修复后的正式版本完全不成立。
问题2:这个行为符合设计预期吗?
完全不符合。Raku的设计里,重绑定本来就是个权限很高的操作,默认应该是受限制的:无sigil的变量默认绑的是值,本来就不该允许重绑定;函数参数默认是只读的,没标rw或者copy的话,根本不该允许在函数内部改绑定;至于非动态变量能通过CALLER::跨作用域重绑定,更是完全违反设计原则的bug。
问题3:官方设计的重绑定规则应该是啥样的?
按Raku的规范设计,合法的重绑定只有这些情况:
- 有sigil的标量
$x默认绑定的是可写容器,允许重绑定;如果声明时加了is ro就不能再绑 - 数组
@、哈希%默认是对应类型的可变容器,允许重绑定到同类型的其他容器 - 函数参数只有明确标了
is rw或者is copy的时候,才允许在函数内部重绑定 - 包限定名(比如
MY::、OUR::这类)只能重绑定那些本来就声明为可写的符号,非动态变量不可能通过CALLER::跨作用域改绑定 - 所有无sigil的名称,不管加不加类型约束,默认都是值绑定,永远不能重绑定
问题4:有没有办法彻底禁止某个名称被重绑定?
修复后的Rakudo版本已经默认做了大部分限制,如果你要额外明确禁止,用下面的方法就行:
- 无sigil的变量直接用
my \x = ...声明就行,本来就不能重绑定 - 有sigil的变量声明时加
is ro修饰符,比如my $x is ro = 42,之后任何重绑定操作都会直接报编译错 - 全局或者包级别的符号直接声明为
constant,完全锁死,不能改也不能重绑定
内容的提问来源于stack exchange,提问作者codesections
相关产品推荐
相关产品推荐

