Quantcast
Channel: 日本語訳タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 249

【PHP8.1】呼び出し元に返らない返り値の型が指定できるようになる

$
0
0
function foo():XXX{ exit; } この関数の返り値の型は何にすればいいでしょうか。 null? void? nullはnullという型ですし、voidは『値を返さない』であって『呼び出し元に返らない』ではありません。 ということで『呼び出し元に返らない』を明記できる型が提案されました。 返らないのに返り値とは。 PHP8.1以降ではこう書けるようになります。 function foo():never{ exit; } 以下は該当のRFC、PHP RFC: noreturn typeの日本語訳です。 PHP RFC: noreturn type Introduction ここ数年の傾向として、元々はPHP docで表現されていた型がPHPネイティブになっていくということがあります。 過去の例としてはスカラー型、返り値の型、UNION型、mixed型、static型などです。 現在、PHPの静的解析ツールは、常に例外を発したり常にexitしたりする関数を示すために、@return noreturn構文をサポートしています。 ツールのユーザは、自分のコードの動作を表すためにこの構文が便利だと感じていると思いますが、PHPネイティブのコンパイル・実行時の型チェックがサポートすればより便利になると思います。 Proposal 戻り値の型としてnoreturnを導入する。 リダイレクトする関数は、この戻り値型のよいサンプルです。 function redirect(string $uri): noreturn { header('Location: ' . $uri); exit(); } function redirectToLoginPage(): noreturn { redirect('/login'); } PHP開発者は、この関数を呼び出したときに、その後の文が評価されないことが保証されるという安心感を得ることができます。 function sayHello(?User $user) { if (!$user) { redirectToLoginPage(); } echo 'Hello ' . $user->getName(); // redirectToLoginPageが呼ばれたらここには絶対に来ない } 後からredirect関数にreturnを追加しようとしても、コンパイルエラーが発生します。 function redirect(string $uri): noreturn { if ($uri === '') { return; // Fatal error: A noreturn function must not return } header('Location: ' . $uri); exit(); } 暗黙的なreturnにしようとした場合は、TypeErrorが発生します。 function redirect(string $uri): noreturn { if ($uri !== '') { header('Location: ' . $uri); exit(); } } redirect(''); // Uncaught TypeError: redirect(): Nothing was expected to be returned noreturn関数内でyieldを使うとコンパイルエラーになります。 function generateList(string $uri): noreturn { yield 1; exit(); } // Fatal error: Generator return type must be a supertype of Generator Applicability noreturn型は、void型と同じく返り値の型としてのみ有効です。 引数やプロパティとして使おうとするとコンパイルエラーになります。 class A { public noreturn $x; // Fatal error } Variance 型理論において、noreturn型はボトム型とされます。 すなわち、PHPの型システムにおいては、noreturn型はvoidを含む全ての型のサブタイプになるということです。 従って、noreturn型は他のサブタイプと同じルールに従います。 返り値の型を狭めることができます。 abstract class Person { abstract public function hasAgreedToTerms(): bool; } class Kid extends Person { public function hasAgreedToTerms(): noreturn { throw new \Exception('Kids cannot legally agree to terms'); } } 返り値の型を広げることはできません。 abstract class Redirector { abstract public function execute(): noreturn; } class BadRedirector extends Redirector { public function execute(): void {} // Fatal error } リファレンスを使うことも可能です。 class A { public function &test(): int { ... } } class B extends A { public function &test(): noreturn { throw new Exception; } } __toStringメソッドにも適用可能です。 class A implements Stringable { public function __toString(): string { return "hello"; } } class B extends A { public function __toString(): noreturn { throw new \Exception('not supported'); } } noreturn型は全ての型のサブタイプであるため、他の型で問題なくアノテーションできます。 function doFoo(): int { throw new \Exception(); } Prior art in other interpreted languages 他のインタプリタ型での同様な実装。 Hacklang noreturn型 TypeScript never型 Python NoReturn型 Prior art in PHP static analysis tools PHP静的解析ツールでの同様な実装。 PsalmとPHPStanは、/** @return noreturn */をサポートしている。 PHPStormは、PHP8のアトリビュートで#[JetBrains\PhpStorm\NoReturn]をサポートしている。 Comparison to void noreturnとvoidは、いずれも返り値の型にしか書けないということは同じですが、類似点はそれだけです。 void型の関数を呼んだ場合、通常はそれ以降のプログラムも実行されるという想定です。 function sayHello(string $name): void { echo "Hello $name"; } sayHello('World'); echo ", it’s nice to meet you"; noreturn型の関数は、その後の文が実行されることはありません。 function redirect(string $uri): noreturn { header('Location: ' . $uri); exit(); } redirect('/index.html'); echo "this will never be executed!"; Naming ネーミングは難しい。 noreturn ・既存のクラス名として使われている可能性は低い。 ・関数っぽい名前。 never ・1単語であり、no-returnみたいに区切りを入れたくなる衝動がおこらない。 ・特定状況で使われるキーワードではなく、本格的な型として扱える。遠い将来ジェネリクスが入ったときにも使える単語だろう。 Backwards Incompatible Changes 互換性のない変更として、neverが予約語に追加されます。 Proposed PHP Version(s) PHP 8.1。 Patches and Tests Support noreturn types #6761 Vote 投票は2021/03/30から2021/04/13に行われ、賛成42反対11で受理されました。 またキーワードについて、noreturn・neverどちらがよいかという投票も同時に行われました。 こちらはnoreturn14票・never34票となっており、neverに決定しました。 RFCの本文はnoreturn前提で書かれているのですが、まあそのうち書き替えられると思います。 感想 never型は、void型やfalse疑似型同様、返り値にしか書けない特殊な型となります。 途中でexitするなんて他の言語ではなかなか考えづらいものがありますが、PHPの場合はリダイレクトという非常に自然な例が存在します。 function redirect(string $url): never { header('Location: ' . $url); exit; } これについては、他の書き方の方がかえって不自然になるでしょう。 こういうところに使えば、サジェストなどに出てくるため使い勝手がよりよくなりますね。 もっともフレームワークを使っていれば、あまり目にする機会はなさそうです。 たとえばLaravelだとユーザコード上でexitすることは考えられておらず、リダイレクトもRedirectResponseを適当に設定してreturnで送り返せ、みたいな設計だったりするので、ユーザから見える範囲にnever型が現れてくることはないでしょう。 ……いや待てddがあった! これでddとdumpの動作の違いをシグネチャで区別できるようになるぞ。 何の意味があるのかはわかりません。

Viewing all articles
Browse latest Browse all 249

Latest Images

Trending Articles