Raku 中的函数

    子例程默认是词法()作用域的,对它们的调用通常在编译时解析。

    子例程可以具有签名,也称为*参数列表*,其指定签名期望的参数(如果有的话)。 它可以指定(或保持打开)参数的数量和类型,以及返回值。

    子例程的内省通过提供。

    创建子例程的基本方法是使用 sub 声明符,后跟可选标识符

    sub 声明符返回可以存储在任何容器中的 类型的值:

    1. my &c = sub { say "Look ma, no name!" }
    2. c; # OUTPUT: «Look ma, no name!
    3. »
    4. my Any:D $f = sub { say 'Still nameless...' }
    5. $f(); # OUTPUT: «Still nameless...
    6. »
    7. my Code \a = sub { say raw containers don't implement postcircumfix:<( )>‘ };
    8. a.(); # OUTPUT: «raw containers don't implement postcircumfix:<( )>
    9. »

    sub 声明符将在编译时在当前作用域内声明一个新名称。因此,任何间接性都必须在编译时解析:

    1. constant aname = 'foo';
    2. sub ::(aname) { say 'oi‽' };
    3. foo;

    一旦将宏添加到 Raku 中,这将变得更有用。

    为了使子程序接受参数,签名被放置在子例程名称和它的函数主体之间,在括号中:

    1. sub exclaim ($phrase) {
    2. say $phrase ~ "!!!!"
    3. }
    4. exclaim "Howdy, World";

    默认地, 子例程是的。即 sub foo {…​}my sub foo {…​} 是相同的并且只被定义在当前作用域中。

    1. sub escape($str) {
    2. # Puts a slash before non-alphanumeric characters
    3. S:g[<-alpha -digit>] = "\\$/" given $str
    4. }
    5. say escape 'foo#bar?'; # foo\#bar\?
    6. {
    7. sub escape($str) {
    8. # Writes each non-alphanumeric character in its hexadecimal escape
    9. S:g[<-alpha -digit>] = "\\x[{ $/.ord.base(16) }]" given $str
    10. }
    11. say escape 'foo#bar?' # foo\x[23]bar\x[3F]
    12. }
    13. # Back to original escape function
    14. say escape 'foo#bar?'; # foo\#bar\?

    子例程不必命名; 这种情况下, 它们被叫做匿名的。

    1. say sub ($a, $b) { $a ** 2 + $b ** 2 }(3, 4) # 25

    但在这种情况下,通常希望使用更简洁的块语法。可以就地调用子例程和块,如上例所示。

    Blocks 和 Lambdas

    每当你看到像

    1. { $_ + 42 }, -> $a, $b { $a ** $b }

    1. { $^text.indent($:spaces) }

    那么这是语法。 它在每个 ifforwhile 等关键字之后使用。

    1. for 1, 2, 3, 4 -> $a, $b {
    2. say $a ~ $b;
    3. }
    4. # OUTPUT: «12
    5. 34
    6. »

    它们也可以作为匿名代码块自己使用。

    1. say { $^a ** 2 + $^b ** 2}(3, 4) # 25

    有关块语法的详细信息,请参阅类型的文档。

    签名

    函数接受的参数在其签名中有描述。

    1. sub format(Str $s) { ... }
    2. -> $a, $b { ... }

    有关签名的语法和使用的详细信息,请参阅 Signature 类的文档。

    自动签名

    如果没有提供签名,但在函数体中使用了两个自动变量 @ % 中的任何一个,则将生成带有 **@** % 的签名。 两个自动变量可以同时使用。

    1. sub s { say @_, %_ };
    2. dd &s.signature # OUTPUT«:(*@_, *%_)
    3. »

    参数

    参数以逗号分隔列表的形式提供。 要消除嵌套调用的歧义, 可以使用圆括号或副词形式。

    1. sub f(&c){ c() * 2 }; # call the function reference c with empty parameter list
    2. sub g($p){ $p - 2 };
    3. say(g(42)); # nest call to g in call to say
    4. f: { say g(666) }; # call f with a block

    当调用函数时,位置参数应该以与函数签名相同的顺序提供。 命名参数可以以任何顺序提供,但是最好将命名参数放在位置参数之后。 在函数调用的参数列表中,支持一些特殊的语法:

    1. sub f(|c){};
    2. f :named(35); # 具名参数(in "adverb" form.)
    3. f named => 35; # 也是具名参数.
    4. f :35named; # 使用缩写的副词形式的具名参数
    5. f 'named' => 35; # 不是具名参数, 而是一个 Pair 位置参数
    6. my \c = <a b c>.Capture;
    7. f |c; # Merge the contents of Capture $c as if they were supplied

    传递给函数的参数在概念上首先被收集在 Capture 容器中。 关于这些容器的语法和使用的细节可以在 类的文档中找到。

    当使用命名参数时,请注意,正常的 List “pair-chaining” 允许在命名参数之间跳过逗号。

    1. sub f(|c){};
    2. f :dest</tmp/foo> :src</tmp/bar> :lines(512);
    3. f :32x :50y :110z; # This flavor of "adverb" works, too
    4. f :a:b:c; # The spaces are also optional.

    返回值

    任何块或例程将把它的最后一个表达式作为返回值提供给调用者。如果 或 return-rw 被调用,它们的参数(如果有的话)将成为返回值。 默认返回值为 。

    1. sub a { 42 };
    2. sub b { say a };
    3. b;
    4. # OUTPUT«42
    5. »

    多个返回值作为列表或通过创建捕获返回。 解构可以用于解开多个返回值。

    1. sub a { 42, 'answer' };
    2. put a.perl;
    3. # OUTPUT«(42, "answer")
    4. »
    5. my ($n, $s) = a;
    6. put [$s, $n];
    7. # OUTPUT«answer 42
    8. »
    9. sub b { <a b c>.Capture };
    10. put b.perl;
    11. # OUTPUT«\("a", "b", "c")
    12. »

    Raku 有很多方式来指定函数的返回类型:

    1. sub foo(--> Int) {}; say &foo.returns; # (Int)
    2. sub foo() returns Int {}; say &foo.returns; # (Int)
    3. sub foo() of Int {}; say &foo.returns; # (Int)
    4. my Int sub foo() {}; say &foo.returns; # (Int)

    尝试返回另外一种类型的值会引起编译错误。

    1. sub foo() returns Int { "a"; }; foo; # Type check fails

    注意,NilFailure 是免于返回类型约束,并且可以从任何子例程返回,而不管其约束:

    1. sub foo() returns Int { fail }; foo; # Failure returned
    2. sub bar() returns Int { return }; bar; # Nil returned

    多重分派

    Raku 允许你使用同一个名字但是不同签名写出几个子例程。当子例程按名字被调用时, 运行时环境决定哪一个子例程是最佳匹配, 然后调用那个候选者。你使用 multi 声明符来声明每个候选者。

    1. multi congratulate($name) {
    2. say "祝你生日快乐, $name";
    3. }
    4. multi congratulate($name, $age) {
    5. say "祝 $age 岁生日快乐, $name";
    6. }
    7. congratulate 'Camelia'; # 祝你生日快乐, Camelia
    8. congratulate 'Rakudo', 15; # 祝你 15 岁生日快乐, Rakudo

    分发/分派(dispatch) 可以发生在参数的数量(元数)上, 但是也能发生在类型上:

    不带任何指定例程类型的 multi 总是默认为 sub, 但是你也可以把 multi 用在方法(methods)上。那些候选者全都是对象的 multi 方法:

    1. class Congrats {
    2. multi method congratulate($reason, $name) {
    3. say "Hooray for your $reason, $name";
    4. }
    5. }
    6. role BirthdayCongrats {
    7. multi method congratulate('birthday', $name) {
    8. say "Happy birthday, $name";
    9. multi method congratulate('birthday', $name, $age) {
    10. say "Happy {$age}th birthday, $name";
    11. }
    12. }
    13. my $congrats = Congrats.new does BirthdayCongrats;
    14. $congrats.congratulate('升职', 'Cindy'); #-> 恭喜你升职,Cindy
    15. $congrats.congratulate('birthday', 'Bob'); #-> Happy birthday, Bob

    proto

    proto 从形式上声明了 multi 候选者之间的`共性`。 proto 充当作能检查但不会修改参数的包装器。看看这个基本的例子:

    1. proto congratulate(Str $reason, Str $name, |) {*}
    2. multi congratulate($reason, $name) {
    3. say "Hooray for your $reason, $name";
    4. }
    5. multi congratulate($reason, $name, Int $rank) {
    6. say "Hooray for your $reason, $name -- you got rank $rank!";
    7. }
    8. congratulate('being a cool number', 'Fred'); # OK
    9. congratulate('being a cool number', 'Fred', 42); # OK
    10. congratulate('being a cool number', 42); # Proto match error

    所有的 multi congratulate 都会遵守基本的签名, 这个签名中有两个字符串参数, 后面跟着可选的更多的参数。 | 是一个未命名的 Capture 形参, 它允许 multi 接收额外的参数。第三个 congratulate 调用在编译时失败, 因为第一行的 proto 的签名变成了所有三个 multi congratulate 的共同签名, 而 42 不匹配 Str

    1. say &congratulate.signature #-> (Str $reason, Str $name, | is raw)

    你可以给 一个函数体, 并且在你想执行 dispatch 的地方放上一个 {*}

    1. # attempts to notify someone -- returns False if unsuccessful
    2. proto notify(Str $user,Str $msg) {
    3. my \hour = DateTime.now.hour;
    4. if hour > 8 or hour < 22 {
    5. return {*};
    6. } else {
    7. # we can't notify someone when they might be sleeping
    8. return False;
    9. }
    10. }

    {*} 总是分派给带有参数的候选者。默认参数和类型强制转换会起作用单不会传递。

    1. proto mistake-proto(Str() $str, Int $number = 42) {*}
    2. multi mistake-proto($str,$number) { say $str.WHAT }
    3. mistake-proto(7,42); #-> (Int) -- coercions not passed on
    4. mistake-proto('test'); #!> fails -- defaults not passed on

    虽然上面描述的调度系统提供了很多灵活性,但是存在一些大多数内部函数以及许多模块中的函数将遵循的约定。 这些将产生一致的外观和感觉。

    吞噬约定

    也许最重要的是处理 slurpy 列表参数的方式。 大多数时候,函数不会自动展平吞噬(slurpy)列表。 罕见的例外是在列表的列表上没有合理行为的那些函数(例如chrs),或者与已建立的习语有冲突的函数,例如 是 push 的逆操作。

    如果你想匹配这个外观和感觉,任何可迭代(Iterable)参数必须使用 **@slurpy 逐个元素地打开,有两个细微差别:

    • 在顶层使用 , 创建的列表只能计数为一个 Iterable。

    这可以通过使用带有 ++@ 而不是 `**`的 slurpy 来实现:

    1. sub grab(+@a) { "grab $_".say for @a }

    这非常接近于:

    1. multi sub grab(**@a) { "grab $_".say for @a }
    2. multi sub grab(\a) {
    3. a ~~ Iterable and a.VAR !~~ Scalar ?? nextwith(|a) !! nextwith(a,)
    4. }

    这导致以下行为,称为「单参数规则」,并且理解什么时间调用 slurpy 函数很重要:

    1. grab(1, 2); # grab 1 grab 2
    2. grab((1, 2)); # grab 1 grab 2
    3. grab($(1, 2)); # grab 1 2
    4. grab((1, 2), 3); # grab 1 2 grab 3

    这也使得用户请求的展平感觉一致,无论有没有子列表,或很多

    1. grab(flat (1, 2), (3, 4)); # grab 1 grab 2 grab 3 grab 4
    2. grab(flat $(1, 2), $(3, 4)); # grab 1 2 grab 3 4
    3. grab(flat (1, 2)); # grab 1 grab 2
    4. grab(flat $(1, 2)); # grab 1 2

    值得注意的是,在这些情况下将绑定和无符号变量混合在一起需要一点技巧,因为在绑定期间没有使用 Scalar 中间人。

    1. my $a = (1, 2); # Normal assignment, equivalent to $(1, 2)
    2. grab($a); # grab 1 2
    3. my $b := (1, 2); # Binding, $b links directly to a bare (1, 2)
    4. grab($b); # grab 1 grab 2
    5. my \c = (1, 2); # Sigilless variables always bind, even with '='
    6. grab(c); # grab 1 grab 2

    函数和其他代码对象可以作为值传递,就像任何其他对象一样。

    有几种方法来获取代码对象。 您可以在声明点将其赋值给变量:

    1. my $square = sub (Numeric $x) { $x * $x }
    2. # and then use it:
    3. say $square(6); # 36

    或者,您可以通过使用它前面的 & 来引用现有的具名函数。

    1. sub square($x) { $x * $x };
    2. # get hold of a reference to the function:
    3. my $func = &square

    这对于高阶函数非常有用,即,将其他函数作为输入的函数。 一个简单高阶函数的是 map,它对每个输入元素应用一个函数:

    1. sub square($x) { $x * $x };
    2. my @squared = map &square, 1..5;
    3. say join ', ', @squared; # 1, 4, 9, 16, 25

    中缀形式

    要像中缀运算符那样调用具有2个参数的子例程,请使用由 [] 包围的子例程引用。

    1. sub plus { $^a + $^b };
    2. say 21 [&plus] 21;
    3. # OUTPUT«42
    4. »

    闭包

    Raku 中的所有代码对象都是闭包,这意味着它们可以从外部作用域引用词法变量。

    1. sub generate-sub($x) {
    2. my $y = 2 * $x;
    3. return sub { say $y };
    4. # ^^^^^^^^^^^^^^ inner sub, uses $y
    5. }
    6. my $generated = generate-sub(21);
    7. $generated(); # 42

    这里 $ygenerate-sub 中的词法变量,并且返回的内部子例程使用了 $y。 到内部 sub 被调用时,generate-sub 已经退出。 然而内部 sub 仍然可以使用 $y,因为它关闭了变量。

    一个不太明显但有用的闭包示例是使用 乘以数字列表:

    1. my $multiply-by = 5;
    2. say join ', ', map { $_ * $multiply-by }, 1..5; # 5, 10, 15, 20, 25

    这里传递给 map 的块从外部作用域引用变量 $multiply-by,使块成为闭包。

    没有闭包的语言不能轻易地提供高阶函数,它们像 map 一样易于使用和强大。

    例程是遵守 类型的代码对象,最明显的是 Sub,,正则表达式和。

    他们携带除了提供的额外的功能:他们可以作为 ,你可以包装它们,并使用 return 提前退出:

    1. my $keywords = set <if for unless while>;
    2. sub has-keyword(*@words) {
    3. for @words -> $word {
    4. return True if $word (elem) $keywords;
    5. }
    6. False;
    7. }
    8. say has-keyword 'not', 'one', 'here'; # False
    9. say has-keyword 'but', 'here', 'for'; # True

    这里 return 不仅仅是将离开它所调用的块的内部,而是离开整个程序。 一般来说,块对于 return 是透明的,它们附加到外部程序。

    例程(Routines)可以是内联的,并且因此为包装设置了障碍。 使用指令 use soft; 以防止内联在运行时允许包装。

    1. sub testee(Int $i, Str $s){
    2. rand.Rat * $i ~ $s;
    3. }
    4. sub wrap-to-debug(&c){
    5. say "wrapping {&c.name} with arguments {&c.signature.perl}";
    6. &c.wrap: sub (|args){
    7. note "calling {&c.name} with {args.gist}";
    8. my \ret-val := callwith(|args);
    9. note "returned from {&c.name} with return value {ret-val.perl}";
    10. ret-val
    11. }
    12. }
    13. my $testee-handler = wrap-to-debug(&testee);
    14. # OUTPUT«wrapping testee with arguments :(Int $i, Str $s)»
    15. say testee(10, "ten");
    16. # OUTPUT«calling testee with \(10, "ten")
    17. returned from testee with return value "6.151190ten"
    18. 6.151190ten»
    19. &testee.unwrap($testee-handler);
    20. say testee(10, "ten");
    21. # OUTPUT«6.151190ten
    22. »

    定义操作符

    操作符只是有趣名字的子例程。 有趣的名称由类别名称(中缀,前缀,后缀,环缀,后环缀)组成,后面跟着冒号,以及一个或多个操作符名称的列表(在环缀和后环缀的情况下为两个组件)。

    这既适用于向现有运算符添加多个候选项,也适用于定义新的运算符。 在后一种情况下,新子例程的定义自动将新运算符安装到 语法(grammar)中,但仅在当前词法作用域中。 通过 useimport 导入操作符也使其可用。

    1. # adding a multi candidate to an existing operator:
    2. multi infix:<+>(Int $x, "same") { 2 * $x };
    3. say 21 + "same"; # 42
    4. # 定义一个新的操作符
    5. sub postfix:<!>(Int $x where { $x >= 0 }) { [*] 1..$x };
    6. say 6!; # 720

    运算符声明变得尽快可用,因此您甚至可以递归到刚才定义的运算符中,如果您真的想要:

    1. sub postfix:<!>(Int $x where { $x >= 0 }) {
    2. $x == 0 ?? 1 !! $x * ($x - 1)!
    3. }
    4. say 6!; # 720

    环缀和后环缀操作符由两个分隔符组成,一个开口和一个闭合。

    后环缀也接收这个术语,在它们被作为参数解析之后:

    1. sub postcircumfix:<!! !!>($left, $inside) {
    2. "$left -> ( $inside )"
    3. }
    4. say 42!! 1 !!; # 42 -> ( 1 )

    块可以直接赋值给操作符名。 使用变量声明符,并在操作符名前加上一个 & 符号。

    1. my &infix:<ieq> = -> |l { [eq] l>>.fc };
    2. say "abc" ieq "Abc";
    3. # OUTPUT«True
    4. »

    优先级

    Raku 中的运算符优先级相对于现有运算符指定。 is tighteris equivis looser 特性能使用一个运算符提供,新的运算符优先级与之相关。 可以应用更多的特征。

    例如,infix:<*> 的优先级高于 infix:<+>,并且在中间挤压一个像这样:

    1. sub infix:<!!>($a, $b) is tighter(&infix:<+>) {
    2. }
    3. say 1 + 2 * 3 !! 4; # 21

    这里 1 + 2 * 3 !! 4 被解析为 1 + ((2 * 3) !! 4),因为新的 !! 运算符的优先级在 +* 之间。

    可以使用下面的代码实现相同的效果:

    1. sub infix:<!!>($a,$b) is looser(&infix:<x>) { ... }

    要将新运算符置于与现有运算符相同的优先级别上,请使用 is equiv(&other-operator)

    结合性

    当同一个操作符在一行中连续出现多次时,有多种可能的解释。 例如

    1. 1 + 2 + 3

    能被解析为

    1. (1 + 2) + 3 # 左结合性

    或者解析为

    1. 1 + (2 + 3) # 右结合性

    对于实数的加法,区别有点模糊,因为 +数学上相关的

    但对其他运算符来说它很重要。 例如对于指数/幂运算符,infix:<**>

    1. say 2 ** (2 ** 3); # 256

    Raku 拥有以下可能的结合性配置:

    您可以使用 is assoc trait 指定运算符的结合性,其中 left 是默认的结合性。

    1. sub infix:<§>(*@a) is assoc<list> {
    2. '(' ~ @a.join('|') ~ ')';
    3. }
    4. say 1 § 2 § 3; # (1|2|3)

    Traits

    特性(traits)是在编译时运行以修改类型,变量,例程,属性或其他语言对象的行为的子例程。

    traits 的例子有:

    1. class ChildClass is ParentClass { ... }
    2. # ^^ trait, with argument ParentClass
    3. has $.attrib is rw;
    4. # ^^^^^ trait with name 'rw'
    5. class SomeClass does AnotherRole { ... }
    6. # ^^^^ trait
    7. has $!another-attribute handles <close>;
    8. # ^^^^^^^ trait

    还有之前章节中的 is tighteris looseris equivis assoc 等。

    Traits 是 trait_mod<VERB> 形式的 subs, 其中 VERB 代表像 isdoeshandles 那样的名字。它接受修改后的东西作为参数, 还有名字作为具名参数。

    1. multi sub trait_mod:<is>(Routine $r, :$doubles!) {
    2. $r.wrap({
    3. 2 * callsame;
    4. });
    5. }
    6. sub square($x) is doubles {
    7. $x * $x;
    8. }
    9. say square 3; # 18

    请参阅内置常规性状文档的类型例程

    重新分派

    在某些情况下,例程可能想从链中调用下一个方法。 这个链可以是类层次结构中的父类的列表,或者它可以是来自多分派的较不具体的 multi 候选者,或者它可以是来自`wrap`的内部例程。

    在所有这些情况下,您可以使用 callwith 通过您自己选择的参数调用链中的下一个例程。

    1. multi a(Any $x) {
    2. say "Any $x";
    3. return 5;
    4. }
    5. multi a(Int $x) {
    6. say "Int $x";
    7. my $res = callwith($x + 1);
    8. say "Back in Int with $res";
    9. }
    10. a 1;
    11. # OUTPUT:
    12. # Int 1
    13. # Any 2
    14. # Back in Int with 5

    这里,a 1 首先调用最具体的 Int 候选者,并且 callwith 重新调度到较不具体的 Any 候选者。

    通常,重新分派传递和调用者接收到的相同的参数,因此有一个特殊的例程:callsame

    1. multi a(Any $x) {
    2. say "Any $x";
    3. return 5;
    4. }
    5. multi a(Int $x) {
    6. say "Int $x";
    7. my $res = callsame;
    8. say "Back in Int with $res";
    9. }
    10. a 1; # Int 1\n Any 1\n Back in Int with 5

    另一个常见的用例是重新分派到链中的下一个例程,之后不执行任何其他操作。 这就是为什么我们有 nextwithnextsame,它使用任意的参数调用下一个例程(nextwith)或与调用者接收(nextsame)相同的参数,但不会返回给调用者。 或者对其进行不同的措辞,nextsamenextwith 变体用下一个候选项替换当前的调用帧(callframe)。

    1. multi a(Any $x) {
    2. say "Any $x";
    3. return 5;
    4. }
    5. multi a(Int $x) {
    6. say "Int $x";
    7. nextsame;
    8. say "back in a"; # never executed, because 'nextsame' doesn't return
    9. }
    10. a 1; # Int 1\n Any 1

    如前所述,multi sub 不是唯一能在 call,call me,nextwith 和 next 中有帮助的情况。 下面是是调度到包装的例程:

    1. # enable wrapping:
    2. use soft;
    3. # function to be wrapped:
    4. sub square-root($x) { $x.sqrt }
    5. &square-root.wrap(sub ($num) {
    6. nextsame if $num >= 0;
    7. 1i * callwith(abs($num));
    8. });
    9. say square-root(4); # 2
    10. say square-root(-4); # 0+2i

    最后一个用例是从父类中重分派给方法。

    1. class LoggedVersion is Version {
    2. method new(|c) {
    3. note "New version object created with arguments " ~ c.perl;
    4. nextsame;
    5. }
    6. }
    7. say LoggedVersion.new('1.0.2');

    如果你需要对被包装的代码进行多次调用或获得一个引用,例如内省它,你可以使用 nextcallee

    1. sub power-it($x) { $x * $x }
    2. sub run-it-again-and-again($x) {
    3. my &again = nextcallee;
    4. again again $x;
    5. }
    6. &power-it.wrap(&run-it-again-and-again);
    7. say power-it(5); # 625

    强制类型可以帮助您在例程中拥有特定类型,但接受更宽的输入。 当调用例程时,参数将自动转换为较窄的类型。

    1. sub double(Int(Cool) $x) {
    2. 2 * $x
    3. }
    4. say double '21'; # 42
    5. say double Any; # Type check failed in binding $x; expected 'Cool' but got 'Any'

    这里的 Int 是参数将被强制的目标类型,而 Cool 是例程接受的作为输入的类型。

    如果接受的输入类型为 Any,则可以将 Int(Any) 缩写为 Int()

    强制只需查找与目标类型具有相同名称的方法即可。 所以你可以为你自己的类型定义强制,像这样:

    1. class Bar {...}
    2. class Foo {
    3. has $.msg = "I'm a foo!";
    4. method Bar {
    5. Bar.new(:msg($.msg ~ ' But I am now Bar.'));
    6. }
    7. }
    8. class Bar {
    9. has $.msg;
    10. }
    11. sub print-bar(Bar() $bar) {
    12. say $bar.WHAT; # (Bar)
    13. say $bar.msg; # I'm a foo! But I am now Bar.
    14. }
    15. print-bar Foo.new;

    强制类型应该在类型工作的任何地方工作,但 Rakudo 当前(2015.02)仅针对子例程参数实现了它们。

    具有特殊名称 MAIN 的 sub 在所有相关 parsers 之后执行,并且其签名是可以解析命令行参数的装置。 支持 multi 方法,如果未提供命令行参数,则会自动生成并显示使用方法。 所有命令行参数在 @*ARGS 中也可用,它可以在被 MAIN 处理之前进行变换。

    MAIN 的返回值被忽略。 要提供除 0 以外的退出代码,请调用 。

    1. sub MAIN( Int :$length = 24,
    2. :file($data) where { .IO.f // die "file not found in $*CWD" } = 'file.dat',
    3. Bool :$verbose )
    4. {
    5. say $length if $length.defined;
    6. say $data if $data.defined;
    7. say 'Verbosity ', ($verbose ?? 'on' !! 'off');
    8. exit 1;
    9. }

    如果对于给定的命令行参数没有找到 MAIN 的多个候选者,则调用 sub 。 如果没有找到此类方法,则输出生成的使用消息。