Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHPに関数型の魂を宿す〜PHP 8.5 で実現する堅牢なコードとは〜 #phpcon_hir...

Avatar for shogogg shogogg
October 10, 2025

PHPに関数型の魂を宿す〜PHP 8.5 で実現する堅牢なコードとは〜 #phpcon_hiroshima / phpcon-hiroshima-2025

2025年10月11日に開催されたPHPカンファレンス広島2025の登壇資料です。
https://phpcon-hiroshima.jp/

Avatar for shogogg

shogogg

October 10, 2025
Tweet

More Decks by shogogg

Other Decks in Programming

Transcript

  1. Shogo Kawase / @shogogg Oct. 11 2025 PHP Conference Hiroshima

    2025 PHPに関数型の魂を宿す 〜PHP 8.5 で実現する堅牢なコードとは〜
  2. PHP 8.5 の新機能(抜粋) • Pipe operator v3 • Marking return

    values as important (#[\NoDiscard]) • Make OPcache a non-optional part of PHP • RFC 3986 and WHATWG URL compliant API • Clone with v2 • Grapheme cluster for levenshtein, grapheme_levenshtein function • Support Closures in constant expressions • First Class Callables in constant expressions
  3. PHP 8.5 の新機能(抜粋) • Pipe operator v3 • Marking return

    values as important (#[\NoDiscard]) • Make OPcache a non-optional part of PHP • RFC 3986 and WHATWG URL compliant API • Clone with v2 • Grapheme cluster for levenshtein, grapheme_levenshtein function • Support Closures in constant expressions • First Class Callables in constant expressions 今日はこの2つについてお話します!
  4. 自己紹介 河瀨 翔吾 / Shogo Kawase 株式会社 PR TIMES ソフトウェアエンジニア(2024.12〜)

    I LOVE... 妻 / 型安全 / アジャイル / ももいろクローバーZ F1 / マリオカート shogogg shogogg
  5. 命令型プログラミング言語 • 実行すべき命令や手続きを順に記述していくことでプログラムを表現 • プロシージャ・サブルーチン・関数などで処理をひとまとめにする • 代表例:C / COBOL /

    BASIC など 手続き型言語 • データと手続きをまとめた「オブジェクト」を組み合わせてプログラムを表現 • クラスを用いるのが主流だが、JavaScript などのプロトタイプ方式を用いる言語も • 代表例:C++ / Java / JavaScript など オブジェクト指向言語
  6. 宣言型プログラミング言語 • 数学的な「関数」と「式」の組み合わせでプログラムを表現 • データの不変性や関数の参照等価性を重視し、副作用を厳密に制御する(ことが多い) • 代表例:Haskell / LISP /

    OCaml / Elixir など 関数型言語 • 事実と規則を定義し、それらに基づいて質問に答える形でプログラムを実行 • 処理を記述するのではなく、知識を記述 • 代表例:Prolog 論理型言語
  7. 第一級関数(First-Class Functions)と高階関数 変数に関数を代入可能 / 引数に関数を渡せる / 関数の戻り値として関数を返せる 副作用の排除と参照等価性 関数は同じ引数に対して必ず同じ戻り値を返し、スコープ外の状態を変更しない データの不変性(Immutability)

    一度定義した変数の書き換えや、破壊的な操作を避ける 文(Statement)ではなく式(Expression)が中心 手続きを実行するための「文」ではなく、常に値を返す「式」で構成される 関数型言語の持つ特徴
  8. なぜ関数型に注目が集まるのか(1) 2000年代 • CPU のクロック周波数が頭打ちになり、マルチコア化 が進む • アプリケーションも処理性能向上のため並列処理・並行処理 のニーズが増加 •

    複数のコア(スレッド)から状態を参照・変更する命令型パラダイムでは 複雑な排他制御が必要となり、バグの温床に 2010年代 • データの不変性 と副作用の排除 を原則とする関数型に注目が集まる • 多くの言語が関数型のエッセンスを取り入れはじめる
  9. なぜ関数型に注目が集まるのか(2) 2020年代 • AI によるコーディング支援 や AI エージェント が爆発的に普及 •

    手続きを記述し、状態を管理する命令型のコードに比べ、参照等価性を持ち 宣言的に記述された関数型(またはそれに近い)コードは少ないコンテキス ト消費で理解・処理が可能 • 完全な関数型言語でなくても、そのエッセンスを取り入れることで AI フレン ドリーなコードを書くことができる!
  10. PHPの歴史(1) PHP Tools :1995年 • プログラミング言語というよりも名前の通り「ツール」に近いものだった PHP/FI :1996年 • 変数や関数の概念が登場、手続き型言語

    の特徴を持ち始める PHP 3:1998年 • if 文や for 文などが導入され、本格的な手続き型言語へと進化 • クラスなどオブジェクト指向言語 の要素を取り入れ始める PHP 4:2000年 • Zend Engine が導入される
  11. PHPの歴史(2) PHP 5:2004年 • メソッド・プロパティの可視性、インターフェース、抽象クラスなど 本格的なオブジェクト指向言語 として記述できるようになる PHP 6 •

    開発が断念された幻のバージョン PHP 7:2015年 • PHP 7.4 でアロー関数式 が導入され、関数を簡潔に記述できるように PHP 8:2020年 • readonly property/class など、データの不変性を担保する仕組みが強化
  12. ここまでのまとめ • PHP は個人のWEBサイトを拡張するツールから手続き型言語を経て オブジェクト指向言語 へと進化してきた • PHP 7 以降、アロー関数式

    や(限定的ながら)データの不変性 のサポートが 加わり、関数型言語 のエッセンスを取り入れ始めている • AI がコードを書く時代になり、コンテキストの消費が少なく、間違いが 起こりにくい宣言的なコードの価値が向上 している
  13. hello_world = " Hello World!! " # パイプ演算子を使わない場合 IO.puts(String.upcase(String.trim(hello_world))) #

    -> HELLO WORLD !! # パイプライン演算子( |>)を使った場合 hello_world |> String.trim |> String.upcase |> IO.puts # -> HELLO WORLD !! Elixirのパイプ演算子
  14. $hello_world = ' Hello World!! '; // パイプ演算子を使わない場合 echo strtoupper(trim($hello_world));

    // -> HELLO WORLD !! // パイプライン演算子( |>)を使った場合 echo $hello_world |> trim(...) |> strtoupper(...); // -> HELLO WORLD !! PHPのパイプ演算子
  15. $input = [' ', 'alice', ' ', '&', ' ',

    'bob', ' ']; echo htmlentities(trim(implode(array_map(ucfirst(...), $input)))); // -> Alice & Bob 「データの流れと記述の順番が一致する」とは なんだかとっても読みづらい
  16. $input = [' ', 'alice', ' ', '&', ' ',

    'bob', ' ']; echo htmlentities( trim( implode( array_map(ucfirst(...), $input) ) ) ); // -> Alice & Bob 「データの流れと記述の順番が一致する」とは 改行すれば少しはマシに……なってる?
  17. $input = [' ', 'alice', ' ', '&', ' ',

    'bob', ' ']; echo htmlentities( trim( implode( array_map(ucfirst(...), $input) ) ) ); // -> Alice & Bob 「データの流れと記述の順番が一致する」とは 処理の流れと記述の順番が逆 → 読みづらい!
  18. $input = [' ', 'alice', ' ', '&', ' ',

    'bob', ' ']; echo $input |> fn ($array) => array_map(ucfirst(...), $input) |> implode(...) |> trim(...) |> htmlentities(...); // -> Alice & Bob 「データの流れと記述の順番が一致する」とは 処理の流れと記述の順番が一致 → 読みやすい!
  19. $input = [' ', 'alice', ' ', '&', ' ',

    'bob', ' ']; echo htmlentities( trim( implode( array_map(ucfirst(...), $input) ) ) ); // -> Alice & Bob 改行しただけでは読みにくいので…… 「中間変数を使わずに済む」とは
  20. $input = [' ', 'alice', ' ', '&', ' ',

    'bob', ' ']; $tokens = array_map(ucfirst(...), $input); $joined = implode($tokens); $trimmed = trim($joined); $escaped = htmlentities($trimmed); echo $escaped; // -> Alice & Bob 「中間変数を使わずに済む」とは 中間変数を導入してコードを整理することがある
  21. $input = [' ', 'alice', ' ', '&', ' ',

    'bob', ' ']; echo $input |> fn ($array) => array_map(ucfirst(...), $input) |> implode(...) |> trim(...) |> htmlentities(...); // -> Alice & Bob 「中間変数を使わずに済む」とは 中間変数がなくなってスッキリ?
  22. 中間変数がもたらすメリットとデメリット 変数が増える=考えることが増える • 適当な変数名では読み手に混乱を与える → 変数名を考える手間が生じる • 変数が増えると、それぞれが「どこで」「どのように」参照されているのかを 考える必要が生じる →

    認知負荷が高くなってしまう • 特にリファクタリングでは「この変数の中身が変わったらどうなる?」「この変数を 消したらどうなる?」など確認することが増える → 変更容易性が下がる デバッグがしやすい • 処理が複数の代入式で構成されるため、ブレークポイントが置きやすい • 処理の途中結果が変数として参照できるため、何が起きているのか確認しやすい
  23. 中間変数 vs パイプ演算子 • ブレークポイントが置きやすい • デバッグ中に処理途中のデータが確認 しやすい 中間変数 •

    中間変数の名前を考える手間がない • 記述がシンプルになり、変更容易性に 優れる パイプ演算子 一概にどちらが優れているとは言いづらい
  24. パイプ演算子:まとめ • F# や Elixir と同じパイプ演算子 が PHP 8.5 で導入される!

    • パイプ演算子は変更容易性の高いシンプルなコード を実現する新たな手段 • 従来の「中間変数」を用いた書き方を完全に置き換えるものではなく コードの内容や文脈によってうまく使い分けたい
  25. NoDiscard アトリビュート • #[\NoDiscard] というアトリビュートが設定された関数やメソッドの戻り 値を無視する(捨てる)と Warning が発生するようになる • flock

    や DateTimeImmutable::setDate() など一部の組み込み関数・ メソッドにも NoDiscard アトリビュートが設定され、戻り値を捨てている と Warning が発生するように • 他言語の例としてはC言語の [[nodiscard]] アトリビュートや Rust の #[must_use] アトリビュートなど
  26. #[\NoDiscard('DO NOT DISCARD THE RESULT')] function hello(): string { return

    'Hello World!!'; } // OK $result = hello(); // OK echo hello(); // OK (void)hello(); // Warning: The return value of function hello() should either be used or intentionally ignored by casting it as (void), DO NOT DISCARD THE RESULT hello(); NoDiscard アトリビュートの例
  27. Result型、流行ってますね! • 成功/失敗を例外ではなく戻り値で表現する Result 型 が近年流行している • Rust が導入し、Swift も採用したことで一気に普及・流行

    • Haskell や Scala など関数型言語の Either 型 が原型 • 他の言語でもサードパーティーライブラリとして実装する例が増えている ◦ neverthrow(TypeScript) ◦ graham-campbell/result-type(PHP) ◦ returns(Python) ◦ kotlin-result(Kotlin)
  28. 検査例外? • Java では特定の種類の例外は try ... catch で処理しないとコンパイル時点で エラーとなる(検査例外) ◦

    IOException(ファイル入出力時に発生するエラー) ◦ SocketException(ネットワーク通信中に発生するエラー) ◦ SQLException(DB 操作時に発生するエラー) • プログラマによる実装が不十分であり、コードに問題があるような場合は catch しなくてもエラーとならない(非検査例外) ◦ NullPointerException(null に対する不正な操作) ◦ IllegalArgumentException(不正な引数) ◦ ArrayIndexOutOfBoundsException(配列の範囲外にアクセス)
  29. /** DoSomething 関数の結果を表す型 */ interface DoSomethingResult {} /** DoSomething 関数が成功した場合の戻り値

    */ final class DoSomethingSuccess implements DoSomethingResult { public function __construct(public readonly string $value) {} } /** DoSomething 関数がファイル入出力に失敗した場合の戻り値 */ final class DoSomethingFileIOFailure implements DoSomethingResult {} /** DoSomething 関数がネットワーク通信に失敗した場合の戻り値 */ final class DoSomethingNetworkIOFailure implements DoSomethingResult {} PHP × Result型 × NoDiscard アトリビュート
  30. #[/NoDiscard] function doSomething(): DoSomethingResult { // ファイルの読み書きに失敗したら DoSomethingFileIOFailure を返す //

    ネットワーク通信に失敗したら DoSomethingNetworkIOFailure を返す // 成功したら DoSomethingSuccess を返す } $result = doSomething(); match (true) { $result instanceof DoSomethingSuccess => // 成功時の処理 $result instanceof DoSomethingFileIOFailure => // ファイル入出力失敗時の処理 $result instanceof DoSomethingNetworkIOFailure => // ネットワーク通信失敗時の処理 }; PHP × Result型 × NoDiscard アトリビュート
  31. • NoDiscard アトリビュートにより、戻り値を無視すると Warning が発生 → 戻り値の処理が強制できる • 型を確定させるまでプロパティやメソッドへのアクセスを制限(静的解析) →

    戻り値の型検査によるエラー制御を強制できる • match 式を使うことで型検査の漏れを実行時エラーにできる → エラー制御の網羅性が担保できる • 引数チェックなどは従来通り例外を投げることで検査例外と非検査例外を区 別する →「例外が起きたらバグ」という状態を実現できる PHP × Result型 × NoDiscard アトリビュート
  32. 本日のまとめ PHP はマルチパラダイム言語 • PHP はオブジェクト指向を軸に様々な言語の「いいとこ」取りをしている • 関数型の考え方をうまく適用すると AI にもやさしい読みやすいコードになる

    PHP 8.5 で起きること • パイプ演算子や NoDiscard アトリビュートなど現代的なプログラミング言語 のエッセンスが追加される • それらをうまく使うことで、これまで以上にシンプルなコード、堅牢なコード になる可能性を秘めている • 他にもステキなアップデートがたくさん!ちゃんとバージョンアップしよ う!
  33. $normalize = fn (string $input): string => $input |> trim(...)

    |> strtoupper(...) |> htmlentities(...); パイプ演算子を使って関数合成っぽいこと(?)
  34. $normalize = fn (string $input): string => $input |> trim(...)

    |> strtoupper(...) |> htmlentities( ?, // <- これ flags: ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, encoding: 'Shift_JIS', ); 次の PHP (8.6?9.0?) で関数の部分適用が来る?