语句§

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

say "Hello";
say "World";

在语句中出现空格的大多数地方,以及分号之前,它们可以拆分为多行。此外,同一行上可以出现多个语句。这会很笨拙,但上面的示例也可以写成

say
"Hello"say "World";

§

与许多其他语言一样,Raku 使用 {} 括起来的 将一系列语句转换为充当单个语句的 Block。在块中的最后一个语句和结束 } 之间省略分号是可以的。

{ say "Hello"say "World" }

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

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

除非它作为单独的语句存在,否则代码块只是创建一个闭包。其中的语句不会立即执行。闭包是另一个主题,它们的使用方式在其他地方有解释。现在重要的是要理解代码块何时运行以及何时不运行。

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

在上面的例子中,在执行第一个语句之后,第一个代码块作为第二个语句单独存在,所以我们执行它里面的语句。第二个代码块是一个闭包,所以它创建了一个类型为Block的对象,但不会执行它。对象实例通常被认为是真值,所以代码不会停止,即使该代码块如果被执行的话会计算为 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␤I will never live!␤ 

这个代码块将首先执行第一个语句,然后die打印第二个语句。$c永远不会获得值。

Phasers§

代码块可能具有phasers:特殊的标记代码块,它们将代码块的执行分成阶段,这些阶段在特定阶段运行。有关详细信息,请参阅phasers页面。

do§

在代码块不能作为单独语句存在的情况下,运行它的最简单方法是在它之前写do

# This dies half of the time 
do { say "Heads I win, tails I die."Bool.pick } or diesay "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~ 123;

换句话说,它遵循与其他所有内容相同的reification规则。

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

do也可以用于裸语句(没有花括号),但这主要只是为了避免在语句是表达式中的最后一项时需要使用括号。

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

start§

异步方式运行语句或代码块的最简单方法是在它之前写start

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

请注意,start和代码块之间需要一个空格。在上面的例子中,start代码块处于接收器上下文中,因为它没有被分配给变量。从版本 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 1042 }
# ... do other stuff 
say "The result is $promise.result()";

如果代码块中的代码还没有完成,对.result的调用将等待它完成。

当异步执行的唯一操作是子例程或方法时,start用于裸语句很有用。

sub get42 { 42 }
my $promise = start get42;
say $promise.result# OUTPUT: «42␤»

请注意,启动代码无法访问其外部块的特殊变量 $!$/,但会收到新的特殊变量,因此每个异步任务都有其特定于任务的状态。

因此,在异步任务中执行的 try 表达式和正则表达式匹配具有其特定于任务的状态。

'a' ~~ /a/# $/ is set to 「a」 
try die;    # $! is defined now with an anonymous AdHoc exception 
# as a code block 
await start { say $! }# OUTPUT: «Nil␤» 
await start { say $/ }# OUTPUT: «Nil␤» 
# as a single statement 
await start $!.say;     # OUTPUT: «Nil␤» 
await start $/.say;     # OUTPUT: «Nil␤»

if§

要条件性地运行一段代码,请使用 if 后跟一个条件。该条件是一个表达式,将在 if 语句之前的语句完成之后立即进行评估。只有当该条件在强制转换为 Bool 时表示 True 时,才会评估附加到该条件的代码块。与某些语言不同,该条件不需要加括号,而是代码块周围的 {} 是必需的。

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 和条件位于要条件性运行的代码之后。请注意,条件始终首先进行评估。

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 = 0say (1, (if 0 { $d += 422}), 3$d); # says "(1 3 0)" 
my $c = 0say (1, (if 1 { $c += 422}), 3$c); # says "(1 2 3 42)" 
say (1, (if 1 { 22 }), 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 默认情况下不会更改主题 ($_)。为了访问条件表达式产生的值,您必须更强烈地请求它。

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

else/elsif§

可以通过在 if 条件之后添加 else 来生成一个复合条件,以提供一个备用代码块,在条件表达式为假时运行。

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!".sayTrue }
sub wrong { "Wrong!".sayFalse }
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"

整个内容要么 Slip 给我们一个空列表(如果没有任何代码块运行),要么返回运行的代码块产生的值。

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

可以在 else 中获取先前表达式的值,该值可以来自 if 或最后一个 elsif(如果有)。

$_ = 1if 0     { } else -> $a { "$_ $a".say } ; # says "1 0" 
$_ = 1if 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" 
 
$_ = 1unless 0 { $_.say }           ; # says "1" 
$_ = 1unless 0 -> $_ { $_.say }     ; # says "0" 
$_ = 1unless False -> $a { $a.say } ; # says "False" 
 
my $c = 0say (1, (unless 0 { $c += 422}), 3$c); # says "(1 2 3 42)" 
my $d = 0say (1, (unless 1 { $d += 422}), 3$d); # says "(1 3 0)"

with orwith without§

with 语句类似于 if,但它测试的是定义性而不是真值,并且它在条件上进行主题化,就像 given 一样。

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

elsif 类似,orwith 可用于链接定义性测试。

# 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 来检查未定义性,但不能添加 else 子句。

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

还有 withwithout 语句修饰符。

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

与其他可链接结构一样,完成 with/if..orwith/elsif 链的 else 本身将主题化为先前(失败)条件的主题的值(无论是 with 的主题还是最后一个 orwithelsif)。

else 紧随 withorwith 之后的情况下,主题化为一个保证未定义的值可能看起来毫无用处。但当与可能失败的操作一起使用时,它会形成一个有用的习惯用法,因为 Failure 值始终未定义。

sub may_fail--> Numeric:D ) {
  my $value = (^10).pick || fail "Zero is unacceptable";
  fail "Odd is also not okay" if $value % 2;
  return $value;
}
 
with may_fail() -> $value { # defined, so didn't fail 
  say "I know $value isn't zero or odd."
} else { # undefined, so failed, and the Failure is the topic 
  say "Uh-oh: {.exception.message}."
}

请注意,虽然将 Failure 作为主题会将其标记为 handled(因此您可以使用 with/else 安全地继续执行),但这并不会使Failure 值本身安全。即使在 else 子句中,如果您尝试直接使用该值,也会导致 else 子句本身失败(或者,在 Rakudo 中,将 Failure 提升为抛出的异常)。

但如上所述,您可以else 主题化的已处理 Failure 对象中使用方法,例如 exception,如果您希望提供诊断信息或查询底层的 Exception

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 的则没有。这会影响如何在没有 $_ 值的情况下在 when 块中使用 X(在这种情况下它是 Any,并且 AnyTrue 智能匹配:Any ~~ True 会产生 True)。考虑以下情况

{
    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 循环遍历列表,在每次迭代时运行 Block 内的语句。如果该块接受参数,则列表的元素将作为参数提供。默认情况下,该块接受一个参数,$_

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 11.122.1 { say "$^x < $^y" }  # OUTPUT: «1 < 1.1␤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␤»

当没有为 for 循环的块指定参数时,可以在其中使用 when,类似于在 given 块中使用它。

# A solution for FizzBuzz: 
for 1..100 {
    when * %% 15 { say 'FizzBuzz' }
    when * %% 3  { say 'Fizz' }
    when * %% 5  { say 'Buzz' }
    default      { say $_ }
}

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

say $_ butterflies! for <♥ ♥ ♥>;
# OUTPUT: «I ♥ butterflies!␤I ♥ butterflies!␤I ♥ butterflies!␤»

for 可以用于延迟列表 - 它只会在需要时从列表中获取元素,因此要逐行读取文件,可以使用

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

迭代变量始终是词法变量,因此您不需要使用 my 来为它们提供适当的范围。此外,它们是只读别名。如果您需要它们可写,请使用 <-> 而不是 ->。或者,您可以添加 is rw 特性;这将执行绑定操作,因此为参数赋值会更改调用方侧的变量值。如果您想修改块内的参数副本,请添加 is copy

my @foo = 1..3;
for @foo <-> $value {
    $value = $value %% 2 ?? "Even" !! "Odd"
}
 
say @foo# OUTPUT: «[Odd Even Odd]␤» 
 
@foo = 1..3;
for @foo -> $value is rw {
    $value = $value %% 2 ?? "Even" !! "Odd"
}
 
say @foo# OUTPUT: «[Odd Even Odd]␤» 
 
@foo = 1..3;
my @bar;
for @foo -> $value is copy {
    $value = $value %% 2 ?? "Even" !! "Odd";
    @bar.push: $value
}
 
say @foo# OUTPUT: «[1 2 3]␤» 
say @bar# OUTPUT: «[Odd Even Odd]␤» 

此规则也适用于主题变量 $_,默认情况下它是一个读写别名;如果它在 -> 循环中使用,它将变为只读。

my @foo = 1..3;
for @foo -> $_ { $_.say }
 
# Error: ...require mutable arguments 
for @foo -> $_ { $_++ }

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

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

这意味着,如果循环的结果没有被分配,它们将处于 sink 上下文

class Sunk {
    has $.titanic;
    method sink {
        say "Sinking $!titanic";
    }
}
Sunk.new:titanic($_) ) for ^3;
for 1 {
    say "About to sink";
    Sunk.new:titanic($_) );
}
# OUTPUT: 
# Sinking 0 
# Sinking 1 
# Sinking 2 
# About to sink 
# Sinking 1 

第一个循环创建了三个元素,但它们处于 sink 上下文,因此调用了它的 `sink` 方法。在第二个循环中,它的最后一个语句将处于 sink 上下文,因此它也将被 sink(从版本 6.d 开始)。

常量 `Empty` 将充当循环的无操作。

say "Not here" for Empty;

不会做任何事情。此常量 等效于一个空的 Slip 或 List

未定义的值将以相同的方式运行。

my @array := Empty;
.say for @array;
say @array# OUTPUT: «()␤» 

将 `Empty` 赋值将有效地取消定义一个 `Array`,使用 `for` 遍历一个未定义的数组甚至不会进入循环,如所示,有效地与直接使用 `Empty` 时相同。

使用 `hyper` 和 `race`,`for` 循环可能并行迭代。另请参阅类 `Map` 中 `hyper` 和 `race` 的文档。

my $primes_h = hyper for ^10_000 -> $number { $number if $number.is-prime };
say $primes_h.elems;   # OUTPUT: «1229␤» 
say $primes_h.tail: 5# OUTPUT: «(9931 9941 9949 9967 9973)␤» 

使用 `hyper` 保留元素的顺序。

my $primes_r = race for ^10_000 -> $number { $number if $number.is-prime };
say $primes_r.elems# OUTPUT: «1229␤» 

与 `hyper` 不同,`race` 不保留元素的顺序。

gather/take§

`gather` 是一个语句或块前缀,它返回一个 序列 的值。这些值来自在 `gather` 代码的动态作用域中对 take 的调用。在下面的示例中,我们使用 `gather` 实现了一个子例程来计算一个整数的因子(注意,因子不是按顺序生成的)

sub factorsInt:D \n ) {
    my $k = 1;
    gather {
        while $k**2 < n {
            if n %% $k {
                take $k;
                take n div $k;
            }
            $k++;
        }
        take $k if $k**2 == n;
    }
}
 
say factors(36); # OUTPUT: «1, 36, 2, 18, 3, 12, 4, 9, 6␤»

`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` 内部调用的子例程或方法中调用 `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" => 1take "bar" => 2};
say %h;                   # OUTPUT: «{bar => 2, foo => 1}␤»

注意:`gather/take` 不能用于收集来自 `react/whenever` 的结果。`whenever` 块不是从运行 `gather/react` 的线程运行的,而是从运行 `emit` 的线程运行的。在这个线程上,没有处理程序来处理由 `take` 抛出的控制异常,导致它出错。

supply/emit§

关键字 `supply` 创建一个 Supply 对象,它是一个 按需供应,您可以从中获取。它与 `emit` 配对,`emit` 可以用在 `supply` 前缀代码中的任何地方。

使用 `emit 方法``emit 例程` 将调用者传递给封闭的 供应

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

另请参阅:`tap``Supplier`

given§

`given` 语句是 Raku 的主题化关键字,类似于 C 等语言中的 `switch` 主题化。换句话说,`given` 在以下块中设置 `$_`。各个 case 的关键字是 `when` 和 `default`。通常的习惯用法如下所示

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

`given` 语句通常单独使用

given 42 { .say.Numeric}

这比以下代码更容易理解

{ .say.Numeric}(42)

default and when§

包含 `default` 语句的块将在 `default` 语句后的子块退出时立即退出。就好像块中的其余语句都被跳过了。

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

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

此外,`when` 语句对主题(`$_`)进行 `智能匹配`,以匹配提供的表达式,这样就可以在指定匹配时检查值、正则表达式和类型。

for 4243"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 语句可以与 签名 进行智能匹配。

继续§

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".sayproceed }
    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".sayproceed } when * > 41;
       "This never says".say;
    }
    "This also says".say;
}
# OUTPUT: «This says␤This says too␤And this says␤This also says␤»

given 作为语句§

given 可以跟随一个语句来设置它所跟随语句的主题。

.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 语句接受三个用 ; 分隔的括号中的语句,分别充当初始化器、条件和增量器。初始化器在第一次测试条件之前执行一次。如果初始化器涉及变量声明,则变量在循环的外部或包含作用域中声明为词法变量,以便它可以在循环语句之后的代码中使用。条件在每次迭代之前执行并强制转换为 Bool;如果为 False,则停止循环。增量器在每次迭代之后执行,并在再次测试条件之前执行。

loop (my $i = 0$i < 10$i++{       # A typical loop 
    say $i;
}
 
my @str = "However Long".comb;          # Our very own .char routine: 
loop (my $l = 0;;) {                    # Declare $l in outer scope 
    last if !@str[$l++]                 # and count chars until we hit an 
}                                       # undefined element (Any) 
say "The string is {--$l} chars long.";

无限循环不需要括号。

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␤»

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

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

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

whileuntil 都可以用作语句修饰符。例如:

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

另请参见下面的 repeat/whilerepeat/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§

子例程 return 将停止执行子例程或方法,运行所有相关的 阶段 并向调用者提供给定的返回值。默认返回值为 Nil。如果提供了返回 类型约束,则将对其进行检查,除非返回值为 Nil。如果类型检查失败,则会抛出异常 X::TypeCheck::Return。如果通过,则会引发控制异常,并且可以使用 CONTROL 捕获。

代码块中的任何 return 都与该代码块外部词法范围中的第一个 Routine 绑定,无论嵌套深度如何。请注意,包根目录中的 return 将在运行时失败。在延迟评估的代码块(例如 map 内部)中的 return 可能会在执行代码块时发现外部词法例程已消失。在几乎所有情况下,last 都是更好的替代方案。请查看 函数文档,以获取有关如何处理和生成返回值的更多信息。

return-rw§

子例程 return 将返回值,而不是容器。它们是不可变的,如果尝试对其进行变异,会导致运行时错误。

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

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

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

return 相同的规则适用于阶段和控制异常。

fail§

离开当前例程并返回提供的 ExceptionStr,将其包装在 Failure 中,在执行所有相关的 阶段 之后。如果调用者通过编译指示 use fatal; 激活了致命异常,则会抛出异常,而不是将其作为 Failure 返回。

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

once§

once 为前缀的代码块或语句将被精确执行一次,即使它被放置在循环或递归例程中。

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

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

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

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

LABELs§

whileuntilloopfor 循环都可以接受一个标签,该标签可以用来标识它们以用于 nextlastredo。支持嵌套循环,例如

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

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

OUTAHERE:
loop ( my $i = 1True$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 = 12345;
for @x -> $x {
    next if $x == 3;
    print $x;
}

打印“1245”。

您也可以在 map 中使用 next:上面的示例看起来像

my @x = 12345;
print @x.map: -> $x {
    next if $x == 3;
    $x
}

打印“1 2 4 5”,因为当 Seq 被字符串化时,会在其条目之间添加空格。请注意,print 没有放在 map 的代码块中,因为它通常被认为是不好的做法,因为它的副作用(在本例中为 print)。

如果存在 NEXT 阶段,它将在下一个迭代之前运行

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.e.PREVIEW 版本(从 2021.07 Rakudo 编译器版本开始提供)中,也可以使用 `next` 语句返回值。这在 `map` 中使用时特别有用。

my @x = 12345;
print @x.map: -> $x {
    next 42 if $x == 3;
    $x
}

打印 "1 2 42 4 5"。

whenever 块中,`next` 会立即退出当前值的块。

react {
    whenever Supply.interval(1{
        next if .is-prime;
        say $_;
        done if $_ == 4;
    }
}

打印 "0"、"1" 和 "4" - 从 0 到 4 的整数,跳过素数。

*从 6.d 版本开始,在收集其最后一个语句值的循环中,`next` 命令会为其运行的迭代返回 `Empty`。*

last§

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

my @x = 12345;
for @x -> $x {
    last if $x == 3;
    print $x;
}

打印 "12"。

你也可以在 `map` 中使用 `last`:上面的例子看起来像

my @x = 12345;
print @x.map: -> $x {
    last if $x == 3;
    $x
}

打印 "1 2",因为当 Seq 被字符串化时,会在其条目之间添加空格。请注意,`print` 没有放在 `map` 的块内,因为它通常被认为是不好的做法,因为 `map` 的副作用(在本例中是 `print`)。

如果存在 LAST 阶段,它会在退出循环之前运行。

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`。

在 6.e.PREVIEW 版本(从 2021.07 Rakudo 编译器版本开始提供)中,也可以使用 `last` 语句返回值。这在 `map` 中使用时特别有用。

my @x = 12345;
print @x.map: -> $x {
    last 42 if $x == 3;
    $x
}

打印 "1 2 42"。

redo§

`redo` 命令会重新启动循环块,而不会再次评估条件。

for 1..5 -> $current-level {
    state $total-attempts = 0;
    $total-attempts++;
    print("Entering #$current-level");
    if $total-attempts %% 3 {
        redo;
    }
}
# OUTPUT: «Entering #1... Entering #2... Entering #3... Entering #3... Entering #4... Entering #5... Entering #5... » 
1 [↑] 还有其他方法可以修改它们的默认行为;它们将在其他部分讨论。