浅谈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 相关文章推荐
JAVA/JSP学习系列之二
Oct 09 PHP
PHP计划任务之关闭浏览器后仍然继续执行的函数
Jul 22 PHP
php HandlerSocket的使用
May 02 PHP
apache和php之间协同工作的配置经验分享
Apr 08 PHP
php的declare控制符和ticks教程(附示例)
Mar 21 PHP
php字符串截取函数用法分析
Nov 25 PHP
在openSUSE42.1下编译安装PHP7 的方法
Dec 24 PHP
Yii2中cookie用法示例分析
Jul 18 PHP
PHP使用微信开发模式实现搜索已发送图文及匹配关键字回复的方法
Sep 13 PHP
PHP支付宝当面付2.0代码
Dec 21 PHP
PHP PDOStatement::fetchObject讲解
Feb 01 PHP
php适配器模式简单应用示例
Oct 23 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代码质量36计
2012/09/05 PHP
php实现的漂亮分页方法
2014/04/17 PHP
php 7新特性之类型申明详解
2017/06/06 PHP
laravel 多图上传及图片的存储例子
2019/10/14 PHP
Laravel6.2中用于用户登录的新密码确认流程详解
2019/10/16 PHP
javascript各种复制代码收集
2008/09/20 Javascript
AJAX异步从优酷专辑中采集所有视频及信息(JavaScript代码)
2010/11/20 Javascript
JavaScript 大数据相加的问题
2011/08/03 Javascript
Node.js的MongoDB驱动Mongoose基本使用教程
2016/03/01 Javascript
举例讲解jQuery中可见性过滤选择器的使用
2016/04/18 Javascript
jQuery限制图片大小的方法
2016/05/25 Javascript
vue2.0结合Element实现select动态控制input禁用实例
2017/05/12 Javascript
用最简单的方法判断JavaScript中this的指向(推荐)
2017/09/04 Javascript
详解 vue better-scroll滚动插件排坑
2018/02/08 Javascript
Vue实现PopupWindow组件详解
2018/04/28 Javascript
详解vue-property-decorator使用手册
2019/07/29 Javascript
js实现点击图片在屏幕中间弹出放大效果
2019/09/11 Javascript
微信小程序pinker组件使用实现自动相减日期
2020/05/07 Javascript
[47:04]EG vs RNG 2019国际邀请赛小组赛 BO2 第二场 8.16
2019/08/18 DOTA
[52:40]完美世界DOTA2联赛PWL S2 Magma vs GXR 第一场 11.29
2020/12/02 DOTA
Python 互换字典的键值对实例
2019/02/12 Python
python获取点击的坐标画图形的方法
2019/07/09 Python
详解Selenium+PhantomJS+python简单实现爬虫的功能
2019/07/14 Python
django如何实现视图重定向
2019/07/24 Python
TensorFlow实现指数衰减学习率的方法
2020/02/05 Python
Django实现whoosh搜索引擎使用jieba分词
2020/04/08 Python
使用css3制作登录表单的步骤
2014/04/07 HTML / CSS
详解CSS3:overflow属性
2020/11/17 HTML / CSS
Spartoo西班牙官网:法国时尚购物网站
2018/03/27 全球购物
澳洲CFL商城:CHEMIST FOR LESS(中文)
2021/02/28 全球购物
大学系主任推荐信范文
2013/12/24 职场文书
给客户的道歉信
2014/01/13 职场文书
政风行风建设整改方案
2014/10/27 职场文书
学校青年志愿者活动总结
2015/05/06 职场文书
居委会工作总结2015
2015/05/18 职场文书
答辩状格式范本
2015/05/22 职场文书