石橋秀仁(zerobase)書き散らす

まじめなブログは別にあります→ja.ishibashihideto.net

関数型とオブジェクト指向という一見相反するプログラミングパラダイムの併用について理解した

最近、ScalaSmalltalkを触っていて思ったこと。

一見すると、関数型は「データ」より「処理」を重視しているように見える。

関数型プログラミングパラダイムそのものは「副作用のない関数」の合成による演算の恩恵を最大限に享受するパラダイムだ。副作用がないので並列演算の高速化に向いている

昨今のマルチコア化やクラスタ化のメリットを最大に活かすには関数型プログラミングパラダイムの導入が鍵だろう。プロセッサ単体での性能向上が頭打ちになってきたのだから、並列演算に対応したプログラミング方式へのシフトは不可避だろう(ただし高性能が要求されない分野は除く)。

関数型プログラミングパラダイムは、データよりも処理を重視したパラダイムのように見える。

一見すると、オブジェクト指向は「処理」より「データ」を重視しているように見える。

オブジェクト指向プログラミングパライダムは、(Smalltalk的には)プリミティブな「データ」をそのまま使わずに、抽象的な概念をあらわす「オブジェクト」としてモデリングして、それらオブジェクト達がメッセージを送信しあうことで演算するパラダイムだ。また、(C++的には)型のカプセル化ポリモーフィズムによりプログラムの再利用性を高めるパラダイムだ。

人間のメンタルモデルに近い抽象的なプログラミングのメリットを手放すことなど、もはやできない。オブジェクト指向は、もはやソフトウェア開発の前提だ。

オブジェクト指向プログラミングパラダイムは、手続きよりもデータを重視したパラダイムにも見える。

オブジェクト指向に関数型を導入する裏口工作

だから関数型とオブジェクト指向は相性が悪いのだと思っていた。けれど、ドメイン駆動設計Scalaを知って、併用可能なのだと理解できた。

その鍵は「不変な値オブジェクト」(immutable value object)というパターンだ。それにより「副作用のない関数」の組み合わせによる演算が可能になった。

そのためには、「クエリとコマンドを分ける」ことで、副作用のあるメソッドと、副作用のないメソッド(関数)を明確に分けておく必要がある。

オブジェクト指向に「不変の値オブジェクト」や「副作用のない関数」というパターンを導入することで、関数型プログラミングパラダイムの恩恵を受けることができるわけだ。

Scalaは言語レベルでオブジェクト指向と関数型の併用に取り組んでいる。Scalaオブジェクト指向であり、関数型であり、使いやすい分散並列処理機能が標準ライブラリとして提供されている。

分散オブジェクトによる並列計算を「副作用のない関数」の合成で行うというパラダイムは、GoogleのMapReduce(やHadoop)に通じる。そもそもmapとreduceが関数型プログラミングパラダイムに由来している(と思う)。

実際にScalaでcase classを使ってmap reduce的なコードを書くことができる。case classは不変の値オブジェクトを作るのに向いている特殊なクラスだ。Scalaではインスタンス変数を「可変のvar」と「不変のval」として明示的に定義できるし、case classでは不変のvalがデフォルトになっている。

というわけで、オブジェクト指向プログラミングパラダイム関数型プログラミングパラダイムを持ち込むことの意味が理解できた。

ちなみに、本来の科学哲学用語の「パラダイム」は決して両立しないと思うし、そもそも「使う」ものではないと思うんだけど、まあ、どうでもいい。