Perl回调函数和闭包
在Perl中,子程序的引用常用来做回调函数(callback)、闭包(closure),特别是匿名子程序。
回调函数(callback)
关于什么是回调函数,见一文搞懂:词法作用域、动态作用域、回调函数、闭包
以File::Find
模块的find函数为例,它用来搜索给定目录下的文件,然后对每个搜索到的文件执行一些操作(通过定义子程序),这些操作对应的函数要传递给find函数,它们就是回调函数。就像unix下的find命令一样,找到文件,然后print、ls、exec CMD操作一样,这几个操作就是find命令的回调函数。
use File::Find;
sub cmd {
print "$File::Find::name\n";
};
find(\&cmd,qw(/perlapp /tmp/pyapp));
其中$File::Find::name
代表的是find搜索到的从起始路径(/perlapp /tmp/pyapp
)开始的全路径名,此外,find每搜索到一个文件,就会赋值给默认变量$_
。它代表的是文件的basename,和$File::Find::name
全路径不一样。例如:
起始路径 $File::Find::name $_
-------------------------------------------
/perlapp /perlapp/1.pl 1.pl
. ./a.log a.log
perlapp perlapp/2.pl 2.pl
回到回调函数的问题上。上面的示例中,定义好了一个名为cmd的子程序,但一直都没有主动地去执行这个子程序,而是将它的引用放进find函数中,由find函数每次去调用它。这就像是unix的find命令的"-print"选项一样,其中"-print"选项对应的函数就是一个回调函数。
上面的子程序只调用了一次,没必要花脑细胞去设计它的名称,完全可以将其设计为匿名子程序,放进find函数中。
use File::Find;
find(
sub {
print "$File::Find::name\n";
},
qw(/perlapp /tmp/pyapp)
);
Perl闭包(closure)简单介绍
关于闭包的详细内容,见一文搞懂:词法作用域、动态作用域、回调函数、闭包
从perl语言的角度来简单描述下闭包:子程序1中返回另一个子程序2,这个子程序2访问子程序1中的变量x,当子程序1执行结束,外界无法再访问x,但子程序2因为还引用着变量x所指向的数据对象,使得子程序2在子程序1结束后可以继续访问这个数据对象。
所以,子程序1中的变量x必须是词法变量,否则子程序1执行完后,变量x可能仍可以被外界访问、修改,如果这样,闭包和普通函数就没有意义了。
一个简单的闭包结构:
sub sub1 {
my $var1=N;
$sub2 =sub {
do something about $var1
}
return $sub2 # 返回一个闭包
}
$my_closure = sub1(); # 将闭包函数存储到子程序引用变量
主要目的是为了让子程序sub1内部嵌套的子程序$sub2
可以访问属于子程序sub1但不属于子程序$sub2
的变量,这样一来,只要把sub1返回的闭包赋值给$my_closure
,就可以让这个闭包函数一直引用$var1
变量对应的数值对象,但是sub1执行完毕后,外界就无法再通过$var1
去访问这个数据对象(因为是词法变量)。也就是说,sub1执行完后,$var1
指向的数据对象只有闭包$my_closure
可以访问。
一个典型的perl闭包:
sub how_many { # 定义函数
my $count=2; # 词法变量$count
return sub {print ++$count,"\n"}; # 返回一个匿名函数,这是一个匿名闭包
}
$ref=how_many(); # 将闭包赋值给变量$ref
how_many()->(); # (1)调用匿名闭包:输出3
how_many()->(); # (2)调用匿名闭包:输出3
$ref->(); # (3)调用命名闭包:输出3
$ref->(); # (4)再次调用命名闭包:输出4
上面将闭包赋值给$ref
,通过$ref
去调用这个闭包,则即使how_many中的$count
在how_many()执行完就消失了(因为是个词法变量,外界无法访问),但$ref
指向的闭包函数仍然在引用这个变量,所以多次调用$ref
会不断修改$count
的值,所以上面(3)和(4)先输出3,然后输出改变后的4。而上面(1)和(2)的输出都是3,因为两个how_many()函数返回的是独立的匿名闭包,在语句执行完后数据对象3就消失了。
Perl语言有自己的特殊性,特别是它支持只执行一次的语句块(即用大括号{}
包围),这使得Perl要创建一个闭包并不一定需要函数嵌套,只需将一个函数放进语句块即可:
my $closure;
{
my $count=1; # 随语句块消失的词法变量
$closure = sub {print ++$count,"\n"}; # 闭包函数
}
$closure->(); # 调用一次闭包函数,输出2
$closure->(); # 再调用一次闭包函数,输出3
在上面的代码中,$count
引用数在赋值时为1,在sub中使用并赋值给$closure
时引用数为2,当退出代码块的时候,count
引用数减为1,由于这是个词法变量,退出代码块后外界就无法通过$count
来访问了,但是闭包$closure
却一直可以继续访问。
闭包的形式其实多种多样。通俗意义上来说,只要一个子程序1可以访问另一个子程序2中的变量,且子程序1不会随子程序2执行结束就丢失变量,就属于闭包。当然,对于Perl来说,可能子程序2并非是必要的,正如上面的例子。
例如,下面的代码段就不属于闭包:
$y=3;
sub mysub1 {
$x=shift;
$x+$y;
}
$nested_ref=\&mysub1;
sub mysub2 {
$x=1;
$z=shift;
return $nested_ref->($z);
}
print mysub2(2);
为mysub2中返回的$nested_ref
是一个子程序mysub1的一个实例,但mysub1中使用的$y
来自于全局变量,而非mysub2,且mysub2执行完后,$y
也不会消失,对于闭包来说这看上去没什么必要。
Perl闭包应用
例如,通过File::Find
模块的find函数,计算出给定目录下的文件数量:
use File::Find;
my $callback;
{
my $count = 0;
$callback = sub { print ++$count, ": $File::Find::name\n" };
}
find($callback, '.'); # 返回数量和文件名
find($callback, '.'); # 再次执行,数量将在上一个find的基础上递增
Perl的语法强大,可以一次性返回多个闭包:
use File::Find;
sub sub1 {
my $total_size = 0;
return(sub { $total_size += -s if -f }, sub { return $total_size });
}
my ($count_em, $get_results) = sub1( );
find($count_em, '/bin');
find($count_em, '/boot');
my $total_size = &$get_results( );
print "total size of /bin and /boot: $total_size\n";
上面两个闭包,因为同时引用同一个对象,所以闭包$count_em
修改的词法变量,$get_results
也可以访问。
或者:
{
my $count=10;
sub one_count{ ++$count; }
sub get_count{ $count; }
}
one_count();
one_count();
print get_count();
由于代码块中的子程序有名称,所以这两个子程序在代码块结束后仍然有效(代码块结束后变量无效是因为加了my修饰符)。
但是,如果将调用语句放在代码块前面呢?
one_count(); # 1
one_count(); # 2
print get_count(); # 输出:2
{
my $count=10;
sub one_count{ ++$count; }
sub get_count{ $count; }
}
上面输出2,也就是说$count=10
的赋值10行为失效。这是因为词法变量的声明和初始化(初始化为undef)是在编译期间完成的,而赋值操作是在执行到它的时候执行的。所以,编译完成后执行到one_count()
这条语句时,将调用已编译好的子程序one_count,但这时还没有执行到语句块,所以$count
的赋值还没有执行。
可以将上面的语句块加入到BEGIN块中:
one_count(); # 11
one_count(); # 12
print get_count(); # 输出:12
BEGIN{
my $count=10;
sub one_count{ ++$count; }
sub get_count{ $count; }
}
state修饰符替代简单的闭包
前面闭包的作用已经非常明显,就是为了让词法变量不能被外部访问,但却让子程序持续访问它。
perl 5.10提供了一个state修饰符,它和my完全一样,都是词法变量,唯一的区别在于state修饰符使得变量持久化,但对于外界来说却不可访问(因为是词法变量),而且state修饰的变量只会初始化赋值一次。
注意:
- state修饰符不仅仅只能用于子程序中,在任何语句块中都可以使用,例如find、grep、map中的语句块。
- 只要没有东西在引用state变量,它就会被回收。
- 目前state只能修饰标量,修饰数组、hash时将会报错。但可以修饰数组、hash的引用变量,因为引用就是个标量
例如,将state修饰的变量从外层子程序移到内层自层序中。下面两个子程序等价:
use 5.010; # for state
sub how_many1 {
my $count=2;
return sub {print ++$count,"\n"};
}
sub how_many2 {
return sub {state $count=2;print ++$count,"\n"};
}
$ref=how_many2(); # 将闭包赋值给变量$ref
$ref->(); # (1)调用命名闭包:输出3
$ref->(); # (2)再次调用命名闭包:输出4
需要注意的是,虽然state $count=2
,但同一个闭包多次执行时不会重新赋值为2,而是在初始化时赋值一次。
而且,将子程序调用语句放在子程序定义语句前面是可以如期运行的(前面分析过一般的闭包不会如期运行):
$ref=how_many2(); # 将闭包赋值给变量$ref
$ref->(); # (1)调用命名闭包:输出3
$ref->(); # (2)再次调用命名闭包:输出4
sub how_many2 {
return sub {state $count=2;print ++$count,"\n"};
}
这是因为state $count=2
是子程序中的一部分,无论在哪里调用到它,都会执行这一句赋值语句。
再例如,state用于while循环的语句块内部,使得每次迭代过程中都持续访问这个变量,而不会每次迭代都初始化:
#!/usr/bin/perl
use v5.10; # for state
while($i<10){
state $count;
$count += $i;
say $count; # 输出:0 1 3 6 10 15 21 28 36 45
$i++;
}
say $count; # 输出空
Perl回调函数和闭包的更多相关文章
- Go基础系列:函数(2)——回调函数和闭包
回调函数和闭包 当函数具备以下两种特性的时候,就可以称之为高阶函数(high order functions): 函数可以作为另一个函数的参数(典型用法是回调函数) 函数可以返回另一个函数,即让另一个 ...
- javascript回调函数,闭包作用域,call,apply函数解决this的作用域问题
在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实 ...
- php 回调函数结合闭包(匿名函数)的使用示例
<?php /** * php 回调函数结合闭包(匿名函数)的使用 */ function callback( $callback ){ $variable = 'program'; $ret1 ...
- 前端(十三)—— JavaScript高级:回调函数、闭包、循环绑定、面向对象、定时器
回调函数.闭包.循环绑定.面向对象.定时器 一.函数高级 1.函数回调 // 回调函数 function callback(data) {} // 逻辑函数 function func(callbac ...
- perl 回调函数
在计算机程序设计中,回调函数,或简称回调(Callback),是指通过函数参数传递到其它代码的,某一块可执行代码的引用.这一设计允许了底层代码调用在高层定义的子程序. 没啥不好理解的呀,就是向函数的参 ...
- golang中匿名函数的应用-回调函数-闭包
package main import ( "fmt" "strconv" ) type funcType func(int, int) int // 自定义函 ...
- javascript 函数初探 (四)--- 回调函数
回调函数 既然函数与任何被赋值给变量的数据是相同的,那么她当然可以像其他数据那样被定义.删除.拷贝,以及当成参数传递给其它函数. 我们定义一个函数,这个函数有两个函数类型的参数,然后他会分别执行这两个 ...
- PHP中的回调函数和匿名函数
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...
- 理解和使用 JavaScript 中的回调函数
理解和使用 JavaScript 中的回调函数 标签: 回调函数指针js 2014-11-25 01:20 11506人阅读 评论(4) 收藏 举报 分类: JavaScript(4) 目录( ...
随机推荐
- 软件光栅器实现(二、VS和PS的运作,法线贴图,切空间的计算)
二.软件光栅器的VS和PS的输入.输出和运作,实现法线贴图效果的版本.转载请注明出处. 这里介绍的VS和PS是实现法线映射的版本,本文仅介绍实现思路,并给出代码供参考.切空间计算.光照模型等相关公式不 ...
- Android Studio 3.1.2 修改字体(font)大小(size) 及老版本修改主题、字体、颜色 参照地址
Android Studio 3.1.2 修改字体(font)大小(size) 步骤:File-Settings-Editor-Color Scheme-Color Scheme Font-Size ...
- 计算机网络三:域名、IP地址和TCP/IP协议
一.域名 域名(Domain Name),简称域名.网域,是由一串用点分隔的字符型标志名字组成的Internet上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时 ...
- Windows下安装BeautifulSoup4显示'You are trying to run the Python 2 version of Beautiful Soup under Python 3.(`python setup.py install`) or by running 2to3 (`2to3 -w bs4`).'
按照网上教程,将cmd的目录定位到解压缩文件夹地址,然后 >>python setup.py install ( Window下不能直接解压tar.giz文件,可以使用7z解压软件提取解压 ...
- Django积木块三——静态文件和上传文件
静态文件和上传的文件 # 静态文件 STATIC_URL = '/static/' STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'static'), ) # ...
- VMware安装xp虚拟机
VMware安装xp虚拟机 1.用到的软件: 2.安装VMware: 接受 选择自定义 要等上一小会. 输入密钥:百度一个就可以了. 安装成功: 禁用VMware网卡: 3.安装xp系统: 创建新的 ...
- PMP:11.项目采购管理
项目采购管理包括从项目团队外部采购或获取所需产品.服务或成果的各个过程. 项目采购管理包括编制和管理协议所需的管理和控制过程,例如,合同.订购单.协议备忘录 (MOA),或服务水平协议 (SLA). ...
- Azure Sphere–“Object reference not set to an instance of an object” 解决办法
在开发Azure Sphere应用时,如果出现项目无法编译,出现“Object reference not set to an instance of an object”时,必须从下面两个方面进行检 ...
- 项目Alpha冲刺(团队6/10)
项目Alpha冲刺(团队6/10) 团队名称: 云打印 作业要求: 项目Alpha冲刺(团队) 作业目标: 完成项目Alpha版本 团队队员 队员学号 队员姓名 个人博客地址 备注 221600412 ...
- 项目文件与 SVN 资源库同步提示错误 Attempted to lock an already-locked dir
问题描述 之前为了图方便,在eclipse中直接把三个jsp文件复制到了eclipse中我新建的一个文件夹中,把svn版本号信息也带过来了,然后我又删除了这三个jsp文件,接着先把这三个jsp复制到桌 ...