2014年6月1日日曜日

関数型とオブジェクト指向型プログラミング

近年、関数型言語というものが流行っている。関数型言語というのは、数学の関数を扱う感覚でプログラミングが行える言語だ。数学の関数は、インプットを与えると一意なアウトプットが得られるもののことである。

一般に、プログラムは数学の関数とは異なり、同じインプットに対しても内部の状態によって異なるアウトプットを返す。内部の状態とは、例えば、C言語の静的変数、オブジェクト指向言語のクラス変数、ディスク上のデータのことである。関数やメソッドが、内部の状態を変える場合には、その関数やメソッドは副作用を持つと言われる。プログラムはデータを保存したりしなければならないから、副作用はあって当然だ。今あるコンピュータは副作用があることを前提にできている。

しかし、副作用があるとプログラミングの難易度が上がる。インプットを網羅することは頑張ればできるが、状態を網羅することは難しい。テストですべての状態を作り出すことも大変だ。また、バグの発生条件が内部の状態にあれば、原因の特定も難しい。だれがその状態を作ったのかを見つけなければならないからだ。

そのため、なるべく副作用をなくしましょうというコンセプトで、関数型言語というのが最近注目されている。プログラミング言語は、書き方を制限してきれいな書き方に矯正しようというものだ。別に関数型言語を使わなくても、関数型プログラミングの目論見にあったコードはかけるが、関数型言語は関数型プログラミングがやりやすい。

最近の代表的なものは、Scalaだろう。Apache SparkがScalaで作られている。ただし、Scalaは純粋な関数型言語ではなく、オブジェクト指向型言語でもある。正直、関数型プログラミングだけではどういうふうに設計していいか分からない。人間の言葉はオブジェクト指向になっているので、人間の頭にはオブジェクト指向が馴染みやすく、設計も簡単だ。数学や論理学のように、記号を用いて表現した方が簡単な事柄には関数型がいい。なので、関数型とオブジェクト指向型をミックスするのは有用だと思う。

しかし、オブジェクト指向プログラミングは副作用を積極的にしましょうという技法とも言える。クラス変数にオブジェクトの状態変数を保存して、オブジェクトの状態を表現しようとしているのだから、当然だ。世の中のモノは、過去に受け取った入力を内部に保持して、それらを畳み込んだものを出力している。世の中のモノをコード上で表現しようとするオブジェクト指向は、副作用ありきだ。

そうすると、副作用が嫌いな関数型プログラミングと、副作用ありきのオブジェクト指向型プログラミングは食い合せが悪いようにも思える。

この問題を軽減する方法がないわけではない。思いつくのは以下のアイデアだ。要するにオブジェクト指向を関数型っぽく改変する。

  1. クラス変数は定数として、コンストラクタに与えられた値のみ保存する
  2. setterメソッドのようなオブジェクトの状態をの変えるメソッドは新しい値が入ったオブジェクトを返す
  3. getterメソッドのような値を返すメソッドは副作用を認めない

副作用があるメソッドとないメソッドを明確に区別することが大事だと思う。

1, 2は処理系の中では副作用をしていても構わないが、外からは副作用がないように見せて、状態変化を射影とするべきという考えである。

Obj obj = new Obj(val0)
obj = obj.setVal(val1)

こんな感じの書き方を強制しましょうということです。setValの中でreturn new Objみたいなことをするのを規定する。

obj .= setVal(val1)
あるいは
obj..setVal(val1)

みたいに省略可能なら冗長にもならない。実際古いobjなんて使わないから、処理系としてはreturn thisとすればいいんじゃないかな。古いobjを使う場合だけ、インスタンスのコピーを作っておけばよいのだ。

3はgetter的なメソッドも、オブジェクトを値に射影する関数と見せようという考えだ。ログ出力や標準出力、エラー出力は普通プログラムの動作に影響しないから副作用とみなさなくていいと思う;でないとデバッグにならない。

オブジェクトの状態を変えつつ、値を返す処理がしたいときは

obj.setVal(val0).method()

と書いたらよい。

Scalaとかこうなっていればよいのに。こういう風に決まっているプログラミング言語はあるのだろうか?