PHP5.3 の新しいバージョンは多くの新機能を踏襲しており、最も目を引く機能の 1 つはクロージャのサポートです。では、将来的には、Ruby、JavaScript、その他のハイテク言語を書いている人たちのように、非常にクールなコードを書くことができるのでしょうか?実際、ほとんどの場合は可能ですが、まだ非常に厄介な点もありますので、ゆっくりお話しましょう。
PHP5.3 の新しいバージョンは多くの新機能を踏襲しており、最も目を引く機能の 1 つはクロージャーのサポートです。では、将来的には、Ruby、JavaScript、その他の「ハイテク言語」を書いている人たちのように、非常にクールなコードを書くことができるのでしょうか?実際、ほとんどの場合は可能ですが、まだ非常に厄介な点もありますので、ゆっくりお話しましょう。
多くの言語は、配列を操作するための非常にエレガントで美しいメソッドを提供しています。次の例では、PHP5.3 などの言語で提供されているクロージャー関数を使用して、反復可能な配列を「客観的に」操作する方法を示します。
翻訳注: オリジナルの作者は Groovy 言語と Scala 言語をあまり知らないので、ここに Javascript 実装を追加します。
始める前に、この例は要点を説明するためだけのものであり、パフォーマンスなどの他の要素は考慮されていないことを説明させてください。
次の配列の簡単な例から始めましょう:
$nums = array(10, 20, 30, 40);
配列内で 15 を超える項目を見つける必要があります。次に、クロージャを考慮せずに、次のように書くかもしれません:
$res = array();<br>foreach ($nums as $n) {<br> if ($n > 15) {<br> $res[] = $n;<br> }<br>}
言語自体がクロージャーをサポートしている場合は、次のように記述できます (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) はすべて美しく、1 行で実行できることがわかります。
もちろん、PHP5.3 クロージャを使用する場合も同様に実行できます
$res = array_filter($nums, function($v) { return $v > 15; });
PHP はこのために Scala よりも多くの文字を使用しますが、前の例と比較すると短くて読みやすいです。
ところで、上記の PHP コードは実際にはラムダ分析を使用しており、実際のクロージャではありません。これは現在注目している点ではありません。 PHP クロージャと Lambda 解析の詳細については、ここを参照してください。
ここまでのところ、かなりうまくいっているように思えますので、問題の難易度を上げてみましょう。15 より大きい項目をすべて見つけて、2 とスコープ内の特定の変数値を掛けて返します。
素晴らしい実装:
def x = 1<br>def res = nums .findAll { it > 15 } .collect { it * 2 + x }
Scala 実装:
val x = 1<br>val res = nums filter (_ > 15) map (_ * 2 + x)
翻訳アノテーション、JavaScript 実装:
var i = 1;<br>var res = nums.filter(function(c){return c > 15}).map(function(c){return c * 2 + i});
および PHP:
$x = 1;<br>$res = array_map(<br> function($v) use ($x) { return $v * 2 + $x; },<br> array_filter(<br> $nums,<br> function($v) { return $v > 15; })<br>);
コードサイズの点で、PHP は他の言語とは異なるようです。コードの文字通りの美しさはさておき、上記の PHP コードにはさらに問題があります。
たとえば、比較に値の代わりに配列キーを使用する必要がある場合はどうすればよいでしょうか?はい、上記のコードは実行できません。また、構文的に言えば、上記のコードは非常に読みにくいです。
自然に立ち返っても、問題を解決するには昔ながらの考え方に戻る必要があります。
$x = 1;<br>$res = array();<br>foreach ($nums as $n) {<br> if ($n > 15) {<br> $res[] = $n * 2 + $x;<br> }<br>}
ふぅ、これまたクリアですね。しかしこのとき、「なぜわざわざいじる必要があるの?これはただの配列演算ではないの?」と再び混乱するかもしれません。
はい、最高のものはまだ来ません。現時点では、自傷行為をする傾向があるように見えるこの「退屈な問題」を解決するために、PHP の高度な機能を活用する時期が来ています。
PHP には SPL という標準ライブラリがあり、その中には ArrayObject というクラスがあり、
のような「クラスを配列のように操作する」機能を提供します。$res = new ArrayObject(array(10, 20, 30, 40));<br>foreach ($res as $v) {<br> echo "$vn";<br>}
ArrayObject は組み込みクラスであるため、他のクラス操作と同様にカプセル化できます。
ArrayObject とクロージャーの機能ができたので、それをカプセル化してみましょう。
class Arr extends ArrayObject<br>{<br> static function make($array)<br> {<br> return new self($array);<br> }<br><br> function map($func)<br> {<br> $res = new self();<br> foreach ($this as $k => $v) {<br> $res[$k] = $func($k, $v);<br> }<br> return $res;<br> }<br><br> function filter($func)<br> {<br> $res = new self();<br> foreach ($this as $k => $v) {<br> if ($func($k, $v)) {<br> $res[$k] = $v;<br> }<br> }<br> return $res;<br> }<br>}
さて、準備は完了です。以下の書き換えられた PHP コードは上記の問題を解決でき、構文的には「ほぼ」同じように見えます。
$res = Arr::make($nums)<br> ->filter(function($k, $v) { return $v > 15; })<br> ->map(function($k, $v) { return $v * 2; });上記のコードは従来の方法とどう違うのでしょうか?まず、再帰呼び出しやチェーン呼び出しができるため、より類似した操作を追加できます。
同時に、配列のキーと値は、コールバックの 2 つのパラメーターを通じてそれぞれ操作できます。$k はキーに対応し、$v は値に対応します。これにより、従来の PHP 関数 array_filter では不可能であった、クロージャでキー値を使用できるようになります。
もう 1 つの追加の利点は、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)<br> ->filter(function($k, $v) { return $v > 15; })<br> ->map(function($k, $v) { return $v * 2; });
以及
val res = nums filter (_ > 15) map (_ * 2)
两者之间的区别。归根结底它们仅是语法而已,本质上都是殊途同归解决了同个问题。程序 语言的应用特性不同,自然孰优孰劣也就无从比较。
最后,这里有此篇文章的代码示例, 相信可以找到更多如何使用 PHP 进行函数式迭代(当然不仅仅是这些)的心得。
-- Split --
坦白讲,虽然在 PHP5.0 之前就了解过提出的新增闭包等功能,但在看到 PHP5.3 提供的闭 包以及 Lambda 功能后,与原本心理期待的还是有些出入。
甚至相对于熟悉的 JavaScript,PHP 的闭包在我看来,像是“别的语言都有了,所以我也要有” 的这种心态下的产物。
但正如上文中所言,相比 JavaScript 等其他动态语言,PHP 出于自身的应用以及实现的哲学 出发,与其他开发语言不尽相同。
因此在某些特性的调用方式、实现方法也会不一样,这难免会让熟悉另外具有类似功能的语言 的人感到的不适应。
从 PHP5.3 推出至今,还不到半年的时间,相比 JavaScript 等这些早已具有闭包等特性的 动态语言相比,自然是显得非常稚嫩。
同时,广大的开发者对于 PHP5.3 提供的包括闭包在内的新特性还在持观望态度。PHP 的闭包特性目前还是存在于实验室中,其应用于实际开发如要突破的不仅仅是语言特性 ,还要经过效率、安全性等方面的考验。
但相信,如原文作者所言,随着 PHP 版本的推进,PHP 的闭包应用场合会越来越频繁。像 当年 PHP4 转换到 PHP5 一样,对语言新特性的适应,其实是种痛并快乐着的过程