有些人喜欢说“一切都是对象”,但实际上在 Raku 中变量不是对用户暴露的对象。

    当编译器遇到类似 的变量声明时,它会将其注册到某个内部符号表中。此内部符号表用于检测未声明的变量,并将变量的代码生成与正确的作用域联系起来。

    在运行时,变量显示为*词法板中*的条目,或*简称*为*lexpad*。这是一个每个作用域的数据结构,它存储每个变量的指针。

    my $x 这种情况下,变量的 $x 的 lexpad 条目是指向 Scalar 类型对象的指针,通常称为*容器*。

    标量容器

    虽然 Scalar 类型的对象在 Raku 中无处不在,但您很少直接将它们视为对象,因为大多数操作*都是去容器化的*,这意味着它们会对 Scalar 容器的内容而不是容器本身起作用。

    在这样的代码中:

    赋值 $x = 42 在标量容器中存储指向 Int 对象 42 的指针,lexpad 条目 $x 指向该标量容器。

    赋值运算符要求左侧的容器将值存储在其右侧。究竟是什么意思取决于容器类型。因为 Scalar 它意味着“用新的值替换先前存储的值”。

    请注意,子例程签名允许传递容器:

    1. sub f($a is rw) {
    2. $a = 23;
    3. }
    4. my $x = 42;
    5. f($x);
    6. say $x; # OUTPUT: «23»

    在子例程内部,lexpad 条目 $a 指向 $x 指向子例程外部的同一容器。这就是为什么给 $a 赋值也修改了 $x 的内容。

    同样,例程可以返回容器,如果它被标记为 is rw

    1. my $x = 23;
    2. sub f() is rw { $x };
    3. f() = 42;
    4. say $x; # OUTPUT: «42»

    对于显式返回,必须使用 return-rw 而不是 return

    返回容器是 is rw 属性访问器的工作方式。所以:

    1. class A {
    2. has $.attr is rw;
    3. }

    相当于

    1. class A {
    2. has $!attr;
    3. method attr() is rw { $!attr }
    4. }

    标量容器对类型检查和大多数只读访问都是透明的。.VAR 使它们可见:

    1. my $x = 42;
    2. say $x.^name; # OUTPUT: «Int»
    3. say $x.VAR.^name; # OUTPUT: «Scalar»

    并且参数上的 is rw 需要存在可写的 Scalar 容器:

    1. sub f($x is rw) { say $x };
    2. f 42;
    3. CATCH { default { say .^name, ': ', .Str } };
    4. # OUTPUT: «X::Parameter::RW: Parameter '$x' expected a writable container, but got Int value»

    Callable 容器

    可调用容器在 Routine 调用语法和存储在容器中的对象的 方法的实际调用之间提供了桥梁。声明容器时需要使用符号 & ,执行时必须省略 Callable。默认类型约束是 Callable

    1. my &callable = -> $ν { say "$ν is", $ν ~~ Int??" whole"!!" not whole" }
    2. callable( );
    3. callable( 3 );

    当提到存储在容器中的值时,必须提供 signal 符号。这反过来允许 Routine 被用作调用的。

    1. sub f() {}
    2. sub caller(&c1, &c2){ c1, c2 }
    3. caller(&f, &g);

    在赋值之后,Raku 还支持 := *绑定*运算符。将值或容器绑定到变量时,会修改变量的 lexpad 条目(而不仅仅是它指向的容器)。如果你这样写:

    1. my $x := 42;
    1. my $x := 42;
    2. $x = 23;
    3. CATCH { default { say .^name, ': ', .Str } };
    4. # OUTPUT: «X::AdHoc: Cannot assign to an immutable value»

    您还可以将变量绑定到其他变量:

    这里,在初始绑定之后,$a 的 lexpad 条目和 $b 的lexpad 条目两者都指向同一个标量容器,因此给一个变量赋值也会改变另一个变量的内容。

    您之前已经看到过这种情况:它正是签名参数标记为 is rw 的情况。

    无符号变量和带有 is raw trait 的参数总是绑定的(无论使用 =:= ):

    1. my $a = 42;
    2. my \b = $a;
    3. b++;
    4. say $a; # OUTPUT: «43»
    5. sub f($c is raw) { $c++ }
    6. f($a);
    7. say $a; # OUTPUT: «44»

    Scalar 容器和 listy things

    在 Raku 中有许多位置容器类型,其语义略有不同。最基本的是 List; 它由逗号运算符创建。

    1. say (1, 2, 3).^name; # OUTPUT: «List»

    列表是不可变的,这意味着您无法更改列表中的元素数。但是,如果其中一个元素恰好是标量容器,您仍然可以给它赋值:

    1. my $x = 42;
    2. ($x, 1, 2)[0] = 23;
    3. say $x; # OUTPUT: «23»
    4. ($x, 1, 2)[1] = 23; # Cannot modify an immutable value
    5. CATCH { default { say .^name, ': ', .Str } };
    6. # OUTPUT: «X::Assignment::RO: Cannot modify an immutable Int»

    所以列表不关心它的元素是值还是容器,它们只是存储和检索给它们的任何东西。

    列表也可以是惰性的; 在这种情况下,最终的元素是根据迭代器的要求生成的。

    Array 就像一个列表,除了它强制所有元素都是容器,这意味着你总是可以给元素赋值:

    1. my @a = 1, 2, 3;
    2. @a[0] = 42;
    3. say @a; # OUTPUT: «[42 2 3]»

    @a 实际上存储了三个标量容器。@a[0] 返回其中一个,赋值运算符用新的整数替换该容器中存储的整数值 42

    赋值和绑定给数组变量

    对标量变量和数组变量的赋值都执行相同的操作:丢弃旧值,并输入一些新值。

    然而,很容易观察到它们有多么不同:

    1. my $x = 42; say $x.^name; # OUTPUT: «Int»
    2. my @a = 42; say @a.^name; # OUTPUT: «Array»

    这是因为 Scalar 容器类型隐藏得很好,但 Array 没有这样的效果。对数组变量的赋值也是强制性的,因此可以将非数组值赋给数组变量。

    要将非 Array 放入数组变量,绑定起作用:

    1. my @a := (1, 2, 3);
    2. say @a.^name; # OUTPUT: «List»

    作为一个奇怪的旁注,Raku 支持绑定到数组元素:

    1. my @a = (1, 2, 3);
    2. @a[0] := my $x;
    3. $x = 42;
    4. say @a; # OUTPUT: «[42 2 3]»

    如果您已经阅读并理解了之前的解释,那么现在是时候知道这是如何工作的了。毕竟,绑定到变量需要该变量的 lexpad 条目,虽然数组有一个 lexpad 条目 ,但每个数组元素都没有 lexpad 条目,因为您无法在运行时展开 lexpad。

    答案是在语法级别识别绑定到数组元素,而不是为正常绑定操作发出代码,在数组上调用特殊方法(BIND-KEY 被调用)。此方法处理与数组元素的绑定。

    请注意,虽然支持,但通常应避免直接将非容器化事物绑定到数组元素中。这样做可能会在以后使用数组时产生反直觉的结果。

    1. my @a = (1, 2, 3);
    2. @a[0] := 42; # This is not recommended, use assignment instead.
    3. my $b := 42;
    4. @a[1] := $b; # Nor is this.
    5. @a[2] = $b; # ...but this is fine.
    6. @a[1, 2] := 1, 2; # runtime error: X::Bind::Slice
    7. CATCH { default { say .^name, ': ', .Str } };

    混合列表和数组的操作通常可以防止发生这种意外情况。

    展平, 项和容器

    1. for @a { }; # 3 iterations
    2. my $a = (1, 2, 3);
    3. for $a { }; # 1 iteration

    @-sigiled 变量不会在列表上下文中展平:

    1. my @a = 1, 2, 3;
    2. my @b = @a, 4, 5;
    3. say @b.elems; # OUTPUT: «3»

    有些操作会使不在标量容器内的子列表被展平:slurpy parameters(*@a)和显式调用 flat

    您还可以使用 | 创建 Slip,将列表引入另一个列表中。

    1. my @l := 1, 2, (3, 4, (5, 6)), [7, 8, (9, 10)];
    2. say (|@l, 11, 12); # OUTPUT: «(1 2 (3 4 (5 6)) [7 8 (9 10)] 11 12)»
    3. say (flat @l, 11, 12) # OUTPUT: «(1 2 3 4 5 6 7 8 (9 10) 11 12)»

    在第一种情况下,@l 的每个元素都作为结果列表的相应元素*滑动*。另一方面,flat *扁平化*所有元素,包括所包含数组的元素,除了 (9 10)

    如上所述,标量容器可防止扁平化:

    1. sub f(*@x) { @x.elems };
    2. my @a = 1, 2, 3;
    3. say f $@a, 4, 5; # OUTPUT: «3»

    @ 字符也可以用作将参数强制为列表的前缀,从而删除标量容器:

    1. my $x = (1, 2, 3);
    2. .say for @$x; # 3 iterations

    但是,*解容器*运算符 <> 更适合去除非列表项:

    1. my $x = ^Inf .grep: *.is-prime;
    2. say "$_ is prime" for @$x; # WRONG! List keeps values, thus leaking memory
    3. say "$_ is prime" for $x<>; # RIGHT. Simply decontainerize the Seq
    4. my $y := ^Inf .grep: *.is-prime; # Even better; no Scalars involved at all

    方法通常不关心他们的调用者是否在标量中,所以:

    1. my $x = (1, 2, 3);
    2. $x.map(*.say); # 3 iterations

    在三个元素的列表上 map,而不是在一个元素上 map。

    自引用数据

    容器类型(包括 ArrayHash)允许您创建自引用结构。

    1. my @a;
    2. @a[0] = @a;
    3. put @a.perl;
    4. # OUTPUT: «((my @Array_75093712) = [@Array_75093712,])»

    虽然 Raku 不会阻止您创建和使用自引用数据,但这样做可能会导致您尝试转储数据。作为最后的手段,您可以使用 Promises 来处理超时。

    任何容器都可以具有类型对象或形式的类型约束。两者都可以放在声明符和变量名之间,也可以放在 trait of。之后。约束是变量的属性,而不是容器的属性。

    1. subset Three-letter of Str where .chars == 3;
    2. my Three-letter $acronym = "ÞFL";

    在这种情况下,类型约束是(编译类型定义的)subset Three-letter

    变量可能没有容器,但仍然提供重新绑定和类型检查重新绑定的能力。原因是在这种情况下绑定运算符 执行类型检查:

    1. my Int \z = 42;
    2. z := 100; # OK
    3. z := "x"; # Typecheck failure

    例如,当绑定到 Hash 键时,情况并非如此,因为绑定随后由方法调用处理(即使语法保持不变,使用 := 运算符)。

    Scalar 容器的默认类型约束是 。.VAR.of 方法提供了对容器类型约束的内省,对于 @% sigiled 变量,它给出了值的约束:

    1. my Str $x;
    2. say $x.VAR.of; # OUTPUT: «(Str)»
    3. my Num @a;
    4. say @a.VAR.of; # OUTPUT: «(Num)»
    5. my Int %h;
    6. say %h.VAR.of; # OUTPUT: «(Int)»

    Definedness 约束

    容器还可以强制执行变量是定义的。在声明中放一个笑脸:

    1. my Int:D $def = 3;
    2. say $def; # OUTPUT: «3»

    您还需要在声明中初始化变量,毕竟变量不能是未定义的。

    也可以在使用 的作用域中声明的所有变量中强制执行此约束。来自其他语言的人们总是会定义变量,他们希望看看。

    自定义容器