セカイノカタチ

世界のカタチを探求するブログ。関数型言語に興味があり、HaskellやScalaを勉強中。最近はカメラの話題も多め

オブジェクト指向と関数型で副作用の扱いが違うって知ってた?(2021年版)

(2021/2/23 加筆訂正。文章を見直して現在の結論を追記しました。文意は変わっていません)

最近、オブジェクト指向と関数型を比べる人が多くなってきたみたいなんで、自分の考えをまとめてみます。

まず、本件ですが、壮大なテーマだと思いますので、全体を網羅して書くのは難しいです。

なので、主眼を絞ります。

主眼は、ズバリ「副作用」です。

副作用とは、薬なんかだとその薬が目的とする「作用」に付随してくる好ましくない「作用」の事を指します。

例えば、風邪薬を飲むと「眠くなる」とか、タミフルを飲むと「ベランダから飛び降りたくなる」*1

副作用によって、プログラム全体の動作が複雑になり、わかりにくくなるため、最近のプログラマー達の議論では「副作用は悪」であるということでコンセンサスが取れているようです。

関数型言語と副作用の戦い

関数型言語における、対副作用兵器は「参照透明性」です。

関数から、副作用そのものを排除してしまおうという発想*2で、変数は定義と同時に値が確定し、その後変更できませんし、関数も戻り値以外にプログラム内部の状態を変更できません

このため、関数は同じ引数ならば必ず同じ結果を返すことが保証され、副作用の心配から根本的に開放されます(やった!)。

ここまでの(過激な)機能が備わっていない言語でも、なるべく副作用がないプログラムを書こう!というのが関数型プログラマーの戦闘スタイルとなります。

しかし、完全に副作用がないプログラムというのは存在できません。起動しても何の入力も受け付けず、何の出力もせずに終了するプログラムは、どれだけ凄い処理をしていたとしても私達にはわかりませんし、まったく役に立たないからです。

そのために実際は副作用の発生する領域と、副作用の発生しない領域を分離して使います。

詳しくは、下記の記事で書きました。

簡単にまとまると、こんな感じです。

  • 副作用の無い領域と、副作用の有る領域を完全に分離する
  • 副作用が無い領域をなるべく多く確保し、副作用が有る領域は慎重に管理する

これが、関数型言語における対副作用戦の生存戦略です。

オブジェクト指向言語と副作用の戦い

「副作用」という発想自体が、関数型方面発祥なため(ですよね?)、オブジェクト指向プログラミングでは、「副作用は野放し」と思われてる方も多いかと思いますが、そうではありません。

オブジェクト指向言語における、対副作用兵器は「カプセル化」です。

森羅万象あらゆるものをオブジェクトとして捉え、クラスを定義しインスタンス化していく事で現実世界をコンピュータの中に再現する。

これがオブジェクト指向プログラマーの戦闘スタイルですが、クラスのインスタンスというのは副作用を内部に封入したカプセルと見ることができます。

逆に言うと、副作用が無いのであればインスタンスの存在価値は希薄になり、静的なクラスがあればよいということになります。

自分は、Haskell→Scalaというコードの移植を(趣味で)よくやるのですが、ほとんどインスタンスが必要ないコードになります。インスタンスが必要なケースは、単純にデータ構造を表すことになるので、クラスである必要がなく、Cで言うところの構造体みたいなもので十分ということになります*3

オブジェクト指向におけるインスタンスは、副作用を管理する箱です。

副作用というのは変化する値ですが、野放しにすると危険なので箱に入れて専用の関数(メソッド)経由でしか変更できなくします。

副作用同士が反応すると危険が増幅する性質があるため、箱はなるべく小さく設計し、箱同士のメッセージ交換によって動作するようにします。

図にすると、こんな感じです。

簡単にまとめます。

  • 副作用をインスタンスの中に封入する
  • インスタンスが大きいと副作用が悪さをするのでなるべく小さく設計する
  • 副作用へのアクセスはメソッドによって慎重に管理する

これが、オブジェクト指向言語の生存戦略です。

まとめると

関数型とオブジェクト指向で、「副作用」に対するアプローチは180度違います。

関数型は副作用を「外的なもの」として扱い、main関数(つまり外界)から繋がったエリアの内、副作用を必要とする領域を隔離する事で安全性を保とうとします。

対して、オブジェクト指向では、副作用をなるべく小さい単位で「内側に封入」しようとします。

この態度の違いは本質的で、コーディングテクニックがどうのこうのという問題では無いと思います。

なので、両者のプログラミングパラダイムは、食い合せが悪いのかな。と思っています。

また、両者とも違ったアプローチを取りながらも同じ目的に向かっているため、優劣の問題ではないです。

両方共、得意不得意があって完璧ではないんで、住み分けたり使い分けたりする必要があるし、お互いの良い所は可能な限り取り込んで、より良いパラダイムを作っていったらいいんじゃないかと思います。

結論「みんな仲良く」

仲良く喧嘩しな。ということで、やっぱりトムとジェリーは偉大だった*4

おしまい。

結論 2021年版

現在私は、オブジェクト指向と関数型のマルチパラダイムプログラミング言語である、Scalaを使ってプログラムを書くことが多いのですが、2021年現在の結論としては、文中にも少し出てきていますが、「副作用を持たない静的なクラスメソッドとデータ構造としてのインスタンス」という構成になっています。インスタンスは、生成時に値を封入し変更不可のデータとして(immutable)使います。

オブジェクト指向的な副作用へのアプローチは、ほとんどのケースにおいて問題をうまく解決することができず、やり方を間違っていたのだと思います。

*1:本当かどうか知りません(その後この説は否定されています 2021年追記)))とかです。

プログラミングにおいては、関数の戻り値を「作用」として、それ以外の作用を(良し悪し関係なく)「副作用」といいます((少なくともここではそういうことにしておきます

*2:実際は因果が逆で関数は引数と戻り値によって定義されていて副作用という概念がない

*3:本当は代数的データ型が欲しい。けど副作用の話とは関係ないので割愛

*4:わからない人は周りの年寄りに聞こう