控制语句

语句

Raku 程序由一个或多个语句组成。简单语句由分号分隔。以下程序将打印 “Hello”,然后在下一行打印“World”。

  1. say "Hello";
  2. say "World";

在语句中出现空白的大多数地方,且在分号之前,语句可能会分成许多行。此外,多个语句可能出现在同一行。这会很尴尬,但上面的内容也可以写成:

  1. say
  2. "Hello"; say "World";

块儿

与许多语言一样,Raku 使用 {} 将 `blocks`括起来以将多个语句转换为单个语句。可以省略块中最后一个语句和闭合 `}`之间的分号。

  1. { say "Hello"; say "World" }

当块单独作为一个语句存在时,它将在前一个语句完成后立即进入,并且其中的语句将被执行。

  1. say 1; # OUTPUT: «1»
  2. { say 2; say 3 }; # OUTPUT: «23»
  3. say 4; # OUTPUT: «4»

除非它作为一个语句单独存在,否则一个块只会创建一个闭包。内部的语句不会立即执行。闭包是另一个主题,如何使用它们在别处有解释。现在,了解块何时运行以及何时不运行是非常重要的:

  1. say "We get here"; { say "then here." }; { say "not here"; 0; } or die;

在上面的示例中,在运行第一个语句之后,第一个块独立作为第二个语句,因此我们运行里面的语句。第二个块不是单独作为一个语句,所以相反,它创建了一个 Block 类型的对象,但不运行它。对象实例通常被认为是 true,因此代码不会死掉,即使该块被计算为 0,它是否被执行。该示例没有说明如何处理`Block`对象,因此它会被丢弃。

下面介绍的大多数流控制结构只是告诉 Raku 何时,如何以及多少次进入像第二个块那样的块。

在我们深入这些之前,关于语法的一个重要的注意事项:如果在通常放置分号的结束大括号之后的行上没有任何内容(或者只有注释),则不需要分号:

  1. # All three of these lines can appear as a group, as is, in a program
  2. { 42.say } # OUTPUT: «42»
  3. { 43.say } # OUTPUT: «43»
  4. { 42.say }; { 43.say } # OUTPUT: «42 43»

…​但是:

  1. { 42.say } { 43.say } # Syntax error
  2. { 42.say; } { 43.say } # Also a syntax error, of course

因此,在换行编辑器中退格时要小心:

  1. { "Without semicolons line-wrapping can be a bit treacherous.".say } \
  2. { 43.say } # Syntax error

无论如何,在大多数语言中你必须注意这一点,以防止代码意外被注释掉。为清楚起见,下面的许多示例可能包含不必要的分号。

对于任何顶级表达式,类主体的行为类似于简单的块; 这同样适用于角色和其他包,如语法(实际上是类)或模块。

  1. class C {
  2. say "I live";
  3. die "I will never live!"
  4. };
  5. my $c = C.new;
  6. # OUTPUT: Fails and writes «I live
  7. II will never live!
  8. I

该块首先运行第一个语句,然后`die`打印第二个语句。$c 永远不会得到值。

Phasers

块可能有*phasers*:即将他们的执行分解成特别阶段运行阶段的特殊标记块。有关详细信息,请参阅页面phasers

do

块不能是独立的语句, 运行这样一个块的最简单方法是在它前面写上一个 do

  1. # This dies half of the time
  2. do { say "Heads I win, tails I die."; Bool.pick } or die; say "I win.";

请注意,您需要在 do 和块之间留一个空格。

整个 do {…​} 计算为块儿的最终值。当需要该值时,将运行该块以计算表达式的剩余部分。所以:

  1. False and do { 42.say };

…​不会打印 42。但是,每次计算包含它的表达式时,只会计算一次:

  1. # This says "(..1 ..2 ..3)" not "(..1 ...2 ....3)"
  2. my $f = "."; say do { $f ~= "." } X~ 1, 2, 3;

换句话说,它遵循与其他所有东西相同的具体规则。

从技术上讲,do 是一个只运行一次迭代的循环。

do 也可以用在一个裸语句上(没有花括号)但这主要是为了避免需要用圆括号扩住语句的语法,如果它是表达式中的最后一个:

  1. 3, do if 1 { 2 } ; # OUTPUT: «(3, 2)»
  2. 3, (if 1 { 2 }) ; # OUTPUT: «(3, 2)»
  3. 3, if 1 { 2 } ; # Syntax error

start

异步运行块的最简单方法是在它之前写上一个 start

  1. start { sleep 1; say "done" }
  2. say "working";
  3. # working, done

请注意,您需要在 start 和块儿之间留一个空格。在上面的示例中,start 块处于 sink 上下文中,因为它未赋值给变量。从版本 6.d 开始,这种块儿附加了一个异常处理程序:

  1. start { die "We're dead"; }
  2. say "working";
  3. sleep 10;

此代码将在版本 6.d 中打印 Unhandled exception in code scheduled on thread 4 We’re dead,而在版本 6.c 中等待 10 秒后它将立即退出。

如果你对块儿的结果不感兴趣, start {…​} 会立即返回一个可被安全忽略的 Promise。如果你对块儿的最终值兴趣,你可以调用返回的 promise 上的 .result 方法。所以:

  1. my $promise = start { sleep 10; 42 }
  2. # ... do other stuff
  3. say "The result is $promise.result()";

如果块内的代码尚未完成,则 .result 调用将等待直到完成。

start 也可用于裸语句(不带花括号)。这主要用于, 当在对象上调用子例程/方法是异步执行的唯一事情时。

if

要有条件地运行代码块,请使用 if 后跟条件。条件,表达式,将在 if 完成之前的语句之后立即进行计算。只有在条件被强转为 Bool 为真时, 才会计算附加到条件的块。与某些语言不同,条件不必用圆括号括起来,而块周围的 {} 是必需的:

  1. if 1 { "1 is true".say } ; # says "1 is true"
  2. if 1 "1 is true".say ; # syntax error, missing block
  3. if 0 { "0 is true".say } ; # does not say anything, because 0 is false
  4. if 42.say and 0 { 43.say }; # says "42" but does not say "43"

还有一种“语句修饰符”的形式的 if。在这种情况下,if 和 then 条件在您想要有条件地运行的代码之后。请注意,仍然始终首先计算条件:

  1. 43.say if 42.say and 0; # says "42" but does not say "43"
  2. 43.say if 42.say and 1; # says "42" and then says "43"
  3. say "It is easier to read code when 'if's are kept on left of screen"
  4. if True; # says the above, because it is true
  5. { 43.say } if True; # says "43" as well

语句修饰符形式最好谨慎使用。

if 语句本身要么 slip我们一个空列表,如果它不运行块,否则就会返回该块产生的值:

  1. my $d = 0; say (1, (if 0 { $d += 42; 2; }), 3, $d); # says "(1 3 0)"
  2. my $c = 0; say (1, (if 1 { $c += 42; 2; }), 3, $c); # says "(1 2 3 42)"
  3. say (1, (if 1 { 2, 2 }), 3); # does not slip, says "(1 (2 2) 3)"

对于语句修饰符,是一样的,除非你有语句的值而不是块:

  1. say (1, (42 if True) , 2); # says "(1 42 2)"
  2. say (1, (42 if False), 2); # says "(1 2)"
  3. say (1, 42 if False , 2); # says "(1 42)" because "if False, 2" is true

if 默认不改变主题变量($_)。为了访问条件表达式生成的值,您必须更强烈地要求它:

  1. $_ = 1; if 42 { $_.say } ; # says "1"
  2. $_ = 1; if 42 -> $_ { $_.say } ; # says "42"
  3. $_ = 1; if 42 -> $a { $_.say; $a.say } ; # says "1" then says "42"
  4. $_ = 1; if 42 { $_.say; $^a.say } ; # says "1" then says "42"

else/elsif

组合条件可以通过用 else 跟在 if 条件后面来产生, 以提供一个备选块,当条件表达式为假来运行:

  1. if 0 { say "no" } else { say "yes" } ; # says "yes"
  2. if 0 { say "no" } else{ say "yes" } ; # says "yes", space is not required

else 不能用分号将条件语句分开,但作为一个特例,换行符是可行的。

  1. if 0 { say "no" }; else { say "yes" } ; # syntax error
  2. if 0 { say "no" }
  3. else { say "yes" } ; # says "yes"

使用 elsif, 额外的条件可以被夹在 ifelse 之间。只有在前面的所有条件都为假的情况下才会计算额外条件,并且只运行第一个真实条件旁边的块。如果你愿意,你可以以一个 elsif 而不是一个 else 结束。

  1. if 0 { say "no" } elsif False { say "NO" } else { say "yes" } # says "yes"
  2. if 0 { say "no" } elsif True { say "YES" } else { say "yes" } # says "YES"
  3. if 0 { say "no" } elsif False { say "NO" } # does not say anything
  4. sub right { "Right!".say; True }
  5. sub wrong { "Wrong!".say; False }
  6. if wrong() { say "no" } elsif right() { say "yes" } else { say "maybe" }
  7. # The above says "Wrong!" then says "Right!" then says "yes"

您不能将语句修饰符形式用于 elseelsif

  1. 42.say if 0 else { 43.say } # syntax error

对于分号和换行, 所有相同的规则都适用,始终如一。

  1. if 0 { say 0 }; elsif 1 { say 1 } else { say "how?" } ; # syntax error
  2. if 0 { say 0 } elsif 1 { say 1 }; else { say "how?" } ; # syntax error
  3. if 0 { say 0 } elsif 1 { say 1 } else { say "how?" } ; # says "1"
  4. if 0 { say 0 } elsif 1 { say 1 }
  5. else { say "how?" } ; # says "1"
  6. if 0 { say 0 }
  7. elsif 1 { say 1 } else { say "how?" } ; # says "1"
  8. if 0 { say "no" }
  9. elsif False { say "NO" }
  10. else { say "yes" } ; # says "yes"

整个东西要么slips我们一个空列表(如果没有运行块)或者返回由运行的块产生的值:

  1. my $d = 0; say (1,
  2. (if 0 { $d += 42; "two"; } elsif False { $d += 43; 2; }),
  3. 3, $d); # says "(1 3 0)"
  4. my $c = 0; say (1,
  5. (if 0 { $c += 42; "two"; } else { $c += 43; 2; }),
  6. 3, $c); # says "(1 2 3 43)"

可以在 else 中获取前一个表达式的值,它可以来自 if 或者最后一个 elsif, 如果存在的话:

  1. $_ = 1; if 0 { } else -> $a { "$_ $a".say } ; # says "1 0"
  2. $_ = 1; if False { } else -> $a { "$_ $a".say } ; # says "1 False"
  3. if False { } elsif 0 { } else -> $a { $a.say } ; # says "0"

unless

当你厌倦了输入 “if not (X)” 时,你可能会用 unless 来反转条件语句的意义。你不能使用把 elseelsifunless 用在一起。因为那最终会让人感到困惑。除了这两个不同, unless 的工作方式和 if 相同:

  1. unless 1 { "1 is false".say } ; # does not say anything, since 1 is true
  2. unless 1 "1 is false".say ; # syntax error, missing block
  3. unless 0 { "0 is false".say } ; # says "0 is false"
  4. unless 42.say and 1 { 43.say } ; # says "42" but does not say "43"
  5. 43.say unless 42.say and 0; # says "42" and then says "43"
  6. 43.say unless 42.say and 1; # says "42" but does not say "43"
  7. $_ = 1; unless 0 { $_.say } ; # says "1"
  8. $_ = 1; unless 0 -> $_ { $_.say } ; # says "0"
  9. $_ = 1; unless False -> $a { $a.say } ; # says "False"
  10. my $c = 0; say (1, (unless 0 { $c += 42; 2; }), 3, $c); # says "(1 2 3 42)"
  11. my $d = 0; say (1, (unless 1 { $d += 42; 2; }), 3, $d); # says "(1 3 0)"

with, orwith, without

with 语句像 if 一样,但它测试 definedness 而不是真假。此外,它在条件上主题化,很像 given

  1. with "abc".index("a") { .say } # prints 0

代替 elsiforwith 可用于链定义性测试:

  1. # The below code says "Found a at 0"
  2. my $s = "abc";
  3. with $s.index("a") { say "Found a at $_" }
  4. orwith $s.index("b") { say "Found b at $_" }
  5. orwith $s.index("c") { say "Found c at $_" }
  6. else { say "Didn't find a, b or c" }

您可以混合基于 if 和基于 with 的子句。

  1. # This says "Yes"
  2. if 0 { say "No" } orwith Nil { say "No" } orwith 0 { say "Yes" };

unless 一样,您可以使用 without 检查 undefinedness,但是您可能不会添加一个 else 子句:

  1. my $answer = Any;
  2. without $answer { warn "Got: {$_.perl}" }

也有 withwithout 语句修饰符:

  1. my $answer = (Any, True).roll;
  2. say 42 with $answer;
  3. warn "undefined answer" without $answer;

when

when 块类似于 if 块,它们中的一个或两个都可以用在外部块中; 他们也都有一个“语句修饰符”形式。但是如何处理外部块中的相同代码是有区别的:当 when 块执行时,控制被传递到封闭块并忽略后面的语句; 但是当 if 块执行时,后面的语句会被执行。[1]以下例子应说明 ifwhen 块的默认行为,假设没有特殊出口或其他副作用的语句被包括在 ifwhen 块中:

  1. {
  2. if X {...} # if X is true in boolean context, block is executed
  3. # following statements are executed regardless
  4. }
  5. {
  6. when X {...} # if X is true in boolean context, block is executed
  7. # and control passes to the outer block
  8. # following statements are NOT executed
  9. }

如果以上 ifwhen 块出现在文件作用域内,则在每种情况下都会执行后面的语句。

还有另外一个功能,when 有而 if 没有的:when 的布尔上下文测试默认为 $_ ~~,而 if 的不是。这会影响如何在没有 $_ (在这种情况下是 Any。 并且 Any 智能匹配`True`:Any ~~ True 产生 True)值的 when 块儿中使用X。请看以下代码:

  1. {
  2. my $a = 1;
  3. my $b = True;
  4. when $a { say 'a' }; # no output
  5. when so $a { say 'a' } # a (in "so $a" 'so' coerces $a to Boolean context True
  6. # which matches with Any)
  7. when $b { say 'b' }; # no output (this statement won't be run)
  8. }

最后,when 语句修饰符形式不影响在另一个块内部或外部执行以下语句:

  1. say "foo" when X; # if X is true statement is executed
  2. # following statements are not affected

由于成功匹配将退出块,这段代码的行为:

  1. $_ = True;
  2. my $a;
  3. {
  4. $a = do when .so { "foo" }
  5. };
  6. say $a; # OUTPUT: «(Any)»

解释了,因为在存储或处理任何值之前放弃了 do 块。但是,在这种情况下:

  1. $_ = False;
  2. my $a;
  3. {
  4. $a = do when .so { "foo" }
  5. };
  6. say $a; # OUTPUT: «False»

因为比较是假的,所以不会放弃该块,因此 $a 实际上会得到一个值。

for

for 循环迭代一个列表,每次迭代, 运行中的语句一次,。如果块接受参数,则列表元素作为参数提供。

  1. my @foo = 1..3;
  2. for @foo { $_.print } # prints each value contained in @foo
  3. for @foo { .print } # same thing, because .print implies a $_ argument
  4. for @foo { 42.print } # prints 42 as many times as @foo has elements

当然,尖括号语法或占位符可用于命名参数。

  1. my @foo = 1..3;
  2. for @foo -> $item { print $item }
  3. for @foo { print $^item } # same thing

可以声明多个参数,在这种情况下,迭代器在运行块之前根据需要从列表中获取尽可能多的元素。

  1. my @foo = 1..3;
  2. for @foo.kv -> $idx, $val { say "$idx: $val" }
  3. my %hash = <a b c> Z=> 1,2,3;
  4. for %hash.kv -> $key, $val { say "$key => $val" }
  5. for 1, 1.1, 2, 2.1 { say "$^x < $^y" } # says "1 < 1.1" then says "2 < 2.1"

尖块的参数可以具有默认值,允许处理缺少元素的列表。

  1. my @list = 1,2,3,4;
  2. for @list -> $a, $b = 'N/A', $c = 'N/A' {
  3. say "$a $b $c"
  4. }
  5. # OUTPUT: «1 2 3
  6. 4 N/A N/A»

If the postfix form of for is used a block is not required and the topic is set for the statement list.

如果使用 for 的后缀形式,则不需要块,并且为语句列表设置主题。

  1. say I $_ butterflies!“ for <♥ ♥>;
  2. # OUTPUT«I ♥ butterflies!
  3. I butterflies!
  4. I butterflies

for 可以在惰性列表上使用 - 只在需要时从列表中取元素,因此要逐行读取文件,您可以使用:

  1. for $*IN.lines -> $line { .say }

迭代变量总是有词法的,因此您无需使用 my 来为它们提供适当的作用域。此外,它们是只读别名。如果您需要它们进行读写,请使用 <→ 而不是 。如果需要 $_ 在 for 循环中进行读写,请明确执行此操作。

  1. my @foo = 1..3;
  2. for @foo <-> $_ { $_++ }

for 循环可以生成每个附加块运行产生的值的 List。要捕获这些值,请将 for 循环放在括号中或将它们赋值给数组:

  1. (for 1, 2, 3 { $_ * 2 }).say; # OUTPUT «(2 4 6)»
  2. my @a = do for 1, 2, 3 { $_ * 2 }; @a.say; # OUTPUT «[2 4 6]»
  3. my @b = (for 1, 2, 3 { $_ * 2 }); @b.say; # OUTPUT: «[2 4 6]»

gather/take

gather 是一个返回值的序列的语句或块前缀。该值来自在 gather 块的动态作用域的take调用。

  1. my @a = gather {
  2. take 1;
  3. take 5;
  4. take 42;
  5. }
  6. say join ', ', @a; # OUTPUT: «1, 5, 42»

gather/take 可以懒惰地生成值,具体取决于上下文。如果要强制延迟计算 ,请使用lazy子例程或方法。绑定到标量或无符号的容器也会导致懒惰。

例如:

  1. my @vals = lazy gather {
  2. take 1;
  3. say "Produced a value";
  4. take 2;
  5. }
  6. say @vals[0];
  7. say 'between consumption of two values';
  8. say @vals[1];
  9. # OUTPUT:
  10. # 1
  11. # between consumption of two values
  12. # Produced a value
  13. # 2

gather/take 是动态作用域的,因此您可以从 gather 里面的 subs 或方法内部调用 take

  1. sub weird(@elems, :$direction = 'forward') {
  2. my %direction = (
  3. forward => sub { take $_ for @elems },
  4. backward => sub { take $_ for @elems.reverse },
  5. random => sub { take $_ for @elems.pick(*) },
  6. );
  7. return gather %direction{$direction}();
  8. }
  9. say weird(<a b c>, :direction<backward> ); # OUTPUT: «(c b a)»

如果值需要在调用方可变,请使用take-rw

请注意,gather/take 也适用于哈希。返回值仍然是一个 Seq 但在以下示例中对散列的赋值使其成为散列。

  1. my %h = gather { take "foo" => 1; take "bar" => 2};
  2. say %h; # OUTPUT: «{bar => 2, foo => 1}»

supply/emit

将调用者发射到闭合的 supply 中:

  1. my $supply = supply {
  2. emit $_ for "foo", 42, .5;
  3. }
  4. $supply.tap: {
  5. say "received {.^name} ($_)";
  6. }
  7. # OUTPUT:
  8. # received Str (foo)
  9. # received Int (42)
  10. # received Rat (0.5)

given

given 语句是 Raku 中的 topicalizing 关键字, 类似于 C 语言中的 switch。换句话说,given 设置后面跟着的块里面的 $_。单独用例的关键词是 whendefault。通常的惯用法看起来像这样:

  1. my $var = (Any, 21, any <answer lie>).pick;
  2. given $var {
  3. when 21 { say $_ * 2 }
  4. when 'lie' { .say }
  5. default { say 'default' }
  6. }

given 语句通常单独使用:

  1. given 42 { .say; .Numeric; }

这比下面的写法更容易理解:

  1. { .say; .Numeric; }(42)

default 和 when

default 语句后面的 sub-block 离开时, 包含 default 语句的块立马离开。好像跳过了块中的其余语句。

  1. given 42 {
  2. "This says".say;
  3. $_ == 42 and ( default { "This says, too".say; 43; } );
  4. "This never says".say;
  5. }
  6. # The above block evaluates to 43

when 语句也将这样做(但 when 语句修饰符将*不会*。)

此外,when 语句针对提供的表达式和 topic$_)进行 智能匹配,以便在指定匹配时可以检查值,正则表达式和类型。

  1. for 42, 43, "foo", 44, "bar" {
  2. when Int { .say }
  3. when /:i ^Bar/ { .say }
  4. default { say "Not an Int or a Bar" }
  5. }
  6. # OUTPUT: «42
  7. 43
  8. Not an Int or a Bar
  9. 44
  10. Bar»

在这种形式中,given/when 结构的行为很像一组 if/elsif/else 语句。注意 when 语句的顺序。下面的代码打印 "Int" 而不是 42

  1. given 42 {
  2. when Int { say "Int" }
  3. when 42 { say 42 }
  4. default { say "huh?" }
  5. }
  6. # OUTPUT: «Int»

when 语句或 default 语句导致外部块返回时,嵌套 whendefault 块不计为外部块,因此只要不打开新块,就可以嵌套这些语句并仍然在同一个“开关”(switch)中:

  1. given 42 {
  2. when Int {
  3. when 42 { say 42 }
  4. say "Int"
  5. }
  6. default { say "huh?" }
  7. }
  8. # OUTPUT: «42»

when 语句可以智能匹配签名

proceed

succeed

proceedsucceed 意在仅用于 whendefault 块的内部。

proceed 语句将立即离开 whendefault 块, 跳过其余的语句,并在块后重新开始。这可以防止 whendefault 退出外部块。

  1. given * {
  2. default {
  3. proceed;
  4. "This never says".say
  5. }
  6. }
  7. "This says".say;

这通常用于进入多个 when 块。proceed 在成功匹配后将恢复匹配,如下:

  1. given 42 {
  2. when Int { say "Int"; proceed }
  3. when 42 { say 42 }
  4. when 40..* { say "greater than 40" }
  5. default { say "huh?" }
  6. }
  7. # OUTPUT: «Int»
  8. # OUTPUT: «42»

请注意,when 40..* 匹配未发生。为了匹配这样的情况,人们需要在 when 42 块中添加 proceed

这不像 Cswitch 语句,因为 proceed 不仅仅是进入直接跟随的块,它还会再次尝试匹配 given 值,请看以下代码:

  1. given 42 {
  2. when Int { "Int".say; proceed }
  3. when 43 { 43.say }
  4. when 42 { 42.say }
  5. default { "got change for an existential answer?".say }
  6. }
  7. # OUTPUT: «Int»
  8. # OUTPUT: «42»

…​匹配 Int,跳过 43, 因为值不匹配,匹配 42,因为这是下一个真实的匹配,但不进入 default 块,因为该 when 42 块不包含 proceed

相反,succeed 关键字短路执行并在此时退出整个 given 块。它也可能需要参数来指定块的最终值。

  1. given 42 {
  2. when Int {
  3. say "Int";
  4. succeed "Found";
  5. say "never this!";
  6. }
  7. when 42 { say 42 }
  8. default { say "dunno?" }
  9. }
  10. # OUTPUT: «Int»

如果您不在 whendefault 块中,则尝试使用 proceedsucceed 是错误的。还要记住,when 语句修饰符形式不会导致任何块被丢弃,并且这样的语句中的任何 succeedproceed 都应用于周围的子句,如果有的话:

  1. given 42 {
  2. { say "This says" } when Int;
  3. "This says too".say;
  4. when * > 41 {
  5. { "And this says".say; proceed } when * > 41;
  6. "This never says".say;
  7. }
  8. "This also says".say;
  9. }

given 作为语句

given 可以跟在语句后面, 以在给它所跟的语句中设置主题(topic)。

  1. .say given "foo";
  2. # OUTPUT: «foo»
  3. printf "%s %02i.%02i.%i",
  4. <Mo Tu We Th Fr Sa Su>[.day-of-week - 1],
  5. .day,
  6. .month,
  7. .year
  8. given DateTime.now;
  9. # OUTPUT: «Sa 03.06.2016»

loop

loop 语句接收 3 个参数, 分别是初始化, 条件和增量, 它们在元括号中用 ; 分隔。初始化执行一次,任何变量声明都将溢出到周围的块中。每次迭代执行一次条件并将其强转为 Bool,如果为 False 则循环停止。每次迭代执行一次增量器。

  1. loop (my $i = 0; $i < 10; $i++) {
  2. say $i;
  3. }

无限循环不需要圆括号。

  1. loop { say 'forever' }

loop 如果出现在列表中,则该语句可用于从附加块的每次运行结果中生成值:

  1. (loop ( my $i = 0; $i++ < 3;) { $i * 2 }).say; # OUTPUT: «(2 4 6)»
  2. my @a = (loop ( my $j = 0; $j++ < 3;) { $j * 2 }); @a.say; # OUTPUT: «[2 4 6]»
  3. my @b = do loop ( my $k = 0; $k++ < 3;) { $k * 2 }; @b.say; # same thing

for 循环不同,不应该依赖于返回的值是否是惰性生成的。最好使用 eager 来保证循环的返回值真实运行:

  1. sub heads-in-a-row {
  2. (eager loop (; 2.rand < 1;) { "heads".say })
  3. }

while, until

只要条件为真,while 语句就会执行该块。所以

  1. my $x = 1;
  2. while $x < 4 {
  3. print $x++;
  4. }
  5. print "\n";
  6. # OUTPUT: «123»

类似地,只要表达式为 false ,until 语句就会执行该块。

  1. my $x = 1;
  2. until $x > 3 {
  3. print $x++;
  4. }
  5. print "\n";
  6. # OUTPUT: «123»

whileuntil 的条件可以用括号括起来,但关键字和条件的左括号之间必须有空格。

whileuntil 两者可作为语句修饰符。例如:

  1. my $x = 42;
  2. $x-- while $x > 12

另见 repeat/while 和下面的 repeat/until

所有这些形式都可以以和 loop 相同的方式产生返回值。

repeat/while, repeat/until

*至少*执行*一次*该块,如果条件允许,则重复执行该块。这与 while/until 的不同之处在于,即使条件出现在前面,也会在循环结束时计算条件。

  1. my $x = -42;
  2. repeat {
  3. $x++;
  4. } while $x < 5;
  5. $x.say; # OUTPUT: «5»
  6. repeat {
  7. $x++;
  8. } while $x < 5;
  9. $x.say; # OUTPUT: «6»
  10. repeat while $x < 10 {
  11. $x++;
  12. }
  13. $x.say; # OUTPUT: «10»
  14. repeat while $x < 10 {
  15. $x++;
  16. }
  17. $x.say; # OUTPUT: «11»
  18. repeat {
  19. $x++;
  20. } until $x >= 15;
  21. $x.say; # OUTPUT: «15»
  22. repeat {
  23. $x++;
  24. } until $x >= 15;
  25. $x.say; # OUTPUT: «16»
  26. repeat until $x >= 20 {
  27. $x++;
  28. }
  29. $x.say; # OUTPUT: «20»
  30. repeat until $x >= 20 {
  31. $x++;
  32. }
  33. $x.say; # OUTPUT: «21»

所有这些形式都可以以和 loop 相同的方式产生返回值。

return

sub return 将停止子程序或方法的执行,运行所有相关的phasers,并提供给定的返回值给调用者。默认返回值是 Nil。如果提供了返回值类型约束,则将检查它,除非返回值为 Nil。如果类型检查失败,则抛出异常 X::TypeCheck::Return。如果它通过了, 则发生控制异常,可以通过 CONTROL 捕获。

无论嵌套有多深,块中的任何 return 都与该块外部词法作用域中的第一个 Routine 绑定。请注意,包的根目录中的 return 将在运行时失败。块中被惰性计算(例如在 map 里面)的`return` 可能发现外部词法例程在块执行时消失了。几乎在任何情况下 last 都是更好的选择。有关如何处理和生成返回值的更多信息,请查看函数文档

return-rw

sub return 将返回值,而不是容器。这些是不可变的,并且在尝试可变(mutated)时会导致运行时错误。

  1. sub s(){ my $a = 41; return $a };
  2. say ++s();
  3. CATCH { default { say .^name, ': ', .Str } };
  4. # OUTPUT: «X::Multi::NoMatch.new(dispatcher …

要返回可变容器,请使用 return-rw

  1. sub s(){ my $a = 41; return-rw $a };
  2. say ++s();
  3. # OUTPUT: «42»

return 适用于关于 phasers 和控制异常的规则。

fail

在执行所有相关的 phasers之后,离开例程并返回提供的 Exception 或包含在 Failure 里面的 Str 。如果调用者通过编译指令 use fatal; 激活致命异常,则抛出异常而不是作为 Failure 返回。

  1. sub f { fail "WELP!" };
  2. say f;
  3. CATCH { default { say .^name, ': ', .Str } }
  4. # OUTPUT: «X::AdHoc: WELP!»

once

带有前缀 once 的块即使放在循环或递归例程中,也只执行一次。

  1. my $guard = 3;
  2. loop {
  3. last if $guard-- <= 0;
  4. once { put 'once' };
  5. print 'many'
  6. } # OUTPUT: «once
  7. manymanymany»

这适用于包含代码对象的每个“克隆”,因此:

  1. ({ once 42.say } xx 3).map: {$_(), $_()}; # says 42 thrice

请注意,当多个线程运行同一个块儿的同一克隆时,这不是线程安全的构造。还要记住,方法每个类只有一个克隆,而不是每个对象。

quietly

quietly 块将抑制其生成的所有警告。

  1. quietly { warn 'kaput!' };
  2. warn 'still kaput!';
  3. # OUTPUT: «still kaput! [...]»

从块内调用的任何例程生成的任何警告也将被抑制:

  1. sub told-you { warn 'hey...' };
  2. quietly { told-you; warn 'kaput!' };
  3. warn 'Only telling you now!'
  4. # OUTPUT: «Only telling you now!
  5. [...] »

LABELs

whileuntilloopfor 循环都可以带一个标签,它可以用来标识 nextlastredo 。支持嵌套循环,例如:

  1. OUTAHERE: while True {
  2. for 1,2,3 -> $n {
  3. last OUTAHERE if $n == 2;
  4. }
  5. }

标签也可以在嵌套循环中用于命名每个循环,例如:

  1. OUTAHERE:
  2. loop ( my $i = 1; True; $i++ ) {
  3. OUTFOR:
  4. for 1,2,3 -> $n {
  5. # exits the for loop before its natural end
  6. last OUTFOR if $n == 2;
  7. }
  8. # exits the infinite loop
  9. last OUTAHERE if $i >= 2;
  10. }

next

next 命令启动循环的下一次迭代。所以代码:

  1. my @x = 1, 2, 3, 4, 5;
  2. for @x -> $x {
  3. next if $x == 3;
  4. print $x;
  5. }

打印 “1245”。

如果存在NEXT phaser,它将在下一次迭代之前运行:

  1. my Int $i = 0;
  2. while ($i < 10) {
  3. if ($i % 2 == 0) {
  4. next;
  5. }
  6. say "$i is odd.";
  7. NEXT {
  8. $i++;
  9. }
  10. }
  11. # OUTPUT: «1 is odd.
  12. 3 is odd.
  13. 5 is odd.
  14. 7 is odd.
  15. 9 is odd

从版本 6.d 开始,对于它们运行的迭代,循环中收集其最后一个语句值的 next 命令将返回 Empty

last

last 命令立即退出当前循环。

  1. my @x = 1, 2, 3, 4, 5;
  2. for @x -> $x {
  3. last if $x == 3;
  4. print $x;
  5. }

打印 “12”。

如果存在LAST phaser,则在退出循环之前运行:

  1. my Int $i = 1;
  2. while ($i < 10) {
  3. if ($i % 5 == 0) {
  4. last;
  5. }
  6. LAST {
  7. say "The last number was $i.";
  8. }
  9. NEXT {
  10. $i++;
  11. }
  12. }
  13. # OUTPUT: «The last number was 5.»

从版本 6.d 开始,对于它们运行的迭代,循环中收集其最后一个语句值的 last 命令将返回 Empty

redo

redo 命令重新启动循环块,而不再计算条件。

  1. loop {
  2. my $x = prompt("Enter a number");
  3. redo unless $x ~~ /\d+/;
  4. last;
  5. }