世界を動かす技術を、日本語で。

PHP 8.5にパイプ演算子が追加されました

概要

PHP 8.5で新たに導入される パイプ演算子(|>) について解説。 この機能の基本的な使い方と 実用例 を紹介。 他言語との比較や、PHPにおける 歴史的経緯 を整理。 今後予定されている 関連機能(部分適用や関数合成) にも触れる。 PHP開発者にとっての メリットと可能性 を強調。

PHP 8.5のパイプ演算子(|>)とは

  • PHP 8.5 で新たに追加される パイプ演算子(|>) の概要
  • 左側の値を右側の関数(callable)へ 単一引数 として渡す構文
  • 例:
    • $result = "Hello World" |> strlen(...);
    • 上記は $result = strlen("Hello World"); と同義
  • パイプライン として複数回連結することで、処理の流れを簡潔に記述可能
  • Unix/Linuxの シェルパイプ(|) に着想を得た設計

パイプ演算子の実用例

  • 配列操作などの 複雑な処理を直感的に記述 できる利点
    • 例:
      • $result = $arr |> fn($x) => array_column($x, 'tags') |> fn($x) => array_merge(...$x) |> array_unique(...) |> array_values(...);
      • $resultは重複を除去し再インデックスされた配列になる
  • 従来のPHPでは ネストが深くなり可読性が低下、または一時変数が必要
  • パイプチェーンにより、 match()ブロックなど一式で記述可能

パイプ演算子の背景と他言語との比較

  • F#やOCaml、Elixir など関数型言語で普及している構文
  • PHPでも Hack/HHVM で独自のパイプ構文が存在
  • 2016年にSara Golemon氏がPHPへの導入を提案
    • Hack流の$$トークン利用は 非標準的かつ限定的 で不採用
  • 2020/2021年には Partial Function Application (PFA) との連携も議論
    • PFAは複雑さから不採用となったが、 First Class Callables が導入

パイプ演算子の応用と発展性

  • シンタックスシュガー としての役割
    • 実装自体は単純だが、他機能との組み合わせで威力を発揮
  • match()式 やクロージャ返却関数との連携で表現力向上
    • 例:
      • $profit = [1, 4, 5] |> loadSeveral(...) |> filter(isOnSale(...)) |> map(sellWidget(...)) |> array_sum(...);
  • Maybeモナド のようなnull安全処理も簡単に実装可能
    • maybe()関数を使い、nullを自動的に伝播
  • KotlinやC#の拡張関数 に近い柔軟性を獲得
  • ストリーム処理やデータ変換にも応用範囲が広い

今後の展望と関連RFC

  • Partial Function Application の再挑戦
    • 1引数化や柔軟な関数適用を目指す
    • PHP 8.5以降に導入予定、開発中
  • 関数合成演算子 の検討
    • 複数関数の合成でパイプラインの最適化を狙う
    • PHP 8.6以降の実装を目指す
  • PHP Foundationチームの Ilija Tovilo氏、Arnaud Le Blanc氏 らの貢献

パイプ演算子の価値と今後の参加

  • constructor property promotion などと並ぶ高コストパフォーマンス機能
  • 記述の簡潔化、可読性・保守性の向上
  • PHP開発の推進に スポンサーとしての参加 も呼びかけ

Hackerたちの意見

一方で、JS界隈はこの提案を10年間待ってるけど、まだステージ2なんだよね。 https://github.com/tc39/proposal-pipeline-operator/issues/23...

正直、これは必要ないと思う。構文の甘さみたいなもので、ドットを使えばほぼ同じことができるし。PHPにはチェイニングがないし、複雑さを増やしても言語が良くなるわけじゃないよ。

TypeScriptでは、こんな感じで書けるよ。 let res = op1() res = op2(res.op1) res = op3(res.op2) 型推論がすごくうまく働いて、デバッグやリファクタリングもめっちゃ楽。 個人的には、結果をパイプでつなぐよりもこっちの方がいいと思う。JavaScriptには十分な機能があるしね。

10年も待ってるだけじゃなくて、進展の最有力候補が提案された時に求めていたものとは全然違うんだ。 ユニアリ関数(部分関数適用で作られるようなやつ)と相性のいいパイプ演算子が欲しかったのに、それはクロージャを使いすぎるプログラミングスタイルになるって理由で却下されたんだよね。 でもPHPはそういう仮定に縛られず、みんなが欲しかった機能を、最も理にかなった形で提供したんだ。

いいね、でも「JSのパイプの実例」は、正直言って期待外れだと思う。

ゴーランのモナドについては触れたくもないけど、どちらも人気があって進化してるね。

最高でも新しいビルドシステムが3つと新しいフレームワークが10個くらいだろうね。

努力は評価するし、全体的なアプローチも好きだけど、この使い方に関しては、Kotlinみたいな拡張機能や、C#のIEnumerable/イテレーターアプローチがいいな。例えば、こんな感じで: $arr = [ new Widget(tags: ['a', 'b', 'c']), new Widget(tags: ['c', 'd', 'e']), new Widget(tags: ['x', 'y', 'a']), ]; $result = $arr |> fn($x) => array_column($x, 'tags') // 配列の配列を取得 |> fn($x) => array_merge(...$x) // 一つの大きな配列にフラット化 |> array_unique(...) // 重複を削除 |> array_values(...) // 配列を再インデックス。 って書くより、$result = $arr->column('tags')->flatten()->unique()->values()の方がずっとシンプルだと思う。

パイプの利点は、戻り値の型を気にしなくていいことだよね。例えば、そのチェーンの途中にreduceを追加したとする。拡張メソッドだと、それがチェーンの最後に呼び出されることになるけど、パイプだと結果を次の関数にそのまま流せる。

PHPにはトレイトがあるから、そのAPIを考案して、トレイトに入れてデータクラスに追加すればいいんじゃない?

Kotlinには、任意のメソッドをチェーンできる拡張関数let(いくつかのバリエーションもあるよ)があるんだ。例えば、こんな感じで使えるよ:

val arr = ...
val result = arr.let {
    column(it, "tags")
    .let { merge(it) }
    .let { unique(it) }
    .let { values(it) }
}

単一引数の関数も関数参照で追加できるよ:

arr.let(::unique) // または(List::unique)、関数によるけど

特別な言語構文を追加することなくね。

いいね!PHPに一番必要なのは、文字列や配列の関数をもっと一貫性のあるものにして、チェイニングできるようにすることだと思う。今は少なくともチェイニングできるけど、...の構文はあまり好きじゃないな、特にスプレッド演算子と混ざると。

Hacker Newsで議論の続きを見る