这是我在知乎上的答题。
// 操作符
$a = $b // $c;
# $a = $b, 如果$b 为undef, $a = $c
常常我会这么写来给变量定义一个默认值
$some_var //= 0;
... yada yada
当我们定义一个函数而打算在未来实现它
sub unimplemented {
...
}
三个dot 这个操作符表示这里还有待实现,程序运行不会报错,但如果使用到这个未定义的函数,则会抛出'unimplement'的异常
这个操作符有个名字 yada yada 是来自日语的词汇,释义为等等,之类的意思
-> 和 autobox
-> 操作符有个比较有意思的一个地方,除了我们常用的
$hash->{key}
$list->[0]
$obj->method()
之外,还有一个神奇的功能来实现autobox,也就是所谓的·一切皆对象·
my $len = sub { length(shift) };
"hello"->$len();
如果想要没有$的autobox, "hello"->len(),可以看看CPAN 上的autobox::Core
s///r
是否遇到希望用s///来得到想要的字符串而不希望改变原来的变量?
$x = "I like dogs.";
$y = $x =~ s/dogs/cats/r;
print "$x $y\n"; # prints "I like dogs. I like cats."
here doc 和 inline file
here doc 可以像下面这样定义一个长的字符串
my $str = <<__;
some string here
bla blah
__
inline file 可以像下面这样在程序内部定义一个file,就像操作文件句柄一样
while( <DATA> ) {
print;
}
__DATA__
a b c d
1 2 3 4
flip-flop
.. 除了在列表上下文作为范围操作符使用
'a'..'z' # ('a', 'b', ... 'z')
在标量上下文也有特别的用法,称作flip-flop操作符,行为就像一个状态触发器。
while(<$fh>)
{
next if 1..100; # skip 1 ~ 100 line
...
}$fh>
autovivification
这个应该是perl所特有的一种用法
使得我可以不必依次定义一个数据的每一级类型,而直接对其进行任意深度的赋值
my $hash = {};
$hash->{a}->{b}->{c} = 1;
方便之外,也可能会带来麻烦
my $hash = {};
if ( exists $hash->{a} ) {
print 'exists'; # no
}
if ( exists $hash->{a}->{b} ) {
print 'exists'; # no
}
if ( exists $hash->{a} ) {
print 'exists now' # yes
}
我们可以使用no autovivification
来关闭这一特性
quote word list: qw
这个非常常用,在写一个字符串数组的时候,不用麻烦的大引号了逗号了
qw( item1 item2 item3 ) => ('item1', 'item2', 'item3')
x
'a' x 4 # 'aaaa'
对数组同样有效
(1,2,3) x 3 # (1,2,3,1,2,3,1,2,3)
label
可以在任意位置定义标签,通过goto 进行跳转。
当然很多人都不推荐在程序大量使用goto, 但是perl中定义标签还有一个非常有用的功能,就是循环中使用next 或 last + 标签来进行跨层使用。
LABEL:
for ( @list_A) {
for ( @list_B ) {
...;
next LABEL;
}
}
@{[...]}
由于perl的sigil的存在,使得perl在print 的时候很容易内插变量,比如在c 中
printf("my name is %s, i'm %d", name, age)
而在perl 中就可以这么写
print "my name is $name, i'm $age";
@{[...]} 更是极大的扩展了这一功能,你可以在字符串中内插表达式
my $x = 1;
my $y = 2;
print "$x + $y = @{[ $x + $y ]}"
($x, $y) = ($y, $x );
python 中的 a, b = b, a
Pegex 是一个语法解析框架,全称是 Parsing Expression Grammars (PEG), with Regular Expessions (Regex) 。 作者是Ingy dot net, 相信大家都听过他的另外一个模块 Inline, Inline::C, 最近Ingy的 Inline Module Supprot 项目也是得到了Perl基金会TPF(The Perl Fundation )的赞助, 相信以后的Inline会更加好用。
最近的China Perl Advent也有介绍一个语法解析模块Regexp::Grammar. 我没有用过这个模块,但看起来和Pegex的思路大致是一样的,因为其作者是Damian Conway, 所以也是很有保障的。
还是回到Pegex, 就像它的名字一样, Pegex是一个基于正则表达式来做语法解析的框架, 灵感来自于Perl6的Rules. 相比普通的正则表达式, Pegex的语法更加清晰易懂和结构化.比如我们想定义一个语法表示所有以#开头的都是注释:
comment: / HASH ANY* EOL /
这里以冒号分割定义了一个 rule 叫 comment, / / 扩起来的其实就是表示正则表达式, 只是用 HASH 来代替 #, 看起来干净一些, 以上的写法和下面的正则表示是一样的
comment: /#.*\r?\n/
这里的HASH ANY EOL也是rule,只是事先定义好的默认rule,所以可以看到,Pegex的语法定义其实就是定义一个个子rule,最后把他们串起来。而rule本质上也是正则表达式。
与perl的正则语法不同的是
在正则语句中,用尖括号括起来的是rule
/ ( <rule1> | 'non_rule' ) /
如果你想省略尖括号,可以在rule前留一个空白
/ ( rule1 | 'non_rule' ) /
正则语句默认是忽略空白的,你可以把一个表达式拆成多行来写,也就是相当于正则 /x模式 默认开启
/ (
rule1+ # 注释
|
rule2
) /
用- + 来表示 \s* 和 \s+
/ - rule3 + / # rule3 前面可以有0-多个空白, 后面有1到多个空白
在perl中任何(?XX ) 形式的语法,在这里都可以把?省略, 也就是下面两个是一样的
/ (: a | b ) /
/ (?: a | b ) /
我们再来看一个解析CSV的例子(来自Pegex::CSV 模块)
csv: row*
row:
/(= ALL)/
value* % /- COMMA/
/- (: EOL | CR | EOS)/
value: /- ( double | plain) /
double: /- DOUBLE (: (: DOUBLE DOUBLE | [^ DOUBLE] )* ) DOUBLE /
plain: /- (: [^ COMMA DOUBLE CR NL]* [^ SPACE TAB COMMA DOUBLE CR NL ] )? /
第一句定义表示csv 是由零到多个row组成
第二句定义表示row 是有逗号分隔的value组成, 这里 % 是Pegex提供的一个操作符,表示用。。分隔的意思,也是来自Perl6的
第三句定义value 是一个double 或者plain
第四句定义double 是一个由双引号扩起来的字符串
第五句定义plain 是一个非逗号,双引号, 换行,空白 等等的 字符串
我们可以看到Pegex的定义本质上也是用的正则. 用DOUBLE, COMMA, EOL 这样的直白的名字来代替'",\r?\n', 用定义rule来将整个语法拆分成每个部分,在写比较复杂的正则表达式时会非常清晰,相信大家都有看一个非常复杂的正则时的头痛。
语法定义好了,我还需要定义一个接受器(Receiver), Pegex提供一个基类Pegex::Tree,供你在此基础上定义你的接收器。
下面是一个CSV的接收器,将解析过的csv转化为一个二维数组的形式(List of List)。
package Pegex::CSV::LoL;
use Pegex::Base;
extends 'Pegex::Tree';
sub got_row {
my ($self, $got) = @_;
$self->flatten($got);
}
sub got_value {
$_[1] =~ s/(?:^"|"$)//g;
$_[1] =~ s/""/"/g;
return $_[1];
}
got_* 方法用来定义当Parser捕获了一个相应的rule后进行的操作, *对应之前在语法定义时的rule名字。
比如这里got_value 就是把得到的value的双引号去掉, flatten是将一个多维数组展平成一个一维数组。
Grammar和Receiver都定义好之后,整个工作就算完成了,可以用定义好的语法和接受器来读csv文件了。
my $parser = Pegex::Parser->new(
grammar => Pegex::CSV::Grammar->new,
receiver => Pegex::CSV::LoL->new,
);
$parser->parse($csv);
我现在用到Pegex地方主要是解析一些各种各样格式的数据, 以及为配置文件定义自己的语法DSL。相比之前用正则和循环来写, 一个是工作量降低, 基本是语法定义好就可以用了的节奏, Pegex为你做了语法解析的事情, 剩下的只是写Receiver得到想要的数据类型。 另一个是代码干净了许多, 后面维护起来很方便。
Moose 大家庭
perl的原生的面向对象是比较简单的,通过bless关键字和hash来建立对象,很灵活但也很简陋
自从出现了Moose之后,perl的面向对象就很富有表现力了。
Moose -> Moos -> Moo -> Mo -> M, 再加上Mouse, 构成了Moose大家庭
从Moose到M依次功能递减, M 代表 nothing, 在不太复杂的项目一般用Moo就足够了
这里简单介绍一下Moo的用法
Moo -- 轻量级面向对象
比如我要建立一个Person对象,用Moo可以这么写:
package Person;
use Moo;
has name => is => 'rw';
has age => is => 'rw';
sub intro {
my $self = shift;
print "My name is ". $self->name;
}
在你的代码里就可以这么建立一个Person对象:
use Person;
my $lip = Person->new(name => 'lip', age => 18 );
$lip->intro();
和perl的原生OOP一样,一个package就是一个对象。
Moo提供了一个has
关键字,用来定义属性
has name => is => 'rw';
表示属性name
是 可读可写 的,如果写成'ro'
就表示是只读的
sub intro
定义了一个方法, $self
是指代实例本身, $self->name
就可以得到name属性的值
下面详细介绍一些Moo的特性
属性定义 -- has
has attr => is => 'rw', default => 'default';
or
has attr => (
is => 'rw',
default => 'default',
)
因为小括号只是起到分组的作用,所以两种写法都是一样的。
has 的 options 有:
is(必要的) : 可以是'rw' 读写, 'ro' 只读, 'lazy' 惰性
isa : 用来定义这个属性的类型, 比如:
isa => sub { die "$_[0] is not a number!" unless looks_like_number $_[0] }
就确保该属性是一个数字。
default: 默认值,需要注意的是这里一般是传个匿名函数作为值,比如你要默认值为一个空数组引用:
default => sub { [] };
builder : 这个一般配合lazy使用,在lazy=1时,属性的值不会在对象建立的时候建立,而是在第一次求值的时候调用builder来计算
lazy : 如上
reader/writer : 你可以定义一个reader给属性, 比如 get_attr, 定义一个writer为 set_attr
只是相比$self->attr既是读又是写,来的更浅显
对象继承 -- extends
方法修改器 -- before after around
perl 变量类型与作用域
根据作用域的不同来区分,perl有两种变量类型:
有三种创建变量的方式:
下面我们详细说一下这几种方式
直接使用 -- 包变量
$foo = 1;
在perl早期的时候,大家都是这么定义,使用变量的。
这么定义变量$foo将会是全局变量,也就是在程序的任何位置都能访问到它。
这样当随着代码规模的增长,会引起一个严重的问题,变量污染,比如:
$foo = 1; # $foo -> 1
f();
# $foo -> 2
sub f {
$foo = 2;
}
如果不小心命名,则很容易在某个意想不到的位置,变量$foo被赋值,出现bug。
所以 perl5 引进了strict 和warnings 两个pragma,在代码上加上use strict后,所有没有
直接声明而使用的变量都会报错。
所以现在这种用法已经废弃了,在一些教程书中会这么开始教学,但这种用法在实际代码中是绝对不建议再使用的。
取而代之的是 my
my -- 词法变量
my $foo = 1;
my 的出现,极大的解决了变量污染的问题,因为my所声明的变量是 -- 局部变量,词法作用域。
局部变量的概念,比较好理解,比如:
my $foo = 1; # $foo -> 1
f();
# $foo -> 1
sub f {
my $foo = 2 # $foo -> 2
}
在函数f中的$foo 之在局部范围内起作用,出了函数之后,就不再起作用,所以也就不会影响到外部的$foo
需要理解的是这里的词法作用域,词法作用域限制了局部变量的名字生存的范围,这里一个词法作用域,基本上就是一个BLOCK,也就是一个花括号阔住的范围。
所以,只要有一个BLOCK就会建立一个词法作用域,比如:
my $foo = 1; # $foo -> 1
f();
# $foo -> 1
sub f {
my $foo = 2 # $foo -> 2
if ( 1 ) {
my $foo = 3 # $foo -> 3
}
}
这里在函数f内部的if语句中,建立一个新的词法作用域,同样对这里的$foo赋值不会影响到外部的$foo
my基本上已经够用了,然而由于之前提到的strict 和warnings ,导致不能使用全局变量。
因此出现了our
our -- 伪装成词法变量的包变量
our $foo = 1;
our 的出现解决了不能使用全局变量的问题,our所声明的变量是全局变量的词法作用域的别名。
意思就是,这里$foo是$main::foo的别名,如果是在模块MyModule中声明的话,就是$MyModule::foo.
但是它同时又是词法作用域的,所以在词法作用域范围外无法访问到它,只能通过模块变量名$MyModule::foo来访问
可以说 our 所声明的变量是伪装成词法变量的全局变量。
举个例子:
package A;
{
our $foo = 1;
print $foo; # 1
}
print $foo; # error
print $A::foo; # 1
package B;
print $A::foo; # 1
这里在花括号之外将无法访问到$foo, 但能通过模块变量名来访问 $A::foo
在模块B中,也能通过$A::foo来访问模块A所定义的变量。
local -- 包变量的临时赋值
local $foo = 1;
local 并不创建一个新的变量,而是对已有的包变量临时赋予一个值,在退出当前scope后将原来的值还回去。
$foo = 1; # $foo -> 1
{
local $foo = 2; # $foo -> 2 将原先的$foo藏起来,赋予一个临时的值
}
$foo # $foo -> 1 将藏起来的值还回去
看起来有点像my,local确实是在my出现以前的解决方案,在my出来之后,使用的情形也就不多了。
比较有意思的是,local是动态作用域,什么意思呢? 我们比较一下下面两个代码:
my $foo = 1;
{
my $foo = 2;
f();
}
sub f {
print "foo is $foo";
}
结果是
foo is 1
因为词法作用域的$foo对函数f也是不可见的,函数f看到的是在最外层的$foo,而local就不同了
$foo = 1;
{
local $foo = 2;
f();
}
sub f {
print "foo is $foo";
}
结果是
foo is 2
这就是动态作用域,对一般的代码来说,local的适用范围并不广,因为本身全局变量就是不提倡使用的,而local只对全局变量起作用,
可能有些时候你需要对内建的全局变量比如 $/, $_ 临时赋值,这时候会用到local。
state -- 词法变量,静态变量
state $foo = 1;
state比较好理解,它就像c中的静态变量一样,只在第一次调用时候创建该变量,之后都是操作的同一个变量
state也是词法变量,所以只在定义该变量的词法作用域中有效,举个例子:
sub count {
state $counter = 0;
$counter++;
return $counter;
}
say count();
say count();
say count();
总结
因为perl的向下兼容性做的非常好,所以perl也抗了很多历史包袱,比如这里提到的不声明直接使用的全局变量。
现在提倡的用法是变量都用my声明,在需要全局包变量的情况下,用our声明,所有代码前都要加上use strict 和use warnings
这两个pragma, 在某些特定的时候会需要local 和 static 。
使用Emacs的时候我总是习惯分三个窗口,Code一个,org一个,dir一个
每次启动Emacs都要C-x-3 C-x-2来建立窗口,后面就自己尝试写了一点Elisp
来每次启动的时候自动分割窗口,如下,
(add-hook 'window-setup-hook (lambda () (split-window-horizontally)
(select-window-2)
(split-window-vertically) )
)
把这段代码放到.emacs文件里或者.emacs.d里的配置文件就可以了