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提供了一个state修饰符,它和my完全一样,都是词法变量,唯一的区别在于state修饰符使得变量持久化,但对于外界来说却不可访问(因为是词法变量),而且state修饰的变量只会初始化赋值一次。

注意:

  • state修饰符不仅仅只能用于子程序中,在任何语句块中都可以使用,例如find、grep、map中的语句块。
  • 只要没有东西在引用state变量,它就会被回收。
  • 目前state只能修饰标量,修饰数组、hash时将会报错。但可以修饰数组、hash的引用变量,因为引用就是个标量

例如,将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是子程序中的一部分,无论在哪里调用到它,都会执行这一句赋值语句。

原文地址:https://www.cnblogs.com/f-ck-need-u/p/9738156.html

时间: 2024-10-07 11:39:51

Perl回调函数和闭包的相关文章

javascript回调函数,闭包作用域,call,apply函数解决this的作用域问题

在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实际上是一种对象,它可以“存储在变量中,通过参数传递给(别一个)函数(function),在函数内部创建,从函数中返回结果值”. 因为function是内置对象,我们可以将它作为参数传递给另一个函数,延迟到函数中执行,甚至执行后将它返回.这是在JavaScript中使用回调函数的精髓.本篇文章的剩余部

前端(十三)—— JavaScript高级:回调函数、闭包、循环绑定、面向对象、定时器

回调函数.闭包.循环绑定.面向对象.定时器 一.函数高级 1.函数回调 // 回调函数 function callback(data) {} // 逻辑函数 function func(callback) { // 函数回调,判断回调函数是否存在 if (callback) callback(data); } func(callback); // 函数回调的本质:在一个函数中(调用函数),当满足一定条件,调用参数函数(回调函数) // 回调函数作为调用函数的参数传入,满足一定的条件,调用回调函数

回调函数和闭包

回调函数和闭包 回调函数 回调函数,官方解释:当程序跑起来时,应用程序会时常通过API调用库里预先备好的函数.但是有些库函数却要求应用先传给他一个函数,好在合适的时候调用,以完成目标任务.这个被传入.后又被调用的函数被称为回调函数. 简单的说,回调就是回溯,先定义好将要使用的函数体,然后使用时再调用这个函数,通常把callback作为一个参数传入先定义的那个函数. 闭包 问:如何从外部读取局部变量 答:那就是在函数的内部,再定义一个函数. function f1() { var n = 999;

JavaScript之callback回调函数

以下内容借鉴老鸟的经验和知识,结合自己的学习,精髓的总结. 一句话:对于以后研究node 和那些热门的前端框架 很有帮助.如果你看过这个文章,对于你来说是质的突变. 理解javascript中的回调函数(`callback`),希望对你有所帮助. 在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,和其它String.Array.Number.Object类的对象一样用于内置对象的管理. function实际上是一种对象,它可以"存储在变量中,通过参数传递给(别一

理解和使用 JavaScript 中的回调函数

原文:http://javascriptissexy.com/ 在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实际上是一种对象,它可以"存储在变量中,通过参数传递给(别一个)函数(function),在函数内部创建,从函数中返回结果值". 因为function是内置对象,我们可以将它作为参数传递给另一个函数,延迟到函数中执行,甚至执行后

【JavaScript】理解与使用Javascript中的回调函数

在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被“存储”在变量中,能作为函数参数被传递,能在函数中被创建,能从函数中返回. 因为函数是第一类对象,我们可以在Javascript使用回调函数.在下面的文章中,我们将学到关于回调函数的方方面面.回调函数可能是在Javascript中使用最多的函数式编程技巧,虽然在字面上看起来它们一直一小段Javascript或者jQuery代码,但是对于许多开发者来说它任然是一个谜.在阅读本文

javascript 函数初探 (四)--- 回调函数

回调函数 既然函数与任何被赋值给变量的数据是相同的,那么她当然可以像其他数据那样被定义.删除.拷贝,以及当成参数传递给其它函数. 我们定义一个函数,这个函数有两个函数类型的参数,然后他会分别执行这两个参数所执行的函数. function her(){ return a() + b(); } function one(){ return 1; } fucntion two(){ return 2; } her(one, two) // 3 实际上,我们也可以直接用匿名函数(即函数表达式)来替代on

PHP匿名函数及闭包

匿名函数在编程语言中出现的比较早,最早出现在Lisp语言中,随后很多的编程语言都开始有这个功能了, 目前使用比较广泛的Javascript以及C#,PHP直到5.3才开始真正支持匿名函数,C++的新标准C++0x也开始支持了. 匿名函数是一类不需要指定标示符,而又可以被调用的函数或子例程,匿名函数可以方便的作为参数传递给其他函数,最常见应用是作为回调函数. 闭包(Closure) 说 到匿名函数,就不得不提到闭包了,闭包是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数,

理解与使用Javascript中的回调函数

在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被“存储”在变量中,能作为函数参数被传递,能在函数中被创建,能从函数中返回. 因为函数是第一类对象,我们可以在Javascript使用回调函数.在下面的文章中,我们将学到关于回调函数的方方面面.回调函数可能是在 Javascript中使用最多的函数式编程技巧,虽然在字面上看起来它们一直一小段Javascript或者jQuery代码,但是对于许多开发者来说 它任然是一个谜.在阅读