浅谈PHP 闭包特性在实际应用中的问题


Posted in PHP onOctober 30, 2009

呃,其实大部分情况下是可以的,而有些方面还是令人非常的困扰,下面慢慢道来。
很多语言的都提供了非常优雅和漂亮的操作数组的方法。在下面的例子中,会使用 PHP5.3 以及其他语言提供的闭包功能,用于展示如何“客观的”操作迭代数组。
译注:原文作者比较火星,我不了解 Groovy 以及 Scala 语言,所以这里我加上 Javascript 的实现。
在开始之前先说明下,本例子仅仅是阐明观点,并没有考虑性能等其他方面的因素。

“货比三家”

用个简单的例子开始,有下面个数组:
$nums = array(10, 20, 30, 40);需要找出数组中大于 15 的项。那么,不考虑闭包的情况下,我们或许会这样写:
$res = array();foreach ($nums as $n) { if ($n > 15) { $res[] = $n; }}如果语言本身有闭包支持的,那么或许会这样写(Groovy 语言)
def res = nums.findAll { it > 15 }或者使用 Scala 语言
val res = nums filter (_ > 15)译注:Javascript 1.6 的话会是如下
var res = nums.filter(function(c){return c > 15});因为循环操作已被抽象起来,所以可以看到 Groovy 、Scala (以及 Javascript) 都很漂亮得用一行就可以搞定。
当然,如果使用 PHP5.3 的闭包,也可以做到
$res = array_filter($nums, function($v) { return $v > 15; });PHP 在这方面使用了比 Scala 更多的字符,但对比先前的例子,它更简短并且能更好得阅读。

顺便说下,上面的 PHP 代码实际上是使用了 Lambda 解析式,并不是个真正的闭包,这个 并不是我们目前关注的重点。详细阐述 PHP 闭包以及 Lambda 解析式的资料,可以参考这里。
目前看来感觉都还不错,那么我们再的题目增加点难度:找到所有大于 15 的项, 然后乘以 2 再加上作用域中的的某个变量值以后再返回。

Groovy 的实现:
def x = 1def res = nums .findAll { it > 15 } .collect { it * 2 + x }Scala 的实现:
val x = 1val res = nums filter (_ > 15) map (_ * 2 + x)译注,Javascript 的实现:
var i = 1;var res = nums.filter(function(c){return c > 15}).map(function(c){return c * 2 + i});以及 PHP:
$x = 1;$res = array_map( function($v) use ($x) { return $v * 2 + $x; }, array_filter( $nums, function($v) { return $v > 15; }));光从代码量方面,现在看起来 PHP 与其他语言有出入了。先抛开代码字面上本身 的审美不谈,上面的 PHP 代码还有个额外的问题。
例如,如果需要使用数组的键而非值作比较,怎么办?是的,上面的代码就办不到了。同时,从语法角度上说,上面的代码非常难以阅读。

返璞归真,这时还是得返回老土的思路去解决问题:
$x = 1;$res = array();foreach ($nums as $n) { if ($n > 15) { $res[] = $n * 2 + $x; }}呼,这样看起来又很清楚了。但这个时候你或许又会迷惑了:“那还瞎折腾啥,这不就是个数组操作吗?”。
是的,好戏还在后头。这个时候该让 PHP 的某些高级特性出场,来搞定这看似有自残倾向 的“无聊问题”。

ArrayObject ? 对数组的封装
PHP 有个称作 SPL 的标准库,其中包含了个叫做 ArrayObject 的类,它能提供“像数组一 样操作类”的功能,例如
$res = new ArrayObject(array(10, 20, 30, 40));foreach ($res as $v) { echo "$vn";}ArrayObject 是个内置的类,所以你可以像其他类类操作一样封装它。

Arr - 包上糖衣

既然我们已经有了 ArrayObject 以及闭包这些特性,我们就可以开始尝试封装它:
class Arr extends ArrayObject{ static function make($array) { return new self($array); } function map($func) { $res = new self(); foreach ($this as $k => $v) { $res[$k] = $func($k, $v); } return $res; } function filter($func) { $res = new self(); foreach ($this as $k => $v) { if ($func($k, $v)) { $res[$k] = $v; } } return $res; }}好了,万事俱备。下面重写的 PHP 代码就可以解决上面提到的问题,并且看起来语法上“差 不多”了:

$res = Arr::make($nums) ->filter(function($k, $v) { return $v > 15; }) ->map(function($k, $v) { return $v * 2; });上面的代码与传统方式有何不同呢?首先,它们可以递归并形成作用链式的调用,因此可以 添加更多的类似操作。

同时,可以通过回调的两个参数分别操作数组的键以及值其项 - $k 对应键以及 $v 对应值 。这使得我们可以在闭包中使用键值,这在传统的 PHP 函数 array_fliter 中是无法实现的。
另外个带来的额外好处就是更加一致 API 调用。使用传统的 PHP 函数操作,它们有可能第一个参数是个闭包,或者是个数组,抑或是多个数组…总之谁知道呢?
这里是 Arr 类的完整源代码,还包含了其他有用的函数(类似 reduce 以及 walk),其实它 们的实现其实方式和代码类似。

博弈

这个问题其实很难回答 - 这需要根据代码的上下文以及程序员自身等众多因素决定。其实 ,当我第一眼看见 PHP 的闭包实现时,我感觉似乎回到了那很久以前的 Java 时期,当时 我在开始使用匿名内置类(anonymous inner classes)来实现闭包。当然,这虽然可以做到, 但看起来实在是些画蛇添足。PHP 闭包本身是没错,只是它的实现以及语法让我感到非常的困惑。
其他具有闭包特性的语言,它们可以非常方便的调用闭包并同时具有优雅的语法。在上面的例子 中,在 Scala 中使用传统的循环也可以工作,但你会这样写吗?而从另个方面,那么有人 说上面这个题目使用 PHP 的闭包也可以实现,但一般情况下你会这样写吗?
可以确定,PHP 闭包在些情况下可以成为锐利的军刀(例如延时执行以及资源调用方面), 但在传统的迭代以及数组操作面前就显得有些为难。不要气馁不管怎么样, 返璞归真编写具有兼容性的、清爽的代码以及 API 是最重要的。

结束语

像所有后来加上的语法特性一样(记得当年 Java 的 Generics 特性不?以及前几年的 PHP OOP 特性),它们都需要时间磨合以及最终稳定下来。随着 PHP5.3 甚至将来的 PHP6 逐渐普及,越来越多的技巧和特性相信在不远的将来逐渐的被聪明的程序员挖掘出来。
回到最初文章开头那个题目,对比
$res = Arr::make($nums) ->filter(function($k, $v) { return $v > 15; }) ->map(function($k, $v) { return $v * 2; });以及
val res = nums filter (_ > 15) map (_ * 2)两者之间的区别。归根结底它们仅是语法而已,本质上都是殊途同归解决了同个问题。程序 语言的应用特性不同,自然孰优孰劣也就无从比较。
最后,这里有此篇文章的代码示例, 相信可以找到更多如何使用 PHP 进行函数式迭代(当然不仅仅是这些)的心得。

不靠谱之博主心得
坦白讲,虽然在 PHP5.0 之前就了解过提出的新增闭包等功能,但在看到 PHP5.3 提供的闭 包以及 Lambda 功能后,与原本心理期待的还是有些出入。
甚至相对于熟悉的 JavaScript,PHP 的闭包在我看来,像是“别的语言都有了,所以我也要有” 的这种心态下的产物。

但正如上文中所言,相比 JavaScript 等其他动态语言,PHP 出于自身的应用以及实现的哲学 出发,与其他开发语言不尽相同。

因此在某些特性的调用方式、实现方法也会不一样,这难免会让熟悉另外具有类似功能的语言 的人感到的不适应。

从 PHP5.3 推出至今,还不到半年的时间,相比 JavaScript 等这些早已具有闭包等特性的 动态语言相比,自然是显得非常稚嫩。

同时,广大的开发者对于 PHP5.3 提供的包括闭包在内的新特性还在持观望态度。PHP 的闭包特性目前还是存在于实验室中,其应用于实际开发如要突破的不仅仅是语言特性 ,还要经过效率、安全性等方面的考验。
但相信,如原文作者所言,随着 PHP 版本的推进,PHP 的闭包应用场合会越来越频繁。像 当年 PHP4 转换到 PHP5 一样,对语言新特性的适应,其实是种痛并快乐着的过程。

PHP 相关文章推荐
一些关于PHP的知识
Nov 17 PHP
php error_log 函数的使用
Apr 13 PHP
PHP 根据IP地址控制访问的代码
Apr 22 PHP
php实现用户在线时间统计详解
Oct 08 PHP
php 按指定元素值去除数组元素的实现方法
Nov 04 PHP
PHP获取当前url的具体方法全面解析
Nov 26 PHP
php实现的百度搜索某地天气的小偷代码
Apr 23 PHP
CI框架中libraries,helpers,hooks文件夹详细说明
Jun 10 PHP
PHP中set_include_path()函数相关用法分析
Jul 18 PHP
PHP入门教程之操作符与控制结构流程详解
Sep 09 PHP
Json_decode 解析json字符串为NULL的解决方法(必看)
Feb 17 PHP
PHP开发之用微信远程遥控服务器
Jan 25 PHP
php实现jQuery扩展函数
Oct 30 #PHP
PHP 读取和修改大文件的某行内容的代码
Oct 30 #PHP
PHP 批量删除数据的方法分析
Oct 30 #PHP
ThinkPHP php 框架学习笔记
Oct 30 #PHP
php pack与unpack 摸板字符字符含义
Oct 29 #PHP
php 显示指定路径下的图片
Oct 29 #PHP
dedecms 批量提取第一张图片最为缩略图的代码(文章+软件)
Oct 29 #PHP
You might like
PHP安全防范技巧分享
2011/11/03 PHP
利用php递归实现无限分类 格式化数组的详解
2013/06/08 PHP
2个比较经典的PHP加密解密函数分享
2014/07/01 PHP
MyEclipse常用配置图文教程
2014/09/11 PHP
php中HTTP_REFERER函数用法实例
2014/11/21 PHP
微信自定义菜单的处理开发示例
2015/04/16 PHP
php htmlentities()函数的定义和用法
2016/05/13 PHP
PHP pthreads v3下同步处理synchronized用法示例
2020/02/21 PHP
仅IE6/7/8中innerHTML返回值忽略英文空格的问题
2011/04/07 Javascript
jQuery下拉美化搜索表单效果代码分享
2015/08/25 Javascript
javascript 中的 delete及delete运算符
2015/11/15 Javascript
jQuery获取元素父节点的方法
2016/06/21 Javascript
jQuery实现的超链接提示效果示例【附demo源码下载】
2016/09/09 Javascript
Bootstrap实现的标签页内容切换显示效果示例
2017/05/25 Javascript
前端防止用户重复提交js实现代码示例
2018/09/07 Javascript
详解如何快速配置webpack多入口脚手架
2018/12/28 Javascript
JS数组方法reverse()用法实例分析
2020/01/18 Javascript
jQuery表单校验插件validator使用方法详解
2020/02/18 jQuery
使用JavaScript获取Django模板指定键值数据
2020/05/27 Javascript
python装饰器与递归算法详解
2016/02/18 Python
python使用threading获取线程函数返回值的实现方法
2017/11/15 Python
python学习笔记之列表(list)与元组(tuple)详解
2017/11/23 Python
python读取raw binary图片并提取统计信息的实例
2020/01/09 Python
pycharm通过anaconda安装pyqt5的教程
2020/03/24 Python
Python Spyder 调出缩进对齐线的操作
2021/02/26 Python
利用CSS3实现的文字定时向上滚动
2016/08/29 HTML / CSS
意大利体育用品和运动服网上商店:Maxi Sport
2019/09/14 全球购物
理货员的岗位职责
2013/11/23 职场文书
手机银行营销方案
2014/03/14 职场文书
广告语设计及教案
2014/03/21 职场文书
幼儿园大班家长评语
2014/04/17 职场文书
英文推荐信格式范文
2014/05/09 职场文书
企业环保标语
2014/06/10 职场文书
2014最新毕业证代领委托书
2014/09/26 职场文书
企业群众路线教育实践活动心得体会
2014/11/03 职场文书
台风停课通知
2015/04/24 职场文书