Types and Programming Languages

28 September 2005


When I surf the "Announcement of Newest Books" of the School Library today, I find the "Types and Programming Languages" (《类型和程序设计语言》)written by Benjamin C. Pierce, translated by 马世龙 & 睦跃飞 etc. and published by "Publishing House of Electronics Industry"(电子工业出版社)。

If you are interested in Pugs, u should know that Autrijus Tang suggests this book in "Pugs Apocryphon 1
Overview of the Pugs project
. Quoted as follows:

Where can I learn more about implementing programming languages?

Types and Programming Languages is an essential read; Pugs started out as a self-initiated study of the text, and it continues to be an important guide during the implementation. Its sequel, Advanced Topics in Types and Programming Languages, is also invaluable. It may also help to get acquainted with other multi-paradigmatic languages, such as Mozart/Oz, Curry and O’Caml. Finally, the detailed GHC commentary describes how GHC itself was implemented.

But when I start to read it, I must admit that it's too difficult to understand, especially for an advertisement student.
I realise that my computer knowledge is so limited. It's just another world, different with what I know but worth learning.
Now design a language is in my TODO list. Though it's hard, I'll repick this book one day.

update

u can buy it from http://www.china-pub.com/computers/common/info.asp?id=24942

用 Perl6::Rules 来写 Formatter

27 September 2005


简述

我用不太习惯 Kwiki 或 Textile 格式。以为我经常要用到模块,所以我更喜欢将
L<Perl6::Rules> 直接映射到 http://search.cpan.org/perldoc?Perl6::Rules

或者我拥有一些朋友,如果程序能自动映射他们的名字到他们的主页那将是比较美好的一件事。
当然还有很多。这些毫无疑问属于定制/customize, 只好自己写代码了。

写这些东西无非是正则表达式替换而已。于是想着试试 Perl6 代码。到 CPAN 上下了 Damain Conway 的 Perl6::Rules 回来。实话说,这模块真是很混,没有很好的完成 Perl6 的 Grammar & Rule, 写起来及其不顺手。
也没啥好办法,只好先折腾了。权当练习 Perl6 了。
说实话,我水平也不咋的。这练习也不考虑什么性能或者更简练的方法了。如有错误可以发 mail 给我。 Thanks.

Exercise

首先来完成自动映射模块:
$text =~ s:g{
(<ws>)L\<([\w|\:]+)\>(<ws>)
}{
$1<a href="http://search.cpan.org/perldoc\?$2">$2</a>$3
};

简单解释下代码:s:g{}{}; 是 s{}{}g 的新版。
<ws>
是一个 rule, 表示空白(可以匹配新行)。

其次来实现常用字映射:

my %mapping_word = (
'chunzi.org">chunzi' => 'blog.chunzi.org',
'joe jiang' => 'www.livejournal.com/users/joe_jiang/',
'cnhackTNT' => 'www.wanghui.org',
'CPAN' => 'search.cpan.org',
);
# my/our/local $p; IS FAILED!
# BE CAREFUL! must be $main::p!
foreach $main::p (keys %mapping_word) {
$text =~ s{\b$::p\b}{<a href="http://$mapping_word{$main::p}">$main::p</a>};
}

本应该先用 split 将 $text 按词分开,然后用 map 将词替换再 join 回去。这应当比正则要效率高一点。
这里有一让我感到混的就是 my/our $p; 怎么都不行,害我调试了老半天才知道。郁闷。
简单解释下代码:这里的 $::p 是变量内插。在 Perl6::Rules 里变量内插必须是 $:: @:: %:: 什么的。
另外注意的是要 $main::p; 不能用 my/our/local $p;
变量内插的是 main 里的变量。而不是当前包的变量。

我比较喜欢 Kwiki 的 = == === ====
变量内插尽然在下面的代码中运行不起来:

# = H1 == H2
# YAP!! FAILED!!!
#for my $n (1 .. 4) {
# $text =~ s:g{[^|\n]=<$::n><ws>(.+?)\n}{\<h$n>$1</h>};
#}
$text =~ s:g{[^|\n]=<1><ws>(.+?)\n}{\<h1>$1</h1>};
$text =~ s:g{[^|\n]=<2><ws>(.+?)\n}{\<h2>$1</h2>};
$text =~ s:g{[^|\n]=<3><ws>(.+?)\n}{\<h3>$1</h3>};
$text =~ s:g{[^|\n]=<4><ws>(.+?)\n}{\<h4>$1</h4>};

迫不得已只好重复写了四次。:(

其他的一些诸如 URL映射,code 代码块 < 自动替换啥的也不说了。有些类如 * li 的也还没实现。
革命尚未成功,同志还需努力呀。


Catalyst Authenticate

26 September 2005


针对不同的情况,Catalyst 提供了十几种验证用户的方法(详细的 svn catalyst)。
一般来说,我们都将用户名和密码存进一个 table 中(如表 users, 字段 usr/pwd 例子 fayland/123456)。
这次我使用 Catalyst::Plugin::Authenticate::CDBI 来验证用户。这个 plugin 非常简单。很容易就看懂源代码。
CPAN 中可能没有,不过 Catalystsvn 目录中有。

假设我有一个 catalyst FForum 工程。根据 Catalyst::Plugin::Authenticate::CDBI 的指导,我们先在 FForum.pm 中的 config 下(或里) setup 上加进

# Authenticate::CDBI
FForum->config->{authenticate}->{cdbi} = {
    class    => 'FForum::M::CDBI::Users',
    username => 'usr',
    password => 'pwd'
};

这里我们得生成一个 model CDBI::Users.
然后我们生成一个 perl script/fforum_server.pl controller Login, 然后在 Login.pm 中:

sub in : Local {
    my ( $self, $c ) = @_;
    
    # Retrieve params of username/password
    my $username = $c->req->param('username');
    my $password = $c->req->param('password');
    
    if ( $c->authenticate_cdbi($username, $password) ) {
        # successful authentication
        $c->res->output('Login!');
    } else {
        $c->res->output('Failed!');
    }
}

这样当我们访问 http://fayland:3000/login/in?username=fayland&password=123456 时就会显示 Login!
而 http://fayland:3000/login/in?username=fayland&password=12345 时就会显示 Failed!
在我们的命令行调式器中会显示错误的原因。比如这次为 User 'fayland' credentials is invalid'.

这里只是用最简单的直接调用 URL, 一般要详细添加用户登陆界面。
写本文并无其他用意,只是告诉各位如果碰到这种情况还可以考虑这种方法。

如果有时间我会介绍下 Session, 这个或许更有趣一些。


Test::Tutorial 译文

22 September 2005


NAME

Test::Tutorial - 如何写一个真正意义上的基本测试文件

DESCRIPTION

AHHHHHHH!!!! NOT TESTING! Anything but testing! Beat me, whip me, send me to Detroit, but don't make me write tests!

sob

Besides, I don't know how to write the damned things.

Is this you? Is writing tests right up there with writing documentation and having your fingernails pulled out? Did you open up a test and read

######## We start with some black magic

and decide that's quite enough for you?

It's ok. That's all gone now. We've done all the black magic for you. And here are the tricks...

Nuts and bolts of testing.

这可能是个最基础的测试程序:

#!/usr/bin/perl -w

print "1..1\n";

print 1 + 1 == 2 ? "ok 1\n" : "not ok 1\n";

由于 1+1 等于 2, 所以它输出:

1..1
ok 1

它的意思为: 1..1 表示“我们将要运行一个测试”,而 ok 1 表示第一个测试已通过。 这就是有关测试的所有魔法。你的基本测试单元就是 ok. 对于每一个你测试的东西,都有一个 ok 被输出。 这很简单。Test::Harness 运行你测试的结果,然后告诉你成功或是失败了(更多的期待下文)。

经常写这些 print 语句会让人很快就觉得乏味。幸运的是我们有 Test::Simple. 它有一个函数,ok().

#!/usr/bin/perl -w

use Test::Simple tests => 1;

ok( 1 + 1 == 2 );

它与上面的代码所做的是一样的。ok() 是 Perl 测试的脊椎,从此时起我们都用它来代替自己手写的部分。 如果 ok() 获得一个 true 值,测试通过。false, 则失败。

#!/usr/bin/perl -w

use Test::Simple tests => 2;
ok( 1 + 1 == 2 );
ok( 2 + 2 == 5 );

这会输出:

1..2
ok 1
not ok 2
#     Failed test (test.pl at line 5)
# Looks like you failed 1 tests of 2.

1..2 表示“我们要运行两个测试”。这个数字用以确定你的测试程序运行了所有的测试而没有中途 die 或跳过某些测试。 ok 1 表示“第一个测试通过”。 not ok 2 表示“第二个测试失败”。 Test::Simple 还能输出一些额外的注释。

It's not scary. Come, hold my hand. 我们将要对一个模块测试给出一个完整的例子。 在此,我们将测试一个日期库,Date::ICal. 它是 CPAN 的一部分,所以我们可以下载它然后跟着我来做。

从哪开始?

这是关于测试最困难的部分,我们从哪开始? 要测试一个完整的模块,人们通常会被这个任务外表看起来的巨大所压倒。 最好开始的位置就是开头。Date::ICal 是一个面对对象模块,这就意味着你可以从定义一个对象开始。所以我们先测试new().

#!/usr/bin/perl -w

use Test::Simple tests => 2;

use Date::!ICal;

my $ical = Date::ICal->new;         # create an object
ok( defined $ical );                # check that we got something
ok( $ical->isa('Date::ICal') );     # and it's the right class

运行它你就会得到:

1..2
ok 1
ok 2

祝贺你,你已经完成了你第一个有用的测试。

Names

这输出看起来不是很有描述性?当你只有 2 个测试时你能指出哪个是第 2 个,但如果你有 102 个呢?

我们可以给每一个测试一点描述性文字作为 ok() 的第二个参数。

use Test::Simple tests => 2;

ok( defined $ical,              'new() returned something' );
ok( $ical->isa('Date::ICal'),   "  and it's the right class" );

现在你会看到:

1..2
ok 1 - new() returned something
ok 2 -   and it's the right class

测试说明手册

最简单的创建一个象样的测试工具是只测试那些说明手册上它做的。 让我们从 Date::ICal/SYNOPSIS 中抽取点什么然后一个片断一个片断的测试它。

#!/usr/bin/perl -w

use Test::Simple tests => 8;

use Date::ICal;

$ical = Date::ICal->new( year => 1964, month => 10, day => 16, 
                         hour => 16, min => 12, sec => 47, 
                         tz => '0530' );

ok( defined $ical,            'new() returned something' );
ok( $ical->isa('Date::ICal'), "  and it's the right class" );
ok( $ical->sec   == 47,       '  sec()'   );
ok( $ical->min   == 12,       '  min()'   );    
ok( $ical->hour  == 16,       '  hour()'  );
ok( $ical->day   == 17,       '  day()'   );
ok( $ical->month == 10,       '  month()' );
ok( $ical->year  == 1964,     '  year()'  );

运行它你会得到:

1..8
ok 1 - new() returned something
ok 2 -   and it's the right class
ok 3 -   sec()
ok 4 -   min()
ok 5 -   hour()
not ok 6 -   day()
#     Failed test (- at line 16)
ok 7 -   month()
ok 8 -   year()
# Looks like you failed 1 tests of 8.

Whoops, 一个失败!Test::Simple 有用地指出了错误发生在哪一行,但没有更多的东西。 我们期望得到 17,但是没得到。那我们得到了什么呢?我不知道。 我们得在调试器中重新运行此测试或者得依靠 print 语句找出我们所得到的。

我们这里不这么做,这里我们用 Test::More 来替代 Test::Simple. Test::More 能做所有 Test::Simple 能做的,还有它所不能做的。 事实上, Test::More 与 Test::Simple 所使用的方法是/一模一样的/。 你可以简单的用 Test::More 替换 Test::Simple 所在的位置。 这也就是我们接下来要做的。

Test::More 比 Test::Simple 做得更多。在这一点上,一个最重要的区别是它在输出 "ok" 时提供了更多的信息。 虽然你几乎可以用一般的 ok() 来写任何一个测试,但是它不能告诉你到底哪来出错了。 相反的,我们将使用函数 is(), 它让我们声明我们期待与另一个是否一样:

#!/usr/bin/perl -w

use Test::More tests => 8;

use Date::ICal;

$ical = Date::ICal->new( year => 1964, month => 10, day => 16, 
                         hour => 16, min => 12, sec => 47, 
                         tz => '0530' );

ok( defined $ical,            'new() returned something' );
ok( $ical->isa('Date::ICal'), "  and it's the right class" );
is( $ical->sec,     47,       '  sec()'   );
is( $ical->min,     12,       '  min()'   );    
is( $ical->hour,    16,       '  hour()'  );
is( $ical->day,     17,       '  day()'   );
is( $ical->month,   10,       '  month()' );
is( $ical->year,    1964,     '  year()'  );

“$ical->sec 是否 47?”“$ical->min 是否 12?” 适当的使用 is(), 你会得到更多的信息:

1..8
ok 1 - new() returned something
ok 2 -   and it's the right class
ok 3 -   sec()
ok 4 -   min()
ok 5 -   hour()
not ok 6 -   day()
#     Failed test (- at line 16)
#          got: '16'
#     expected: '17'
ok 7 -   month()
ok 8 -   year()
# Looks like you failed 1 tests of 8.

这样我们知道了 $ical->day 返回了 16, 而我们期待的是 17。一个快速检查让我们发现代码运行是正常的,我们在写测试的时候犯了个小错误。所以我们将测试改一下:

is( $ical->day,     16,       '  day()'   );

这样所有的都运行通过了。

总之,当你在做这些“这个等于那个”类的测试时,使用 is() 会比较好。 它还能在数组上使用。测试总是在标量上下文下,所以你可以用这种方法来测试列表中包含了多少个元素。

is( @foo, 5, 'foo has 5 elements' );

有时候测试是错误的

上面我们有了一个重大的教训。代码是有 bugs 的,而测试也是代码。因此,测试也可能有 bugs 的。一个失败的测试意味着代码中有一个 bug, 但是小心考虑有可能是测试写错了。

另一方面,不要冲动地过早宣称某一测试是错误的,而原因只是你找不到 bug. 不要轻易的让某一测试无效,也不要让它作为你逃避工作的借口。

测试大量的值

接下来我们将要测试大量的数据,在许多不同的边缘情况下试图对代码做一些测试。测试它是否在 1970 年前也是可行的?2038 年后呢,或者 1904?10,000 年会不会出错?闰年正确吗?我们可以重复上述的代码,或者更好的建立一个 try/expect 循环。

use Test::More tests => 32;
use Date::ICal;

my %ICal_Dates = (
        # An ICal string     And the year, month, date
        #                    hour, minute and second we expect.
        '19971024T120000' =>    # from the docs.
                            [ 1997, 10, 24, 12,  0,  0 ],
        '20390123T232832' =>    # after the Unix epoch
                            [ 2039,  1, 23, 23, 28, 32 ],
        '19671225T000000' =>    # before the Unix epoch
                            [ 1967, 12, 25,  0,  0,  0 ],
        '18990505T232323' =>    # before the MacOS epoch
                            [ 1899,  5,  5, 23, 23, 23 ],
);


while( my($ical_str, $expect) = each %ICal_Dates ) {
    my $ical = Date::ICal->new( ical => $ical_str );

    ok( defined $ical,            "new(ical => '$ical_str')" );
    ok( $ical->isa('Date::ICal'), "  and it's the right class" );

    is( $ical->year,    $expect->[0],     '  year()'  );
    is( $ical->month,   $expect->[1],     '  month()' );
    is( $ical->day,     $expect->[2],     '  day()'   );
    is( $ical->hour,    $expect->[3],     '  hour()'  );
    is( $ical->min,     $expect->[4],     '  min()'   );    
    is( $ical->sec,     $expect->[5],     '  sec()'   );
}

所以现在我们想测试更多的数据时只需要将它们加入 %ICal_Dates 就可以了。 既然测试更多的数据是个很轻松的工作,那你将更倾向于测试比你所想的更多的数据。 唯一的问题是,每一次我们加入数据都需要更改 use Test::More tests => ##> 行。 这很快就让人厌烦。但我们有两种办法让生活更美好。

第一种,我们可以通过 plan() 函数动态地计算运行的项目。

use Test::More;
use Date::ICal;

my %ICal_Dates = (
    ...same as before...
);

# For each key in the hash we're running 8 tests.
plan tests => keys %ICal_Dates * 8;

或者更灵活的,我们使用 no_plan. 这意味着我们要运行很多测试,但不知道到底有多少。

use Test::More 'no_plan';   # instead of tests => 32

现在我们就能直接加进测试而不需要数出我们到底要运行多少项了。

Informative names

看一下下面这行:

ok( defined $ical,            "new(ical => '$ical_str')" );

我们已经对我们所要进行的测试加进了更多的细节, and the ICal string itself we're trying out to the name. 所以你得到类如下面的结果:

ok 25 - new(ical => '19971024T120000')
ok 26 -   and it's the right class
ok 27 -   year()
ok 28 -   month()
ok 29 -   day()
ok 30 -   hour()
ok 31 -   min()
ok 32 -   sec()

如果这里的某个测试失败了,那么我们就知道到底是哪一个,这样使跟踪问题变得简单。 所以请试着在测试名称中加入更多的调试信息。

描述我们在进行什么测试,这让调试一个失败的测试变得容易,不论是对于你或是任何运行你测试的人来说。

跳过测试

在已有的 Date::ICal 测试文件中找寻,我们会在 t/01sanity.t 中发现:

#!/usr/bin/perl -w

use Test::More tests => 7;
use Date::ICal;

# Make sure epoch time is being handled sanely.
my $t1 = Date::ICal->new( epoch => 0 );
is( $t1->epoch, 0,          "Epoch time of 0" );

# XXX This will only work on unix systems.
is( $t1->ical, '19700101Z', "  epoch to ical" );

is( $t1->year,  1970,       "  year()"  );
is( $t1->month, 1,          "  month()" );
is( $t1->day,   1,          "  day()"   );

# like the tests above, but starting with ical instead of epoch
my $t2 = Date::ICal->new( ical => '19700101Z' );
is( $t2->ical, '19700101Z', "Start of epoch in ICal notation" );

is( $t2->epoch, 0,          "  and back to ICal" );

新纪元的开头部分在大多数非 Unix 系统中是不一样的。 即使 Perl 对大部分差异进行了处理,但还是有某些地方仍然是不一样的。 MacPerl 是让我头疼的其中一种。我们知道上面的代码在 MacOS 中是不能运行的。 所以与其在每个测试前注释掉,我们还不如直接说这不能运行然后跳过这个测试。

use Test::More tests => 7;
use Date::ICal;

# Make sure epoch time is being handled sanely.
my $t1 = Date::ICal->new( epoch => 0 );
is( $t1->epoch, 0,          "Epoch time of 0" );

SKIP: {
    skip('epoch to ICal not working on MacOS', 6) 
        if $^O eq 'MacOS';

    is( $t1->ical, '19700101Z', "  epoch to ical" );

    is( $t1->year,  1970,       "  year()"  );
    is( $t1->month, 1,          "  month()" );
    is( $t1->day,   1,          "  day()"   );

    # like the tests above, but starting with ical instead of epoch
    my $t2 = Date::ICal->new( ical => '19700101Z' );
    is( $t2->ical, '19700101Z', "Start of epoch in ICal notation" );

    is( $t2->epoch, 0,          "  and back to ICal" );
}

这里有一点点小魔术。当我们不是运行在 MacOS 时,所有的测试项都会正常运行。但是在 MacOS 时, skip() 将跳过整个 SKIP 块的内容,它将不会被运行。还有,它将输出一些特定的东西告诉 Test::Harness 这些测试项已经被跳过。

1..7
ok 1 - Epoch time of 0
ok 2 # skip epoch to ICal not working on MacOS
ok 3 # skip epoch to ICal not working on MacOS
ok 4 # skip epoch to ICal not working on MacOS
ok 5 # skip epoch to ICal not working on MacOS
ok 6 # skip epoch to ICal not working on MacOS
ok 7 # skip epoch to ICal not working on MacOS

这就意味着你的测试项在 MacOS 中并非是失败的。这也意味着更少的 MacPerl 用户会发 emails 告诉你这些测试项失败了,而你知道这些是不可运行的。但是你需要对跳过的测试项加以注意。运行不正常和/不可运行/是不同的。它不是用于跳过真正的 bugs (我们马上就要讲了)。

这些所有的测试项将被完整的跳过。下面这样是可行的。

SKIP: {
    skip("I don't wanna die!");

    die, die, die, die, die;
}

Todo 测试项

翻译 Date::ICal 用户手册,我看到了:

ical

    $ical_string = $ical->ical;

Retrieves, or sets, the date on the object, using any
valid ICal date/time string.

“找回或设置”。Hmmm, 没有看见这个测试项:在 Date::ICal 测试套中用 ical() 来设置日期。所以我写了一个:

use Test::More tests => 1;
use Date::ICal;

my $ical = Date::ICal->new;
$ical->ical('20201231Z');
is( $ical->ical, '20201231Z',   'Setting via ical()' );

运行,然后得到:

1..1
not ok 1 - Setting via ical()
#     Failed test (- at line 6)
#          got: '20010814T233649Z'
#     expected: '20201231Z'
# Looks like you failed 1 tests of 1.

Whoops! 看起来这个是还没被实现的。让我们假设我们没有时间来修复它。 一般情况下,你可能直接将其注释掉或者在 todo 列表或其他地方加以说明。 这里,我们将直接通过将其放在 TODO 块中来声明“这个测试项将会失败”。

use Test::More tests => 1;

TODO: {
    local $TODO = 'ical($ical) not yet implemented';

    my $ical = Date::ICal->new;
    $ical->ical('20201231Z');

    is( $ical->ical, '20201231Z',   'Setting via ical()' );
}

现在当你运行的时候,会有一点点不同:

1..1
not ok 1 - Setting via ical() # TODO ical($ical) not yet implemented
#          got: '20010822T201551Z'
#     expected: '20201231Z'

Test::More 不再说“看起来你在 1 个测试中失败了 1 个”/"Looks like you failed 1 tests of 1"。 '# TODO' 告诉了 Test::Harness “这本就该是失败的”,然后它会将这个失败认为是正常的。 这样你就可以在你修复代码前写下测试。

如果一个 TODO 测试项通过了,Test::Harness 会报告 “未预料的成功”/"UNEXPECTEDLY SUCCEEDED". 当这个发生时,你只需要将 TODO 块和 local $TODO 删除掉。剩下来的就是一个真正的测试项了。

=head2 在污染 taint 模式下测试

Taint 污染模式是一件有趣的事。It's the globalest of all global features. 当你打开它时,它会影响你程序中/所有的/代码和/所有的/模块(和它们使用的模块)。 只要某一单独的代码片断不是 taint clean, 所有的事情都会被激发。在此观念下,保证你的模块在 taint 模式下运行是非常重要的。

设置在 taint 模式下运行代码是非常简单的。只要在 #! 行中增加一个 -T. Test::Harness 会读取此开关然后在你的测试中使用它。

#!/usr/bin/perl -Tw

...test normally here...

当 make test 时,它会运行在 taint 和 warnings 模式下。

AUTHORS

Michael G Schwern (schwern at pobox.com) and the perl-qa dancers!

本文由 Fayland (fayland at gmail.com) 翻译。

COPYRIGHT

Copyright 2001 by Michael G Schwern (schwern at pobox.com).

This documentation is free; you can redistribute it and/or modify it under the same terms as Perl itself.


Tips from Perl Best Practices, Part 2

22 September 2005


没时间看 Perl Best Practices 或不喜欢看英文的可以看看本文。
我还是推荐看原版书的。因为所有的 practices, Damain Conway 都解释了为什么。而我只是把它们摘录一些出来。

  • Don't use bareword filehandles, Use indirect filehandles.
    不要使用裸字句柄,而使用间接句柄。
    open FILE, '<', $filename
        or croak "Can't open '$filename': $OS_ERROR";
    open my $FILE, '<', $filename
        or croak "Can't open '$filename': $OS_ERROR";
    $next_line = <$FILE>;
    二者的区别在于裸字句柄纪录在符号表中,而间接句柄只定义了一个词法作用域变量。
    裸字句柄你无法知晓是否会构成冲突,而间接句柄却很明显,一旦打开了 warnings ,重复定义就会引发警告:
    "my" variable $FILE masks earlier declaration in same scope
  • Always put filehandles in braces within any print statement.
    在任何 print 语句中用大括号将句柄括起来。
    print $file $name, $rank, $serial_num, "\n";
    print {$file} $name, $rank, $serial_num, "\n";
  • 关于测试/test
    1. 写长代码之前先写短测试
    2. 使用 Test::Simple 或 Test::More 来写测试 *.t 文件
    3. 使用 Test::Harness/prove 来运行目录下所有 *.t 文件
    这就是一个简单的测试流程。当你添加或修改一个测试文件后,简单的再次运行 prove -r 就可以了。(prove 是使用 Test::Harness 的一个程序,在 bin 目录下)
    它会自动搜索目录和子目录下的 *.t 文件,然后输出测试结果。

LAST

抱歉,实在摘不了什么东西了。其他太复杂,我也是半懂不懂,所以就免摘了。
Perl Best Practices 我也大致看完了一遍(当然是跳着看的)。
怎么讲呢,这么多 practices 中让我觉得有可以采纳的地方又有不以为然的地方。
比如他讲类的时候花了很多篇幅讲他写一个模块 Class::Std, 虽然这模块真的非常棒,但我总感觉使不上力。
这本书用我的话讲就是不是标准配备书(必备书),更适合于扩展自己对 Perl 某些地方或细节的理解。
我个人比较敬佩 Damian Conway 先生,如果此书出中文版,我还是很乐意掏钱买一本的。


Scalar::Util, List::Util, List::MoreUtils

14 September 2005


Util 是 Utility 的缩写,意为有用的工具。
此三模块以前看过但没用过,昨晚在 Perl Best Practices 里听 Damain Conway 又提及。觉得用处挺大的,就简单翻译下用途。需要的参阅 perldoc, 此三模块在 5.8 后为标准模块,安装 Perl 后就有了。

Scalar::Util

blessed $scalar

如果 $scalar 是一个对象的引用,则返回这个对象的类的名字,否则返回 undef

refaddr $scalar

如果 $scalar 是一个引用,则返回一个数字代表该引用的所指向的内存地址,否则返回 undef
这个可以对两个变量是否同一提供判断。

reftype $scalar

如果 $scalar 是一个引用,则返回相应的类型值(如 'SCALAR', 'HASH', 'ARRAY', 'CODE', 'Regexp'),否则返回 undef

readonly $scalar

如果 $scalar 只读返回真

openhandle $scalar

如果不是一个文件句柄或着该文件句柄没有开着的话,返回 undef
use Scalar::Util qw/openhandle/;

open(FH, 'nonexist');
print openhandle(*FH); # 返回 undef

open(FILE, 'exist');
print openhandle(*FILE); # 返回如 GLOB(0x162538c)
close(FILE);
print openhandle(*FILE); # 返回 undef

weaken $scalar/is_weak $scalar

使用这个对防止循环数据结构的内存泄露有帮助。详细参阅该书和 perldoc

dualvar NUM, STRING

设置一个变量在数字环境和字符串环境下返回不同的值。
$foo = dualvar 10, "Hello";
$num = $foo + 2;                    # 12
$str = $foo . " world";             # Hello world

looks_like_number, set_prototype etc.

参阅书籍或 perldoc Scalar::Util

List::Util

first { <condition> } @list

返回 @list 中满足该 condition 的第一个元素。该函数与 grep 有点类似,但它是搜寻到第一个匹配时就停止了,而 grep 要搜索完整个 @list

max/min @list

返回数组中用 >/< 号比较后的最大最小值

maxstr/minstr @list

返回数组中用 gt/lt 比较后的最大最小值

shuffle @list

象 ipod shuffle 一样,随机在 @list 挑一个元素

sum @list

对整个 @list 求和,然后返回值

reduce BLOCK LIST

这个其实挺有用的。上面的这些 max, min, first, sum 等都可以用 reduce 来操作。
该函数首先将 LIST 中的第一和第二个元素赋予 $a 和 $b (与 sort 有点类似),然后执行 BLOCK, 将其返回值赋予 $a, 然后取第三个元素赋予 $b, 再执行 BLOCK, 就这样多次执行,然后返回值。
$foo = reduce { $a < $b ? $a : $b } 1..10; # 这句与 min 1..10 一样效果
$foo = reduce { defined($a) ? $a : defined($b) ? $b : undef } undef, @list; # 这句效果与 $foo = first { defined($_) } @list 同
my $overall_probablity = reduce { $a * $b } @partial_probabilities; # 将列表中的所有元素相乘

List::MoreUtils

all/any/notall/none { <condition> } @list

# @list 中有任意一个元素有定义
print "At least one value undefined" if any { !defined($_) } @list;

# @list 中所有的元素都有定义
print "All items defined" if all { defined($_) } @list;

# @list 中没有元素被定义,它是 any 的反义
print "No value defined" if none { defined($_) } @list;

# @list 中至少有一个元素没定义,它是 all 的反义
print "Not all values defined" if notall { defined($_) } @list;

first_index { <condition> } @list

返回第一个满足条件的列表索引号。last_index 返回最后一个满足的索引号。没有找到满足的都返回 -1
my @list = (1, 4, 3, 2, 4, 6);
printf "item with index %i in list is 4", firstidx { $_ == 4 } @list;
__END__
item with index 1 in list is 4

apply { <transform> } @list

对于每个 @list 中的元素执行 transform, 然后在列表环境下返回改变后的列表,在标量环境下返回最后一个值。
它与 map 有点类似,但是 apply 不改变原来 @list 的值。
my @list = (1 .. 4);
my @mult = apply { $_ *= 2 } @list;
print "\@list = @list\n";
print "\@mult = @mult\n";
__END__
@list = 1 2 3 4
@mult = 2 4 6 8
my @nice_words = apply { s/$EXPLETIVE/[DELETED]/gxms } @words;

# 这面的这句如果用 map 写的话
my @nice_words
    = map {
          my $copy = $_;
          $copy =~ s/$EXPLETIVE/[DELETED]/gxms;
          $copy;
      } @words;

after/before BLOCK LIST

返回满足 BLOCK 后面或前面的列表元素。如
@x = after { $_ % 5 == 0 } (1..9);    # returns 6, 7, 8, 9

而 after_incl/before_incl 也包括满足 BLOCK 的元素,如上面就会多一个 5

pairwise BLOCK ARRAY1 ARRAY2

ARRAY1 ARRAY2 对应平行的元素(设置成 $a 和 $b)做 BLOCK 后返回。如
@a = (1 .. 5);
@b = (11 .. 15);
@x = pairwise { $a + $b } @a, @b;	# returns 12, 14, 16, 18, 20

# mesh with pairwise
@a = qw/a b c/;
@b = qw/1 2 3/;
@x = pairwise { ($a, $b) } @a, @b;	# returns a, 1, b, 2, c, 3

如果数组元素个数不同的话,自己试验下。
注意这里的 $a 和 $b 是绑定数组元素,所以如果对 $a/$b 进行更改的话会对应修改数组。

zip/mesh @array1, @array2, ...

跟 Perl 6 中一样。不同数组依次取一个,然后循环。
@x = qw/a b c d/;
@y = qw/1 2 3 4/;
@z = zip @x, @y;	    # returns a, 1, b, 2, c, 3, d, 4

uniq @list

uniq 是 unique 的缩写,独一无二的。
my @x = uniq 1, 1, 2, 2, 3, 5, 3, 4; # returns 1 2 3 5 4
my $x = uniq 1, 1, 2, 2, 3, 5, 3, 4; # returns 5 不同的个数

Others

  • insert_after BLOCK VALUE LIST / 在满足 BLOCK 的列表元素后插入 VALUE
  • insert_after_string STRING VALUE LIST / 在等于 STRING 的列表元素后插入 VALUE
  • indexes BLOCK LIST / 与 first_index 不同,它返回一个由所有的索引号组成的列表

不想写太多了。详细的查阅文档。其实不是 Perl 不够强大,只是你没发现而已。


Tips from Perl Best Practices

13 September 2005


  • use Readonly instead of use constant.
    用 use Readonly 来代替 use constant

原因有二:一是 use constant 不能内插。例如:

# 用 Readonly 的两个例子
use Readonly;
Readonly my $DEAR      => 'Greetings to you,';
Readonly my $SINCERELY => 'May Heaven guard you from all misfortune,';

$msg = <<"END_MSG";
$DEAR $target_name

$scam_pitch

$SINCERELY

$fake_name
END_MSG

Readonly my $LINES_PER_PAGE => 42;
$margin{$LINES_PER_PAGE}                 # sets $margin{'42'}
    = $MAX_LINES - $LINES_PER_PAGE;
# 对应麻烦的用 constant
use constant (
    DEAR      => 'Greetings to you,',
    SINCERELY => 'May Heaven guard you from all misfortune,',
);

$msg = DEAR . $target_name
       . "$scam_pitch\n\n"
       . SINCERELY
       . "\n\n$fake_name";

use constant (
    LINES_PER_PAGE => 42
);

$margin{LINES_PER_PAGE}               # sets $margin{'LINES_PER_PAGE'}
    = MAX_LINES - LINES_PER_PAGE;

第二个原因是作用域范围。Readonly 能定义不同的作用域,而 constant 只有包作用域。

  • If you're forced to modify a package variable, localize it.
    如果需要改变一个包变量,请直接 local.
use YAML;

# Do
local $YAML::Indent = 4;

# NOT
$YAML::Indent = 4;

注意要初始化此变量。当 local 此变量时,它的值默认为 undef.

local $YAML::Indent; # 这里的值为 undef
local $YAML::Indent = $YAML::Indent; # 这样才可能是你想要的
  • Avoid subscripting arrays or hashes within loops.
    避免在循环中使用下标数组或散列。
# Do
for my $client (@clients) {
    $client->tally_hours(  );
    $client->bill_hours(  );
    $client->reset_hours(  );
}

# Do NOT
for my $n (0..$#clients) {
    $clients[$n]->tally_hours(  );
    $clients[$n]->bill_hours(  );
    $clients[$n]->reset_hours(  );
}
  • Use grep instead of for when searching for values in a list.
    当在列表中搜寻值时,首先考虑 grep 而不是 for.
    Use for instead of map when transforming a list in place.
    当在列表中改变其中的值时,首先考虑 for 而不是 map.
  • Use scalar reverse to reverse a scalar.
    使用 scalar reverse 来反转一个标量。
    在某些情况下,前面不加 scalar 也是可以成功的,但是很容易造成错误。如:
# 下两句是一样效果的
my $visible_email_address = reverse $actual_email_address;
my $visible_email_address = scalar reverse $actual_email_address;

# 但这两句效果不一样,子程序默认接受数组。所以可能就没有达到你要的目的。
add_email_addr(reverse $email_address); # 列表环境
add_email_addr(scalar reverse $email_address); # 标量环境
  • 对于在 sort 中重复运行多次的函数,考虑使用用散列缓存函数运行结果或使用 Memoize
# 对于如下代码
Use Digest::SHA qw( sha512 );

# Sort by SHA-512 digest of scripts
@sorted_scripts
    = sort { sha512($a) cmp sha512($b) } @scripts;

# 请用下面两则代替
my %sha512_of;

# Sort by SHA-512 digest of scripts
@sorted_scripts
    = sort { ($sha512_of{$a} ||= sha512($a))
                     cmp
             ($sha512_of{$b} ||= sha512($b))
           }
           @scripts;

# 或者更简洁
# Make the SHA-512 digest function self-caching...
use Memoize;
memoize('sha512');

# Sort by auto-cached SHA-512 digest of scripts...
@sorted_scripts = sort { sha512($a) cmp sha512($b) } @scripts;

Damain 还推荐了 Sort::Maker 来执行相关 sort 任务。

Finally

说实话我对英文也不是很感冒,所以读起来也是跳着读的。没怎么仔细看。所以也就这样了。

一种代码风格

13 September 2005


刚拿起盗版电子书《Perl Best Practices》在读。没机会买正版(没 Visa/MasterCard + 穷),也只好捧着盗版先读下了。
有机会还是挺想买正版书支持下 Damain Conway 先生的。

既然读着,也就随手翻译点,写点东西。不保证任何东西。

Mr Conway 在 Chapter 2 Code Layout 中讲了下代码布局风格,我看着挺好的,也值得推广下。

  • 括弧的风格,推荐第一种而不是二,三。
    # 推荐
    my @names = (
        'Damian',    # Primary key
        'Matthew',   # Disambiguator
        'Conway',    # General class or category
    );

    for my $name (@names) {
        for my $word ( anagrams_of(lc $name) ) {
            print "$word\n";
        }
    }
    # 不推荐这两种

    # Don't use BSD style...
    my @names =
    (
        'Damian',    # Primary key
        'Matthew',   # Disambiguator
        'Conway',    # General class or category
    );

    for my $name (@names)
    {
        for my $word (anagrams_of(lc $name))
        {
            print "$word\n";
        }
    }

    # And don't use GNU style either...

    for my $name (@names)
      {
        for my $word (anagrams_of(lc $name))
          {
            print "$word\n";
          }
      }
  • 将控制流程的关键字与紧跟后面的括弧分开(加一空格)。
    Separate your control keywords from the following opening bracket.
# 推荐
for my $result (@results) {
while ($min < $max) {
if ($value[$try] < $target) {

# 不推荐
for(@results) {
while($min < $max) {
if($value[$try] < $target) {
  • 不要将子程序或变量名与后面的开括弧分开。
    Don't separate subroutine or variable names from the following opening bracket.
# Do
get_candidates($marker);
if open_region($i);
$candidates[$i]{region}

# DONOT
get_candidates ($marker);
if open_region ($i);
$candidates [$i] {region}
  • 对于内建函数没必要带括号的就不要带括号。这样加强可读性。
    Don't use unnecessary parentheses for builtins and "honorary" builtins.
  • 对复杂的键或索引,将其与周围的括弧分开。
    Separate complex keys or indices from their surrounding brackets.
# Do
$candidates[$i] = $incumbent{ $candidates[$i]{ get_region( ) } };
print $incumbent{ $largest_gerrymandered_constituency };

# Do NOT
$candidates[$i] = $incumbent{$candidates[$i]{get_region( )}};
print $incumbent{$largest_gerrymandered_constituency};
  • 使用空格将二元操作符与它的操作数分开。
    Use whitespace to help binary operators stand out from their operands.
 # Do
my $displacement = $initial_velocity * $time  +  0.5 * $acceleration * $time**2;
my $price = $coupon_paid * $exp_rate  +  ($face_val + $coupon_paid) * $exp_rate**2;

# Do NOT
my $displacement=$initial_velocity*$time+0.5*$acceleration*$time**2;
my $price=$coupon_paid*$exp_rate+(($face_val+$coupon_val)*$exp_rate**2);
  • 在每一语句后加分号。但是对于只有一个单独语句的 map 和 grep, 请省略。
    Place a semicolon after every statement.
# Do
if ( $line =~ s{\A (\s*) -- (.*)}{$1#$2}xms ) {
    push @comments, $2;
}
my @sqrt_results = map { sqrt $_ } @results;

# Do NOT
if ( $line =~ s{\A (\s*) -- (.*)}{$1#$2}xms ) {
    push @comments, $2
}
my @sqrt_results = map { sqrt $_; } @results;
  • 在多行列表的每一个值后面加逗号。
    Place a comma after every value in a multiline list.
  • 使用 78 列的行。
    Use 78-column lines.
  • 使用四列的缩进水平(每一缩进为四个空格)。
    Use four-column indentation levels.
  • 使用空格缩进,而不是制表 Tab. 因为在不同的环境下,制表所表现的空格不一。可以用文本编辑器设置只动转换制表。
    Indent with spaces, not tabs.
  • 永远不要在同一行上写两个语句。
    Never place two statements on the same line.
 # Do
chomp $record;
next RECORD if $record eq $EMPTY_STR;

# Do NOT
chomp $record; next RECORD if $record eq $EMPTY_STR;
  • 请使用段落编写代码。将不同的功能的语句分段(同功能的为一段),在每一段前注释这段的用处。
    Code in paragraphs.
  • 不要使用拥挤格式的 else/elsif
    Don't cuddle an else.
# Do
}
else {
}
elsif ($sigil eq '@' && $subsigil eq '?') {

# NOT
} else {
} elsif ($sigil eq '@' && $subsigil eq '?') {
  • 垂直将相关项目排列。
    Align corresponding items vertically.
# Do
my %expansion_of = (
    q{it's}    => q{it is},
    q{we're}   => q{we are},
    q{didn't}  => q{did not},
    q{must've} => q{must have},
    q{I'll}    => q{I will},
);
$name   = standardize_name($name);
$age    = time - $birth_date;
$status = 'active';


# Do NOT
my %expansion_of = (
    q{it's} => q{it is}, q{we're} => q{we are}, q{didn't} => q{did not},
    q{must've} => q{must have}, q{I'll} => q{I will},
);
$name = standardize_name($name);
$age = time - $birth_date;
$status = 'active';
  • 在操作符将长表达式截断。缩进时注意与前面句子相关部分对齐。
    Break long expressions before an operator.
# Do
push @steps, $steps[-1]
             + $radial_velocity * $elapsed_time
             + $orbital_velocity * ($phase + $phase_shift)
             - $DRAG_COEFF * $altitude
             ;

# NOT
push @steps, $steps[-1] +
     $radial_velocity * $elapsed_time +
     $orbital_velocity * ($phase + $phase_shift) -
     $DRAG_COEFF * $altitude;
  • 如果长表达式在语句的中间,可以额外提出来。
    Factor out long expressions in the middle of statements.
# Do
my $next_step = $steps[-1]
                + $radial_velocity * $elapsed_time
                + $orbital_velocity * ($phase + $phase_shift)
                - $DRAG_COEFF * $altitude
                ;
add_step( \@steps, $next_step, $elapsed_time);

# Do NOT
add_step( \@steps, $steps[-1]
                   + $radial_velocity * $elapsed_time
                   + $orbital_velocity * ($phase + $phase_shift)
                   - $DRAG_COEFF * $altitude
                   , $elapsed_time);
  • 总是在可能的低优先级的操作符那而不是高优先级那,将长表达式截断。
    Always break a long expression at the operator of the lowest possible precedence.
# Do
push @steps, $steps[-1]
             + $radial_velocity * $elapsed_time
             + $orbital_velocity
                 * ($phase + $phase_shift)
             - $DRAG_COEFF * $altitude
             ;

# NOT
push @steps, $steps[-1] + $radial_velocity
             * $elapsed_time + $orbital_velocity
             * ($phase + $phase_shift) - $DRAG_COEFF
             * $altitude
             ;
  • 在赋值操作符前将长表达式截断。
    Break long assignments before the assignment operator.
# Do
$predicted_val
    = $average + $predicted_change * $fudge_factor;

# NOT
$predicted_val = $average
                 + $predicted_change * $fudge_factor
                 ;
  • 将三元层叠用列格式排好。
    Format cascaded ternary operators in columns.
 # Do
my $salute = $name eq $EMPTY_STR                      ? 'Customer'
           : $name =~ m/\A((?:Sir|Dame) \s+ \S+) /xms ? $1
           : $name =~ m/(.*), \s+ Ph[.]?D \z     /xms ? "Dr $1"
           :                                            $name
           ;

# NOT
my $salute = $name eq $EMPTY_STR ? 'Customer'
           : $name =~ m/\A((?:Sir|Dame) \s+ \S+)/xms ? $1
           : $name =~ m/(.*), \s+ Ph[.]?D \z/xms ? "Dr $1" : $name;
  • 将长列表加上括弧。
    Parenthesize long lists.
# Do
push @items, (
    "A brand new $item",
    "A fully refurbished $item",
    "A ratty old $item",
);

# NOT
push @items, "A brand new $item"
           , "A fully refurbished $item"
           , "A ratty old $item"
           ;
  • Enforce your chosen layout style mechanically.

当然风格是自己选的,并非强制。 Perl 世界是自由的世界。


m:nth(19)/Perl6Examples/

06 June 2005


  1. 本来修饰符是放后面的,现在都要放前面了。且每个修饰符都是前面加冒号。
  2. /x 没有了,默认就包含了。需要空格相关的话见下面的操作符 :w(:word)
  3. /m /s 也没了,我们用 $$ 等元字符来操作。
  4. /e 也没了。用 { ... } 来执行代码。
  5. 每一个修饰符都有它的长名字,我们可以高兴写长名就长名高兴写短的就短的。如 :i 的长名字为 :ignorecase 而 :g 的长名字为 :global
  6. 我们可以用 :p5 :perl5 来写 Perl5 的正则表达式。唯一的限制是不能在后面放修饰符。
    if ('22' ~~ m:p5/^\d+$/) { say 'OK'; }
  7. 新修饰符 :c(:countinue), 表示从当前位置 .pos 后开始匹配
    # Perl5
    while $str =~ m/\G(w+)/gc;
    # 等同与 Perl 6
    while $str =~ m:c/ (\w+) /;
  8. :p(:pos) 修饰符表示只在当前位置 .pos 处匹配。它的作用跟以前的 \G 相同(\G 也没了)
  9. :once 只匹配第一次
  10. :w(:words) 将空白字符用 定义的规则代替。
    m:w/ next cmd =   <condition>/
    # 等同于
    m/ <ws> next <ws> cmd <ws> = <ws> <condition>/
    而其中 <ws> 到底是 \s+ 还是 \s*, 由 <?ws> 规则定义。你可以定义你自己的 <?ws>
  11. Unicode 级别的修饰符:
    1. m:bytes / .**{2} / # 匹配两个字节
    2. m:codes / .**{2} / # 匹配两个编码点/codepoint
    3. m:graphs/ .**{2} / # 匹配两个字形/graphemes
    4. m:langs / .**{2} / # 匹配由不同语言定义的两个字符
  12. 前面是数字的修饰符(以数字 3 为例):
    1. 如果后面跟x,这种形式表示匹配的次数。它也可以这么写:x(3)。下面三种是等义的:
      s:3x { (<ident>) = (\N+) $$}{$1 => $2}; # 将 = 转为 =>, $$ 在这里的作用等同于 /m
      s:x(3) { (<ident>) = (\N+) $$}{$1 => $2}
      # 也等同于
      $_.pos = 0;
      s:c{ (<ident>) = (\N+) $$}{$1 => $2} for 1..3;
      上面两种跟最下面那种的区别在于上面是要匹配四次以上才会发生四次(不足四次一次也不发生)。
      如果需要范围,可以使用 :x(1..4)
    2. 如果后面跟的是st, nd, rd 或 th, 则表示只处理第几次。它也可以写作:nth(3)。下面在三种也是等义的:
      s:3rd/(\d+)[email protected][$1]/; # 将第三次匹配的数字转为 @data[$1], $1 为匹配的数字
      s:nth(3)/(\d+)[email protected][$1]/;
      # 也等同于
      m/(\d+)/ && m:c/(\d+)/ && s:c/(\d+)[email protected][$1]/;
      nth() 是接受列表和 junction 的。如要第 1, 4, 6 次可以写为 nth(1|4|6)
      它还可以接受闭包:nth{ something() }
  13. :ov(:overlap) 修饰符,它表示匹配结果的位置都是不一样的(就是说每一个位置最多只能有一个结果)。
    在列表上下文返回所有匹配结果,在标量上下文返回匹配结果的反结/disjunction.
    $str = "abracadabra";
    @substrings = $str ~~ m:overlap/ a (.*) a /;
    # bracadabr cadabr dabr br
  14. :ex(:exhaustive) 修饰符,它将匹配所有可能的结果。其返回值在不同的上下文与 :ov 返回做同样处理。
    $str = "abracadabra";
    @substrings = $str ~~ m:exhaustive/ a (.*) a /;
    # br brac bracad bracadabr c cad cadabr d dabr br

Unfinished

To be continued.

my $queen; rule me {Perl6|Examples18}

06 June 2005


preface

终于将 embed parrot in pugs, 现在可以试试 Perl6 的 Rule 了。
Thank God.

我们已经有一些中文文章介绍或详细描述了 Rule. 里面的一些基本东西我就不赘述了,我只写点我自己需要理解的东西。

Immediate Match

立即匹配,这是一种最简单最常用的匹配方式。由操作符 m// 或 s/// 定义的规则都是属于立即匹配的。
if ($string ~~ m/\w+/)      { . . . }
if ($string ~~ s/\w+/word/) { . . . }
// 操作符在智能匹配操作符下属于立即匹配,或在空,布尔,字符或数字上下文下属于立即匹配。
if ($string ~~ /\w+/)       { . . . } # ~~ 定义了立即匹配
$truth  = ?/\d+/;       # 布尔上下文,立即匹配 $_ 并返回真假
$count  = +/(\d+\s+)*/; # 数字上下文,立即匹配 $_ 并返回个数 
$string = ~/^\w+/;      # 字符串上下文,理解匹配 $_ 并返回字符串

Deferred Matche

延后匹配,定义一种匿名规则,以后匹配的时候可以调用该规则(复用)。一般这么定义:
$digits = rx/\d+/;
// 操作符除上面的立即匹配环境外,其余都属于延后匹配:
$digits = /\d+/; # store rule
除了上面这两种定义外,还可以使用 rule 关键字定义:
$digits = rule {\d+};
上面其实是定义了一个匿名规则。我们可以用 rule 定义带名字的规则:
rule digits {\d+}
我们可以在迟些时候调用这些规则:
if ($string ~~ $digits) {...}
if ($string ~~ /<$digits>/) {...} # 这种也应当是可用的,但目前 pugs 还没实现
如果是带名规则 rule digits, 我们可以怎么用:
$string ~~ /<digits>/;

Grammar

一个语法规则/grammar 是许多个规则/rule 的集合。事实上一个 grammar 就是一个类,只不过继承了基类 Rule. 而 rule 我们就可以把它看作类 grammar 的方法。
这也意味着 grammar 可以继承其他 grammar,且它们给了 rule 一个命名空间。
grammar Hitchhikers {
    rule name {Zaphod|Ford|Arthur} 
    rule id   {\d+} 
    . . .  
}
如果在该语法规则属于当前包,我们可以像前面一样直接调用 等,而如果该语法规则不属于当前包(外面的),那我们调用的时候就得加命名空间:
if $my_id ~~ /<id>/ { ... } # grammar Hitchhikers 在当前包
f $my_id ~~ /<Hitchhikers.id>/ { ... } # 不属于当前包
语法对于解析复杂的数据结构非常有用。

see u later

learning never stops.