perl精粹解析 - closure 与 静态局部变量

赤道海的蓝 2011-01-05 02:29:38
一.什么是closure

perl 中有一个概念叫“closure”(闭环,来源于数学):

假如有一个subroutine, 它访问了在其外部声明的私有变量,那么这个subroutine就叫做closure.

例如:

{
my $count = 0;
sub callback { print ++$count; };
sub get_count{return $count;};
}
&callback(); //此时, $count = 1
&callback(); //此时, $count = 2

这里,callback和get_count就是一个closure,因为它访问了在其外部声明的私有变量$count.

closure在实际应用中提供了c语言中局部静态变量的功能。

它有以下特性:

1.访问的变量是私有变量,保证只有少数subroutine能访问到该变量,这些具有访问权限的subroutine就构成了一个环,因此也叫闭环;

在上面的例子中,$callback和get_count构成了能访问$count的环;

由于环中的subroutine都可以访问$count(与c语言中的静态变量作用一样),使得每一次访问都可以改变$count的值。

在这个例子中,第一次调用&callback()后, $count的值变为1,第2次调用后,值变为2.

2.该私有变量的生命周期是与环中的生命周期最长的subroutine一样。这是通过perl中的引用计数机制来实现的:

上面的例子中,在括号内的代码部分, $count的引用计数是3,出了括号,$count的引用计数变成了2,

callback生命周期结束的时候,$count的引用计数减去1,此时变为1

get_count生命周期结束的时候,$count的引用计数减去1,此时变为0

当$count的引用计数变为0之后,被perl回收。

二.perl中的closure与C语言中静态局部变量的区别

1.C语言本身提供了静态局部变量机制--static关键字,程序员在使用该功能特性的时候更方便;

perl本身要实现同样的功能,需要程序员额外的努力,在第三部分perl中closure的常见用法中,

大家就可以看到这些技巧。

2. C语言中,只有静态局部变量所在的函数对该变量具有访问权;

而perl中,闭环中的所有subroutine都可以访问和修改“静态变量”。

perl的这个特性,使得它在某些情况下,比c语言更加便利。参见下节中的用法一。

三.perl中closure的常见用法

以下摘自“Learning Perl Objects, References & Modules” 的第6章:

用法一 在subroutine中返回subroutine的引用,通常作为回调函数:

use File::Find;
sub create_find_callbacks_that_sum_the_size {
my $total_size = 0;
return(sub { $total_size += -s if -f }, sub { return $total_size });
}
my ($count_em, $get_results) = create_find_callbacks_that_sum_the_size( );
find($count_em, "bin"); //寻找bin目录下所有的文件,并对找到的所有文件执行回调函数$count_em
my $total_size = &$get_results( );
print "total size of bin is $total_size\n"
这段代码用于计算某个目录下所包含的所有文件的大小之和.

这里,create_find_callbacks_that_sum_the_size返回了两个subroutine的引用,一个用来计算

total_size的值,一个用来获取total_size的值.

create_find_callbacks_that_sum_the_size相当于函数生成器,生成了两个subroutine。

如果用c语言来实现同样的功能,有两种方法:

1.改变find函数的接口,增加参数total_size,当find函数返回时,设置total_size作为真正的返回值

      #define int (* SUM)(int);
int get_sum(int filesize);
{
static int count = 0;
count += filesize;
return count;
}
bool find(SUM callback, char *path, int pathLen, int *total_size )

这里假设callback的返回值是total_size,那么find可实现如下:

bool find(SUM callback, char *path, int pathLen, int *total_size )

bool result = false;
for(...) //寻找path下的每一个文件
{
...
if (file is find)
{
total_size = callback(filesize); //get_sum取代形参callback
result = true;
}
}
return result;

此时,要想获取totalsize的值,只需要执行:

int totalsize = 0;
find(get_sum, path, pathlen, &totalsize);

2.改变callback函数的接口

#define int (* SUM)(int, bool);
int get_sum(int filesize, bool ret = false);
{
static int count = 0;
if (!ret)
{
count += filesize;
}
return count;
}
bool find(SUM callback, char *path, int pathLen)

bool result = false;
for(...) //寻找path下的每一个文件
{
...
if (file is find)
{
callback(filesize); //get_sum取代形参callback
result = true;
}
}
return result;

此时,要想获取totalsize的值,只需要执行:

     find(get_sum, path, len);
int totalsize = get_sum(anyinteger, true);

对方法一和方法二进行小结:

1. 方法一改变了find函数的接口,而通常find函数是系统提供的库函数,接口无法改变.

2. 方法二更糟糕,由于改变了回调函数get_sum的接口,函数指针SUM的类型也发生了改变(增加了一个bool型参数)

, 导致所有被find所调用到的SUM类型的回调函数都需要改变接口和实现,这对代码的扩展和维护来说简直是个灾难!

相比之下,用perl实现同样的功能更加简洁通用,扩展性更好,对现有的代码影响更小。

用法二 使用闭环变量作为输入,用作函数生成器,来生成不同的函数指针:

use File::Find;
sub print_bigger_than {
my $minimum_size = shift;
return sub { print "$File::Find::name\n" if -f and -s >= $minimum_size };
}
my $bigger_than_1024 = print_bigger_than(1024);
find($bigger_than_1024, "bin");


print_bigger_than在这里相当于一个函数生成器,不同的输入变量可以生成不同的函数指针.

这里生成了一个可以打印出文件大小大于1024字节文件名的回调函数.



用法三 作为静态局部变量使用,提供了c语言静态局部变量的功能:



BEGIN {
my $countdown = 10;
sub count_down { $countdown-- }
sub count_remaining { $countdown }
}



这里用到了关键字BEGIN.

BEGIN的作用就是,当perl编译完这段代码之后,停止当前编译,然后直接进入运行阶段,执行BEGIN块内部的代码.然后再回到编译状态,

继续编译剩余的代码.

这就保证了无论BEGIN块位于程序中的哪个位置,在调用count_down之前,$countdown被确保初始化为10.

...全文
377 6 打赏 收藏 转发到动态 举报
写回复
用AI写文章
6 条回复
切换为时间正序
请发表友善的回复…
发表回复
iambic 2011-01-05
  • 打赏
  • 举报
回复
lexical variables是准确的词汇,中文不知道怎么翻译,但是这个情境下基本上可以用local variable代替。那个private的意思可能是说这个变量在函数外面不能直接访问吧,但在表达和定义上不是很准确。
赤道海的蓝 2011-01-05
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 iambic 的回复:]
原创?现在很少有代码在调用函数的时候前面加&了,&callback()一般都写callback()。另外“私有变量”并不准确,closure和私有与否没什么联系。准确些讲是局部变量(或者说局部引用)。闭环也是编程里很少用的术语,一般叫闭包比较多。对C语言的比较那一部分也不是很到位,closure是动态语言才有的,最大的优势在于可以动态创建新的closure,这是与C语言最大的区别。……
[/Quote]

你好!感谢指正.
偶是perl新手,正在学"Learning Perl Objects, References & Modules",这是一点读后感.
1.关于&,大多数情况下都可以省略.本文中的示例代码应该省略.
只有当用户写的函数名和perl的内置函数重名,需要调用用户写的函数时,才需要加&前缀.
2."私有变量"用在这里的确不准确,应该叫"局部变量(或者说局部引用)"
偶看到perl英文书里面用的"private (lexical) variables",就翻译成私有变量了... 汗!
3.闭环应该改成闭包
4.偶对closure的应用以及动态语言的认识还处于初级阶段,回去继续看书充实自己.
赤道海的蓝 2011-01-05
  • 打赏
  • 举报
回复
你好!感谢指正.
偶是perl新手,正在学"Learning Perl Objects, References & Modules",这是一点读后感.
1.关于&,大多数情况下都可以省略.本文中的示例代码应该省略.
只有当用户写的函数名和perl的内置函数重名,需要调用用户写的函数时,才需要加&前缀.
2."私有变量"用在这里的确不准确,应该叫"局部变量(或者说局部引用)"
偶看到perl英文书里面用的"private (lexical) variables",就翻译成私有变量了... 汗!
3.闭环应该改成闭包
4.偶对closure的应用以及动态语言的认识还处于初级阶段,回去继续看书充实自己.
奔跑哥 2011-01-05
  • 打赏
  • 举报
回复
楼上点评的不错。
iambic 2011-01-05
  • 打赏
  • 举报
回复
原创?现在很少有代码在调用函数的时候前面加&了,&callback()一般都写callback()。另外“私有变量”并不准确,closure和私有与否没什么联系。准确些讲是局部变量(或者说局部引用)。闭环也是编程里很少用的术语,一般叫闭包比较多。对C语言的比较那一部分也不是很到位,closure是动态语言才有的,最大的优势在于可以动态创建新的closure,这是与C语言最大的区别。

37,720

社区成员

发帖
与我相关
我的任务
社区描述
JavaScript,VBScript,AngleScript,ActionScript,Shell,Perl,Ruby,Lua,Tcl,Scala,MaxScript 等脚本语言交流。
社区管理员
  • 脚本语言(Perl/Python)社区
  • IT.BOB
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧