跳转到内容

Raku 编程/块和闭包

来自维基教科书,开放的书籍,开放的世界

关于块

[编辑 | 编辑源代码]

当我们谈论子程序时,我们看到子程序声明由三个部分组成:子程序名称、子程序参数列表和子程序内部代码块。块在 Raku 中非常基础,我们现在将使用它们来做各种有趣的事情。

我们已经看到了一些在各种结构中使用的块

# if/else statements
if $x == 1 {
}
else {
}

# subroutines
sub thisIsMySub () {
}

# loops
for @ary {
}

loop (my $i = 0; $i <= 5; $i++) {
}

repeat {
} while $x == 1;

所有这些块都用于将代码行分组在一起以用于特定目的。在 if 块中,当 if 条件为真时,块内的所有语句都会被执行。如果条件为假,整个块将不被执行。在循环中,循环块中的所有语句都会一起重复执行。

作用域

[编辑 | 编辑源代码]

除了将类似的代码放在一起之外,块还引入了作用域的概念。在块内定义的 my 变量在块外不可见。作用域确保变量仅在需要时使用,并且在不应修改时不修改它们。块不需要与任何特定结构相关联,例如 ifloop。块可以独立存在

my $x = 5;
my $y = 5;
{
   my $y = 3;
   say $x;         # 5
   say $y;         # 3
}
say $x;            # 5
say $y;            # 5

该示例很好地展示了作用域的概念:块内的变量 $y 与块外的变量 $y 不同。即使它们具有相同的名称,但它们具有不同的作用域。这是一个稍微不同的示例

my $x = 5;
{
   my $y = 7;
   {
      my $z = 9;
      say $x;  # 5
      say $y;  # 7
      say $z;  # 9
   }
   say $x;     # 5
   say $y;     # 7
   say $z;     # ERROR: Undeclared variable!
}
say $x;        # 5
say $y;        # ERROR! Undeclared variable!
say $z;        # ERROR! Undeclared variable!

变量 $x 从其定义的位置开始,在其定义的作用域内的所有作用域内都是可见的。$y 仅在其定义的块内以及该块内的块内可见。$z 仅在最内部的块内可见。

作用域变量

[编辑 | 编辑源代码]

在存在歧义的情况下,可以精确地指定作用域。我们可以使用 OUTER 这样的关键字来指定来自当前作用域直接上方的作用域的变量

my $x = 5;
{
   my $x = 6;
   say $x;           # 6
   say $OUTER::x    # 5
}

子程序可以使用 CALLER 作用域访问其被调用的作用域,假设外部作用域中的变量被声明为 is context

my $x is context = 5;
mySubroutine(7);

sub mySubroutine($x) {
   say $x;         # 7
   say $CALLER::x; # 5
}

代码引用

[编辑 | 编辑源代码]

块可以存储在一个单独的标量变量中作为代码引用。一旦存储在代码引用变量中,块就可以像常规子程序引用一样执行

my $dostuff = {
   print "Hello ";
   say "world!";
}

$dostuff();

我们在上面的示例中看到,块可以存储在变量中。此操作会创建一个闭包。闭包是存储的代码块,它保存其当前状态和当前作用域,这些作用域可以稍后访问。让我们看看闭包在实践中的应用

my $block;
{
    my $x = 2;
    $block = { say $x; };
}
$block();   # Prints "2", even though $x is not in scope anymore

闭包在创建闭包时保存了对 $x 变量的引用。即使该变量在代码块执行时不再处于作用域中。

当我们稍后更改 $x 时,闭包将看到更改的值,因此如果您想创建多个包含不同封闭变量的闭包,则每次必须创建一个新变量

my @times = ();
for 1..10 {
   my $t = $_;          # each subroutine gets a different $t
   @times[$_] = sub ($a) { return $a * $t; };
}

say @times[3](4);       # 12
say @times[5](20);      # 100
say @times[7](3);       # 21

尖括号块

[编辑 | 编辑源代码]

我们可以使用 sub 关键字来创建子程序或子程序引用。这并不是唯一的方法,实际上对于匿名(“匿名”)子程序或子程序引用的常见情况,它有点过于冗长。对于这些情况,我们可以使用一种称为尖括号块的结构。尖括号块在其他语言中称为lambda 块,非常有用。它们可以像匿名子程序一样创建代码引用,并且还可以创建带有参数的代码块。尖括号块非常像一个未命名的子程序。更一般地,它就像一个带有参数的块。我们在讨论循环时已经简要地看到了尖括号块。我们在与循环结构相关的使用尖括号块,以便为循环变量命名,而不是依赖默认变量 $_。这就是我们在这些情况下使用尖括号块的原因:它们使我们能够指定变量名称作为任意代码块的参数。

我们将展示一些示例

my @myArray = (1, 2, 3, 4, 5, 6);

# In a loop:
for @myArray -> $item {
    say $item;

# Output is:
#    1
#    2
#    3
#    4
#    5
#    6

}
# In a loop, multiples
for @myArray -> $a, $b, $c {
    say "$a, $b, $c";

# Output is:
#    1, 2, 3
#    4, 5, 6

}
# As a condition:
my $x = 5;
if ($x) -> $a { say $a; }  # 5
# As a coderef
my $x = -> $a, $b { say "First: $a.  Second: $b"; }
$x(1, 2);       # First: 1, Second: 2
$x("x", "y");   # First: x, Second: y
# As an inline coderef
-> $a, $b { say "First: $a, Second: $b"; }(1, 2)
#In a while loop
while ($x == 5) -> $a {
   say "Boolean Value: $a";
}

占位符参数

[编辑 | 编辑源代码]

在块中,如果我们不想费力地写出参数列表,我们可以使用占位符参数。占位符使用特殊的 ^ twigil。传递的值按字母顺序分配给占位符变量

for 1..3 {
  say $^a;    # 1
  say $^c;    # 3
  say $^b;    # 2
}
华夏公益教科书