オブジェクト指向と関数型プログラミングは両立するのか?

オブジェクト指向と関数型プログラミングは両立すると言われる。実際に両方のパラダイムを持つプログラミング言語も存在し普及もしている。確かに言語というレベルでは両立しているように見えるが、果たしてプログラムデザインのレベルではどうなのだろうか? 関数型プログラミングに入門してから一年余り、どうもこの二つの考え方は両立しないのではないかという思いが強くなってきた。あるいは、これらが両立するという前提が混乱の元になっているのではないかと思うようになった。

例えば、「関数型つまみ食い: 関数型プログラミングの何が嬉しいのか?」では、オブジェクト指向と関数型での、状態に対するアプローチの違いについて触れた。オブジェクト指向では状態とその遷移を隠蔽することによってシステムを単純化する一方、関数型では(多少冗長になっても)状態遷移を明確にすることによってシステムの動作を予測しやすくするという対比だ。つまり両者は、状態に関しては真逆のアプローチを取っている。

オブジェクト指向でも、不可変なデータを扱うことによって意図しない状態遷移を防ぐことができるし、それによってオブジェクト指向でなくなってしまうわけではないと考えることもできる。あるいは、副作用を避ける(不可変なデータを扱う)場所と、副作用を扱う場所を分けることによって、両者の考え方をうまく共存させることができるのではないかと。

しかし、先月開催された Elm Tokyo Meetup にて教えて頂いた、Elm Europe 2017 のアーカイブ動画を見ていたら、オブジェクト指向と関数型はそもそも根本的に異なる思想なのだという思いを新たにした。ちなみに Elm は Web のフロントエンド開発に特化した、純粋関数型のプログラミング言語である。

この発表では、プログラムデザインの流れをもう一度基礎から再考すべきであるということが語られている。まずは一枚岩で構造化されてない状態から初めて、「Build -> Discover -> Refactor」というサイクルを回しながら、構造化すること自体を目的化せず、今行おうとしているそのリファクタリングによって「そもそも何を保障したいのか?」を問う。

ここでの狙いは、過去の経験によって培われた「構造化の先走り」を防ぐことにある。「構造化の先走り」とは何か? Elm で有名な問題に「コンポーネント・アンチパターン」というものがある。

以前の Elm のチュートリアルでは、UI をコンポーネントの集まりとして表現する、というそれまで当たり前に考えられてきた文脈に沿って、状態とその変更ロジック、そしてその状態を表示するためのビューをひとまとめにしたコンポーネントというものを定義し、それらを組み合わせることによってアプリケーションを構築するという手法を紹介していた。しかし、このデザインでは、アプリケーションが大きくなるにつれてコンポーネント同士を連携させるためのボイラープレートが膨大になったり、柔軟性に欠けるケースや無駄な重複が現れるとして、その後、コンポーネント化はできるだけ避けるべきだと明言されるようになった。

Elm Europe の動画では、コンポーネントとは結局のところ、状態と操作をひとまとめにする、オブジェクト指向の構造化手法であることが指摘されている。長らくオブジェクト指向に親しんできたプログラマーが Elm に同様の手法を適用した結果、思ったような柔軟性を得られなかったという流れだ。

このブログでは過去に「オブジェクト指向とは何だったのか?」という記事で、オブジェクト指向をその起源に遡って解釈しようと試みたことがある。

ケイ氏は、オブジェクトを、ネットワークを形成してメッセージを送り合うコンピュータのメタファーとして捉えており、インターネット上のサーバーのように、リクエストをメッセージとして受け取り、そのメッセージをサーバー側で解釈して何らかの処理を行うというモデルを想定していた。

この全てがオブジェクトであるという原則と、大きなシステム(オブジェクト)は小さなシステム(オブジェクト)の組み合わせで作られるという「再帰的デザイン」によって、どんな複雑なシステムをデザインするときでも、覚えなくてはならない原則は少なくて済むようになる。

オブジェクト指向では上のような再帰的構造を持つものとしてシステムを捉える。これがアプリケーションに応用されると「アプリケーションはより小さなアプリケーションの集合によって表現される」ことになる。凝集性の観点から言えば、これはとても魅力的なアプローチに思える。アプリケーションを構成する、より小さなサブアプリケーションは各々が独立していて、変更の影響も局所化される。

しかし、関数型の世界(少なくとも Elm の世界)では、このモデルに疑いの目が向けられることになった。

件の動画では、アプリケーションはアプリケーション固有の(そもそも必要とされる)複雑さを持つのだから、その複雑さに対応するために、データから操作を切り離し、両者をより柔軟に組み合わせることによってその複雑性に対応すべきだということが示唆されている。その結果として、Elm ではグローバルな状態やロジックを集めたコードが長くなることについて、他の言語よりも寛容である、ということが説明される。これはあるいは Elm の持つ強力な型システムの支えもあるかもしれないが。

アプリケーションの再帰的構造というオブジェクト指向のモデルは、昨今のフロントエンドのような複雑なシステムを表現するには単純に過ぎるという問題は、オブジェクトとリレーショナルモデルをマッピングするときに問題視されたインピーダンスミスマッチの問題に似ているかもしれない。

最近、この問題に関係していると思われる大変興味深い記事に遭遇した。Cindy Sridharan 氏による「小さな関数は有害だと考えられる」というタイトルの記事だ。

タイトルが若干ミスリーディングであることは本人も認めているが、彼女が言わんとしていることはこういうことである。名著「Clean Code」などに書かれていたり、あるいはソフトウェア開発における著名人が喧伝するような、今では当たり前とされる、オブジェクト指向時代に生まれた設計指針が果たして今も本当に有効なのかどうか今一度確認してみるべきではないのか、と。

彼女の問題意識は、適切な抽象化というものがそもそも難しいのだというところから出発して、DRY原則などによって無条件に構造化を推し進めようとする既存の設計手法は、多くの場合に失敗に終わる抽象化によって、可読性や柔軟性を欠いたコードになることが多いという発見に辿り着く。オブジェクト指向時代の多くのプラクティスは抽象化が適切に行われることを前提にしているため、物事が複雑に揺れ動く現実の世界では、そもそもそのメリットを享受することが難しい。

duplication is far cheaper than the wrong abstraction, and thus to prefer duplication over the wrong abstraction

そもそも完璧な抽象化というものは存在しない。抽象化された概念というのは、ある文脈を前提にした主観的なものに過ぎないからだ。そしてのその文脈はあらゆる要因によって常に揺れ動いている。Sridharan 氏は、既存のコードが持つ影響についても指摘している。すでに書き上がったコードはある種の成果であるため、たとえ文脈が変わってしまっても、そもそもの文脈を廃棄することには心理的な抵抗が働くのだと。その結果、いびつな形で増改築が行われた建築物のように、構造化はされているが、全体像を捉えるのは難しいプログラムが出来上がってしまう。

より速くそして複雑に変化するシステムを捉えるためには、無条件な構造化については慎重にならなければならない。下手に構造化するぐらいであれば、多少関数が長くなっても実直に書かれている方が遥かに可読性が高く修正もしやすい。そして、過剰な抽象化を避けるためにコードのライフサイクルについても思いを馳せるべきだと Sridharan 氏は指摘する。これまでの設計指針では、コードを追加したり、削除したりする際は Open-Closed Principle(開放/閉鎖原則)に則るべきだと言われてきた。しかし、その原則がうまく働くのは抽象化がうまく行われた時だけである。であれば、「修正しやすい」こととは何かということを今一度考え直すべきなのかもしれない。

さて、この議論を呼びそうな記事に対しては、当然のことながら、既存の設計指針を支持する陣営から反論が行われている。以下はその一例である。

反論の要旨は以下のような感じだ。

  • 大きな関数は変更が局所化されないので脆弱である
  • ユニットテストが書きづらくなるため、結果としてテストされないコードが増える
  • 適切に抽象化されれば、やたらと長い名前問題はそもそも起こらない
  • コードは Open-Closed Principle に従って修正されるべきである
  • 大きな関数は副作用を持つ可能性が高くなる

こういう反論はいかにもありそうだという印象を個人的には受ける。「あなたはそもそも適切に抽象化できていないから」小さな関数が有害だと感じるのだという、オブジェクト指向界では比較的馴染みのあるものだ。「そもそも抽象化が難しい」というところには応答しておらず、議論はすれ違っている印象を受ける。

「そもそもやり方が悪いんだ」問題は、以前このブログで紹介した「Is TDD Dead? 会談」にも現れていた。

DHH氏の想定するようなTDDは、Beck氏やFowler氏の想定するTDDとは異なり、単にやり方が悪いからうまく行かないだけで、DHH氏の発言は「わら人形論法」に過ぎないというのが、TDD肯定派の反論である。それに対して「うまく行かないのはやり方が間違えているからであって、正しく実践すればいつかは正解に辿り着ける」というのは「faith-based TDD」という信仰であって、そのようなものは認められないというのが DHH氏の立場であり、ここがすれ違いの中心になっている。

そして、「ユニットテストが書きづらくなる」という主張も、関数型(特に Elm のような静的型付けの)ではユニットテスト の役割が相対的に小さくなること、あるいは「TDD再考」でも取り上げたように、ユニットテストそのものの価値の問題を考えると、それほどクリティカルな反論であるとは言えないように思える。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中