読者です 読者をやめる 読者になる 読者になる

PHPのinterfaceなめんな

PHP

はいタイトルは釣りです。

OOPのインターフェースはただの実装漏れチェック機能じゃないし、ましてや継承は差分プログラミングツールじゃないぞ。というのはわりと一般的な話だけど、Ruby(respond_to?でホントにいいの)とJava(インターフェースが自然すぎてユーザが意識しないのよ)が、PHPに対してOOPどうこうで偉そうに言うのはどうかなと思ったもので。まあそれと同時に、PHPの人自身がその意義を発見してるのかなという疑問もあったりしたんですけどね。

Rubyというのは「オブジェクト指向ってのはつまりメソッドに応答できるアヒルはみんなアヒルとみなせるよね」というレベルのダックタイピングで割り切った言語だと、個人的に認識しています。継承とミックスインにはis_aが応答するけど本質はrespond_to?のほうで、インターフェースを宣言してなくてもメッセージ送れたらいいあの感じ。

そんなRubyでオブジェクトが特定の機能の「セット」を実装しているかを調べて、アヒルかどうかを分岐するコードはこんなふうになります。

めんどくさいですね。おっと、ここで継承かミックスインで解決してis_aを使えばいいというのはナシです。単一継承の言語における継承はそうそう安易にできるものではないですね。OOPで設計してる人なら、本質的に意味の違うものを同じ継承ツリーの枝に入れないといけないとき、辛い思いをしたことは何度となくあるはず。

ミックスインなら同じ継承ツリーじゃない、ってことでミックスインをマーカーに使おうとするとこんな感じ。

機能的にはだいぶいいけどちょっとだけ残念。でも「ミックスインは機能の混ぜ込み」だという本来のコンセプトに反した使い方なので、人にわかってもらえにくい気がします。

いっぽう、Javaではインターフェースとメソッド実装のコピペ自動生成を分けてやることで、オブジェクトに複数の顔をもたせられます。まあその代わり、メソッドに渡す型の制約が強すぎて、アヒルを扱うにはいろいろめんどくさい子です。でもたまに型がRubyお得意のダックタイピングに勝ることもあります。

いまこれRubyのダックタイピングの例と同じ意味のことをやってるんですが、そうは見えませんね。ここちゃんとやり始めると、MLとかそっちの「型」の本質の話になってしまって、PHPをdisる人よりもっと恐い人が来るのでやめときます。分岐をコンパイル時ではなく実行時に持ってくると、こうなります。

だいぶLLっぽくなりました。インターフェースがくっついているかどうかを分岐の根拠にしたりするテクニックはマーカーインターフェースと呼ばれ、マイナーですがあります。ああでもやっぱ、型のある言語の本質は前のやつが何も意識しなくてもできることなんですよねホント... いいなぁ。

さてさて、PHPだとどうなるか。Javaのコードは手抜きしちゃいましたが、ここが本題なのでというわけで:

PHPさん意外にも超スッキリ!

PHPみたいに変数に型がない(値に型があるのとは違う意味ね)言語におけるインターフェースというのは、Javaの教科書に書いてある、「実装の制約」と「入出力型のコンパイラチェック」用ではなくて、むしろJavaでは応用的なほうだと思われてる、マーカーの意味合いが強いと思うのです。PHP で instanceof があのグローバル関数のカオスに紛れていないファーストクラスの演算子だというのは、そういう意味なんじゃないでしょうか。たしかにアノテーションの記述性が高ければそっちでも良かったぐらいのものなんだけど、それに十分な表現力を持たせようとすると、必然的にJavaのinterfaceみたいになるので、偶然にもPHPの選択は正解だったとようやく最近思っています。

たしかに、Javaみたいにコンパイル時に型の分岐が済んでいるのはありがたいのですが、オブジェクト指向以外に型理論まで意識しないといけないのは、楽しい楽しいプログラミングの本質を遠ざけてしまうと思います。これはずっと最初からRubyが教えてくれたコンセプトだと思うし、だからこそLAMPブームが起こったわけで、なので、安直に型に支配されるほうが良いとはあまり言いたくないです。(OCamlScalaの人がなんか言いそうですが、型推論は記述量を減らせても型の本質的高度さから逃れることはできないと思うのでちょっと...)

PHPのコードでもっとも大事なのは、

  • 抽象クラス => インターフェース + 実装差分
  • インターフェース + 実装差分 => 抽象クラス

というセオリーが成り立ってるということです。だだ綺麗なのが目的じゃなくて、意図が明確でトリッキーじゃないというのが大事。

僕は抽象クラスの継承がインターフェースと実装差分に分けて考えられないのは、ちょっと不健全な気がするんです。そういうのって、結局継承システムがその本質である「概念の抽象レベルの階層化」に使われず、ただの実装差分としてしか意識されなくなって、ロジックさえ全部書いてあればいいんじゃないかという錯覚に陥る危険性もあるんじゃ...という危惧があります。
やはり、継承とミックスインだけあってインターフェースがないとか、継承とインターフェースだけあってミックスインがないというのは、ちぐはぐな感じがするんですね。普通に生産的で使いやすいオブジェクト指向言語というのは、差分プログラミングするかどうかと、APIインターフェースの設計をどうするかという問題とは、直交しているべきだと思います。

たしかにPHPも、5.3まではJavaのようにちぐはぐでした(おっと5.3未満のことは言うな)が、5.4でトレイトが入ってくるので、やっとこれで役者が揃ったように思います。(クロージャという名のRunnableの無名インナークラスはすでに5.3で入ってますしね)


しかしarrayとリファレンスと文法パーザの作りはそれ以前の問題だ、という声が聞こえなくもないですが、たとえば何の取り柄もない幼馴染みがごくまれに上手にお菓子とか作ったらかわいいじゃないですか。