ジェネリクスではない…ジェネリクスではないのだよ………
ざっくり言うとvar_dump()の型引数です。
var_dumpにはプリミティブ値にオブジェクトにリソース型にと、どんな値でも渡すことができるのですが、PHP7.4時点の型システムではvar_dumpの引数の型を表すことができません。
PHP8.0で導入予定のunion型を使うとarray|bool|callable|int|float|null|object|resource|string
となるのですが、実はresource
型はPHP8.0でもまだ使えないので、mixed
型を完全に再現することはできません。
ということでMixed Type v2のRFCが提出されました。
投票は2020/05/21まで、受理には2/3+1の賛成が必要です。
が2020/05/11時点では賛成35反対6で、おそらく受理されます。
Introduction
PHP7のスカラー型、7.1のnull許容型、7.2のobject型、そして最新8.0のUNION型とPHPの型システムは進化し続けており、PHPの開発者はほとんどの関数において引数と返り値、そしてプロパティについて明示的に型情報を宣言することができるようになりました。
しかし、PHPは常に型をサポートしてきたわけではありません。
そしてこれは、型情報が欠落している際にその意味が曖昧になってしまうという問題に繋がります。
・特定の型に決まっているが、プログラマが宣言を忘れてしまった
・特定の型に決まっているが、古いバージョンのPHPと互換を保つためにあえて省略している
・現在のPHPの型システムでは表現できない型である
明示的なmixed
型を用意することで、引数や返り値、プロパティに型を追加して、型情報を忘れていたわけではなく、正確に指定できなかったりあえて広げているのだという主張をを示すことができます。
現在のところmixed
型はPHPDocの中でのみ使用することができますが、これは適切ではありません。
PHPDocでmixed
型が使用されている顕著な例としては、PHP標準ライブラリ関数の返り値などがあります。
ネイティブにmixed
型があれば、これらをより正確に表現することができるでしょう。
またmixed
型はPHPマニュアルにおいても広く使用されています。
var_dump ( mixed $expression [, mixed $... ] ) : void
Proposal
PHPの型システムにmixed
型を追加します。
これはarray|bool|callable|int|float|null|object|resource|string
と等価です。
これはPHPの継承時の型検査の実装に適合する正しい動作です。
LSP, Covariance and Contravariance
このProposalは、リスコフの置換原則に準拠しています。
PHP7.4以降、PHPは共変戻り値と反変パラメータに対応しています。
PHPではLSP原則に従うように、パラメータの拡大を許容しています。
サブクラスにおいて、親クラスより広い、特殊でない型を使用することができます。
PHPではLSP原則に従うように、返り値の縮小を許容しています。
サブクラスにおいて、親クラスより狭い、特殊な型を使用することができます。
Parameter types are contravariant
引数は、特定の型からmixed
型に拡大することができます。
// 正しい例
class A
{
public function foo(int $value) {}
}
class B extends A
{
// intからmixedに拡大は許可
public function foo(mixed $value) {}
}
引数の縮小は、LSP原則に違反するため許可されません。
// 不正な例
class A
{
public function foo(mixed $value) {}
}
class B extends A
{
// mixedからintに縮小は不可
// Fatal errorが出る
public function foo(int $value) {}
}
Return types are covariant
返り値は、mixed
型から特定の型に縮小することができます。
// 正しい例
class A
{
public function bar(): mixed {}
}
class B extends A
{
// mixedからintに縮小は許可
public function bar(): int {}
}
返り値の拡大は、LSP原則に違反するため許可されません。
// 不正な例
class C
{
public function bar(): int {}
}
class D extends C
{
// intからmixedに拡大は不可
// Fatal errorが出る
public function bar(): mixed {}
}
Property types are invariant
プロパティ型指定のRFCに従い、プロパティの型は不変です。
// 不正な例
class A
{
public mixed $foo;
public int $bar;
public $baz;
}
class B extends A
{
// プロパティ型は縮小不可
// Fatal errorが出る
public int $foo;
}
class C extends A
{
// プロパティ型は拡大不可
// Fatal errorが出る
public mixed $bar;
}
class D extends A
{
// 未指定にmixed型を追加するのも駄目
// Fatal errorが出る
public mixed $baz;
}
class E extends A
{
// 型指定の削除も駄目
// Fatal errorが出る
public $foo;
}
Void return type
void
型の返り値については、LSPに適合していたとしても拡張は許可されません。
class A
{
public function bar(): void {}
}
class B extends A
{
// Fatal error: Declaration of B::bar(): int must be compatible with A::bar(): void
public function bar(): int {}
}
このRFCは、既存の振る舞いに従います。
すなわち、void
型をmixed
型に広げることはできません。
Signature checking of function when no parameter type present
引数に型が存在しない場合の型チェックは、mixed
型が指定されたかのように動作します。
class A
{
// 引数の型がないのでmixedとみなす
public function foo($value) {}
}
class B extends A
{
// mixed型を追加したが、親クラスと動きは同じ
public function foo(mixed $value) {}
}
class C extends B
{
// mixed型を削除したが、親クラスと動きは同じ
public function foo($value) {}
}
class D extends B
{
public function foo(mixed $value = null) {}
}
現在のところ、これはクラスの継承に限った動作です。
PHPで型を定義できるようになれば、他のところでも動くようになるかもしれません。
Signature checking of function when no return type present
返り値に型が指定されていない場合の型チェックは、mixed|void
型が指定されたかのように動作します。
サブクラスでオーバーロードする際は、返り値を未指定にするか、void
型にするか、mixed
型およびそのサブタイプの何れかを指定しなければなりません。
そして、一度変更したあとの型を未指定に戻すことはできません。
class A
{
// 返り値に型がないのでmixed|voidとみなす
public function foo() {}
}
class B extends A
{
// mixedを指定した。voidは禁止になる
public function foo(): mixed {}
}
class C extends B
{
// mixed|voidはmixedより広いのでNG
// Fatal errorが出る
public function foo() {}
}
class D extends B
{
// voidはmixedのサブタイプではないのでNG
// Fatal errorが出る
public function foo(): void {}
}
The mixed|void union type
このRFCは、mixed|void
のUNION型は必要ないので許可しないという立場です。
今後ユースケースが見つかれば許可される可能性はあります。
Nullability
mixed
型にはnullが含まれます。
従ってmixed型のnull許容型は情報の重複となります。
このRFCは、mixed型のnull許容型を許容しないという立場です。
今後ユースケースが見つかれば許可される可能性はありますが、その際はどの冗長な型指定を許可して、どの冗長な型指定は許可しない、という議論が必要になるでしょう。
// NG、既にnull許容なので
function foo(?mixed $arg) {}
// NG、既にnull許容なので
function bar(): ?mixed {}
Explicit returns
返り値にmixed
型を使用する場合、明示的にreturn
を記述する必要があります。
さもなければTypeErrorが発生します。
function foo(): mixed {}
foo(); // Uncaught TypeError: Return value of foo() must be of the type mixed, none returned
既存のnull許容型と同じ動作です。
function bar(): ?int {}
bar(); // Uncaught TypeError: Return value of bar() must be of the type int or null, none returned
Resource 'type'
PHPではresource
型の値を変数に割り当てることができますが、ユーザランドでは引数、返り値、プロパティの型としてresource
型を使用することができません。
このRFCの立場としては、resource
型はresource
型チェックをパスすべきである、というものです。
Mixed vs any
PHPではマニュアルやPHPStanなどの静的解析ツールで広くmixed
型が使われているため、mixed
になりました。
またmixed
はPHP7以降弱い予約語とされていますが、anyは予約語に含まれません。
RFC Impact
Proposed PHP Version(s)
PHP8.0
Backward Incompatible Changes
クラス名としてのmixed
が禁止されますが、PHP7.0以降mixed
は弱い予約語です。
To SAPIs
特になし。
To Existing Extensions
特になし。
To Opcache
特になし。
Vote
2020/05/07に投票開始、2020/05/21に投票終了。
受理には2/3+1の賛成が必要です。
Patches and Tests
References
・PHP RFC: Reserve Even More Types in PHP 7
・phpDocumentor type reference
感想
これ、UNION型のRFCの将来の展望にある型宣言そのものですよね。
ついでだから型宣言自体もできるようにしてしまえばいいのでは。
ということでPHP8からmixed
型が使えるようになります。
var_dumpのような、仕様としてあらゆる値を受け取る必要のある関数のためにこれが必要なことは確かでしょう。
しかし、ユーザランドで何も考えず適当にこれを使って大惨事、という未来が目に見えますね。
まあでも、そんな人はそもそも型引数を書かないだろうから問題ないかな?