当前位置: 首页 > 文档资料 > Perl6 入门指导 >

第二章 元运算符

优质
小牛编辑
129浏览
2023-12-01

什么是元运算符

Perl6使用了六种元运算符(meta-operators)以扩充常规运算符的功能,其中部分运算符与Perl5或者其他语言中的相同,其余则是Perl6所特有的,它们分别是:

  • 赋值运算符(assignment operators)
  • 否定关系运算符(negated relation operators)
  • 反转运算符(reversed operators)
  • 超算符(hyper operators)
  • 归约运算符(reduction operators)
  • 交叉运算符(cross operators)

详情请见S03

译注:
事实上,现在的doc中介绍了不止六种元运算符,还有:

  • 替换运算符(substitution operators)
  • 拉链运算符(zip operators)
  • 时序运算符(sequential operators)

缺少的运算符将不会在本文中得以补充,欲知详情请见Meta Operators

赋值运算符

相信大家已经习惯于在其他语言中使用快捷的赋值运算符,它们能够在一次操作内同时完成运算与赋值的功能。

tutorial/arrays/assignment_shortcut.p6

#!/usr/bin/env perl6
use v6;

my $num = 23;
say $num + 19;     # 42
say $num;          # 23

$num += 19;
say $num;          # 42

在赋值时调用方法

Perl6中允许使用.运算符对对象调用相应的方法,考虑如下情况:
subst方法能够将字符串中的部分子串替换成其他内容,并将结果返回,但是它并不会修改原串本身。

如果你想在替换的同时直接作用于原串,可以这么写:$str = $str.subst('B', 'X');,当然也可以使用它的简化写法:

tutorial/arrays/assignment_operators.p6

#!/usr/bin/env perl6
use v6;

my $str = 'ABBA';
say $str.subst('B', 'X');    # AXBA
say $str;                    # ABBA

say $str .= subst('B', 'X'); # AXBA
say $str;                    # AXBA

在赋值时调用函数

同上一小节,在使用函数并赋值时可以写成$lower = min($lower, $new_value);,也可以简化成$lower min= $new_value;

tutorial/arrays/assignment_function_shortcuts.p6

#!/usr/bin/env perl6
use v6;

my $lower = 2;
$lower min= 3;
say $lower;         # 2

$lower min= 1;
say $lower;         # 1

这种写法应该同样适用于逗号运算符,这样就可以快速地将新元素加入到数组中,遗憾的是Rakudo尚且没有实现该功能。

my @a = (1, 2, 3);
@a ,= 4;
@a.say;

否定关系运算符

Perl6中,相等运算符==用于对数值进行比较,eq用于对字符串进行比较。否定关系运算符只需要在上述运算符的前面加上叹号(!)即可,即!==!eq

幸运的是这两个否定关系运算符都有相应的简化版本,分别写作!=ne,Perl5程序员对此应该并不感到惊讶。

其他运算符同样有否定的版本,也就是说你可以使用!>=来表示不大于,对于字符串则是!gt,然而我个人认为上述这两个没什么必要。

译注:
!>=的意义应该是小于而非不大于,对于字符串应该是!ge而非!gt,原文表述有误。

2 !>= 3; # True
2 !>= 2; # False
2 !>= 1; # False

"aaa" !ge "aab"; # True
"aaa" !ge "aaa"; # False
"aaa" !ge "aa"; # False

不过有一个好处就是如果你自己定义了一个运算符I,就等于同时自动拥有了另一个!I,也就是它的否定。

tutorial/arrays/negated_operators.p6

#!/usr/bin/env perl6
use v6;

# Equality
say 1 ==  1 ?? 'y' !! 'n'; # y
say 1 !== 1 ?? 'y' !! 'n'; # n
say 1 !=  1 ?? 'y' !! 'n'; # n

say 'ac' eq  'dc' ?? 'y' !! 'n'; #n
say 'ac' !eq 'dc' ?? 'y' !! 'n'; #y

say 1 <  2  ?? 'y' !! 'n'; # y
####say 1 !< 2  ?? 'y' !! 'n'; # n

say 1 <=  2  ?? 'y' !! 'n'; # y
####say 1 !<= 2  ?? 'y' !! 'n'; # n

say 1 >=  2  ?? 'y' !! 'n'; # n
####say 1 !>= 2  ?? 'y' !! 'n'; # y

反转运算符

反转运算符将会反转两个操作数的意义。

译注:
这里说成“反转(或翻转)两个操作数的位置”应该更加准确。

如果你想调换两个参数的位置,例如$b cmp $a,你可以写成$a Rcmp $b

现在这个操作符还没有在Rakudo中实现,并且坦白的说,我还没看见别人用过这个,但我肯定总会有人用的。

我想知道反转运算符会不会也能够同时搭配gt这样的运算符,就像$x Rgt $y能够表示$y gt $x,同时我自己也想弄明白这种写法到底好在哪里。

译注:

  1. 现在Rakudo已经能正常使用反转运算符了。
  2. 关于飞船操作符(spaceship operators,这个大概是根据外形取的名字),其在REPL中输出的结果与原文也有出入,但本质上结果还是0、1、-1。
  3. 现在反转运算符已经能够搭配gt这类运算符使用了。 ```perl 2 / 4; # 0.5 2 R/ 4; # 2

1 <=> 2; # Less 2 <=> 1; # More 1 R<=> 2; # More 2 R<=> 1; # Less

1 <=> 1; # Same my $result = 1 <=> 1; # Same 1 + $result; # 1 $result = 1 <=> 2; # Less 1 + $result; # 0

"a" gt "b"; # False "a" Rgt "b"; # True

tutorial/arrays/reversed_operators.p6

#!/usr/bin/env perl6
use v6;

# spaceship operators
say 1 <=> 2;  # -1
say 2 <=> 1;  # 1

say 1 R<=> 2;  # 1
say 2 R<=> 1;  # -1

输出结果

tutorial/arrays/reversed_operators.p6.out

Increase
Decrease
Decrease
Increase

超算符

超算符真的非常有趣,它能够将针对单个标量的运算符推广至整个标量列表(a list of scalars)。

真正的超算符使用Unicode字符来表示,对于我们而言,使用两个尖括号就行。

正常情况下箭头指向内部的运算符,两边分别是标量列表。这样就能将中缀运算符分别应用在两边列表所对应的每一对数据上,并返回运算后的结果列表。

如果两边的列表长度不一致,Perl会抛出一个异常:非DWIMMY的超算符无法用在两个不同长度或不同维度的数组上(Non-dwimmy hyperoperator cannot be used on arrays of different sizes or dimensions)。

译注:

  • Unicode字符指»(\x[BB])和«(\x[AB])。
    (1, 2, 3) »+» 1; # (2 3 4)
    (1, 2, 3) >>+>> 1; # (2 3 4)
    
  • DWIM(do what i means),DWIMMY是它的形容词形式。
    当你将尖括号同时指向更短的那一个列表时,就等于告诉Perl用那边的标量做我想做的事,当你不能确定两边集合的尺寸时就可以在两边都做我想做的$left «*» $right;
  • 两边列表长度不一致时抛出的异常现在应该是:非DWIMMY的超中缀运算符+两边的列表长度不一致(Lists on either side of non-dwimmy hyperop of infix:<+> are not of the same length)。

如果你想让运算符变得DWIMMY,就将尖括号调转方向指向你想让它DWIM的那一端,也可以同时指向两端。如果某一端的列表被指定,那么在这一端列表长度相对较短的情况下,Perl会反复使用此列表中的最后一个标量以满足另一端的配对需要。

译注:
这种说法现在已经不正确了,在需要DWIM的一端,Perl会反复地、从头至尾地依次使用这个列表中的元素(而非原文中的反复使用最后一个元素)来满足另一端的配对需要。
请同时注意原文中代码示例的错误结果。

(1, 2, 3, 4) >>+>> (1, 2); # (2 4 4 6)
# 原文中的[2, 4, 5, 6]现在是错误的

(1, 2, 3, 4) <<+>> (1, 2); # (2 4 4 6)
# 原文中的[2, 4, 5, 6]现在是错误的

在你无法确定两边列表哪个更长的时候可以让两边都变得DWIMMY,这样的话无论怎样都行的通了。

在特殊情况下,如果超算符的箭头指向单个标量,那么这个量就会直接与另一端列表中的每一个值进行配对。

tutorial/arrays/hyper.p6

#!/usr/bin/env perl6
use v6;

my @x = (1, 2) >>+<< (3, 4);
say @x.perl;  # [4, 6]

#my @d = (1, 2) >>+<< (3);
#say @d.perl;  # [4, 6]
# Non-dwimmy hyperoperator cannot be used on arrays of different sizes or dimensions

my @z = (1, 2, 3, 4) >>+>> (1, 2);
say @z.perl;      # [2, 4, 5, 6]

@z = (1, 2, 3, 4) <<+>> (1, 2);
say @z.perl;      # [2, 4, 5, 6]

@z = (4) <<+>> (1, 2);
say @z.perl;      # [5, 6]

my @y = (1, 2) >>+>> 1;
say @y.perl;      # [2, 3]

超算符还有很多,但就现阶段而言这些已经足够了。

tutorial/arrays/hyper.p6.out

Array.new(4, 6)
Array.new(2, 4, 4, 6)
Array.new(2, 4, 4, 6)
Array.new(5, 6)
Array.new(2, 3)

归约运算符

tutorial/arrays/reduction_operators.p6

#!/usr/bin/env perl6
use v6;

say [+] 1, 2;   # 3
say [+] 1..10;  # 55

# 阶乘
say [*] 1..5;   # 120

say [**] 2,2,2; # 16    == 2**2**2

my @numbers = (2, 4, 3);

# 检查列表内元素是否递增
say [<] @numbers;      # 0   

say [<] sort @numbers; # 1

归约三角运算符

译注:
归约三角运算符并不是将列表元素归约成一项,而是对列表中的每一项以及之前的项都归约至这一项,所以运算结果仍是列表。

运算符前的~仅用于字符串化(stringification)并打印列表时,在每两项之间插入空格。

tutorial/arrays/triangle_operators.p6

#!/usr/bin/env perl6
use v6;

say ~[\+] 1..5;  # 1 3 6 10 15
say ~[\*] 1..5;  # 1 2 6 24 120

输出结果

tutorial/arrays/triangle_operators.p6.out

1 3 6 10 15
1 2 6 24 120

交叉运算符

tutorial/arrays/cross_operators.p6

#!/usr/bin/env perl6
use v6;

my @x = (1, 2) X+ (4, 7);
say @x.perl;        # [5, 8, 6, 9]

my @y = 1, 2 X+ 4, 7;
say @y.perl;        # [5, 8, 6, 9]

my @str = 1, 2 X~ 4, 7;
say @str.perl;      # ["14", "17", "24", "27"]

# 后面不跟任何运算符  (即直接使用X)
my @z = 1, 2 X 4, 7;
say @z.perl;        # [1, 4, 1, 7, 2, 4, 2, 7]

输出结果

tutorial/arrays/cross_operators.p6.out

Array.new(5, 8, 6, 9)
Array.new(5, 8, 6, 9)
Array.new("14", "17", "24", "27")
Array.new(1, 4, 1, 7, 2, 4, 2, 7)