第二章 元运算符
什么是元运算符
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
,同时我自己也想弄明白这种写法到底好在哪里。
译注:
- 现在Rakudo已经能正常使用反转运算符了。
- 关于飞船操作符(spaceship operators,这个大概是根据外形取的名字),其在REPL中输出的结果与原文也有出入,但本质上结果还是0、1、-1。
- 现在反转运算符已经能够搭配
gt
这类运算符使用了。 ```perl 2 / 4; # 0.5 2 R/ 4; # 21 <=> 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)