Perl 6 中的星号

Posted YoungForPerl6

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Perl 6 中的星号相关的知识,希望对你有一定的参考价值。

在今年的 Perl 6 Advent Calendar 中, 雪花被今天的博客文章承包了。 我们将检阅使用了 * 字符的结构。 在 Perl 6 中,根据上下文的不同,您可以叫它星星(或者,如果你愿意的话,可以叫它星号)或者 whatever

Perl 6 不是一个隐秘的编程语言, 在许多方面它的语法比 Perl 5 更加一致。另一方面,有些地方需要花时间来开启对语法的信心。

让我们看看 * 的不同用法,从最简单的开始,旨在了解最烧脑的例如 * ** *

前两种用法很简单,不需要太多的讨论:

1. 乘法

单个星号用于乘法。严格来讲, 这是一个中缀操作符 infix:<*>, 它的返回值为 Numeric

say 20 * 18; # 360 

2. 幂

两个星号 ** 是幂操作符。再次, 这是一个中缀操作符 infix:<**>, 它返回 Numeric 结果, 计算两个给定值点幂。

say pi ** e; # 22.4591577183611 


正则表达式中同样也使用了两个标记(*  **),它们表示不同的东西。 Perl 6 的一个特点是它可以很容易地在不同的语言之间切换。 正则表达式和 grammar 都是这样的内部语言的例子,其中同样的符号在 Perl 6 中可能意味着不同的含义。

3. 零或多次重复

* 号量词这个语法条目和 Perl 5 中点行为类似: 允许原子的零次或多次重复。

my $weather = '*****';

my $snow = $weather ~~ / ('*'*) /;

say 'Snow level is ' ~ $snow.chars; # Snow level is 5 

当然, 我们还在这儿看到了同一个字符的另一种用法, * 字面量。

4. Min 到 Max 次重复

两个 ** 号是另一个量词的一部分,它指定了最小和最大重复次数:

my $operator = '..';

say "'$operator' is a valid Perl 6 operator"

    if $operator ~~ /^ '.' ** 1..3 $/;

在这个例子中,预计这个点会被重复一次,两次或三次; 不多也不少。

让我们超前一点儿,以 Whatever 符号的角色(剧场中的角色,而不是 Perl 6 的面向对象编程)使用星号:

my $phrase = 'I love you......';

say 'You are so uncertain...'

    if $phrase ~~ / '.' ** 4..* /;

范围的第二个端点是打开的,这个正则表达式接受所有其中包含四个点以上的短语。

5. 吞噬参数

在子例程签名的数组参数之前的星号意味着吞噬参数 - 将单独的标量参数吞噬进单个数组中。

list-gifts('chocolade''ipad''camelia''perl6');

 

sub list-gifts(*@items) {

    say 'Look at my gifts this year:';

    .say for @items;

}

哈希也允许吞噬参数:

dump(alpha => 'a'beta => 'b'); # Prints: 

                                 # alpha = a 

                                 # beta = b 

 

sub dump(*%data) {

    for %data.kv {say "$^a = $^b"}

}

请注意,与 Perl 5 不同的是,如果您省略函数签名中的星号,代码将无法编译,因为 Perl 6 就是说一不二:

Too few positionals passed; expected 1 argument but got 0

6. 吨吨吨吨吨吨吨

**@ 也能工作,但是当你传递数组或列表的时候请注意其中的区别。

带一颗星星:

my @a = < chocolade ipad >;

my @b = < camelia perl6 >;

 

all-together(@a@b);

all-together(['chocolade''ipad'], ['camelia''perl6']);

all-together(< chocolade ipad >, < camelia perl6 >);

 

sub all-together(*@items) {

    .say for @items;

}

目前,无论参数列表传递的方式如何,每个礼物都被单独打印了出来。

chocolade

ipad

camelia

perl6

chocolade

ipad

camelia

perl6

chocolade

ipad

camelia

perl6

带俩颗星星:

keep-groupped(@a@b);

keep-groupped(['chocolade''ipad'], ['camelia''perl6']);

keep-groupped(< chocolade ipad >, < camelia perl6 >);

 

sub keep-groupped(**@items) {

    .say for @items;

}

这一次,@items 数组只有两个元素,反映了参数的结构类型:

[chocolade ipad]

[camelia perl6]

(chocolade ipad)

(camelia perl6)

7. 动态作用域

* twigil,引入了动态作用域。 动态变量和全局变量很容易搞混淆,所以最好测试下面的代码。

sub happy-new-year() {

    "Happy new $*year year!"

}

 

my $*year = 2018;

say happy-new-year(); # 输出 Happy new 2018 year! 

如果你省略了星号, 那么代码就运行不了:

Variable '$year' is not declared

更正它的唯一方法是将 $year 的定义移到函数定义的上面。 使用动态变量 $*year,函数被调用的地方定义了结果。 $*year 变量在子例程的外部作用域中是不可见的,但是在动态作用域内是可见的。

对于动态变量,将新值赋给现有变量还是创建新变量并不重要:

sub happy-new-year() {

    "Happy new $*year year!"

}

 

my $*year = 2018;

say happy-new-year();

 

{

    $*year = 2019;        # New value 

    say happy-new-year(); # 2019 

}

 

{

    my $*year = 2020;     # New variable 

    say happy-new-year(); # 2020 

}

8. 编译变量

Perl 6 提供了许多伪动态常量, 例如:

say $*PERL;      # Perl 6 (6.c) 

say @*ARGS;      # Prints command-line arguments 

say %*ENV<HOME>; # Prints home directory 

9. All methods

.* postfix 伪操作符调用给定名称的所有方法,名称可以在给定的对象中找到,并返回一个结果列表。 在微不足道的情况下,你会得到一个学术上荒诞不羁的代码:

6.*perl.*say# (6 Int.new) 

带星号的代码与不带星号代码有些不同:

pi.perl.say# 3.14159265358979e0 (notice the scientific 

             # format, unlike pi.say) 

.* postfix 的真正威力来自于继承。 它有时有助于揭示真相:

class Present {

    method giver() {

        'parents'

    }

}

 

class ChristmasPresent is Present {

    method giver() {

        'Santa Claus'

    }

}

 

my ChristmasPresent $present;

 

$present.giver.say;             # Santa Claus 

$present.*giver.join('').say# Santa Claus, parents 

一个星号就差别很大!


现在,到了 Perl 6 最神秘的部分。接下来的两个概念,Whatever  WhateverCode 类,很容易混淆在一起。 让我们试着做对吧。

10. Whatever

单个星号 * 能表示任何东西(Whatever)。 Whatever 在 Perl 6 中是一个预定义好的类, 它在某些有用的场景下引入了一些规定好的行为。

例如,在范围和序列中,最后的 * 表示无穷大。 我们今天已经看到了一个例子。 这是另一个:

.say for 1 .. *;

这个单行程序具有非常高的能量转换效率,因为它产生了一个递增整数的无限列表。 如果你要继续,请按 Ctrl + C

范围 1 .. *  1 .. Inf 相同。 您可以清楚地看到,如果您跳转到 Rakudo Perl 6 源文件并在 src/core/Range.pm 文件的 Range 类的实现中找到如下定义:

multi method new(Whatever \min,Whatever \max,:$excludes-min,:$excludes-max){

    nqp::create(self)!SET-SELF(-Inf,Inf,$excludes-min,$excludes-max,1);

}

multi method new(Whatever \min, \max, :$excludes-min, :$excludes-max) {

    nqp::create(self)!SET-SELF(-Inf,max,$excludes-min,$excludes-max,1);

}

multi method new(\min, Whatever \max, :$excludes-min, :$excludes-max) {

    nqp::create(self)!SET-SELF(min,Inf,$excludes-min,$excludes-max,1);

}

这三个 multi 构造函数描述了三种情况:* .. ** .. $n  $n .. *,它们被立即转换为 -Inf .. Inf-Inf .. $n  $n .. Inf

作为一个圣诞故事,这里有一个小小的插曲,表明 * 不仅仅是一个 Inf。 有两个到 src/core/Whatever.pm 的提交:

首先,2015年9月16日,"MakeWhatever.new == Inf True:"

  my class Whatever {

      multi method ACCEPTS(Whatever:D: $topic) { True }

      multi method perl(Whatever:D:) { '*' }

+     multi method Numeric(Whatever:D:) { Inf }

  }

几周之后, 在2015年10月23日,"* no longer defaults to Inf",这是为了保护其他 dwimmy 情况下的扩展性:

  my class Whatever {

      multi method ACCEPTS(Whatever:D: $topic) { True }

      multi method perl(Whatever:D:) { '*' }

-     multi method Numeric(Whatever:D:) { Inf }

  }

回到我们更实际的问题,让我们创建自己的使用 whatever 符号 * 的类,。 下面是一个简单的例子,它带有一个接收 Int 值或者 Whatever 的 multi-方法。

class N {

    multi method display(Int $n) {

        say $n;

    }

 

    multi method display(Whatever) {

        say 2000 + 100.rand.Int;

    }

}

在第一种情况下,该方法只是打印该值。 第二种方法是打印一个在 2000 到 2100 之间的随机数。 因为第二种方法的唯一参数是 Whatever,所以签名中不需要变量。

下面是你如何使用这个类:

my $n = N.new;

$n.display(2018);

$n.display(*);

第一个调用回显它的参数,而第二个调用打印某些随机的东西。

Whatever 符号可以作为一个裸的 Whatever。 假如,你创建一个 echo 函数,并将 * 传递给它:

sub echo($x) {

    say $x;

}

 

echo(2018); # 2018 

echo(*);    # * 

这一次,没有魔术发生,该程序打印一个星号。

现在我们正处在一个四两拨千斤的节骨眼上。

11. WhateverCode

最后, 我们来谈谈 WhateverCode

取一个数组然后打印出它的最后一个元素。如果你使用 Perl 5 的风格来做, 你会键入 @a[-1] 那样的东西。在 Perl 6 中, 那会产生错误:

Unsupported use of a negative -1 subscript

to index from the end; in Perl 6 please

use a function such as *-1

编译器建议使用一个函数, 例如 *-1。它是函数吗?是的, 更准确的说, 它是一个 WhateverCode 块:

say (*-1).WHAT; # (WhateverCode) 

现在, 打印数组的后半部分:

my @a = < one two three four five six >;

say @a[3..*]; # (four five six) 

数组的索引的范围是 3 .. * Whatever 作为 range 的右端意味着从数组中取出所有剩余的元素。 3 .. * 的类型是 Range:

say (3..*).WHAT; # (Range) 

最后,减少一个元素。 我们已经看到,要指定最后一个元素,必须要使用诸如 *-1 的函数。 在 range 的右端可以做同样的事情:

say @a[3 .. *-2]; # (four five) 

在这个时候,发生了所谓的 Whatever-柯里化Range 变成了 WhateverCode:

say (3 .. *-2).WHAT; # (WhateverCode) 

WhateverCode 是一个内置的 Perl 6 类名称; 它可以很容易地用于方法分派。 让我们更新上一节中的代码,并添加一个方法变体,它需要一个 WhateverCode 参数:

class N {

    multi method display(Int $n) {

        say $n;

    }

 

    multi method display(Whatever) {

        say 2000 + 100.rand.Int;

    }

 

    multi method display(WhateverCode $code) {

        say $code(2000 + 100.rand.Int);

    }

}

现在,参数列表中的星号要么落入 display(Whatever), 要么落入 display(WhateverCode):

N.display(2018);     # display(Int $n) 

 

N.display(*);        # display(Whatever) 

 

N.display(* / 2);    # display(WhateverCode $code) 

N.display(* - 1000); # display(WhateverCode $code) 

我们再来看看 display 方法中的签名:

multi method display(WhateverCode $code)

$code 参数被用作方法内的函数引用:

say $code(2000 + 100.rand.Int);

该函数需要一个参数,但它会去哪里? 或者换句话说,函数体是什么,在哪里? 我们将该方法调用为 N.display(* / 2)  N.display(* - 1000)。 答案是 * / 2  * - 1000 都是函数! 还记得编译器关于使用诸如 *-1 之类的函数的提示吗?

这里的星号成为第一个函数参数,因此 * / 2 相当于 {$^a / 2},而 *-1000 相当于 {$^a - 1000}

这是否意味着可以在 $^a 的旁边使用 $^b? 当然! 使 WhateverCode 块接受两个参数。 你如何指出其中的第二个? 毫不惊喜,再用一个星号! 让我们将 display 方法的第四个变体添加到我们的类中:

multi method display(WhateverCode $code

                     where {$code.arity == 2}) {

    say $code(2000, 100.rand.Int);

}

这里,使用 where 块来缩小调度范围,只选择那些有两个参数的 WhateverCode 块。 完成此操作后,方法调用中将允许含有两个雪花:

N.display( * + * );

N.display( * - * );

这些调用定义了用于计算结果的函数 $code。 所以,N.display(* + *) 背后的实际操作如下:2000 + 100.rand.Int

需要更多的雪花吗? 多添加点星星:

N.display( * * * );

N.display( * ** * );

类似地, 里面实际的计算是:

2000 * 100.rand.Int

2000 ** 100.rand.Int

恭喜! 你现在可以像编译器那样毫不费力地解析 * ** * 结构了。

作业

到目前为止,Perl 6 给了我们很多圣诞礼物。 让我们回过头来做一下练习并回答一下问题:下面代码中的每个星号在意味着什么?

my @n =

    ((0, 1, * + * ... *).grep: *.is-prime).map: * * * * *;

.say for @n[^5];

D'哦。 我建议我们从转换代码开始来摆脱所有的星号,并使用不同的语法。

序列运算符 ... 之后的 * 意味着无限地生成序列,所以用 Inf 来代替它:

((0, 1, * + * ... Inf).grep: *.is-prime).map: * * * * *

生成器函数中的两个星号 * + * 可以用一个带有两个显式参数的 lambda 函数来替换:

((0, 1, -> $x$y {$x + $y} ... Inf).grep:

    *.is-prime).map: * * * * *

现在,简单的语法交替。 用带圆括号的方法调用替换 .grep。 它的参数 *.is-prime 变成一个代码块,并且星号被替换为默认变量 $_。 请注意,代码使用 * 时不需要花括号。

(0, 1, -> $x$y {$x + $y} ... Inf).grep({

    $_.is-prime

}).map: * * * * *

最后,与 .map 相同的技巧:但是这次这个方法有三个参数,因此,你可以编写 {$^a * $^b * $^c} 而不是 * * * * *,这里是新的 完整程序的变体:

my @n = (0, 1, -> $x$y {$x + $y} ... Inf).grep({

        $_.is-prime

    }).map({

        $^a * $^b * $^c

    });

.say for @n[^5];

现在很明显,代码打印了三个斐波那契素数组积的前五个。

附加题

在教科书中,最具挑战性的任务是用 * 标记的。 这里有几个由你自己来解决。


    1. Perl 6 中的 chdir('/')  &*chdir('/') 有什么区别?


    1. 解释下面的 Perl 6 代码并修改它以展示其优点:.say for 1 ... **

❄❄❄

今天就这样了。 我希望你喜欢 Perl 6 的强大功能和表现力。今天,我们只谈到了一个 ASCII 字符。 想象一下,如果考虑到该语言在当今编程语言中提供了最好的 Unicode 支持,Perl 6 的 Universe 是多么的庞大。

今天享受 Perl 6,并传播这个词! 请继续关注 Perl 6 Advent Calendar; 更多的文章正在等待你的关注,明天就要来了。

Andrew Shitov

Day 11 — All the Stars of Perl 6, or **


以上是关于Perl 6 中的星号的主要内容,如果未能解决你的问题,请参考以下文章

星号发起掉线

如何有条件地将 C 代码片段编译到我的 Perl 模块?

perl中的队列

PERL 中的模式匹配以句点“.”结尾

Perl 6 Deep Dive

Xtype 中的红色星号:'label'