Ruby
JavaScript
Go
Scala
オブジェクト指向

「オブジェクト指向プログラミング」と「関数型プログラミング」のたった一つのシンプルな違い

More than 1 year has passed since last update.

はじめに

関数型プログラミングとオブジェクト指向の抜き差しならない関係について整理して考えるという記事がkenokabeさんという方が挙げていて、拙著の 新人プログラマに知っておいてもらいたい人類がオブジェクト指向を手に入れるまでの軌跡について言及があったので、補考として挙げておく。

暗黙的状態と明示的状態

これまで、関数を「わかりやすくきれいに書く方法」とオブジェクト指向が「どのようにして生まれてきたか」について話してきた。

一見、それぞれ関係ないように思うかもしれないが、実は大きなテーマでつながっている。

『それは「状態」をどのように取り扱い単純化するか。』ということだ。そして、これがいわゆる関数型プログラミングとオブジェクト指向プログラミングの最大の違いとなる。

関数をきれいに書くためには理解を困難にする「状態」を排除する方法を学んだ。それは例えば再代入を避けることや、ネストを減らして今どのような状態にあるかを覚えなくてよいようにした。

また、オブジェクト指向の歴史は、状態とそれに関わる処理を明示的にするための歴史だと学んだ。実際的なプログラミングをするのには直面してしまう「状態」に対して、名前を付け、階層管理し、必要以上にそれらが依存しないようにし、関連する処理を一纏めにして、その処理以外を受け付けないことで一つの部品・仮想機械として取り扱えるようにすることで、より大きな課題を解決していけるようにした。

これらのように状態を意識しないように隠蔽した状態を「暗黙的状態」(または宣言的状態)と呼び、明示的に名前を付けて管理した状態を明示的状態と言う。

このように、プログラム中で動的に変化する状態をオブジェクトや構造体、変数の粒度で管理されるとき、その状態は「明示的状態」と呼ばれ、プログラム中で変数間の関係性が宣言的に記述され、イミュータブル性が維持される状態を「暗黙的状態」または「宣言的状態」という。このパラダイムを採用しているプログラミングスタイルのことを関数型プログラミングと呼ぶ。これは参照透過性を表から見るか裏から見るかの違いではあるが、手続き型プログラミングの対比としては、この観点で見るのがもっともわかりやすいだろう。(7/15文意がとりにくいため修正)

ガウディ本として知られるコンピュータプログラミングの概念・技法・モデルから明示的状態について、引用すると

手続きの中の明示的状態とは、その生存期間が二度以上の手続き呼び出しにわたるような1つの状態で、手続きの引数に現れないものである。(6.1.2 明示的状態)

明示的状態の取り扱いは非常に難しい。なぜならそれは、人間が「覚えておくべきこと」がシステムの中に残ってしまうからだ。それが残っている限り、バグを見つけ出すのは人間の仕事になる。

オブジェクト指向のよいところは、「状態」を明示的にして、人間がそのコントロールをしやすくするところにある。だが、複雑に絡み合う状態をわかりやすく明示的に管理するのが人間の仕事である以上、不断の努力が必要になる。

ある程度サイズまでのプログラムであれば、明示的状態を持つプログラミングスタイルは、明快でわかりやすい。オブジェクト指向プログラミングが一定の成果を上げたのもそれが大きな要因だ。

一方で、近年、関数型プログラミングが再び注目を集めている最大の理由は、オブジェクト指向だけで取り扱うには複雑になりすぎてしまう問題に対して、1つのソリューションを提供してくれるからだと考えている。たとえば、並列性の問題などを明示的状態ではなく、暗黙的状態として宣言できることで、人間が明示的に管理する必要がなくなり、バグが入り込む余地が大幅に減る。

暗黙的状態しかもたないプログラミングスタイルは、抽象度がどれだけあがっても管理する問題の複雑性は変化しないため、明示的状態を持つプログラミングスタイルで対応が難しくなる問題への回答となりうるということだ。

明示的状態と暗黙的状態
list = [1, 2, 3, 4, 5, 6, 7]

# 暗黙的状態のsum関数
def sum_dec(list, s)
  return s if list.empty?
  x, *xr = list
  sum_dec(xr, s + x)
end

# 明示的状態のsum関数
def sum_exp(list)
  s = 0
  list.each { |i| s += i }
  s
end

p sum_dec(list, 0)
p sum_exp(list)

状態ありプログラミングの限界

ガウディ本では、OOPのような状態ありプログラミングの限界(6.9.1)として、次の2つを挙げている。

  • 現実世界は並行的である
  • 現実世界は分散的である

並行性、分散性どちらも現代的なプログラミングにとって重要なファクターである。GoのChannel、ScalaのActorなど並列性・分散性に意識を向けたプログラミング言語が注目を浴びていることからも、これらの問題は今そこにあるものとして認識されつつあるのであろう。

元記事ではFRPの話が、取りざたされていた。リアクティブプログラミングとは、離散イベントにおける値の関係を宣言的に記述する(暗黙的・宣言的状態)ことで、並列性、非同期性の諸問題を隠蔽し、モジュラーに扱えることが最大の特徴だ。

これもまた、状態の明示性と暗黙性の問題だ。

まとめ

  • OOPと関数型の違いは状態の明示性
  • 一定レベルの抽象化までは明示的状態は扱いやすい
  • 並行分散非同期に注目が集まる昨今、ふたたび宣言的状態に注目があつまりつつある