php-logo

【導入決定!】PHP7で実装されるスカラー型宣言とは?

  • このエントリーをはてなブックマークに追加
  • Evernoteに保存

さる2015年3月17日の午前6時(JST)、幾つかのドラマを経て、とうとうスカラー型宣言(別名スカラータイプヒンティング:略して STH)の RFC が投票で可決されました。これは PHP 7 への導入決定を意味します。

この提案は既存のタイプヒンティングの適用範囲をスカラー型にまで押し広げ、またスカラー型について弱い型検査厳密な型検査2つのモードを導入し、ファイル単位で2つを切り替えて使うことができるようにする、というものです。
これに伴い、 PHP 7 ではクラスインターフェーストレイトの名前に int、 float、 string、 bool を使うことはできなくなります。

この記事ではスカラー型宣言がどのように使うものなのか、そもそもタイプヒンティングって何だっけという話、そしてスカラー型に対するタイプヒンティングがこれまでどのように議論されてきたものなのかや、今回の提案が可決に至るまでの経緯について簡単にまとめます。
対象読者は以下のような人です。

  • PHP 7 の新機能に興味がある人
  • PHP 5 のタイプヒンティングでスカラー型が指定できないのをつねづね苦々しく思っていた人
  • とても暇な人

なお、この記事はリスペクトのプログラマー、 @sji_ch が書きました。内容についてご指摘などありましたら、ぜひ twitter やはてブのコメントにてお伝えください。

スカラー型宣言の使い方

利用が任意である点は、従来のタイプヒンティングと変わりありません。

function hige(int $huge) {} // これができるようになるが
function hoge($hage) {} // いらなければタイプヒンティングを使わなくともよい

弱いモード

デフォルトで使われるのは弱い型検査です。
関数定義での宣言と異なる型の値で呼び出す際、可能な場合は暗黙の型変換が行われ、それ以外は例外を投げます。

function foo (float $bar){}
foo(3); // OK
foo(‘123’); // OK
foo(‘abc’); // NG

暗黙の型変換は以下のようなルールで行われます。

宣言型 int float string bool object
int yes yes* yes** yes no
float yes yes yes** yes no
string yes yes yes yes yes***
bool yes yes yes yes no

* NaN でなく PHP_INT_MIN から PHP_INT_MAX までの範囲内の値に限る
** 数値形の文字列のみ。数値形の文字列に他の文字が続く場合も使えるが、 Notice が出る
*** __toString メソッドを持つもののみ

弱い型検査モードで行われる暗黙の型変換は、PHP が従来他の構文(算術演算子や条件式、組み込み関数の呼び出しなど)で行ってきたのと同等のものです。

厳密モード

一方、厳密な型検査のモードを使う場合は、関数は宣言されたのと正確に一致する型のみを受け付けるようになります(int → float の拡大変換をのぞく)。
ファイルの先頭に declare(strict_types=1); と書くことで、そのファイル内での関数呼び出しの際に厳密モードでの型検査が行われるようになります*1

以下のように使います。

<?php
declare(strict_types=1);

function foo(int $bar) {}
function fizz(float $buzz) {}

foo(123); // OK
foo(‘123’); // NG
fizz(123); // OK

include(‘external_function.php’);
// ↑のファイルが↓のような内容だとする
// <?php
// function external_function(int $mogera) {}
//  [EOF]

external_function(‘123’); // NG

float をとるよう宣言された関数が整数(int)でも呼び出せるようになっていますが、このケースに限っては Java などにある基本型の拡大変換と同様に、厳密モードであっても暗黙的変換が許容されています。

この厳密な型検査モードについて重要なのは、ユーザーランドのコードで型検査が厳密なものになるというだけではなく、 PHP の組み込み関数や拡張ライブラリ関数についても、同様に厳密な型検査を行うよう振る舞いが変わるということです。デフォルトではこれらの関数は弱い型検査のみを行います。

型検査モードの適用範囲はファイル単位です。冒頭で declare(strict_types=1) が使われたファイル内での関数呼び出しにのみ、厳密な型検査が適用されます。
呼び出される関数の定義がどちらのモードのファイルに書かれたかではなく、どちらのモードのファイルから呼び出されるかで、使われる型検査が変わることに注意してください。
例えば上の例で、external_function.php には declare(strict_types=1) が書かれていません。

ただし PHP 7 で別途導入が決定された返り値の型宣言については少し特殊で、返り値の型にスカラー型を指定した場合の型検査には、その関数定義が書かれたファイルのモードが使われます。

declare(strict_types=1);

function hoge(int hoge) : int { // intをとりintを返す関数
    return ‘123’; // どのモードのファイルから呼ばれてもNG
}

引数に対するタイプヒンティングとのこの違いは、引数へ誤った型の値を渡すのは呼び出し側の問題である一方、誤った型の値を返すのは関数の実装側の問題であるということによります。

  • *1ところで比較的どうでもよい点なのですが、厳密な型宣言の利用を開始するという記述が strict_types=true ではなく strict_types=1 の形をとるというのは、ちょっと面白い話ですね

タイプヒンティングとは

PHP 5 において導入されたタイプヒンティングは、関数が引数としてとるべき値の型を、その関数定義において指定するものです。
これは一見 C や C++/Java/C# などの静的型付けの言語における関数定義と近い形のものですが、主としてコンパイル時に型の整合性が検査されるそれらの言語とは違い、 PHP においては実行時に指定された以外の型の値が関数へ渡された場合、例外を投げる、という機能です。
コンパイル時にほぼ全ての実行パスについてチェックが可能な静的型付けの言語のものほど強力な仕組みではないかもしれませんが、関数へ本来あり得ない型の値が渡されるというような、単純なプログラム上のミスは早期に発見しやすくなります。

タイプヒンティングは最初に PHP 5.0 でオブジェクトまたはインターフェースが、 5.1 で配列が、 5.4 で callable が指定できるようにと進化してきました。
しかし PHP が 5.6 になっても、 int や string といった基本的な型(スカラー型)に対しては指定することができませんでした。

function accepts_object(ClassFooBar $objectFooBar) { // ClassFooBarの値をとる関数
    // 5.0から可能
}

function accepts_array(array $arrayFooBar) { // 何かしらの配列をとる関数
    // 5.1から可能
}

function accepts_callable(callable $funcFooBar) { // クロージャや関数名をとる関数
    // 5.4から可能
}

function accepts_scalar(int $integerFooBar) { // 整数値をとる関数
    // 7.0から可能に!
}

導入に至るまでの経緯

「2つのモードを切り替えて使える」という話を聞いた人の反応は、おそらく以下の2つに分かれるのではないかと思います。

  • わお!どっちも使えるなんて便利
  • ワオ!なんでPHPはまたそんなクソみてえなめんどくせえ選択をしちまったんだ?

せっかくなので、この RFC がどのようにして提案され、可決に至ったかの経緯について簡単にまとめます。

スカラータイプヒンティングに関する過去の議論

スカラー型に対するタイプヒンティングはかねてから議論されており、PHP5.1の頃には既にパッチを用意した人がいたようですし*1、PHP 5.3 やPHP 5.4 の頃にも様々な方式の提案が出されていました。そしてこれらはどれもコミュニティの十分な合意が得られず、実現には至りませんでした。

5.4 向けに議論されていた頃の動向については、PHP 開発者の1人である nikic さん(ジェネレータ変数以外への empty()可変個引数の … 表記などを PHP 5 に持ち込んだ人)の記事で詳しくまとまっています。
Scalar type hinting is harder than you think 06. March 2012

ここで抑えておくべきなのは、この機能に対する要望自体は以前から根強くあり、幾つかの方法が議論の中で挙げられていて、どれも長所短所があり、結局は何にも決まらなかったのだということ、しかし当時から行われてきたこれらの議論が、現在可決された提案に対してもある程度影響を与えているのだということです。

厳密なタイプヒンティングと弱いタイプヒンティングの論点

厳密なタイプヒンティング

動的型付け言語である PHP へ型チェックを望む人は、おそらくその多くが他の静的型付け言語の経験者です。
タイプヒンティングの方式についてそういった人々が最初に思い付くのは、他の言語で採用されているような比較的厳密な型検査となるでしょう。
関数定義に書かれているのと違う型の値は渡せないようにしてほしいという、つまり、以下のようなコードに文字列や浮動小数点数を渡そうとするのはプログラマの間違いなので、ぜひともエラーを吐いてほしいという考え方です。

function foo(int $bar) {
    echo $bar;
}

しかし、この考え方は単純で分かりやすそうに見えて、幾つか困った問題があります。
たとえば上の関数を foo(‘1′) のように文字列の「1」を渡して呼び出す場合、タイプヒンティングは本当に例外を投げるべきでしょうか?またその逆に、文字列をとる関数へ数値型の値を渡す場合はどうでしょうか?

そもそも、 PHP は型を前面に出さない動的言語です。
暗黙的な型変換がことあるごとに行われ、ある値が文字列として扱われるか、それとも数値として扱われるか、ということが、使用される文脈に応じてコロコロ変わります。
PHP では ‘1’+1 は 2 という結果を返しますし、 1.’23’ は ‘123’ を返します。おそらく PHP 以外の言語の出身者からは(あるいは多くの PHP プログラマにとってでさえ)驚くべきことかもしれませんが、 ‘123abc’+4 という式は、 PHP では 127 という結果を返すのです。
組み込み関数や拡張ライブラリの関数もまた、数値や文字列といった型の値について、その内容次第で扱い方を変えます。

このような言語である PHP において、関数の呼び出しでのみ厳密な型検査を行おうとすることには、はたして本当に意味があるのでしょうか?
他の多くの構文において当然のように暗黙的な型変換が行われ、タイプヒンティングについてはそれが許されない、というような一貫性のなさは、果たして本当に全ての PHP 利用者にとって望ましいものなのでしょうか。

また厳密なタイプヒンティングは、おそらく既存コードに対して明示的なキャストの利用を助長しがちになります。 PHP ではオブジェクトからスカラー型へのキャストのような一部のケースをのぞいて、ほとんどの場合明示的なキャストに失敗しません。たとえば、PHP では (int)’abc’ は警告すら出さず、 0 への変換という形で成功します。このことがバグの温床となり、結局はタイプヒンティングの恩恵を台無しにしてしまうというのは、いかにも起こりそうな話です。

PHP の組み込み関数や拡張ライブラリの関数にとっては、厳密な型検査を採用する場合、タイプヒンティングの導入とこれまでの(暗黙的な型変換を前提とした)インターフェースとの後方互換性を両立させることができず、双方を天秤にかけた上でどちらかを選ばねばならなくなります。

しかしたとえそのような諸々の問題があろうとも、厳密な型検査こそが型検査に求めるもの、と考える PHP プログラマは一定数以上存在します。
実際のところ、 PHP が弱い型付けの言語だからこそ、タイプヒンティングでは型の扱いを厳密にすることによってコードへ一定の縛りを与える、そうできるのが利点である、という考え方も、確かに否定しきれるものではありません。
さんざん不都合な点を挙げた厳密方式のタイプヒンティングですが、例えばコードの静的解析のしやすさといった面では、次に述べる弱いタイプヒンティングよりも、厳密なタイプヒンティングの方に軍配が上がるといった話があります。

弱いタイプヒンティング

厳密なタイプヒンティングには PHP という言語の機能として馴染まない部分がある、となれば、次に考えるのはより緩いルールでの型検査です。
たとえば PHP の他の部分、演算子や組み込み関数で行われるのと同じような方法で、関数呼び出し時に暗黙的な型変換がなされ、変換できない場合にのみ例外を投げる、という弱いタイプヒンティングであれば、言語内での一貫性は損なわれません。
多くの PHP プログラマは、こちらのタイプヒンティングの導入によって十分に幸せに暮らせるようになりそうです。
暗黙的な型変換により、たとえ弱いタイプヒンティングであっても、関数の実装者はその関数へ渡される値がどんな型を持つかについて、最低限の前提をもって開発することができるようになります。

弱いタイプヒンティングは大抵のケースでうまく動く筈ですが、しかし単にこの弱いタイプヒンティングを採用する場合、一定数以上存在する厳密な型検査を求める人々のことはないがしろにすることになります。
暗黙的な型変換で渡される値なんて見たくない、変な値が入ってくるのは間違いなくプログラムのミスなのでさっさと死んでほしい、例外投げてほしい、という考え方は、決して不思議なものではありません。

2つの内のどちらがより合理的な選択であるかというのは、実際のところ難しい問題なのです。

Dual-Mode RFC(めんどいし、いっそ両方入れちまおうぜ!)

今回可決された提案は、一部の人々には Dual-Mode STH と呼ばれています。
厳密なタイプヒンティングと弱いタイプヒンティング、2つのうちのどちらが良いものなのかはケースバイケースだったり好みの問題であるとして、ならばプログラマが必要に応じてどちらを使うか選べるようにすればよい、という考え方です。

始まり

この RFC の最初のバージョンである 0.1 は Andrea Fauld (ajf) によって2014年12月に書かれ、当初は弱いタイプヒンティングのみを持つ提案でした。
https://wiki.php.net/rfc/scalar_type_hints?rev=1418527424

ベースになっているのは2012年にAnthony Ferrara (ircmaxell) によって提案されたもの*1で、Andrea は最初この RFC に少し手を加えた形でのスカラータイプヒンティングの導入を目指していました。
http://news.php.net/php.internals/75383
しかしこの RFC はコミュニティでの議論と修正を重ねていくうちに、厳密路線と弱い路線のツギハギのような形になり、どちらの派閥にとっても魅力的ではないものとなってしまったことで、結局は棄てられることになりました。

そしてそれまでスカラータイプヒンティングについて出された提案が何故失敗したのかを分析し、とにかく PHP の構文や組み込み関数などとの不整合をできる限り避け、PHP の他の部分と同じルールでの型変換を行うものとして作られたのが 0.1 の RFC です。

この案は幾つか細かい修正点や懸念点が挙げられながらも、おおむね PHP 開発コミュニティの中で受け入れられた案でした。

厳密モードの導入

その後2015年1月に改定されて出た 0.2 において、やや唐突に、この提案へ厳密なタイプヒンティングを用いるモードが追加されます。
https://wiki.php.net/rfc/scalar_type_hints?rev=1421192225

主として、「厳密な方を望む人が結構いるっぽいから」というのが追加の理由です。
http://news.php.net/php.internals/80497

この RFC は、このやり方でモード切り替えによって2つの型検査の方法を同じ構文で持つことには、多くの利点があると主張しています。

  • 厳密派も弱い派も、どちらも好きな方を選んで使える
  • 各種ライブラリの API は、ユーザにどちらを選ぶか強制する必要がなくなる
  • デフォルトが弱い方なので、ライブラリなどの既存コードが利用側との互換性を保ったままでもスカラータイプヒンティングを導入しやすくなり、古いコードに少しずつ型検査を導入していくような漸進的な型付けが可能となる
  • 厳密な方が好きな人にとっては、モード切り替えによって拡張ライブラリや組み込み関数での型検査の挙動も変わるため、ユーザーランドの部分に限らない一貫した型検査の方法を手に入れることができる
  • 厳密な型検査モードはデフォルトでは無効なため、厳密モードであれば組み込み関数も拡張ライブラリの関数も全てが型検査の失敗時に E_RECOVERABLE_ERROR を投げるという、一貫した挙動を後方互換性を崩さずに得られる

議論の紛糾

この改訂によって議論は紛糾します。

「厳密な方が好きな奴なんか本当にいるのかよ、 0.1 のままでよかったじゃん!」というような意見が多数出ました。
たとえば Zeev Suraski がその1人で、この人は元々2010年にも弱い路線のスカラータイプヒンティングの提案に関わっていたことがあり*1、これは Andrea の 0.1 の提案内容に近い発想のものでした。その際に彼は厳密な型検査が PHP にとっては不自然であることを訴え、またちょうどその話の中で、「利用が任意ならこの機能入れてもいいじゃろ?」という発想への反対意見も表明していたりします。
http://news.php.net/php.internals/48568

反対意見が出ながらも、Andrea の RFC は 厳密モードを保ったまま2月に再度改訂され、返り値型の宣言(PHP 7 向けの別の RFC で受理されたもの)でのスカラー型の利用を取り込んだ 0.3 が公開されました。
https://wiki.php.net/rfc/scalar_type_hints?rev=1422834086

それから更に若干の改訂が行われ後、コミュニティにおいて十分な議論が出尽くしたという判断により、 0.3.1 の RFC は投票フェーズへと進むこととなります。
結果がどうなったかの話へ進む前に、まずは PHP RFC の投票プロセスについて簡単に触れておきます。

PHP RFC の投票プロセス

PHP の言語機能はコミュニティにもとづいて決定され、提案が受理されるか否かは投票によって決まります。
以下の説明は、投票プロセスについての提案がされた元々の RFC の記述にもとづいています。

まず PHP Wiki に RFC が作られ、これが php-internals において告知されることで、正式に提案が発行されたことになります。
十分な議論を行うため、告知から投票へ進むまでには最低でも2週間を空けることが要求されています。
提案が受理されるには、新たな構文などの言語自体に関する提案なら投票者の 2/3 の Yes、それ以外については過半数の Yes が必要です。
投票権を持つ者は以下の二通りに分かれます。

  • php.net の SVN アカウントを持ち、PHP にコードで貢献したことのある者
  • PHP コミュニティの代弁者といえる、php.net の SVN アカウントを持つ以下のような者に選ばれた者
    • PHP のフレームワークや CMS、ツールなどのリードデベロッパー
    • php-internals の常連参加者

スカラータイプヒンティングは言語自体への変更であるため、2/3 の Yes が必要となります。
つまり、1票の No は2票の Yes と同等の影響力を持ちます。
この投票方式は、数々のスカラータイプヒンティングに関する提案が失敗し、その後に Dual-Mode RFC が生まれた背景の1つであるとも言えます。
つまり、厳密な型検査を求める派閥と弱い型検査を求める派閥があるとすると、どちらに嫌われる提案であっても、決して投票フェーズで生き残ることはできないということです。

投票の成り行き、提案者の突如の引退、他の者による RFC の復活

スカラータイプヒンティングの RFC にはかつてない非常に多くの投票が寄せられました。
そしてYes が 67 に No が 34、あわせて 101 票の投票が集まり、期限まで4日を残した投票10日目、RFC 作者の Andrea は、突如として PHP の開発コミュニティからの引退を宣言します。

これに伴い、スカラータイプヒンティングの RFC も投票半ばにして提案者自身が取り下げることとなってしまいました。
https://wiki.php.net/rfc/scalar_type_hints

「PHP の開発は時間を食い過ぎる」というのが本人の挙げた理由です。
頑張りすぎて疲れてしまった感じなのでしょうか。

とはいえ、それによって一度広がったスカラータイプヒンティングについての議論が止まるということはなく、時をおかずして別の人から別の内容の RFC を用意する、という話の素案が php-internals に流れてきます。
この際のメッセージ件名は「Scalar Type Hints v0.4」というものでした。
http://news.php.net/php.internals/83011

結局この「別の内容の RFC を用意する」という 0.4 の話は、それ以上大きな流れにはつながらなかったのですが、かわりにかつて Andrea のRFC のベースとなる提案を行った Anthony(ircmaxell) が 0.3 の RFC から fork を作り、わずかな修正を加えた上で『PHP RFC: Scalar Type Declarations』として公開しました。それまでの経緯を踏まえ、RFC のバージョンは 0.5.0 から始まっています。
これが後に投票を生き延び、PHP 7 への追加が承認されることとなった現在の RFC です。
PHP RFC: Scalar Type Declarations

決着

それから 0.2 からの厳密モード追加に反対していた Zeev が、別の方式でのスカラータイプヒンティングの RFC を出したり*1、2つが票を奪い合って個人攻撃まがいの言い合いになったり、投票期限の数時間前まで 0.5 のRFCが 2/3 ギリギリの状態をずーっとウロウロし続けたり(1人が No を入れては2人が Yes を入れるという感じで、合否ライン上を何度も行き来するデッドヒートでした)、共倒れになった場合に備えて別の人が 0.1 と同等の別の RFC を出したり*2、Zeev が途中からその RFC を推しはじめたり、決着がつくまでには色々な事がありました。
結局は「さすがに共倒れはアカンわ、納得はいかねーが PHP 7 で何も手に入らないよりはマシ」と Zeev が折れ(*)、彼が賛同者とともに 0.5 の RFC へ Yes を入れたことが決め手となり、 0.5 の RFC は 108 対 48 という結果で無事可決されることとなりました。

おわりに

だいぶ長々と書いてきました。

議論の流れをざっと追いかけてみた限り、弱い方のスカラータイプヒンティングについては、PHP の開発者コミュニティの中でだいぶコンセンサスが取れていたように思います。個人的にも、こちらについてはあまり利用上の問題が出ないのではないかと思っています。

気になるのは、実際に PHP 7 がリリースされ広まったとき、 PHP の利用者から厳密モードがどのように扱われるかということです。厳密モードを有効にして引数や返り値の型について厳密な扱いをするようになったとしても、変数の型がコロコロ変わる PHP のその他の性質までが、一気に Java のような堅いものに変わるわけではありません。

2つのモードの導入は、おそらく PHP にある程度複雑さや面倒さももたらします。例えば自分自身は片方しか積極的には使わなかったとしても、他の人のコードでもう片方が使われるのであれば、他人のコードを読んだり修正したりする必要のあるプログラマは、どちらのモードについてもある程度挙動を知っておき、何が起こり得るかの心構えを作っておく必要があります。はたしてその面倒さに見合った見返りが得られるのかどうか。

皆さんは、弱いモードと厳密モード、どちらのスカラータイプヒンティングを使ってみたいと思いますか?

以前に可決されたタイムライン通りであれば、PHP 7 は今年6月頃から RC 版が出始め、10月にはリリースされることになっています。

PHP 7 はスカラータイプヒンティング以外にも新たな言語機能が目白押しなので、今からリリースが楽しみです。

  • このエントリーをはてなブックマークに追加
  • Evernoteに保存