2014-04-09

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

近年、関数型プログラミング重要はいろんなところで叫ばれています

Javaの最新バージョン関数型プログラミングに関する新機能が加わりました。

Rubyも昨今、関数型プログラミングへのサポートが手厚くなってきています

プログラミング教科書大手オライリーからJavascript関数型プログラミングを行うための解説書が発行されました。

関数型プログラミングへの注目度は高まってきています

おそらく、みなさんは既にオブジェクト指向が何か、を知っています

でも関数型プログラミングとは何か、胸を張って語れる人は、周りに見当たらないかと思います

実際、オブジェクト指向によってプログラミングする方法は、わかりやすい解説があちこちにある一方で、

関数型プログラミングとは何か、何が良いのか、ということについての、よいまとめは見つけることはできませんでした。

この記事を読む方の中で、「関数型プログラミングを取り入れるか・取り入れないか」で切実に悩んでいる人は、おそらくいないでしょう。

この記事はあまりかいところに立ち入りません。関数型プログラミングを使う側の立場に立って、利点や向き・不向き、それが導くスタイルを書きました。

みなさんは鳥のように飛んで、高い空から関数型プログラミングとは何か、何が良いのか、を見渡してください。

ふたつのアプローチ比較

オブジェクト指向アプローチは、名前をつけてプログラムを整理する

関数型プログラミングアプローチは、汎用部品でなんとかする

オブジェクト指向アプローチ

Googleが近年リリースした言語、Goには、”継承”を直接サポートする仕組みが無いことが話題になりました。

また、Mac OSXの基幹ライブラリCore Foundationは、ライブラリ自体C言語で書かれているにもかかわらず、その設計方針は明確にオブジェクト指向です。

継承クラスは、オブジェクト指向必須条件ではありません。

オブジェクト指向本質とは、何でしょうか。

その本質とは"名前をつけて対象を識別し、それを扱うこと"、にあります

最もプリミティブなオブジェクト指向対象は、ファイルハンドラです。あるファイルを開いて、読み込んで、あるいは書き込んで、ファイルを閉じる。

これらの処理をまとめたら、わかりやすいですよね?

対象に関する処理を、対象の周りにまとめる。これがオブジェクト指向の基礎的な理念です。

識別することとイコール比較できることは、とても良く似ています

イコールによる比較は、オブジェクト指向では鬼門であることが知られています

PointクラスインスタンスとColoredPointクラスイコール演算をどう決めればいいかに、正解はありません(詳しくは"effective java"をご参照ください)。

また名前をつけて識別する対象は、フワフワしていてはいけません。

たとえば、"軍人階級"をオブジェクトにしたとしましょう。"大佐"クラスのある兵士名前フィールドや、性別フィールドを持っているでしょう。

ところで彼が昇格したときに何が起こるでしょうか。

新たに"少将"クラスインスタンスが作られます。"大佐"クラスを破棄する前に、名前性別、その他沢山のデータを引き継がなくてはいけません。フィールドを増やしたい場合はその都度コード修正を加える必要があります(*)。

なるべくイコール比較を避けたい。対象不安定なものはいけない。では何に名前をつけて、識別するか。そこにオブジェクト指向技術者の熟練度が現れるのです。

関数型プログラミングアプローチ

一方、関数型プログラミングでは、特定の何かに名前をつけるより、極力、汎用部品でなんとかしようとしま

さな関数を、集めて撚り合わせて、新しい関数を作る。

関数自体リストなどのデータ構造に詰めることもよく行われます

実は、関数型プログラミングというのは本質を表していません。

その真の名は、"値指向プログラミング"です。

関数をはじめとして、リスト・ツリーのようなコンテナ手続きを抽象化したもの、回路を抽象化したもの

あらゆる対象を値として、合成し、ときに分解し、新しい値を作ります

変数という概念必要ありません。

変数適用する処理を作りあげることが、とても簡単だからです。

四則演算定義されたデータを詰めたデータ構造もまた、四則演算可能だったり。

値をイコール比較することも、なんのそのです。

誤解を恐れずに言うと、オブジェクト指向トップダウンなのに対し、関数型プログラミングボトムアップです。

関数型プログラミングの利点

読みやすい・理解やす

関数型プログラミングサポートする言語には、沢山の汎用部品定義されています

このような構造インターフェイスとして、様々なライブラリが組まれているので、

たとえばモナドを知っていれば、30分程度でパーサー(解析機)を理解することができて、

パーサーを理解できれば、JSONパーサー・ XMLパーサー・markdownパーサー・C++パーサー ... などを理解するのはとても容易です。

理解やすいこと。これが関数型プログラミングの大きな利点です。

追記:

また、汎用部品と型のお陰で、ライブラリドキュメントが圧倒的にひきやすい、というメリットも有ります

Haskellな人がPythonにトライした結果 - Togetterまとめ

書きやす

関数型プログラミングは「厳密な事前設計必要とするため、簡単なことをやるのにも時間が掛かる」。

よく誤解されていますが、これはウソです。

スクラッチプログラムするのは、非常に手軽です。

>> map (*2) [1,2,3]
[2,4,6]

邪魔な”儀式”や、"おまじない"のコードが徹底的に撤廃されているためです。

関数型プログラミングコードは、潔癖かつ濃密です。

たとえばC言語でint hoge(int x,int y)が定義されているときhoge(3)はなんの意味も持ちませんが(コンパイルコケますが)、関数型プログラミングでは意味があり、実際に有用です。

上の例では、「掛け算をする」(*)関数は、二引数関数ですが、それに引数を渡して作られた「2を掛ける」関数(*2)は、一引数関数になります

関数型プログラミングでは、「簡単なことは簡単にでき、複雑なことは複雑にできる。ただし、間違ったことは殆どできないか、全くできない」。

多くのバグは、コンパイルエラーとして検出されます

また、静的型付けの力によって、コード補完は非常に強力になっていますインテリセンスの比ではないです。

たとえば、関数中のある表記の型を任意に表示できます(GHC/TypedHoles - HaskellWiki)。

やがてやってくる未来には、プログラムテキストエディタで書くことは時代遅れになっているでしょう。

統合環境サポートで、バグミスの少ない、スムーズプログラミングができます

そしてその環境で動くプログラミング言語は、関数型プログラミングサポートした言語なのです。

いつ関数型プログラミング

以下の様な兆候を感じたら、あなたはそのプログラム関数型プログラミングで書くべきです。

一般に、オブジェクト同士の相互作用が複雑になるほど、オブジェクト指向では手に負えなくなっていきます

そういうときは、オブジェクトを直接扱わず、替わりにその"相互作用"を扱うことで、複雑さを軽減するアプローチ有効です。

それこそが関数型プログラミングアプローチです。

オブジェクト指向の利点

初心者にとっては読みやすい・理解やす

特にオブジェクト指向有効なのはプログラミング初心者がそのコードをいじるかもしれないときです。

関数型プログラミングは、強固さと柔軟さの代償として、高い学習コストを伴います

そのため、初学者にとってはハードルが高いのです。

扱う対象があまり複雑でない時は、書きやす

オブジェクト間の相互作用が複雑でなく、着目している(名前をつけている)概念が安定しているとき

そして、プログラムをいじる人たちの間で共通理解が図れているならば、オブジェクト指向が有利です。

関数型プログラミングの得意分野はなにか

数値計算

遅延評価という機能によって、レガシー言語で扱えなかった、巨大な数を扱うことができます

分数を扱うことができます虚数もです。

関数型プログラミングで書かれたプログラムは、正確さが要求される、金融関連の業界で使われています

テキスト処理

手続きとしてパーサーを記述できるので、テキスト処理プログラムはより理解やすく、メンテナンスやすものになります

関数型プログラミングを知らない人は、「正規表現おk」と言いますが、

彼の書いた複雑な正規表現は、半年後には(書いた本人でさえ)理解できなくなっていることでしょう。

並行処理

手続き一般を扱うことができるので、途中で割り込みのある手続きの表現も容易です。

関数型プログラミングサポートしていない言語ではコルーチン(ファイバー)などをつかってなんとかするしかありません。

さもなくば、非並行処理では普通に関数として記述できるところを、並行処理のために、Builder,Strategy,Command,Interpreterパターンを駆使して書き直すことになります

Javascript使いの方は、Deferredなどの構造を使うでしょう(http://qiita.com/KDKTN/items/4c6986049d204f0645d8)。

C++使いの方はBoostで頑張りましょう。破滅的に解りにくいコンパイルエラーメッセージと格闘してください。

レシピ

もう少し簡単な例をあげます

あなたは、あるレシピにしたがって、自動的料理を行うマシン制御プログラムを書いているとしましょう。

料理レシピは、"手続き"ですよね?たとえば、カレー

1. まず玉ねぎを炒める。

2. 飴色になったら、肉を加えて炒める。

3. 野菜を加える。

4. 水を加えて煮る。

5. スパイスを加える。

しかあなたはこの手続きを関数として表現できるでしょうか。

…できませんよね?何故ならば、各ステップの"間に"、マシンのロボアームの位置や動きを調整する処理が必要からです。

これをオブジェクト指向でやろうとすると、各ステップ副作用として、それらの処理を行うことになります

そうすると、マシンが二機に増えた時などの変更量は、絶望的なものになります

あるいは関数として表現するのを諦め、手順全体をDSL記述できるようにします。

このアプローチ関数型プログラミング的です。しか関数型プログラミングサポートした言語の助けなしでは、そのDSL記述するために沢山のユーティリティコードを書かなくてはならないでしょう。

オブジェクト指向アプローチでこの問題をエレガントに解こうとすると、クラス化の粒度を上げる事になります

野菜クラスフライパンクラス、ボイルクラスフライクラス、焼き加減クラス、アームクラス野菜の大きさクラス、切り方クラス、焼き方クラス、"焦げたよ"クラスetc...

こうすると早晩レシピプログラムコードから消え去ることになります。上記のたった5行は、依存性注入のオブジェクトグラフを構築するコードに取って代わることになります。そこには沢山の挙動制御オプションとして付記されているのです。

カレーなど、ある種のレシピ限定することで、見た目の理解やすさを得ることができますが、一方それは表現力を損なうことを意味します。

C言語などではマクロを使うこともできますが、それは結局、関数型プログラミングアプローチ意味するところと同じになります。すなわち、補助のために沢山のコードを書くことになるでしょう。

GUI

iOSのAppstoreアプリは、"無料"と書かれたボタンを押すと、それが"インストール"ボタンに変わり、それをもう一度押すと、ダウンロードの進捗を表すインジケータに変わり、それを押すとダウンロードキャンセルできます

このように、位置は同じなのに、ステートに依って見た目と機能が変わるボタンは複雑です。

これをオブジェクト指向で実現しようとすると、

1. 三つの異なるボタンを同じ位置に置くか

2. 同じボタンが三つの異なる機能を持つか

という下らない問題にぶつかります

一方関数型では、"機能"、"見た目"、"状態"、を独立に扱って、それらを合成してボタンを作るので、迷うことはありません。

「同じ位置にあるUIオブジェクトは、コード上で(インスタンスとして)独立して、他から干渉を受けない」

この条件が満たされているうちは、オブジェクト指向GUIを実現することに無理はありません。

しかし、携帯端末のような小さい画面で、多くの機能を達成するためには、UI要素はコンテキスト依存的に複雑になりがちです。

近年、PCのディスプレイの大きさは、頭打ちになってきました。

画素数は増えているのですが、MacにおけるRetinaのように、複数ピクセルひとつドットを表すようになってきています

これは、ひとつの画面に置かれるボタンなどのUI要素の数は、これから先の未来で増えることはない、ということを意味します。

したがって、未来GUIプログラミングは、注意深く機能ピックアップして制限するというデザイナー努力を脇におけば、

関数型プログラミングの力を頼るしか無いでしょう。

はじめよう、関数型プログラミング

まり

Haskell さいこうなのおおおおおおおおおおおおおおおおおお!! おしっこ漏れちゃうのおおおおおおおおおおおおおおおおおおおお(゜∀。)ワヒャヒャヒャヒャヒャヒャ

1. google:すごいHaskellたのしく学ぼう を注文する。

2. Download Haskell自分のPCに導入する。

3. コンソールghciと入力して、対話コンソールを立ち上げる。

4. 次の関数コンソールに打ち込んで、結果を見る。即値で書かれているところとかをいろいろ変更してみて、感動する。

take 4 $ map (*2) [1..]

5. ステップ1で買った教科書を読んで、学ぶ。


追記:

いかがでしたか

ちまたには、関数型プログラミングの利点は変数が無いことだ、とか、より安全から、とか、より速いから、などという妄言が満ち溢れています

オブジェクト指向関数型プログラミングは、水と油ではありません。プログラマ自分プログラムに最適なアプローチを選ぶことができます

一般にはあまり知られていないことですが、Haskellにもオブジェクト指向へのサポートがあるんです(Lensライブラリ、これを使用したサードパーティライブラリ最近増えてきています)。

この記事を読んだオブジェクト指向プログラマあなたが、少しでも関数型プログラミングに(そしてHaskell)興味を持ってくださって、ホームセンター大人用オシメのコーナーが大賑わいになれば幸いです。。

  • だから数値計算が得意とか適当こいてんじゃねーよ。 金融で使われてるのはデリバティブの合成が記述しやすいからだろうが。 数値計算が得意とか言うならゲーム機でも動く高速・高効...

  • 関数型言語ユーザは「信者」なんだってことはよくわかりました

  • オブジェクト指向 v.s. 関数型プログラミング http://anond.hatelabo.jp/20140409010816 数値計算が得意とか言うならゲーム機でも動く高速・高効率なGIレンダラとか流体シミュレータとかの実用的...

  • Haskellの話を関数型言語一般の話みたいに敷衍して話すのやめてもらえませんかねぇ(困惑)

  • いくつかまとめて反論したい まず最初に言っておきたいけれども、僕自身はHaskellが大好きな関数型言語大好き人間である、ということを先に述べておきたい、それを踏まえた上で以下を...

    • id:minamiyama1994 さん、反論してくださってありがとうございます。 Haskellファンのご意見がいただけて嬉しいです。元増田です。 記事全体で「関数型言語」と呼ばれているものは「関数型...

      • 君数値計算やったことないでしょ? なんか「金融でHaskell使われてる」って聞いた!金融!数字の計算!数値計算だ!!!お金の計算だから厳密だ!!! くらいの中学生的なノリを感じ...

      • キミさ、大きなプログラム完成させたことある? プログラムに行き詰まって 「自分に作れないのはオブジェクト指向が駄目なせいだ、関数型ならできるはず」 って言ってる人に見える...

        • 大きなってどれくらいを指してるのか分からないけど、実際、ざっくり汚いコードで書いても関数型はコード量半分で済むよ。 1万行書くのは無理って感じても、5千行なら届かなくは...

        • もちろんですとも。 オブジェクト指向が駄目なせい ところでご意見を聞きたいのですが、私自身は記事でオブジェクト指向をディスったつもりは無いんですが、そのように読めました...

          • うん。 オブジェクト指向は初心者向け、複雑じゃないシステム向けって書いてあるじゃん。 複雑な問題をプログラムにすっきり落としこむのはスキルの問題であって、関数型とオブジ...

      • あ、短く書ける云々での「C++」は「Haskell」のtypoです(たまにこの手のアホなtypoやらかす)、失礼しました

  • ベースはオブジェクト指向で書いて、 ちょっと便利になるところだけ関数型にするのがプログラマにとっては理想なんじゃないかなぁ・・・

  • 内容読んでないけど、とりあえずオブジェクト指向と関数型プログラミングが両方そなわり最強に見えるScalaを使えばいいと思う。

  • どうぞ 関数型料理 http://blog.practical-scheme.net/shiro/20140129-functional-cooking プログラミング言語の基礎知識 http://d.hatena.ne.jp/lemniscus/20100701/1277990423 関数型とオブジェクト指向という一見相...

    • オブジェクト指向って、オブジェクト内部にミュータブルな状態を保持してる、ってのが本質の1つなんじゃないのかなー。 内部状態がイミュータブルなオブジェクトだけ使ってたら、...

      • コストを気にしなければオブジェクトを変更する代わりにそのつど新しいオブジェクトを生成するという手があるので オブジェクト指向の「本質」とまでは言えないのでは。 オブジェク...

        • できるだけイミュータブルにすべき、だと関数型でいいじゃんってなるよね。 多態とかはHaskellでもClojureでもできるし。

    • 元増田です。読んでくださってありがとうございます。 2つ目の記事は初見です。面白そうですね…あとでじっくり読みます。 実は元記事の「レシピ」の項は、「関数型料理」にインス...

記事への反応(ブックマークコメント)

ログイン ユーザー登録
ようこそ ゲスト さん