Perl 5.10 新特性详解


本站有一篇关于5.10的简介。
显著的新特性
Perl 5.10 发布于2007年12月18日,也就是Perl 20岁生日的当天,它带给我们的不仅是新的版本号,最令人兴奋的改进是全新的智能匹配操作符(smart match operator)。该操作符实现了一种全新的比较方式,而其具体作用是随操作符接受的输入而有所不同的。举例而言,要看标量 $needle 是否存在于数组 @haystack 中,只要使用新的 ~~ 操作符:
if ($needle ~~ @haystack) ...
其结果将会"正如你所愿",而这正是 Perl 语言一贯的标志性做法。在此操作符的基础之上,Perl 语言终于有了switch 语句 ,而且它比任何传统的 switch 语句,像 C、C++和JAVA 拥有的那些,要先进得多。正则表达式也变得更加强大了。现在可以在正则表达式中使用用命名的捕获变量 (named captures) ,而不用靠括号的位置来表达捕获到的东西 。Perl 5.10 还支持嵌套匹配,使得我们现在可以使用许多有效的匹配结构,尤其是在句法解析时。尽管添加了新的特性,正则表达引擎在很多方面做了精心调整并且提高了速度。
其他的改进包括可在函数内持续的状态变量(state variable) ,使用户可以自己调整 Perl 运行方式的用户自定义 pragmata ,一个 "定义或" 的操作符 ,为翻转对象(inside-out objects)提供的符号哈希表(field hashes) ,以及改进的错误信息。让我们来看看究竟有哪些新的特性:
简单的'say'
say "Hello, world";
等同于
print "Hello, world\n";
自动添加换行符,也许很不起眼的一个新函数,但是减少了按键次数。
直观的正则表达式命名捕捉
以往使用perl的内置变量$1,$2...来记录正则表达式的匹配内容,比如:
my $line = "Symbol:AGO1 Chromosome:2R";
if($line =~ /Symbol:(\w+)\s*Chromosome:(\w+)/) {
say "Gene $1 is on chromosome $2!";
}
如果变量在后续部分还要使用,会使程序易读性下降;重新赋值于新变量又使得程序无端多了两行;于是,懒惰的程序员可以使用命名捕捉了:
my $line = "Symbol:AGO1 Chromosome:2R";
if($line =~ /Symbol:(?<gene>\w+)\s*Chromosome:(?<chr>\w+)/) {
say "Gene $+{gene} is on chromosome $+{chr}!";
}
所以可以看出,?<name>的捕捉方式,实际上是把括号内匹配的内容存储在%+这样一个奇怪的哈希里了。所以要调用捕捉的内容,用$+{name}即可了!虽然看起来很诡异,但是在大的程序块里会相当好用。
同样对于反向引用\1,\2,同样可以采用命名捕捉方式,例如可以把以下代码
my $line = "Symbol:AGO1 Chromosome:2R Location:2R:9,834,047..9,845,594";
if($line =~ /Symbol:(\w+)\s*Chromosome:(\w+).*(\2:[.,\d]+))/ {
say "The location of Gene $1 is $3!";
}
更替为
my $line = "Symbol:AGO1 Chromosome:2R Location:2R:9,834,047..9,845,594";
if($line =~ /Symbol:(\w+)\s*Chromosome:(?<chr>\w+).*(\g{chr}:[.,\d]+))/ {
say "The location of Gene $1 is $3!";
}
即用\g{name}代替\2咯,也可以用\k<name>等效代替:
my $line = "Symbol:AGO1 Chromosome:2R Location:2R:9,834,047..9,845,594";
if($line =~ /Symbol:(\w+)\s*Chromosome:(?<chr>\w+).*(\k<chr>:[.,\d]+))/ {
say "The location of Gene $1 is $3!";
}
这一点,在反向引用数目过多,数不清括号来的时候便颇为好用了。
新增的短路操作符:定义或// Logical Defined-Or (//=)
The //= operator is the assignment operator version of the // or 'logical defined-or' operator.In the context of a my variable declaration, the variable is initially undefined so it is equivalent to assignment.
此为逻辑或(||)操作符的一个变种,即逻辑“定义或”操作符//。
In general, though:
$i //= '08';
is a shorthand for:
$i = (defined $i) ? $i : '08';
It is documented in the Perl operators (perldoc perlop) in two places (tersely under the assignment operators section, and in full in the section on 'logical defined-or'). It was added in Perl 5.10.0.
EXPR1 //= EXPR2;
is the same as
EXPR1 = EXPR1 // EXPR2;
except that EXPR1 is only evaluated once. It's a convenient way of writing
EXPR1 = EXPR2 if !defined(EXPR1);
defined(EXPR1) ? EXPR1 : EXPR2;
我们知道,善用短路操作符&&(即 and)、||(即or)会使得程序简洁而直观,比较常见的是打开文件句柄的例子:
open INPUT, "<", "$ARGV[0]" || die "Cannot open file: $!";
即当||的左边为真(打开文件成功)时,右边短路不会报错。5.10版的Perl新添了定义否的短路操作符//。它有什么功效呢?看看这个例子:
my $signal = $gene_signal{$gene} || "NaN";
say "$gene\t$signal"; #当$gene_signal{$gene}等于0时,会输出NaN
这个代码里,我们想在哈希表里检索微阵列的gene对应的信号,如果检索的gene在哈希表里不存在,就是这块芯片不含该基因的探针,我们将信号值定为缺失值NaN,便于下一步用R处理。但这里有个问题,就是当gene对应的信号值为0的时候,||左边的表达式为假,$signal将会定义为缺失值,这显然不是我们的本意,解决办法是采用定义否操作符//:当只有//左边的表达式为undef时,才使用右边的值。
my $signal = $gene_signal{$gene} || "NaN";
say "$gene\t$signal"; #当$gene_signal{$gene}等于0时,会输出0。
解释器的改进
并非只有程序语言的改进,Perl 解释器本身已变得更快,更少内存占用(memory footprint) ,还有了一些 UTF-8 及线程方面的改进。Perl 的安装方式变成了可移动的,这对系统管理员以及操作系统集成者来说实在是好消息。源代码现在变得更易移植,很多小 bug 也被顺手改正了。所有这一切都为了造就到目前为止最棒的Perl。
智能匹配符
智能匹配符就是~~号,它是个返回布尔值的二元操作符。比如说当写下$a~@b时,perl将会判断$a的值是否包含在@b中,并且返回布尔结果。具体的智能判断规则列表如下:
Hash Hash hash keys identical [sort keys %$a]~~[sort keys %$b]
Hash Array hash slice existence grep {exists $a->{$_}} @$b
Hash Regex hash key grep grep /$b/, keys %$a
Hash Any hash entry existence exists $a->{$b}
Array Array arrays are identical[ * ]
Array Regex array grep grep /$b/, @$a
Array Num array contains number grep $_ == $b, @$a
Array Any array contains string grep $_ eq $b, @$a
Any undef undefined !defined $a
Any Regex pattern match $a =~ /$b/
Code() Code() results are equal $a->() eq $b->()
Any Code() simple closure truth $b->() # ignoring $a
Num numish[!] numeric equality $a == $b
Any Str string equality $a eq $b
Any Num numeric equality $a == $b
Any Any string equality $a eq $b
下表是智能匹配操作符对不同操作数的处理方式
例子 匹配方式
%a ~~ %b 哈希的键是否一致
%a ~~ @b 至少 %a 中的一个键在列表@b中
%a ~~ /Fred/ 至少一个键匹配给定的模式
%a ~~ 'Fred' 哈希中某一指定键$a{Fred}是否存在 $a{Fred}
@a ~~ @b 数组是否相同
@a ~~ /Fred/ 有一个元素匹配给定的模式
@a ~~ 123 至少有一个元素转化为数字后是123
@a ~~ 'Fred' 至少有一个元素转化为字符串后是'Fred'
$name ~~ undef $name确实尚未定义
$name ~~ /Fred/ 模式匹配
123 ~~ '123.0' 数字和字符串是否相等
'Fred' ~~ 'Fred' 字符串是否相同
123 ~~ 456 数字是否相等
当使用只能匹配操作符时,Perl会按此表自上而下查看使用的操作数,先找到哪一种匹配就选择对应的操作。
它能快速比较两个数组是否相同(当然,数组元素的顺序也要考虑);它的功能远不止如此,基本标量,数组,哈希,甚至匹配模式,任意两两组合,它就能进行你想要的(一般情况下)的智能匹配,另外一个神奇的例子是:
say "Yes, miR-310 is a newly evolved miRNA!" if %new_miR_target ~~ /miR(-?)310/i;
这段代码会在%new_miR_target的key中检索/miR(-?)310/i这个pattern,如果有符合的对象便say yes。事实上,一张较完整的'~~'功能表如下(摘录自Learning Perl, 5ed, p224)
注意:当两个标量以字符串的形式存储像'123','123.12'这些数字的时候,使用智能匹配操作符进行比对时会默认将这些字符串转换为数字,然后在进行比对。
类switch控制结构-given-when结构
这其实是Perl提供的switch结构,将在perl6中正式使用,这里提前引入了。C程序员常会抱怨Perl为什么没有switch这样的控制结构,而不得不选择繁杂的多层if...elsif..elsif.....else。事实上,give-when就是一个新版升级的switch。基于智能匹配的表达式,given-when显然更加强大。
用法如下:
use feature ":5.10";
given($foo) {
when (undef) {
say '$foo is undefined';
}
when ("foo") {
say '$foo is the string "foo"';
}
when ([1,3,5,7,9]) {
say '$foo is an odd digit';
continue; # Fall through
}
when ($_ < 100) {
say '$foo is numerically less than 100';
}
when (\&complicated_check) {
say 'complicated_check($foo) is true';
}
default {
die q(I don't know what to do with $foo);
}
}
given($flybase_id) {
when(/^fbgn/i) {say "It's a gene!"}
when(/^fbtr/i) {say "It's a transcript!"}
when(/^fbpp/i) {say "It's a polypeptide!"}
default {say "I've no idea what it is."}
}
可能会注意到:没有添加和switch语句配套的break,当表达式为真时不会跳出而会一直往下落(fall-through)。事实上,和switch颇为不同的是,switch是默认fall-through,而Perl中的given-when默认是当表达式为真时执行语句,然后跳出控制块!所以以上的程序并没有错,如果要实现fall-through,需要添加continue控制符。
given($flybase_id) {
when(!/^fbgn/i) {say "It's not a gene!"; continue}
when(!/^fbtr/i) {say "It's not a transcript!"; continue}
when(!/^fbpp/i) {say "It's not a polypeptides!"; continue}
default {say "I've no idea what it is."}
}
奇妙的是,如果判断对象不是标量,而是数组,或者是哈希展开的键值(总而言之,是列表即可),直接用foreach替代given可以达到遍历整个列表的效果:
foreach (@flybase_id) {
when(/^fbgn/i) {say "It's a gene!"}
when(/^fbtr/i) {say "It's a transcript!"}
when(/^fbpp/i) {say "It's a polypeptide!"}
say "Moving to default...";
default {say "I've no idea what it is."}
}
是的,还可以在when语句之间添加其他的语句。Perl因缺少像switch..case这样的语句而受到了一些批评,当然你也可以自己写一个这样的控制结构。后来出现了'Switch'模块在一定程度上解决这个问题,从5.10开始,它又被内置的'given-when'这样的结构所取代,在此介绍这样一种结构是从一家介绍perl的网站上所转引过来,尾部有原始链接。
One of the complaints about Perl was always that it lacks a real case or switch statement. While you could always fake one, people were not satisfied. Of course if you give them a switch statement they will find something else....So in accordance with the Perl 6 design perl will NOT have a case or switch. It will have a given keyword. It will also behave differently than the usual case. It will do The Right Thing (tm).
The syntax is quite simple:
given($value) {
when(3) { say "Three"; }
when(7) { say "Seven"; }
when(9) { say "Nine"; }
default { say "None of the expected values"; }
}
We are taking the value in $value and comparing it to the values within the when() statements. When we find one that matches, the block after the when() is executed AND the given statement is terminated. That is, no more when() is checked.If non of the when() cases fit then the (optional) default {} block is executed.
Let's see another example:
given($value) {
when(/^\d+$/) { say "digits only"; }
when(/^\w+$/) { say "Word characters"; }
when(/^[a-zA-Z0-9.-]+$/) { say "Domain namish"; }
default { say "None of the expected"; }
}
It is very similar but now we have regular expressions instead of fixed values in the when() statement. Each regex on its turned is tried against the value. When one of them matches its block is executed and the given() statement is terminated.
Yes, what you are suspecting is right. The when() statements are actually applying ~~, the smart match operator.
I have already written about it earlier in Smart Matching in Perl 5.10. So you can use any kind of value in the when() statement. A number will check numeric equality using ==, a string will use eq, a regex will try to match the given value and if you supply a subroutine reference then Perl will call that subroutine using the given value as a parameter and check the true-ness of the return value.
See this example:
given($value) {
when(10) {
say "Number 10";
}
when([11, 23, 48]) {
say "In the list";
}
when(/^\d+$/) {
say "digits only";
}
when(\&is_number) {
say "Is number";
}
default {
say "None of the above";
}
}
sub is_number {
return $_[0] =~ /\d/ and $_[0] =~ /^[+-]?\d*\.\d*$/;
}
There are few more minor issues:
Perl will automatically break out from the given() statement after the execution of block. If you would like to force checking the additional when() statements use the continue keyword.
On the other hand if you would like to break out from a given() statement before reaching the end of the when() block, you can use the break statement yourself.
The given() actually assigns the $value to $_ so you can use that as well to write when clauses such as this:
when($_ < 18) { say "Less than 18"; }
when() can be used outside the given() block as well.
其中所提及的'Smart Matching in Perl 5.10',可以认为是perl正则匹配的傻瓜版,非常容易上手及理解。可参考原文地址
Perl中的given-when控制结构能够根据given的参数,执行某个条件对应的语句块。这样就可以实现像switch一样的功能。
given( $ARGV[0] ) {
when( /freeoa/i ) { say 'Name has freeoa in it' }
when( /^Freeoa/ ) { say 'Name starts with Freeoa' }
when( 'Freeoa' ) { say 'Name is Freeoa' }
default { say "I don't see a Freeoa" }
}
如果$_不能满足任何when条件,perl就会执行default语句块,default块相当于一个测试条件永远为真的when语句。因此可以说default就是一个特殊的when:
when( 1 == 1 ) { say "I don't see a Freeoa" }
与if-elsif-else语句最大的不同在于given-when可以在满足某个条件的基础上,继续测试其他的条件,但if-elsif-else一旦满足了某个条件,就只能执行对应的那个语句块。实际上前面的例子可以写成如下的方式:
given( $ARGV[0] ) {
when( $_ ~~ /freeoa/i ) { say 'Name has freeoa in it'; break }
when( $_ ~~ /^Freeoa/ ) { say 'Name starts with Freeoa'; break }
when( $_ ~~ 'Freeoa' ) { say 'Name is Freeoa'; break }
default { say "I don't see a Freeoa"; break }
}
照此情形,第一条测试语句如果传来的参数匹配/freeoa/i,后面的所有语句就不可能执行了,这时候就会直接跳出控制结构。如果在when语句块的末尾使用continue,Perl就会尝试执行后续的when语句了。当另一个when的条件满足时,会执行对应语句块。在每个when语句块的末尾写上continue,就意味着所有的条件判断都会执行:
given( $ARGV[0] ) {
when( $_ ~~ /freeoa/i ) { say 'Name has freeoa in it'; continue }
when( $_ ~~ /^Freeoa/ ) { say 'Name starts with Freeoa'; continue }
when( $_ ~~ 'Freeoa' ) { say 'Name is Freeoa'; continue } # 注意!
default { say "I don't see a Freeoa" }
}
实际上这样写是有问题的,这里的default总是会运行。如果在default之前的when语句使用了continue,Per就会继续执行default语句。要解决这个问题,只要拿掉最后一个when的continue就可以了,改写成如下形式:
given( $ARGV[0] ) {
when( $_ ~~ /freeoa/i ) { say 'Name has freeoa in it'; continue }
when( $_ ~~ /^Freeoa/ ) { say 'Name starts with Freeoa'; continue }
when( $_ ~~ 'Freeoa' ) { say 'Name is Freeoa'; break } # 现在就对了!
when( 1 == 1 ) { say "I don't see a Freeoa" } # 这里的when(1==1)可改写成default
}
与foreach搭配来处理数组循环
foreach ( @names ) {
when( /freeoa/i ) { say 'Name has freeoa in it'; continue }
when( /^Freeoa/ ) { say 'Name starts with Freeoa'; continue }
when( 'Freeoa' ) { say 'Name is Freeoa'; }
default { say "I don't see a Freeoa" }
}
若要用智能匹配,当前元素就只能是$_了。当然也可以在foreach语句块中写上其他的语句。
when( 'Freeoa' ) { say 'Name is Freeoa'; }
say "I am move to default...";
default { say "I don't see a Freeoa" }
state关键字
用state关键字取代my,可以定义一个持久变量,与C语言中'static'关键字同理。比如下面的代码维护一个私有变量,每次函数被调用时都会返回一个递增的值:
use feature 'state';
sub gimme_another { state $x; return ++$x }
gimme_another(); # return 1
gimme_another(); # return 2
并且由于$x是词法变量,因此在作用域之外不能被访问或修改,也就是说它是私有的。
上面的代码在5.10之前可以用闭包替代:
sub generate {
my $x = 0;
return sub { ++$x; }
}
my $gimme_another = generate();
$gimme_another->(); # return 1
$gimme_another->(); # return 2
注意:state是从Perl 5.9.4开始引入的,state仅能创建闭合作用域为子程序内部的变量,state可以声明标量、数组、哈希。但在声明数组和哈希时不能对其初始化(Perl 5.16后支持)。
use v5.12;
sub getit1{
state %h={'k1'=>1,'cnt'=>2,'k2'=>'freeoa'};
say $h{cnt}++;
}
sub getit2{
state $h={'k1'=>3,'cnt'=>2,'k2'=>'freeoa'};
say $h->{cnt}++;
}
getit2() for(1..5);
引入了'features'指令字
可以参考"理解perl feature指令"
总结
Perl一个令人'诟病'之处在于其内置了相当多的奇怪变量,这次5.10版本在捕捉命名功能上更是采用了'%+'。其实Perl程序员会觉得这样写起来更得心应手。
想要了解更多关于5.10所有的改动,请看 Perl 5.10 官方 perldelta 文档。
显著的新特性
Perl 5.10 发布于2007年12月18日,也就是Perl 20岁生日的当天,它带给我们的不仅是新的版本号,最令人兴奋的改进是全新的智能匹配操作符(smart match operator)。该操作符实现了一种全新的比较方式,而其具体作用是随操作符接受的输入而有所不同的。举例而言,要看标量 $needle 是否存在于数组 @haystack 中,只要使用新的 ~~ 操作符:
if ($needle ~~ @haystack) ...
其结果将会"正如你所愿",而这正是 Perl 语言一贯的标志性做法。在此操作符的基础之上,Perl 语言终于有了switch 语句 ,而且它比任何传统的 switch 语句,像 C、C++和JAVA 拥有的那些,要先进得多。正则表达式也变得更加强大了。现在可以在正则表达式中使用用命名的捕获变量 (named captures) ,而不用靠括号的位置来表达捕获到的东西 。Perl 5.10 还支持嵌套匹配,使得我们现在可以使用许多有效的匹配结构,尤其是在句法解析时。尽管添加了新的特性,正则表达引擎在很多方面做了精心调整并且提高了速度。
其他的改进包括可在函数内持续的状态变量(state variable) ,使用户可以自己调整 Perl 运行方式的用户自定义 pragmata ,一个 "定义或" 的操作符 ,为翻转对象(inside-out objects)提供的符号哈希表(field hashes) ,以及改进的错误信息。让我们来看看究竟有哪些新的特性:
简单的'say'
say "Hello, world";
等同于
print "Hello, world\n";
自动添加换行符,也许很不起眼的一个新函数,但是减少了按键次数。
直观的正则表达式命名捕捉
以往使用perl的内置变量$1,$2...来记录正则表达式的匹配内容,比如:
my $line = "Symbol:AGO1 Chromosome:2R";
if($line =~ /Symbol:(\w+)\s*Chromosome:(\w+)/) {
say "Gene $1 is on chromosome $2!";
}
如果变量在后续部分还要使用,会使程序易读性下降;重新赋值于新变量又使得程序无端多了两行;于是,懒惰的程序员可以使用命名捕捉了:
my $line = "Symbol:AGO1 Chromosome:2R";
if($line =~ /Symbol:(?<gene>\w+)\s*Chromosome:(?<chr>\w+)/) {
say "Gene $+{gene} is on chromosome $+{chr}!";
}
所以可以看出,?<name>的捕捉方式,实际上是把括号内匹配的内容存储在%+这样一个奇怪的哈希里了。所以要调用捕捉的内容,用$+{name}即可了!虽然看起来很诡异,但是在大的程序块里会相当好用。
同样对于反向引用\1,\2,同样可以采用命名捕捉方式,例如可以把以下代码
my $line = "Symbol:AGO1 Chromosome:2R Location:2R:9,834,047..9,845,594";
if($line =~ /Symbol:(\w+)\s*Chromosome:(\w+).*(\2:[.,\d]+))/ {
say "The location of Gene $1 is $3!";
}
更替为
my $line = "Symbol:AGO1 Chromosome:2R Location:2R:9,834,047..9,845,594";
if($line =~ /Symbol:(\w+)\s*Chromosome:(?<chr>\w+).*(\g{chr}:[.,\d]+))/ {
say "The location of Gene $1 is $3!";
}
即用\g{name}代替\2咯,也可以用\k<name>等效代替:
my $line = "Symbol:AGO1 Chromosome:2R Location:2R:9,834,047..9,845,594";
if($line =~ /Symbol:(\w+)\s*Chromosome:(?<chr>\w+).*(\k<chr>:[.,\d]+))/ {
say "The location of Gene $1 is $3!";
}
这一点,在反向引用数目过多,数不清括号来的时候便颇为好用了。
新增的短路操作符:定义或// Logical Defined-Or (//=)
The //= operator is the assignment operator version of the // or 'logical defined-or' operator.In the context of a my variable declaration, the variable is initially undefined so it is equivalent to assignment.
此为逻辑或(||)操作符的一个变种,即逻辑“定义或”操作符//。
In general, though:
$i //= '08';
is a shorthand for:
$i = (defined $i) ? $i : '08';
It is documented in the Perl operators (perldoc perlop) in two places (tersely under the assignment operators section, and in full in the section on 'logical defined-or'). It was added in Perl 5.10.0.
EXPR1 //= EXPR2;
is the same as
EXPR1 = EXPR1 // EXPR2;
except that EXPR1 is only evaluated once. It's a convenient way of writing
EXPR1 = EXPR2 if !defined(EXPR1);
defined(EXPR1) ? EXPR1 : EXPR2;
我们知道,善用短路操作符&&(即 and)、||(即or)会使得程序简洁而直观,比较常见的是打开文件句柄的例子:
open INPUT, "<", "$ARGV[0]" || die "Cannot open file: $!";
即当||的左边为真(打开文件成功)时,右边短路不会报错。5.10版的Perl新添了定义否的短路操作符//。它有什么功效呢?看看这个例子:
my $signal = $gene_signal{$gene} || "NaN";
say "$gene\t$signal"; #当$gene_signal{$gene}等于0时,会输出NaN
这个代码里,我们想在哈希表里检索微阵列的gene对应的信号,如果检索的gene在哈希表里不存在,就是这块芯片不含该基因的探针,我们将信号值定为缺失值NaN,便于下一步用R处理。但这里有个问题,就是当gene对应的信号值为0的时候,||左边的表达式为假,$signal将会定义为缺失值,这显然不是我们的本意,解决办法是采用定义否操作符//:当只有//左边的表达式为undef时,才使用右边的值。
my $signal = $gene_signal{$gene} || "NaN";
say "$gene\t$signal"; #当$gene_signal{$gene}等于0时,会输出0。
解释器的改进
并非只有程序语言的改进,Perl 解释器本身已变得更快,更少内存占用(memory footprint) ,还有了一些 UTF-8 及线程方面的改进。Perl 的安装方式变成了可移动的,这对系统管理员以及操作系统集成者来说实在是好消息。源代码现在变得更易移植,很多小 bug 也被顺手改正了。所有这一切都为了造就到目前为止最棒的Perl。
智能匹配符
智能匹配符就是~~号,它是个返回布尔值的二元操作符。比如说当写下$a~@b时,perl将会判断$a的值是否包含在@b中,并且返回布尔结果。具体的智能判断规则列表如下:
Hash Hash hash keys identical [sort keys %$a]~~[sort keys %$b]
Hash Array hash slice existence grep {exists $a->{$_}} @$b
Hash Regex hash key grep grep /$b/, keys %$a
Hash Any hash entry existence exists $a->{$b}
Array Array arrays are identical[ * ]
Array Regex array grep grep /$b/, @$a
Array Num array contains number grep $_ == $b, @$a
Array Any array contains string grep $_ eq $b, @$a
Any undef undefined !defined $a
Any Regex pattern match $a =~ /$b/
Code() Code() results are equal $a->() eq $b->()
Any Code() simple closure truth $b->() # ignoring $a
Num numish[!] numeric equality $a == $b
Any Str string equality $a eq $b
Any Num numeric equality $a == $b
Any Any string equality $a eq $b
下表是智能匹配操作符对不同操作数的处理方式
例子 匹配方式
%a ~~ %b 哈希的键是否一致
%a ~~ @b 至少 %a 中的一个键在列表@b中
%a ~~ /Fred/ 至少一个键匹配给定的模式
%a ~~ 'Fred' 哈希中某一指定键$a{Fred}是否存在 $a{Fred}
@a ~~ @b 数组是否相同
@a ~~ /Fred/ 有一个元素匹配给定的模式
@a ~~ 123 至少有一个元素转化为数字后是123
@a ~~ 'Fred' 至少有一个元素转化为字符串后是'Fred'
$name ~~ undef $name确实尚未定义
$name ~~ /Fred/ 模式匹配
123 ~~ '123.0' 数字和字符串是否相等
'Fred' ~~ 'Fred' 字符串是否相同
123 ~~ 456 数字是否相等
当使用只能匹配操作符时,Perl会按此表自上而下查看使用的操作数,先找到哪一种匹配就选择对应的操作。
它能快速比较两个数组是否相同(当然,数组元素的顺序也要考虑);它的功能远不止如此,基本标量,数组,哈希,甚至匹配模式,任意两两组合,它就能进行你想要的(一般情况下)的智能匹配,另外一个神奇的例子是:
say "Yes, miR-310 is a newly evolved miRNA!" if %new_miR_target ~~ /miR(-?)310/i;
这段代码会在%new_miR_target的key中检索/miR(-?)310/i这个pattern,如果有符合的对象便say yes。事实上,一张较完整的'~~'功能表如下(摘录自Learning Perl, 5ed, p224)
注意:当两个标量以字符串的形式存储像'123','123.12'这些数字的时候,使用智能匹配操作符进行比对时会默认将这些字符串转换为数字,然后在进行比对。
类switch控制结构-given-when结构
这其实是Perl提供的switch结构,将在perl6中正式使用,这里提前引入了。C程序员常会抱怨Perl为什么没有switch这样的控制结构,而不得不选择繁杂的多层if...elsif..elsif.....else。事实上,give-when就是一个新版升级的switch。基于智能匹配的表达式,given-when显然更加强大。
用法如下:
use feature ":5.10";
given($foo) {
when (undef) {
say '$foo is undefined';
}
when ("foo") {
say '$foo is the string "foo"';
}
when ([1,3,5,7,9]) {
say '$foo is an odd digit';
continue; # Fall through
}
when ($_ < 100) {
say '$foo is numerically less than 100';
}
when (\&complicated_check) {
say 'complicated_check($foo) is true';
}
default {
die q(I don't know what to do with $foo);
}
}
given($flybase_id) {
when(/^fbgn/i) {say "It's a gene!"}
when(/^fbtr/i) {say "It's a transcript!"}
when(/^fbpp/i) {say "It's a polypeptide!"}
default {say "I've no idea what it is."}
}
可能会注意到:没有添加和switch语句配套的break,当表达式为真时不会跳出而会一直往下落(fall-through)。事实上,和switch颇为不同的是,switch是默认fall-through,而Perl中的given-when默认是当表达式为真时执行语句,然后跳出控制块!所以以上的程序并没有错,如果要实现fall-through,需要添加continue控制符。
given($flybase_id) {
when(!/^fbgn/i) {say "It's not a gene!"; continue}
when(!/^fbtr/i) {say "It's not a transcript!"; continue}
when(!/^fbpp/i) {say "It's not a polypeptides!"; continue}
default {say "I've no idea what it is."}
}
奇妙的是,如果判断对象不是标量,而是数组,或者是哈希展开的键值(总而言之,是列表即可),直接用foreach替代given可以达到遍历整个列表的效果:
foreach (@flybase_id) {
when(/^fbgn/i) {say "It's a gene!"}
when(/^fbtr/i) {say "It's a transcript!"}
when(/^fbpp/i) {say "It's a polypeptide!"}
say "Moving to default...";
default {say "I've no idea what it is."}
}
是的,还可以在when语句之间添加其他的语句。Perl因缺少像switch..case这样的语句而受到了一些批评,当然你也可以自己写一个这样的控制结构。后来出现了'Switch'模块在一定程度上解决这个问题,从5.10开始,它又被内置的'given-when'这样的结构所取代,在此介绍这样一种结构是从一家介绍perl的网站上所转引过来,尾部有原始链接。
One of the complaints about Perl was always that it lacks a real case or switch statement. While you could always fake one, people were not satisfied. Of course if you give them a switch statement they will find something else....So in accordance with the Perl 6 design perl will NOT have a case or switch. It will have a given keyword. It will also behave differently than the usual case. It will do The Right Thing (tm).
The syntax is quite simple:
given($value) {
when(3) { say "Three"; }
when(7) { say "Seven"; }
when(9) { say "Nine"; }
default { say "None of the expected values"; }
}
We are taking the value in $value and comparing it to the values within the when() statements. When we find one that matches, the block after the when() is executed AND the given statement is terminated. That is, no more when() is checked.If non of the when() cases fit then the (optional) default {} block is executed.
Let's see another example:
given($value) {
when(/^\d+$/) { say "digits only"; }
when(/^\w+$/) { say "Word characters"; }
when(/^[a-zA-Z0-9.-]+$/) { say "Domain namish"; }
default { say "None of the expected"; }
}
It is very similar but now we have regular expressions instead of fixed values in the when() statement. Each regex on its turned is tried against the value. When one of them matches its block is executed and the given() statement is terminated.
Yes, what you are suspecting is right. The when() statements are actually applying ~~, the smart match operator.
I have already written about it earlier in Smart Matching in Perl 5.10. So you can use any kind of value in the when() statement. A number will check numeric equality using ==, a string will use eq, a regex will try to match the given value and if you supply a subroutine reference then Perl will call that subroutine using the given value as a parameter and check the true-ness of the return value.
See this example:
given($value) {
when(10) {
say "Number 10";
}
when([11, 23, 48]) {
say "In the list";
}
when(/^\d+$/) {
say "digits only";
}
when(\&is_number) {
say "Is number";
}
default {
say "None of the above";
}
}
sub is_number {
return $_[0] =~ /\d/ and $_[0] =~ /^[+-]?\d*\.\d*$/;
}
There are few more minor issues:
Perl will automatically break out from the given() statement after the execution of block. If you would like to force checking the additional when() statements use the continue keyword.
On the other hand if you would like to break out from a given() statement before reaching the end of the when() block, you can use the break statement yourself.
The given() actually assigns the $value to $_ so you can use that as well to write when clauses such as this:
when($_ < 18) { say "Less than 18"; }
when() can be used outside the given() block as well.
其中所提及的'Smart Matching in Perl 5.10',可以认为是perl正则匹配的傻瓜版,非常容易上手及理解。可参考原文地址
Perl中的given-when控制结构能够根据given的参数,执行某个条件对应的语句块。这样就可以实现像switch一样的功能。
given( $ARGV[0] ) {
when( /freeoa/i ) { say 'Name has freeoa in it' }
when( /^Freeoa/ ) { say 'Name starts with Freeoa' }
when( 'Freeoa' ) { say 'Name is Freeoa' }
default { say "I don't see a Freeoa" }
}
如果$_不能满足任何when条件,perl就会执行default语句块,default块相当于一个测试条件永远为真的when语句。因此可以说default就是一个特殊的when:
when( 1 == 1 ) { say "I don't see a Freeoa" }
与if-elsif-else语句最大的不同在于given-when可以在满足某个条件的基础上,继续测试其他的条件,但if-elsif-else一旦满足了某个条件,就只能执行对应的那个语句块。实际上前面的例子可以写成如下的方式:
given( $ARGV[0] ) {
when( $_ ~~ /freeoa/i ) { say 'Name has freeoa in it'; break }
when( $_ ~~ /^Freeoa/ ) { say 'Name starts with Freeoa'; break }
when( $_ ~~ 'Freeoa' ) { say 'Name is Freeoa'; break }
default { say "I don't see a Freeoa"; break }
}
照此情形,第一条测试语句如果传来的参数匹配/freeoa/i,后面的所有语句就不可能执行了,这时候就会直接跳出控制结构。如果在when语句块的末尾使用continue,Perl就会尝试执行后续的when语句了。当另一个when的条件满足时,会执行对应语句块。在每个when语句块的末尾写上continue,就意味着所有的条件判断都会执行:
given( $ARGV[0] ) {
when( $_ ~~ /freeoa/i ) { say 'Name has freeoa in it'; continue }
when( $_ ~~ /^Freeoa/ ) { say 'Name starts with Freeoa'; continue }
when( $_ ~~ 'Freeoa' ) { say 'Name is Freeoa'; continue } # 注意!
default { say "I don't see a Freeoa" }
}
实际上这样写是有问题的,这里的default总是会运行。如果在default之前的when语句使用了continue,Per就会继续执行default语句。要解决这个问题,只要拿掉最后一个when的continue就可以了,改写成如下形式:
given( $ARGV[0] ) {
when( $_ ~~ /freeoa/i ) { say 'Name has freeoa in it'; continue }
when( $_ ~~ /^Freeoa/ ) { say 'Name starts with Freeoa'; continue }
when( $_ ~~ 'Freeoa' ) { say 'Name is Freeoa'; break } # 现在就对了!
when( 1 == 1 ) { say "I don't see a Freeoa" } # 这里的when(1==1)可改写成default
}
与foreach搭配来处理数组循环
foreach ( @names ) {
when( /freeoa/i ) { say 'Name has freeoa in it'; continue }
when( /^Freeoa/ ) { say 'Name starts with Freeoa'; continue }
when( 'Freeoa' ) { say 'Name is Freeoa'; }
default { say "I don't see a Freeoa" }
}
若要用智能匹配,当前元素就只能是$_了。当然也可以在foreach语句块中写上其他的语句。
when( 'Freeoa' ) { say 'Name is Freeoa'; }
say "I am move to default...";
default { say "I don't see a Freeoa" }
state关键字
用state关键字取代my,可以定义一个持久变量,与C语言中'static'关键字同理。比如下面的代码维护一个私有变量,每次函数被调用时都会返回一个递增的值:
use feature 'state';
sub gimme_another { state $x; return ++$x }
gimme_another(); # return 1
gimme_another(); # return 2
并且由于$x是词法变量,因此在作用域之外不能被访问或修改,也就是说它是私有的。
上面的代码在5.10之前可以用闭包替代:
sub generate {
my $x = 0;
return sub { ++$x; }
}
my $gimme_another = generate();
$gimme_another->(); # return 1
$gimme_another->(); # return 2
注意:state是从Perl 5.9.4开始引入的,state仅能创建闭合作用域为子程序内部的变量,state可以声明标量、数组、哈希。但在声明数组和哈希时不能对其初始化(Perl 5.16后支持)。
use v5.12;
sub getit1{
state %h={'k1'=>1,'cnt'=>2,'k2'=>'freeoa'};
say $h{cnt}++;
}
sub getit2{
state $h={'k1'=>3,'cnt'=>2,'k2'=>'freeoa'};
say $h->{cnt}++;
}
getit2() for(1..5);
引入了'features'指令字
可以参考"理解perl feature指令"
总结
Perl一个令人'诟病'之处在于其内置了相当多的奇怪变量,这次5.10版本在捕捉命名功能上更是采用了'%+'。其实Perl程序员会觉得这样写起来更得心应手。
想要了解更多关于5.10所有的改动,请看 Perl 5.10 官方 perldelta 文档。