オブジェクトは OCaml の鬼子

前から言いたかったことでもあるので、釣られてみることにした。(長文)

without objects!? より:

継承を使わないのは分かる。オブジェクト使わないってなんだよ!?多相レコード使いたい時にJSCの人々はどうやって書いてるんだ!?

なんか、FP vs OOの構図でOO嫌いといいたいのかもしれないけど、こーゆー激しく誤解を生みそうな話を広めないでほしい。JSCの人々はせっかくすばらしい仕事して影響力あるんだから。

はい、OCamlにおけるオブジェクト、クラスはとっても大事だと思います。というか、オブジェクトなしでOCamlのプログラミングなんて考えられません。別に惹玖先生の肩を持つとかそーゆー意味でなく、純粋に技術的に。

これは誤解でも何でもありません。J社 ではオブジェクトは使っていないようです。Caml team でもまずオブジェクトは使いません。Jacques Garrigue も普段オブジェクトは使いません。私もオブジェクトは使いません。オブジェクトは普通は使わない。これは OCaml ではごく常識的なことです。*1

OCaml で class, object を積極的には使わない理由にはいくつかあります。

downcast の不在

OCaml は実行時に型情報を持ちません、つまり、実行時に downcast を行いたくても型情報がないので、downcast の安全性を確認できない。よって、基本的に downcast なしのプログラミングを行わなければなりません。そもそも downcast は常に成功するとは限らないので、通常の OO 言語でもエラー処理は必要だし、このエラー処理自体がしばしばバグの元になるため、できるだけ使わないほうがよいのですが、OCamlの(特殊なプログラミングテクニックを使わない限り)全く downcast できない、という性質は OO としては受け入れがたいものがあります。しばしば、これを補うために、安全であることを確かめた上で敢えて Obj.magic を使うことがありますが、経験上、これはまず、後日の解析不能なバグの原因となり、信頼性のあるソフトウェアを作る場合には絶対に避けなければいけないことです。

強力かつ抽象的過ぎる型

OCaml の object 周りの subtyping には非常に興味深いものがありますが、実際に使っているとあまりに強力かつ抽象的過ぎるために使いづらかったり、バグフィックスがほとんど不可能だったりすることがあります。Object の型が微妙に合わないためにプログラムが型検査に通らない。その原因を探すために、数十個あるメソッドの型のどこかに subtyping のおかしな部分があるのだが、、、探すのに労力がかかりすぎる、といったことは OCaml で複雑な OO プログラミングを行うとしばしば直面します。その苦労に見合うほど object にしたことに意義があるのでしょうか?あればよいのですが、たいていの場合、あまり意味はありません。それならば、多相レコードではなく、ちょっとタイプ数は増えるが、単相レコードを使ったほうがより簡単です。

その割には不完全な型

class は通常のデータ型とは文法が全く異なるために、class とデータ型の相互再帰的な定義ができません。時にこれは非常に不便です。class など導入せず、もっと簡単に多相レコードを導入してくれれば事態はより良かったのかもしれませんが、後の祭りです。(現在の OCaml では mutual recursive module を使うことで、 class とデータ型の相互再起を定義することができますが、不自然なことに変わりはありません。)

そもそもバグがあるかもしれない

OCaml の object 周りの型付けは日々変わっています。caml-list を眺めればわかりますが、現在でも class 周りの型付けにバグが見つかることがあります。前のバージョンのコンパイラではちゃんと型チェックに通っていたプログラムがコンパイラのアップデートで今度は通らなくなる、ということも時々起きます。通常、型付けが変わるということは、そこにあったバグが修正された、ということで、歓迎すべきものではありますが、同時に、もしかしたら、object 周りの型付けに、まだバグがあるかもしれない、ということでもあります。バグのあるかもしれない、枯れていない object 型システムを使ったプログラムに時に数十億円の資金リスクを任せることができるでしょうか?

静的解析の難しさがプログラムの可読性を下げる

Object を使えばプログラムは一見すっきりし、読みやすくなると思われていますが、ほんとうにそうでしょうか?妙な OO は却ってプログラムの可読性を下げ、フィックスしにくいバグの温床となります。プログラム中に o#m というバグを引き起こすメソッド呼び出しがあったとして、ではバグを起こすメソッド m はいったいどこで定義されているのか、o の型を見ても何も語ってくれません。o の class で定義されているメソッドならよいのですが、その subclass で定義されたものかもしれないし、もしかすると、全く別の、型だけ同じ class の method かもしれない。Object があまりに dynamic で、静的解析しづらい、というのは、こういう趣旨での発言です。

そもそも OO は万能ではない

全てを物として解釈すればプログラミングが楽になる、という OO のパラダイムは嘘です。ちょっと注意してみれば、何でもかんでも適当にクラスにしたためにぽしゃった OO プロジェクトがいくつもあることがわかりますし、そういう駄目駄目クラス階層デザインの元で神経を磨り減らしている Java プログラマはたくさんいます。私自身も適当に書き始めた OCaml OO プログラムが、出だしは良かったものの、後でにっちもさっちも行かなくなった、という経験が何度かあります。クラス階層デザインというものはよほど能力のある人が、本当にクラスとして相応しいものに対して、注意を持って行うか、さもなければ、そもそもクラスなど使わないほうがよほど良いのです。OCaml には object を使わなくても、module によって十分に安全な抽象化や継承を行うことができます。(Jane street の Core library を見てもらうとわかりますが、Core では module inclusion による継承を多用しています。)

金融と OO は合わない

関数型言語を金融概念のモデリングに適用した Jean-Marc Eber によれば、金融概念はオブジェクトとしてよりは関数として見た方がより自然です。Object を使う動機がありません。

では、逆に、OCaml で class, object を積極的に使うべき場面について。これは、ほんの少ししかありません:

  • OCaml の本を書くとき。subtyping で遊ぶとき。
  • OOスタイルの外部ライブラリのOCaml API を書くとき。そしてその API を使うとき。

前者は、まあ冗談だが、後者の場合、元のライブラリのクラス階層をOCamlのクラス階層に対応させるというデザインを用います。例えば LablGtk がそうですね。LablGtk のソースを見ればわかりますが、非常に苦心して使いやすいクラスライブラリを構築しています。いかに、まともな OO プログラムを構築するのが難しいか、わかると思います。ちなみに、J社では GUI に LablGTK を使っていますが、新しいクラスを作ったりはまずしません。*2

最後に、この件に関して誰かを応援するなら、Jacques Garrigue は少しお門違い。Caml Special Light にオブジェクトを導入して OCaml を作った Jerome Vouillon の肩を持ってあげてほしいと思います。

追記

OCaml で OO してはいけないという趣旨ではありません。よくよく考えてからやったほうがいいよ、という主張です。

*1:OO 言語プログラマOCaml に移ってハマる罠に、下手に object があるために無理やり OCaml で OO をやってしまい、失敗する、というのがあります。

*2:J社 について、見てきたかのように書いていますが、それがなぜなのかは想像にお任せします。(笑