21
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【PHP8.2】PHPで選言標準形 (Disjunctive Normal Form) 型が使えるようになる

Last updated at Posted at 2022-07-04

タイトルの意味はよくわからない。

さてPHPでは、PHP8.0で型のORPHP8.1で型のANDが使えるようになりました。

しかし、この両者を組み合わせて使うことはできません。

function f(A | B | C $param){} // OK
function f(A & B & C $param){} // OK
function f(A | B & C $param){} // NG ←

ということで、これを可能にしようというRFCが提出されました。

既に投票は終わっており、賛成多数で可決されました。
PHP8.2からDNF型が使用可能になります。

以下は該当のRFC、Disjunctive Normal Form Typesの日本語訳です。

PHP RFC: Disjunctive Normal Form Types

Introduction

Disjunctive Normal Form (DNF) とは、論理式を正規化する標準的な方法のひとつです。
具体的には、ANDをまとめたものをORで並べる形に構造化します。
これを型宣言に適用すると、PHPのパーサーが扱えるUNION型とIntersection型を組み合わせた書き方が可能になります。

Proposal

Examples

これ以降の例では、次の定義が存在するものとします。

interface A {}
interface B {}
interface C extends A {}
interface D {}
 
class W implements A {}
class X implements B {}
class Y implements A, B {}
class Z extends Y implements C {}

本RFCでは、以下のような形で、プロパティ・引数・返り値にDNFで型宣言を行うことができるようになります。

// A型かつB型、もしくはD型
(A&B)|D

// C型、もしくはX型かつD型、もしくはnull
C|(X&D)|null

// A型かつB型かつD型、もしくはint、もしくはnull
(A&B&D)|int|null

DNFでない形の複合型宣言はパースエラーになります。
その場合はDNFに書き替える必要があります。

A&(B|D)
// (A&B)|(A&D) と書き替える

A|(B&(D|W)|null)
// A|(B&D)|(B&W)|null と書き替える

セクション内の順番は無関係であり、以下の型宣言は全て等価です。

(A&B)|(C&D)|(Y&D)|null
(B&A)|null|(D&Y)|(C&D)
null|(C&D)|(B&A)|(Y&D)

DNFを採用した理由は、エンジンにとって解析が容易であり、人の目や静的解析にとっても理解しやすく、理論的にはあらゆる論理式をDNFで表すことが可能であるためです。

なお、DNF型においては&を囲む括弧は必須です。
&だけの交差型であれば括弧は必要ありません。

Return co-variance

返り値の共変性について。

extendsする場合、返り値は狭めることしかできません。
実用的には、ANDを追加することはできますが、ORを追加することはできない、ということになります。

// A型かつB型、もしくはD型
interface ITest {
  public function stuff(): (A&B)|D;
}

// 狭まったのでOK
class TestOne implements ITest {
  public function stuff(): (A&B) {}
}

// 狭まったのでOK
class TestTwo implements ITest {
  public function stuff(): D {}
}

// OK CはA&Bより狭い
class TestThree implements ITest {
  public function stuff(): C|D {}
}

// NG A型であり、B型でないものが通ってしまう
class TestFour implements ITest {
  public function stuff(): A|D {}
}


interface ITestTwo {
  public function things(): C|D {}
}
// NG A型かつB型であり、C型ではないものが通ってしまう
class TestFive implements ITestTwo {
  public function things(): (A&B)|D {}
}

Parameter contra-variance

引数の反変性について。

extendsする場合、引数は広げることしかできません。
実用的には、ORを追加することはできますが、ANDを追加することはできない、ということになります。

// A型かつB型、もしくはD型
interface ITest {
  public function stuff((A&B)|D $arg): void {}
}

// Z型まで広がったのでOK
class TestOne implements ITest {
  public function stuff((A&B)|D|Z $arg): void {}
}

// A型まで広がったのでOK
class TestOne implements ITest {
  public function stuff(A|D $arg): void {}
}

// NG D型が通らなくなった
class TestOne implements ITest {
  public function stuff((A&B) $arg): void {}
}

interface ITestTwo {
  public function things(C|D $arg): void;
}
// OK A型かつB型であり、C型ではないものまで広がった
class TestFive implements ITestTwo {
  public function things((A&B)|D $arg): void;
}

Property invariance

プロパティの不変性について。

オブジェクトのプロパティの型は、継承元と同じでなければなりません。
このRFCでは、その仕様はそのままです。
ただし論理的に同一であれば、順序の入れ替わりは許容します。

Duplicate and redundant types

重複・冗長な型指定。

クラスを読み込まずに検出できる冗長な型指定はコンパイルエラーになります。
これは既存のUNION型・交差型に適用されているロジックと同等です。

DNFの各セグメントは一意である必要があります。
(A&B)|(B&A)は不正な型です。
これは等価なので冗長です。

なお、(A&B)|Cは有効な型指定です。
AとBを含むC以外の型を作ることが可能です。

他のセグメントの部分集合であるセグメントは許可されません。
例として(A&B)|Aは、(A&B)Aの部分集合であるため無効です。

ただし、この判定は、この型が最小であることを保証するものではありません。

Reflection

このRFCでは、新しいReflectionクラスは導入されません。

ReflectionUnionType::getTypes()に変更が入ります。
返り値の定義はReflectionTypeの配列となっています。
実際はReflectionNamedTypeの配列を返しますが、これがReflectionUnionTypeReflectionIntersectionTypeの配列を返すようになります。
メソッド定義は変更ありません。

Backward Incompatible Changes

互換性のない変更。

ReflectionUnionTypeの返り値は、これまでは必ずReflectionNamedTypeでしたが、ReflectionIntersectionTypeが入ってくることがあります。

Proposed PHP Version(s)

PHP8.2。

Proposed Voting Choices

投票期間は2022/06/17から2022/07/01まで。

このRFCは、賛成25反対1の圧倒的賛成多数で受理されました。

唯一の反対がDmitry Stogovなのはちょっと気になるところ。
※Dmitry StogovはPHP本体のcontribute数1位

Future Scope

この項目は将来の展望であり、今回のRFCには含まれません。

Non-DNF types

DNFでない型のサポート。

理論的には、正規形でない型についても、コンパイル時の解析によってサポートすることは可能です。
しかし、DNFの時点で全ての型を表現できること、人間の目と解析エンジンいずれにとってもコードを単純化できることもあるため、著者としてはこちらを追求するつもりはありません。

Type aliasing

DNFの型表現はかなり長くなる可能性があります。
そのため、便利な表現である型エイリアスを導入する動機となるかもしれません。

Patches and Tests

プルリクエスト

感想

DNFの型表現ができるようになり、やろうと思えばPHP上で※ほぼ※あらゆる型を表現できるようになりました。
※resource型が表現できない

型?なにそれおいしいの?
という世界だった過去のPHPとは全く隔世の感がありますね。

ただ正直、個人的にはこの手の型パズルにいまいちメリットを感じられません。
これができるようになることで何がどう良くなるってのがさっぱりわからないんですよね。

まあでも、すごい人たちが熟考して導入を決めたわけですし、きっとすごい人たちがすごい使い方を教えてくれるに違いありません。

PHPの型の歴史

せっかくなのでPHPで使える型がどのように広がっていったのかを並べたものです。

PHP7以降、中でも8.0からの拡充っぷりがすごいですね。

PHP5.0

・クラス名 / self / parent

PHP5.0で引数の型宣言が初めて導入された。
PHP7まではタイプヒンティングと呼ばれていた。

function hoge(stdClass $class){}

PHP5.1

・array

function hoge(array $arr){}

PHP5.4

・callable

function hoge(callable $c){}

PHP7.0

・int / float / bool / string

基本型がようやく使用可能になった。

・返り値の型宣言

返り値の型が指定できるようになった。

function hoge(int $int):int{
	return $int;
}

・strict_types

厳密な型宣言が可能になった。
PHPの型宣言は、デフォルトでは暗黙の型変換が働いてしまうが、それを無効にして型指定を厳密に適用することができる。

function hoge(int $i){}
hoge('1'); // 通る。1になる

declare(strict_types=1);
hoge('1'); // NG

PHP7.1

・iterable

foreach可能な値を表す。

function hoge(iterable $param){
  foreach($param as $v){}
}

・null許容型

null許容型は、呼び出しの引数を省略することもできる。

function hoge(?int $i){}
hoge();

・void

返り値としてのみ使用可能。
値を返さないことを明示する。

function hoge():void{}

PHP7.2

・object

インスタンスであればなんでもいい型。

function hoge(object $i){}
hoge(new stdClass());
hoge(new DateTimeImmutable());

PHP8.0

・mixed

var_dumpの引数など、なんでもいい型。
引数を書いていないのではなく、不定ということを明示する。

・static

返り値としてのみ使用可能。
自分と同じクラスを返すことを明示する(厳密には異なるが面倒いので割愛)。

class A{
    public function foo():static{
        return new static();
    }
}

・UNION型

型のOR
型を組み合わせで表現できるようになった。

function hoge(int|stdClass $int_or_stdClass){}

・ false疑似型 / null疑似型

UNION型の要素としてのみ使用可能で、単独使用はできない疑似型。
null許容型?intはUNION型int|nullの省略形という扱いになった。

function hoge(int|false|null $int_or_false_or_null){}

PHP8.1

・readonlyプロパティ

readonlyプロパティ自体は型宣言ではないが、readonlyを宣言したプロパティには型宣言が必須になる。

・交差型

型のAND
UNION型と組み合わせての使用はできない。

function hoge(Traversable&Countable $Traversable_and_Countable){}

・never

返り値としてのみ使用可能。
呼び出し元に戻らないことを明示する。

function hoge():never{ exit; }

PHP8.2

・readonlyクラス

readonlyクラス自体は型宣言ではないが、readonlyを宣言したクラスのプロパティには型宣言が必須になる。

・false / null

falseとnullが疑似型から正式な型に昇格。
単独で使えるようになる

function hoge(false $false){}

・true

false型に合わせてtrue型も使えるようになる。

function hoge(true $true){}

・選言標準形

UNION型と交差型を同時使用可能になる。

function hoge(int|(Traversable&Countable) $int_or_traversableandcountable){}
21
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?