控制流

语句

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

say "Hello";
say "World";

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

say
"Hello"; say "World";

块儿

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

{ say "Hello"; say "World" }

When a block stands alone as a statement, it will be entered immediately after the previous statement finishes, and the statements inside it will be executed.

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

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

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

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

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

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

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

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

…但是:

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

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

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

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

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

class C {
    say "I live";
    die "I will never live!"
};
my $c = C.new;                              │
# OUTPUT: Fails and writes «I live␤II will never live!␤I

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

Phasers

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

do

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

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

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

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

False and do { 42.say };

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

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

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

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

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

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

start

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

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

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

start { die "We're dead"; }
say "working";
sleep 10;

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

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

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

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

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

if

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

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

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

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

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

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

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

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

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

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

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

else/elsif

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

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

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

if 0 { say "no" }; else { say "yes" }  ; # syntax error 
if 0 { say "no" }
else { say "yes" }                     ; # says "yes" 

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

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

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

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

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

if 0 { say 0 }; elsif 1 { say 1 }  else { say "how?" } ; # syntax error 
if 0 { say 0 }  elsif 1 { say 1 }; else { say "how?" } ; # syntax error 
if 0 { say 0 }  elsif 1 { say 1 }  else { say "how?" } ; # says "1" 
if 0 { say 0 } elsif 1 { say 1 }
else { say "how?" }                                    ; # says "1" 
 
if 0 { say 0 }
elsif 1 { say 1 } else { say "how?" }                  ; # says "1" 
 
if        0 { say "no" }
elsif False { say "NO" }
else        { say "yes" }                              ; # says "yes" 

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

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

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

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

unless

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

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

with, orwith, without

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

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

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

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

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

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

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

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

也有 withwithout 语句修饰符:

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

when

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

{
    if X {...} # if X is true in boolean context, block is executed 
    # following statements are executed regardless 
}
{
    when X {...} # if X is true in boolean context, block is executed 
                 # and control passes to the outer block 
    # following statements are NOT executed 
}

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

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

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

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

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

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

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

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

$_ = False;
my $a;
{
    $a = do when .so { "foo" }
};
say $a; # OUTPUT: «False» 

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

for

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

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

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

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

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

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

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

my @list = 1,2,3,4;
for @list -> $a, $b = 'N/A', $c = 'N/A' {
    say "$a $b $c"
}
# OUTPUT: «1 2 3
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 的后缀形式,则不需要块,并且为语句列表设置主题。

say „I $_ butterflies!“ for <♥ ♥ ♥>;
# OUTPUT«I ♥ butterflies!
I ♥ butterflies!
I ♥ butterflies!» 

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

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

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

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

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

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

gather/take

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

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

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

例如:

my @vals = lazy gather {
    take 1;
    say "Produced a value";
    take 2;
}
say @vals[0];
say 'between consumption of two values';
say @vals[1];
 
# OUTPUT: 
# 1 
# between consumption of two values 
# Produced a value 
# 2 

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

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

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

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

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

supply/emit

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

my $supply = supply {
    emit $_ for "foo", 42, .5;
}
$supply.tap: {
    say "received {.^name} ($_)";
}
 
# OUTPUT: 
# received Str (foo) 
# received Int (42) 
# received Rat (0.5) 

given

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

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

given 语句通常单独使用:

given 42 { .say; .Numeric; }

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

{ .say; .Numeric; }(42)

default 和 when

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

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

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

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

for 42, 43, "foo", 44, "bar" {
    when Int { .say }
    when /:i ^Bar/ { .say }
    default  { say "Not an Int or a Bar" }
}
# OUTPUT: «42
43
Not an Int or a Bar
44
Bar» 

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

given 42 {
    when Int { say "Int" }
    when 42  { say 42 }
    default  { say "huh?" }
}
# OUTPUT: «Int» 

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

given 42 {
    when Int {
      when 42  { say 42 }
      say "Int"
    }
    default  { say "huh?" }
}
# OUTPUT: «42» 

when 语句可以智能匹配签名

proceed

succeed

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

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

given * {
    default {
        proceed;
        "This never says".say
    }
}
"This says".say;

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

given 42 {
    when Int   { say "Int"; proceed }
    when 42    { say 42 }
    when 40..* { say "greater than 40" }
    default    { say "huh?" }
}
# OUTPUT: «Int» 
# OUTPUT: «42» 

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

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

given 42 {
    when Int { "Int".say; proceed }
    when 43  { 43.say }
    when 42  { 42.say }
    default  { "got change for an existential answer?".say }
}
# OUTPUT: «Int» 
# OUTPUT: «42» 

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

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

given 42 {
    when Int {
        say "Int";
        succeed "Found";
        say "never this!";
    }
    when 42 { say 42 }
    default { say "dunno?" }
}
# OUTPUT: «Int» 

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

given 42 {
    { say "This says" } when Int;
    "This says too".say;
    when * > 41 {
       { "And this says".say; proceed } when * > 41;
       "This never says".say;
    }
    "This also says".say;
}

given 作为语句

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

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

loop

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

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

无限循环不需要圆括号。

loop { say 'forever' }

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

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

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

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

while, until

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

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

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

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

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

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

my $x = 42;
$x-- while $x > 12

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

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

repeat/while, repeat/until

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

my $x = -42;
repeat {
    $x++;
} while $x < 5;
$x.say; # OUTPUT: «5» 
 
repeat {
    $x++;
} while $x < 5;
$x.say; # OUTPUT: «6» 
 
repeat while $x < 10 {
    $x++;
}
$x.say; # OUTPUT: «10» 
 
repeat while $x < 10 {
    $x++;
}
$x.say; # OUTPUT: «11» 
 
repeat {
    $x++;
} until $x >= 15;
$x.say; # OUTPUT: «15» 
 
repeat {
    $x++;
} until $x >= 15;
$x.say; # OUTPUT: «16» 
 
repeat until $x >= 20 {
    $x++;
}
$x.say; # OUTPUT: «20» 
 
repeat until $x >= 20 {
    $x++;
}
$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)时会导致运行时错误。

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

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

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

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

fail

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

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

once

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

my $guard = 3;
loop {
    last if $guard-- <= 0;
    once { put 'once' };
    print 'many'
} # OUTPUT: «once
manymanymany» 

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

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

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

quietly

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

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

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

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

LABELs

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

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

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

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

next

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

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

打印 “1245”。

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

my Int $i = 0;
while ($i < 10) {
  if ($i % 2 == 0) {
    next;
  }
 
  say "$i is odd.";
 
  NEXT {
    $i++;
  }
}
# OUTPUT: «1 is odd.
3 is odd.
5 is odd.
7 is odd.
9 is odd.» 

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

last

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

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

打印 “12”。

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

my Int $i = 1;
while ($i < 10) {
  if ($i % 5 == 0) {
    last;
  }
 
  LAST {
    say "The last number was $i.";
  }
  NEXT {
    $i++;
  }
}
# OUTPUT: «The last number was 5.» 

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

redo

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

loop {
    my $x = prompt("Enter a number");
    redo unless $x ~~ /\d+/;
    last;
}