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

PHP 5.3版本跟随了很多新特性,其中比较惹眼的特性之一就是支持了闭包。文章将使用PHP 5.3 以及其他语言提供的闭包功能,用于展示如何“客观的”操作迭代数组。在开始之前先说明下,本例子仅仅是阐明观点,并没有考虑性能等其他方面的因素。

51CTO推荐专题: PHP开发基础入门

“货比三家”

用个简单的例子开始,有下面个数组:

$nums=array(10, 20, 30, 40);

需要找出数组中大于15的项。那么,不考虑闭包的情况下,我们或许会这样写:

$res=array();foreach ($nums as $n)

{

if ($n>15)

{

$res[] = $n;

}}

如果语言本身有闭包支持的,那么或许会这样写(Groovy 语言)

defres=nums.findAll { it>15 }

或者使用Scala语言:

valres=numsfilter (_>15)

译注:Javascript 1.6 的话会是如下:

varres=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解析式,并不是个真正的闭包,这个并不是我们目前关注的重点。目前看来感觉都还不错,那么我们再的题目增加点难度:找到所有大于15的项, 然后乘以2再加上作用域中的的某个变量值以后再返回。

Groovy的实现:

defx=1defres=nums.findAll { it>15 } .collect { it * 2 + x }

Scala的实现:

valx=1valres=numsfilter (_>15) map (_ * 2 + x)

Javascript 的实现:

vari=1;varres=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=newArrayObject(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=newself();

foreach ($this as $k=>$v)

{

$res[$k] = $func($k, $v);

}

return $res;

}

function filter($func)

{

$res=newself();

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甚至将来的PHP 6逐渐普及,越来越多的技巧和特性相信在不远的将来逐渐的被聪明的程序员挖掘出来。回到最初文章开头那个题目,对比:

$res=Arr::make($nums)

->filter(function($k, $v)

{ return $v>15; })

->map(function($k, $v)

{ return $v * 2; });以及

valres=numsfilter (_>15) map (_ * 2)

两者之间的区别。归根结底它们仅是语法而已,本质上都是殊途同归解决了同个问题。程序语言的应用特性不同,自然孰优孰劣也就无从比较。最后,这里有此篇文章的代码示例, 相信可以找到更多如何使用PHP进行函数式迭代(当然不仅仅是这些)的心得。

文章转自李魁的博客,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值