你已经见过了函数(functions)和作用域(scope)是如何工作的。你知道了每次 控制流程进入函数后,该函数将得到代表本次调用的词法作用域的新环境。你现在可以使 用函数引用(references)以及匿名函数(anonymous_functions)了。
你已经学到了理解闭包所需的全部知识。
闭包 是封闭于外部词法环境之上的函数。你也许早已创建并使用了闭包,只是没有意识 到:
这段代码的行为平淡无奇。你也许并未觉察有何特殊之处。显然 函数 get_filename()
可以在词法层面见到 $filename
。作用域就是这样工作的!闭包还可以封闭于 转瞬即逝 词法环境上。
假设你想迭代一个列表,而不愿自行管理迭代器。你可以创建一个返回函数的函数,当它被 调用时,将返回迭代过程中的下一个元素:
即使 make_iterator()
已经返回,但此匿名函数仍将引用词法变量 @items
和 $count
。它们的值会一直保持(reference_counts)。这个匿名函数,存放于 $cousins
, 在调用 make_iterator()
的特别词法环境中闭合于这些值上。
很容易演示词法环境是独立于对 make_iterator()
的调用:
因为对 make_iterator()
的每次调用都为词法量创建分离的词法环境,匿名子过程 就此产生且返回唯一的词法环境。
因为 make_iterator()
并非按值或按引用返回这些词法量,其他闭包外的 Perl 代 码无法访问它们。他们和其他词法量一样被有效地封装。
多个闭包可以闭合于同一批词法变量上,这是一个偶尔会用到的惯用语,可以为本将全 局可见的变量提供一个更好的封装:
……但注意你不可以 嵌套 具名函数。具名函数有着包全局作用域。任一在嵌套函数间 共享的词法变量,在外层函数销毁它的第一层词法环境时,将变为非共享 如果你还是 不太清楚,可以想像一下它的实现。
闭包可以构造针对定长列表的高效迭代器,但是他们在迭代那些不适合直接引用其元素的 列表时更胜一筹,那写列表不是一次性计算全部元素的代价过于昂贵,就是尺寸太大以至 于无法直接塞进内存中去。
考虑一个按需创建 Fibonacci 序列的函数。不必递归地重计算这个序列,而应使用缓存并 惰性地按需要求出各个元素:
每次调用由 gen_fib()
返回的函数需提供一个参数,即 Fibonacci 序列的第 n 个 元素。该函数按需要生成序列中所有前导值,缓存起来,并且返回所需的元素。它将延后 计算过程直到不得不这样做。
如果你所需的全部就是计算 Fibonacci 数的话,这个方法也许太过复杂。然而,考虑到 函数 gen_fib()
可以变得惊人地通用:它初始化一个数组,用于缓存,执行一些定制 的代码来填充缓存的各类值,并从缓存中返回已计算的结果。抽掉计算 Fibonacci 值的 部分,你可以使用这段代码来实行所有带缓存的、惰性迭代器的行为。
换句话说,你可以提取出一个函数,generate_caching_closure()
,并按该函数的方 式重写 gen_fib()
:
该程序的行为和以往一致,但对高阶函数和闭包的使用允许从 Fibonacci 序列的计算中有 效分离出缓存的初始化过程。代码行为的定制————就此而言,gen_caching_closure()
————通过传递高阶函数,带来了极大的抽象性和灵活性。
闭包除了抽象结构化细节外还可以做更多事。它允许你定制特定的行为。从某种意义上讲, 它还可以 去掉 不必要的泛化。考虑一例接受若干参数的函数:
所有这些定制也许和你那位于购物中心内商品齐全的主力店正相称,但如果你在天桥附近有 一小辆冰淇淋专卖车,那里只出售安在卡文迪什香蕉上的法式香草冰淇淋,那样在调用 make_sundae()
时,你必须每次都传递一个恒久不变的参数。
一个名为 部分应用 的技巧将一部分参数绑定给函数以便你可以在后续的调用过程中 填入其他值。用闭包来模拟再简单不过了:
现在不必调用 make_sundae()
了,你可以直接使用 $make_cart_sundae->()
并只将相关的参数传入,而无需顾及忘传或错传不变量。你还可以使用来自 CPAN 的 Sub::Install
把这个函数直接安装到你的名称空间中。。
Hey! The above document had some coding errors, which are explained below:
- Around line 5:
-
A non-empty Z<>
- Around line 109:
-
Deleting unknown formatting code N<>
- Around line 215:
-
A non-empty Z<>
- Around line 255:
-
Deleting unknown formatting code N<>