The power of returning functions

在学习Common Lisp的间隙,禁不住手痒,写了两个Perl脚本,第一次尝试将函数作为返回值,感觉相当美妙。其实,返回一个函数并不是什么神奇的法术,在C或C++这样的语言里,我们可以返回一个函数指针,该指针指向程序里的某个函数;然而在Perl里面,作为返回值的函数,不需要是已经存在的函数,我们可以动态地创建一个函数,将其返回。

要理解这里面的不同,首先要理解什么是closure。

sub make_prompt {
    my $n = 0;
    return sub {
        ++$n;
        "shell[$n]>";
    }
}

函数make_prompt返回一个匿名函数,该匿名函数的返回值是个shell提示符,每次执行该函数得到的shell提示符都是不同的,类似于CPAN shell,提示符里含有一个数字,表示提示符出现的次数。

my $prompt = make_prompt();
my $p1 = $prompt->(); # shell[1]>
my $p2 = $prompt->(); # shell[2]>

理解函数make_prompt的关键是其局部变量$n在该函数返回后仍然存在,或者更准确地说,那段空间仍然存在,只是$n不再与其绑定(binding)在一起。换句话说,make_prompt返回的不仅仅是个函数,还包括定义这个函数时与其相关的那部分环境(此例中为$n的空间),使得返回函数可以正确执行。

需要注意的是,每次调用make_prompt时的$n是不同的,所以返回的函数之间没有任何依赖关系

my $prompt1 = make_prompt();
my $prompt2 = make_prompt();
my $p11 = $prompt1->(); # shell[1]>
my $p12 = $prompt1->(); # shell[2]>
my $p21 = $prompt2->(); # shell[1]>

可以看出,closure具有某种程度的封装功能。

理解了closure之后,可以学习currying了。我们稍微修改一下make_prompt的定义:

sub make_prompt {
    my $n = shift;
    return sub {
        my $shell = shift;
        my $m = $n++;
        "$shell[$m]>";
    }
}
my $prompt1 = make_prompt(1);
my $promtp2 = make_prompt(100);
my $p11 = $prompt1->("shell"); # shell[1]>
my $p21 = $prompt2->("root"); # root[100]>

现在,通过传给make_prompt不同的参数,其返回的函数的行为也不一样了,第一个从1开始,第二个从100开始。而且通过让返回函数也接受参数,使得shell提示符可以非常个性化。

Extract quoted string with Regexp::Common

为了增强电子书搜索功能,我们引入两个元字符:"。当我们想搜索一个词组(而不是单词)的时候,可以使用双引号将多个单词引起来,此时,双引号本身不作为搜索内容的一部分。当我们确实想搜索双引号时,需要写成",反斜杠取消了双引号的元字符功能。当我们想搜索本身时,就要写连续两个反斜杠。

对用户输入要分两步处理,首先,逐个取出引号引起来的部分,然后对剩下的部分分词。前一部分工作可以用Regexp::Common来完成:

my @quoted_string;
while ($search_string =~ s/$RE{delimited}{-delim=>q{"}}{-keep}//) {
    push @quoted_string, escape_special_char($3);
}

sub escape_special_char {
    my $string = shift;

    $string =~ s/\([\"])/$1/g;
    return $string;
}

其中$3是指被引号引起来的部分(不包含两边的引号),而escape_special_char"替换为",将\替换为。完整源程序参见这里,版本号为1,其中还有警告双引号不匹配的功能。程序截图如下: