perl tricks

Tags:

这是我在知乎上的答题。

// 操作符

$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 ... }

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 语法解析模块

Tags:

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得到想要的数据类型。 另一个是代码干净了许多, 后面维护起来很方便。

用Moo来写面向对象perl

Tags:

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 变量 my our local state

Tags:

perl 变量类型与作用域

根据作用域的不同来区分,perl有两种变量类型:

  • 词法变量(Lexical Variable) -- 词法作用域,只在当前词法作用域内可见。

  • 包变量(Package Variable) -- 全局变量,在任何位置可见

有三种创建变量的方式:

  • my -- 创建一个词法变量

  • our -- 创建一个词法变量,但实际上是包变量的别名,或者说是伪装成词法变量的包变量

  • 直接使用 -- 不加任何声明的使用一个变量,将创建一个包变量

下面我们详细说一下这几种方式

直接使用 -- 包变量

$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 自动分割窗口

Tags:

使用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里的配置文件就可以了