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

RFCから見るPHP8

toyoaki-k
June 30, 2021
67

 RFCから見るPHP8

toyoaki-k

June 30, 2021
Tweet

Transcript

  1. Before/After // PHP7 class Point { public float $x; public

    float $y; public float $z; public function __construct( float $x = 0.0, float $y = 0.0, float $z = 0.0, ) { $this->x = $x; $this->y = $y; $this->z = $z; } } // PHP8 class Point { public function __construct( public float $x = 0.0, public float $y = 0.0, public float $z = 0.0, ) {} } オブジェクトプロパティの初期化の ために 1. プロパティの宣言 2. コンストラクタ引数の記述 3. コンストラクタ処理内での 代入 をする必要があり、同じ変数名が 繰り返され冗長だった。 変数名を繰り返す必要 がなくなった
  2. Before/After オブジェクトプロパティの初期化のために 1. プロパティの宣言 2. コンストラクタ引数の記述 3. コンストラクタ処理内での代入 をする必要があり、同じ変数名が繰り返され冗長だった。 //

    PHP7 htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false); // PHP8 htmlspecialchars($string, double_encode: false); double_encodeという引数名を キーにして引数を渡せる
  3. メリット① • 指定したいオプション引数のみ指定できる // PHP7 htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8',

    false); // PHP8 htmlspecialchars($string, double_encode: false); htmlspecialchars ( string $string, int $flags, string|null $encoding, bool $double_encode) : string $flags, $encoding, $double_encodeは デフォルト値ありのオプション引数。 第四引数のオプション (double_encode)のみを設定する ために第二/第三引数のオプション にわざわざデフォルト値を設定する 必要があり冗長だった。
  4. Named arguments class ParamNode extends Node { public string $name;

    public ExprNode $default; public TypeNode $type; public bool $byRef; public bool $variadic; public function __construct(string $name, array $options = []) { $this->name = $name; $this->default = $options['default'] ?? null; $this->type = $options['type'] ?? null; $this->byRef = $options['byRef'] ?? false; $this->variadic = $options['variadic'] ?? false; parent::__construct( $options['startLoc'] ?? null, $options['endLoc'] ?? null ); } } // Usage: new ParamNode($name, ['variadic' => true]); new ParamNode($name, ['variadic' => $isVariadic, 'byRef' => $passByRef]); コンストラクタ引数の配列に 指定したいオプションを渡すだけで 済む。(必要のないオプションを渡 す必要がない) 引数としてオプションの key-value配列を受け取る オプションの配列内の要素ごとに nullチェックをしてオプションの設定 有無を判定する
  5. • Before/After // PHP7 switch (8.0) { case '8.0': $result

    = "Oh no!"; break; case 8.0: $result = "This is what I expected"; break; } echo $result; //> Oh no! // PHP8 echo match (8.0) { '8.0' => "Oh no!", 8.0 => "This is what I expected", }; //> This is what I expected
  6. メリット① // PHP7 switch ($x) { case '8.0': $result =

    "Oh no!"; break; case 8.0: // $result = "This is what I expected"; break; } echo $result; //> Oh no! // PHP8 echo match ($x) { '8.0' => "Oh no!", 8.0 => "This is what I expected", }; //> This is what I expected • switch文だと代入忘れが起こりえるがmatch式だと起こり得ない $resultへの代入を忘 れてもとりあえず動く マッチ後の結果値を 記述しないと実行時 にParseErrorが出る
  7. • switchはマッチ判定に==(緩い比較)を使うがmatchは===(厳密な比較)を使う メリット② // PHP7 switch (5) { case '5':

    $result = "Oh no!\n"; break; case 5: $result = "This is what I expected\n"; break; } echo $result; // >Oh no! // PHP8 echo match (5) { '5' => "Oh no!\n", 5 => "This is what I expected\n" } // >This is what I expected intの5がstringの”5”にマッチしてしまう
  8. • breakを忘れて意図せず後続のcaseが評価されることを防げる メリット③ // PHP7 switch ($pressedKey) { case Key::RETURN_:

    save(); // Oops, forgot the break case Key::DELETE: delete(); break; } // PHP8 match ($pressedKey) { Key::RETURN_ => save(), Key::DELETE => delete(), }; 暗黙のbreakが入るので複数ケー スにマッチする心配はない breakを書き忘れると後続の case Key::DELETEも評価される
  9. • ハンドリング漏れに気付ける メリット④ // PHP7 switch ($operator) { case BinaryOperator::ADD:

    $result = $lhs + $rhs; break; } // PHP8 $result = match ($operator) { BinaryOperator::ADD => $lhs + $rhs, }; $operatorが BinaryOperator::SUBTRACT の時はマッチするケースがない のでエラーを出してくれる。 $operatorが BinaryOperator::SUBTRACTと う定数になる可能性があるとす る。 BinaryOperator::SUBTRACTが 渡ってきた時のハンドリングを記 述していないのでバグなのだが エラーは出ない。
  10. • Before/After // PHP7 $country = null; if ($session !==

    null) { $user = $session->user; if ($user !== null) { $address = $user->getAddress(); if ($address !== null) { $country = $address->country; } } } // PHP8 $country = $session?->user?->getAddress()?->country; 変数のプロパティや関数にアクセスす る度にnullチェックを行っていた
  11. 1. Short Circuitingは使わないNullsafe operator a. foo()もbar()もbaz()も評価される。foo(bar())の評価はnullになり、nullに対してbaz()を呼ぶの で”Call to a member

    function on null”エラーが発生する。 b. Facebook製のHackというPHPの姉妹言語がこの実装方針を採用している c. 実は2014年ごろにもnullsafe operatorのRFCが出ていたが、この実装方針 (おそらくHackの影 響)で行こうとしてrejectされた。 2. 関数の引数に対してのみShort Circuitingを使うNullsafe operator a. foo()とbaz()は評価されるが、bar()は(foo()の引数なので)評価されない。foo()の評価はnullにな り、nullに対してbaz()を呼ぶので”Call to a member function on null”エラーが発生する。 b. RubyやKotlinなど複数の言語がこの実装方針を採用している Short Circuitingについての実装方針 null?->foo(bar())->baz();
  12. 代数的データ型(v8.1~) • RFC • PHPに代数的データ型を導入したい ◦ 不正な状態は表現できないようにして堅牢なプログラムを書けるようにしようというモチベーション ◦ 代数的データ型については以前勉強会で松澤さんが発表されていた こちらの資料がとても勉強

    になった。 • 複数のステップに分けて実装を進めていく予定 ◦ Enumerations - RFC採択・実装済み。v8.1に入る予定 ◦ Tagged Unions - RFC作成済み ◦ Pattern Matching - RFC作成済み ◦ isIdentical() method override (future scope) - RFC未作成 ◦ Advanced pattern matching (future scope) - RFC未作成 • RFC作成済みのEnumerations, Tagged Unions, Pattern Matchingについて紹介
  13. Enumerations(v8.1確定) • RFC • 列挙型の導入 • enum Suit { case

    Hearts; case Diamonds; case Clubs; case Spades; } function pick_a_card(Suit $suit) { ... } pick_a_card(Suit::Clubs); // OK pick_a_card('Clubs'); // TypeError: pick_a_card(): Argument #1 ($suit) must be of type Suit, string given SuitはHearts, Diamonds, Clubs, Spadesのどれか 内部的にはこれらの実体はオブ ジェクト pick_a_card関数はSuit::Hearts, Suit::Diamonds, Suit::Clubs, Suit::Spadesのいずれかのみを引数として 受け付ける
  14. • Caseに対してスカラ値(intやstring)を紐付けることができる Backed Enums enum Suit: string { case Hearts

    = 'H'; case Diamonds = 'D'; case Clubs = 'C'; case Spades = 'S'; } print Suit::Clubs->value; // Prints "C"
  15. • 列挙されたcaseは関数を持てる Enumerated Methods enum Suit { case Hearts; case

    Diamonds; case Clubs; case Spades; public function shape(): string { return "Rectangle"; } } print Suit::Diamonds->shape(); // prints "rectangle" Hearts, Diamonds, Clubs, Spades はshape()という関数を持つ
  16. • RFCは作成済みだが内容に関する議論はされていない • 列挙された各Caseが複数の値(スカラ値以外もOK)を持てるようにする • Tagged Unions(v8.1希望) enum Distance {

    case Kilometers(public int $km); case Miles(public int $miles); } $my_walk = Distance::Miles(500); print $my_walk->miles; // prints "500" $my_walk2 = Distance::Kilometers(500); print $my_walk2->km; // prints "500"
  17. • 列挙されたCaseは関数を持てる Enumerated Methods enum Maybe { case None {

    public function bind(callable $f) { return $this; } }; // This is a Tagged Case. case Some(private mixed $value) { public function bind(callable $f): Maybe { return $f($this->value); } }; public function value(): mixed { if ($this instanceof None) { throw new Exception(); } return $this->val; } } Someの引数として任意の型の値 を与えることができる。 Someのプロパティとして値が紐付 けられる。 前述のconstructor property promotionを使っている。
  18. • 型に対してmatchさせることができる Type pattern $foo is string; // Equivalent to

    is_string($foo) $foo is int|float; // Equivalent to is_int($foo) || is_float($foo) $foo is Request; // Equivalent to $foo instanceof Request $foo is User|int; // Equivalent to $foo instanceof User || is_int($foo) $foo is ?array; // Equivalent to is_array($foo) || is_null($foo)
  19. • オブジェクトのプロパティに対してmatchさせることができる Object property pattern class Point { public function

    __construct(public int $x, public int $y, public int $z) {} } $p = new Point(3, 4, 5); $p is Point {x: 3}; // Equivalent to: $p instanceof Point && $p->x === 3; $p is Point {y: 37, x: 2,}; // Equivalent to: $p instanceof Point && $p->y === 37 && $p->x === 2;
  20. • matchした値を変数にバインドする。Elixirでよくやるやつ。 • バインドさせたい変数には%をつける • 今回のRFCではバインド可能な対象はObjectのみだがいずれ代数的データ型に 対しても使えるようにする予定。 Variable binding $result

    = match ($p) is { Point{x: 3, y: 9, %$z} => "x is 3, y is 9, z is $z", Point{%$z, %$x, y: 4} => "x is $x, y is 4, z is $z", Point{x: 5, %$y} => "x is 5, y is $y, and z doesn't matter", // This will always match. Point{%$x, %$y, %$z} => "x is $x, y is $y, z is $z", };