Raku 中的函数

例程(Routines)是 Raku 中代码重用的最小手段。它们有几种形式,最明显的是属于类和角色并与对象相关联的方法,还有函数, 也叫做子例程或短子程序,它们独立于对象而存在。

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

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

子例程的内省通过例程提供。

定义/创建/使用 函数

子例程

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

  1. sub my-func { say "Look ma, no args!" }
  2. my-func;

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 容器中。 关于这些容器的语法和使用的细节可以在 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.

返回值

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

  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) 可以发生在参数的数量(元数)上, 但是也能发生在类型上:

  1. multi as-json(Bool $d) { $d ?? 'true' !! 'false' }
  2. multi as-json(Real $d) { ~$d }
  3. multi as-json(@d) { sprintf '[%s]', @d.map(&as-json).join(', ') }
  4. say as-json([True, 42]); # [true, 42]

不带任何指定例程类型的 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. }
  10. multi method congratulate('birthday', $name, $age) {
  11. say "Happy {$age}th birthday, $name";
  12. }
  13. }
  14. my $congrats = Congrats.new does BirthdayCongrats;
  15. $congrats.congratulate('升职', 'Cindy'); #-> 恭喜你升职,Cindy
  16. $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)

你可以给 proto 一个函数体, 并且在你想执行 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),或者与已建立的习语有冲突的函数,例如 poppush 的逆操作。

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

  • Scalar 容器内的 Iterable 不计数。

  • 在顶层使用 , 创建的列表只能计数为一个 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,因为它关闭了变量。

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

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

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

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

Routines

例程是遵守 Routine 类型的代码对象,最明显的是 Sub方法正则表达式Submethod

他们携带除了提供的额外的功能:他们可以作为 multis,你可以包装它们,并使用 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 circumfix:<START END>(*@elems) {
  2. "start", @elems, "end"
  3. }
  4. say START 'a', 'b', 'c' END; # start a b c end

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

  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. 2 * ($a + $b)
  3. }
  4. 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
  2. say (2 ** 2) ** 3; # 64

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

A

Assoc

Meaning of $a ! $b ! $c

L

left

($a ! $b) ! $c

R

right

$a ! ($b ! $c)

N

non

ILLEGAL

C

chain

($a ! $b) and ($b ! $c)

X

list

infix:<!>($a; $b; $c)

您可以使用 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)仅针对子例程参数实现了它们。

sub MAIN

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

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

  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. }

sub USAGE

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

  1. sub MAIN(Int $i){ say $i == 42 ?? 'answer' !! 'dunno' }
  2. sub USAGE(){
  3. print Q:c:to/EOH/;
  4. Usage: {$*PROGRAM-NAME} [number]
  5. Prints the answer or 'dunno'.
  6. EOH
  7. }