Perl 6 中的函数

作者: 焉知非鱼 | 来源:发表于2016-10-18 01:17 被阅读31次

    Functions and Functional Programming in Perl 6

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

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

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

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

    定义/创建/使用 函数

    子例程

    要创建一个函数,通常所需要的是使用 sub 声明符定义一个子例程:

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

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

    sub exclaim ($phrase) {
        say $phrase ~ "!!!!"
    }
    exclaim "Howdy, World";
    

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

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

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

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

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

    Blocks 和 Lambdas

    每当你看到像

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

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

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

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

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

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

    签名

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

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

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

    自动签名

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

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

    参数

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

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

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

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

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

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

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

    返回值

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

    sub a { 42 };
    sub b { say a };
    b;
    # OUTPUT«42»
    

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

    sub a { 42, 'answer' };
    put a.perl;
    # OUTPUT«(42, "answer")»
    
    my ($n, $s) = a;
    put [$s, $n];
    # OUTPUT«answer 42»
    
    sub b { <a b c>.Capture };
    put b.perl;
    # OUTPUT«\("a", "b", "c")»
    

    返回类型约束

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

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

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

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

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

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

    多重分派

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

    multi congratulate($name) {
        say "祝你生日快乐, $name";
    }
    
    multi congratulate($name, $age) {
        say "祝 $age 岁生日快乐, $name";
    }
    
    congratulate 'Camelia'; # 祝你生日快乐, Camelia
    congratulate 'Rakudo', 15; # 祝你 15 岁生日快乐, Rakudo 
    

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

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

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

    class Congrats {
        multi method congratulate($reason, $name) {
            say "Hooray for your $reason, $name";
        }
    }
    
    role BirthdayCongrats {
        multi method congratulate('birthday', $name) {
            say "Happy birthday, $name";
        }
        multi method congratulate('birthday', $name, $age) {
            say "Happy {$age}th birthday, $name";
        }
    }
    
    my $congrats = Congrats.new does BirthdayCongrats;
    
    $congrats.congratulate('升职', 'Cindy');   #-> 恭喜你升职,Cindy
    $congrats.congratulate('birthday', 'Bob'); #-> Happy birthday, Bob 
    

    proto

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

    proto congratulate(Str $reason, Str $name, |) {*}
    multi congratulate($reason, $name) {
       say "Hooray for your $reason, $name";
    }
    multi congratulate($reason, $name, Int $rank) {
       say "Hooray for your $reason, $name -- you got rank $rank!";
    }
    
    congratulate('being a cool number', 'Fred');     # OK
    congratulate('being a cool number', 'Fred', 42); # OK
    congratulate('being a cool number', 42);         # Proto match error
    

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

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

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

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

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

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

    约定和惯用法

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

    吞噬约定

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

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

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

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

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

    这非常接近于:

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

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

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

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

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

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

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

    函数是一等对象

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

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

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

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

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

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

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

    中缀形式

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

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

    闭包

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

    sub generate-sub($x) {
        my $y = 2 * $x;
        return sub { say $y };
        #      ^^^^^^^^^^^^^^  inner sub, uses $y
    }
    my $generated = generate-sub(21);
    $generated(); # 42
    

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

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

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

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

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

    Routines

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

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

    my $keywords = set <if for unless while>;
    
    sub has-keyword(*@words) {
        for @words -> $word {
            return True if $word (elem) $keywords;
        }
        False;
    }
    
    say has-keyword 'not', 'one', 'here';       # False
    say has-keyword 'but', 'here', 'for';       # True
    

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

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

    sub testee(Int $i, Str $s){
        rand.Rat * $i ~ $s;
    }
    
    sub wrap-to-debug(&c){
        say "wrapping {&c.name} with arguments {&c.signature.perl}";
        &c.wrap: sub (|args){
            note "calling {&c.name} with {args.gist}";
            my \ret-val := callwith(|args);
            note "returned from {&c.name} with return value {ret-val.perl}";
            ret-val
        }
    }
    
    my $testee-handler = wrap-to-debug(&testee);
    # OUTPUT«wrapping testee with arguments :(Int $i, Str $s)»
    
    say testee(10, "ten");
    # OUTPUT«calling testee with \(10, "ten")
    returned from testee with return value "6.151190ten"
    6.151190ten»
    &testee.unwrap($testee-handler);
    say testee(10, "ten");
    # OUTPUT«6.151190ten»
    

    定义操作符

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

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

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

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

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

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

    sub circumfix:<START END>(*@elems) {
        "start", @elems, "end"
    }
    
    say START 'a', 'b', 'c' END;        # start a b c end
    

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

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

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

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

    优先级

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

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

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

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

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

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

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

    结合性

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

    1 + 2 + 3
    

    能被解析为

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

    或者解析为

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

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

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

    say 2 ** (2 ** 3);      # 256
    say (2 ** 2) ** 3;      # 64
    

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

    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 是默认的结合性。

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

    Traits

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

    traits 的例子有:

    class ChildClass is ParentClass { ... }
    #                ^^ trait, with argument ParentClass
    has $.attrib is rw;
    #            ^^^^^  trait with name 'rw'
    class SomeClass does AnotherRole { ... }
    #               ^^^^ trait
    has $!another-attribute handles <close>;
    #                       ^^^^^^^ trait
    

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

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

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

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

    重新分派

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

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

    multi a(Any $x) {
        say "Any $x";
        return 5;
    }
    multi a(Int $x) {
        say "Int $x";
        my $res = callwith($x + 1);
        say "Back in Int with $res";
    }
    
    a 1;
    # OUTPUT:
    # Int 1
    # Any 2
    # Back in Int with 5
    

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

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

    multi a(Any $x) {
        say "Any $x";
        return 5;
    }
    multi a(Int $x) {
        say "Int $x";
        my $res = callsame;
        say "Back in Int with $res";
    }
    
    a 1;        # Int 1\n Any 1\n Back in Int with 5
    

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

    multi a(Any $x) {
        say "Any $x";
        return 5;
    }
    multi a(Int $x) {
        say "Int $x";
        nextsame;
        say "back in a";    # never executed, because 'nextsame' doesn't return
    }
    
    a 1;        # Int 1\n Any 1
    

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

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

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

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

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

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

    强制类型

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

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

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

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

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

    class Bar {...}
    
    class Foo {
       has $.msg = "I'm a foo!";
    
       method Bar {
           Bar.new(:msg($.msg ~ ' But I am now Bar.'));
       }
    }
    
    class Bar {
       has $.msg;
    }
    
    sub print-bar(Bar() $bar) {
       say $bar.WHAT; # (Bar)
       say $bar.msg;  # I'm a foo! But I am now Bar.
    }
    
    print-bar Foo.new;
    

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

    sub MAIN

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

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

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

    sub USAGE

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

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

    相关文章

      网友评论

        本文标题:Perl 6 中的函数

        本文链接:https://www.haomeiwen.com/subject/mwbzyttx.html