先日PHP8.5がリリースされましたが、PHPの開発はさっそくPHP8.6に向けて走り始めています。
その目玉はなんといっても部分適用でしょう。
ということで以下はPHP Foundation公式ブログから、PHP 8.6 kicks off with partial function applicationの紹介です。
PHP 8.6 kicks off with partial function application
PHP8.5はまだホットですが、PHPの進化に向けた次の取り組みは既に始まっています。
早くもPHP8.6の目玉となる部分適用のRFCが受理されました。
The gist
PHP8.6では、クロージャ全体を記述せずに、次に委任するクロージャを簡潔に作成できるようになります。
// 部分適用
$underscore = str_replace(' ', '_', ?);
// これまでの書き方
$underscore = fn(string $s): string => str_replace(' ' , '_', $s);
生成されるオペコードはほぼ同じですが、前者の方が型や変数を記述する必要もなく、読むのも書くのもはるかに簡単です。
関数・メソッドの呼び出しにプレースホルダ?・...を使用して、その関数を部分的に呼び出すことができます。
あるいは引数を部分的に適用すると言ってもいいでしょう。
function complex(int $a, int $b, int $c, int $d): string { ... }
// 2引数を受け取るクロージャを作成する
$f = complex(?, 2, ?, 4);
// ↑と同じ、名前付き引数で作成
$f = complex(b: 2, d: 4, ...);
// $fの引数は第一引数が$c、第二引数が$aになる
$f = complex(b: 2, d: 4, c: ?, a: ?);
// 全引数をバインドしない、それはすなわち第一級callable
$f = complex(...);
// 実行時に引数が不要なクロージャも作れる
$f = complex(1, 2, 3, 4, ...);
PFAは、引数の並べ替え、名前付き引数、可変長引数など多種多様なユースケースをサポートしています。
しかし実際のところ、用途のほとんどは引数を残りひとつにすること、すなわちカリー化であると思われます。
その用途に対してPFAは最適です。
PHPのコールバック関数の多くは引数をひとつしか受け取りませんが、一部の関数はキーと値など2つの引数を受け取ることがあったりします。
そのような場合にPFAは最適です。
// 部分適用
$result = array_map(in_array(?, $legal, strict: true), $input);
// こう書くよりずっとわかりやすい
$result = array_map(fn(string $s): bool => in_array($s, $legal, strict: true), $input);
またPFAは、パイプライン演算子の完璧な補完でもあります。
例をいくつか紹介しましょう。
$numberOfAdmins = getUsers()
|> array_filter(?, isAdmin(...))
|> count(...);
$result = "Hello World"
|> htmlentities(...)
|> str_split(...)
|> array_map(strtoupper(...), ?)
|> array_filter(?, fn($v) => $v != 'O')
;
さらにパイプライン演算子の最適化により、これらのケースでは実際にはクロージャの作成を行っておらず、パフォーマンス上のオーバーヘッドはゼロです。
もっと詳しい話はRFCを見てください。
The long view
これらの構文が第一級callableの拡張のように見えるのであれば、そのとおりです。
むしろ第一級callableが部分関数の機能限定版であるというのが正確なところです。
PFAの初めてのRFCは、2021年にJoe Watkins・Levi Morrison・Paul Crovella、そして私のチームによって提出されました。
そのバージョンは表面的には現在とおおむね同じでしたが、内部的には大きな違いがあったため、一部メンバーの忌避感を招きました。
特に当時PHPの事実上のリードエンジニアだったNikita Popovが、当時の実装はPHPエンジンに過剰すぎる複雑さをもたらすと懸念していました。
そのため当時のRFCは却下されましたが、同時に多くの関心と指示も寄せられたため、Nikitaが「foo(...)とすることで、RFCの残りの部分はスキップできないか?」と提案しました。
その結果として、PHP8.1で第一級callableとしてリリースされることになりました。
それ以来、いつかはPFAを復活させたいと考えていましたが、時期と協力者が必要でした。
第一級callableは明らかにPHPに大きな恩恵をもたらしたので、PFAにも挑戦するべきでしょう。
ところが2025年初めにパイプライン演算子のRFCが可決されたことで事態が変わりました。
PHP財団のArnaud Le Blancを巻き込み、PFAに協力してもらうことができました。
タイミングの問題でPHP8.5には間に合いませんでしたが、PHP8.6で使用可能になります。
それでは当時と何が変わったのでしょう。
まず第一級callableには、今回のPFAに必要となる機能の実装が既にたくさん含まれていたので、それを活用することができました。
また内部的な実装も異なります。
当時は特殊な疑似クロージャを用いていましたが、今回は長年使われていた通常のクロージャをそのまま作成します。
これによって実装がはるかに簡単になり、多くのエッジケース問題を解決できました。
そして最後に、パイプライン演算子が使えます。
これはまさに、エキサイティングな組み合わせです。
A long time coming
PFAは、実際には2021年より前から始まっていました。
2016年に、Sara GolemonがHHVMのパイプを移植することを提案しました。
$result = $arr
|> array_column($$, 'tags')
|> array_merge(...$$)
|> array_unique($$)
|> array_values($$)
;
これは承認されませんでしたが、この提案はふたつのアイデアを生み出しました。
すなわち、パイプライン演算子と、部分適用です。
2021年にも両方を実現しようと試みましたが、当時はまた失敗しました。
パイプライン演算子に対して目にした批判のひとつが、パーサの都合で、複数引数の関数はアロー関数でラップしたうえでさらにクロージャ全体を括弧で囲まねばならないというものです。
それは、まったく正当な批判です。
そしてその批判に対する完璧な回答こそが、部分適用です。
今これが実現し、ひとつのアイデアから生まれた双子の兄弟が、ふたたびひとつになったのです。
PHP breaks the mold
PHPは他言語の機能を節操もなくなんでも取り入れるとよく言われます。
正直なところ、それは決して悪いことではありません。
英語と同じように、PHPは他言語から優れたアイデアを取り入れ、そしてPHP独自のものとすることで進化してきたのです。
部分適用は新しい概念ではありません。
多くの関数型言語の基盤となってきた概念であり、たとえばHaskellは全ての関数が部分適用関数であり、単に最後の引数を省略するだけで部分適用になります。
しかし、どの言語にも見られないものは、任意のパラメータを部分適用するという機能です。
これはPHPにとって重要です。
Haskellは標準ライブラリの全てが右部分適用を前提として設計されているのに対し、PHPはそうではないからです。
PHPで部分適用を導入するためには、任意の関数を単項関数に変換できなければなりませんでした。
そしてついに、それが可能になりました。
PHP8.6ほど柔軟で強力でコンパクトな部分適用構文を持つ言語を他に知りません。
この点において、PHPは革新者であると言えるでしょう。
What comes next?
パズルの重要なピースが、あとひとつ残っています。
それは関数合成です。
関数合成は複数の関数を繋げることで、新しい関数を作成します。
Sara Golemonの尽力によって実現されましたが、正式に承認されるまでには、まだまだ作業が残っています。
これが完成すれば、長年PHPに導入しようと考えてきた関数型機能の3要素が完成し、より自然な関数型記述が可能になります。
これらのRFCはそれぞれ単体でも動作しますが、単体ではさほど驚くほどの機能ではありません。
しかし、これらがひとつに集まると……『シナジー』という単語はコンサル界隈以外ではろくでもない言葉の代表ですが、今回の場合はまさにその単語が当て嵌まります。
PHPは5.2でオブジェクト指向機能を完全実現したように、関数型機能を実現する日ももうすぐ近くまで来ています。
そしてマルチパラダイム言語として、オブジェクト型と関数型のアプローチを、最適な形で自由に組み合わせることができるようになります。
もう、待ちきれません!
感想
いやーPHP8.1で第一級callableが入ったときは、正直そんな大した機能だとは思ってなかったので、ここまで発展するなんてびっくりだよ。