こんにちは。@penguin_no_045です。
先日開催された[PHPerKaigi 2020で登壇させてもらいました。皆様どうもありがとうございます。
資料
「PHPerがこれから「型」とお付き合いしていくために 」というタイトルでした。最近のPHPは型に関する進化が多いような気がしたのでテーマとして選定しました。
また、私の観測範囲内ですが、一時期別の言語ユーザーの間で静的型付き言語
vs動的型付き言語
だったり、動的型付き言語は型推論が入っているので云々といった話題が見られました。そこで、これからPHPerとして型と触れ合う機会が増える前に知識を整理しておこうと思い立ったわけです。
私はもともと静的型付き言語ユーザーだったので、型システムは身近なものでした。そこで軽い気持ちでプロポーザルをまとめたのですが、実際は今回のセッションの準備中に学んだ知識のほうが多かったです。
概念としてはずっと触れてきたものでも、改めて体系的に学ぶとあやふやな理解をしていた個所などはすぐに化けの皮が剥がれるものですね。
とくに型システムというテーマは不正確な情報がけっこう頻繁に流布されている印象があったので、自分もそうならないように気を付けました。諸先輩方からはどのように映ったのでしょうか?
発表内容について触れると、立て付けとしては型システム入門以前、といった感じになったと思います。そのため、今回のセッションでは簡単のために説明を省いた部分や、意図的にセッションのスコープから除外した領域が結構あります。
型システムに詳しい方は物足りなさを感じられたかもしれませんし、今回のセッションの中でも必要な概念が一部説明できないままの個所もあります。その部分についてはいつか語りたいなと思っています。
また、質疑などでより深い内容について質問をいただくことができ、その場で捕捉したいことを話すことができたのは幸いでした。改めて皆様ありがとうございました。
少しだけの補足
そんな質疑の中で1つ特に印象的だったものがありまして、「PHPで型安全に配列を扱うには?」というものがありました。 これについてはよくある話で、私も「コレクションを定義するのが良い」と回答しましたが、そこにはたくさんの壁が待ち受けているということを説明できていませんでした。 実際にコレクションライブラリの自作にチャレンジして、心が折れる、というのは昔から繰り返されてきたものではないかと思います。(PHPに限らず) そういった際にこういった機能が使えると勝率が上がる、というものを紹介します。
ジェネリクス
現在のPHPでコレクションクラスを作るとしたらどのようなシンタックスになるでしょうか。 簡単のためインターフェースなどの定義は省くと、こんなクラスになるでしょう。
class MyCollection {
public function __construct() {
}
//実装は省略
}
これでは何も得ることはできないですね。そこでほしくなるのがジェネリクスです。 型を変数のように扱うことで、コレクションの「中身」の型を決めずにコレクション型を定義できるわけです。
class MyCollection[T] { /** * @var T[] */ private $elements; /** * MyCollection constructor. */ public function __construct() { } //実装は省略 // こうやって使う $foo = new MyCollection[MyType]();
これができると、1つのコレクションの定義で色々な型を操作できます。
しかもこの型引数([T]
のT
の部分)がインターフェースの場合、継承したクラスをなんでも入れられるという寸法です。便利ですね。
そして沼へ続く
ただし、これを便利に使おうとすると大きな問題が発生します。次は引数の型が欲しくなるのです。
たとえば配列関数の中にあるarray_filter
やarray_map
を実装するとします。するとこう書きたくなります。
// class MyCollection[T] 内 public function filter(closure[T, boolean] $filterFunc): MyCollection[T] { // .... } public function map[S](closure[T, S] $mapFunc): MyCollection[S] { // .... }
もう少し便利な関数も追加しましょう。
flatMap
というものをご存じでしょうか。これは関数型の考え方が入ったコレクションライブラリではよくある機能(というより基本的な機能)です。
どういう機能かというと、map
してflat
にするのですが、ソースを見るとイメージしやすいと思います。
$mapFunc = function($elem){ return new MyCollection($elem, $elem + 1); } ); // 自分と、自分に1足したものをコレクションにして返す $example = new MyCollection(1, 2, 3); $mappedSample = $example->map($mapFunc); // MyCollection( // MyCollection(1, 2), // MyCollection(2, 3), // MyCollection(3, 4) // ) $flatMappedSample = $example->flatMap($mapFunc); // MyCollection(1, 2, 2, 3, 3, 4)
と、こんな風にネストをはがしてくれるものです。これを実装しようとすると型引数はこのようになります。
// class MyCollection[T] 内 public function flatMap[S](closure[T, MyCollection[S]] $mapFunc): MyCollection[S] { // .... }
ね?簡単でしょ?と言わんばかりですが、型を書くことでコレクションライブラリの実装ができそうな雰囲気は伝わりましたでしょうか?さらに言えば、ここまで定義してきたMyCollection
をさらに抽象化して、ほかのコレクションや集合、Optional
のようなものを定義できそうにも思えないですか? 共通の概念のようなもの?それはモナド・・・
これ以上は関数型プログラミングの世界にも踏み込んでいくことになるので、追いかけるのはこのぐらいにしておきましょう。
まだまだ課題はある
もちろんこのままでは型引数を書かないといけない、毎度毎度型を合わせないといけないなど実際に使用していくうえで面倒と感じる部分が多いままです。しかも、こういったときに頼りになる型推論は言語の仕組み上安全に実装できないなど、いろいろな壁が立ちはだかってくるでしょう。このようなジェネリクスを導入しただけでは解決できない課題はたくさんあります。そのほかにも様々なトレードオフと戦わないといけない個所が山のようにありますが、面倒ではあるが安全に倒しきった形でのコレクションライブラリの実装はジェネリクスがあればできそうな予感がします。
まとめ
さきほど「面倒」というキーワードが出てきましたが、静的型付き言語のデメリットとしてよく「実装が面倒」ということを上げられます。そうです。これをそのまま使うとその「面倒」を持ち込んでしまうのです。これをもっと便利に使えるようにしたい!となった場合は、より高度な型システムの概念が必要です。これからPHPがどのような方向へ進化していくのかはふんわりと見えている状態ではありますが、高度な型システムを獲得できるのは少なくともまだ先のこととなるでしょう。当分の間は将来の進化を期待しながらarray
と付き合っていくのが得策ではないか、と思います。
イベントの話といいつつ、ほとんどジェネリクスの話になってしまいましたがここまでとします。 今回のイベントで関わってくださった皆様、本当にありがとうございました!