[TechDays 2010] Visual Studio 2010 でプチパラダイムシフトへ
TechDays 2010 では、「CM-204 Visual Stduio 2010 でプチ・パラダイムシフトせよ!」というBOFがありました。このBOFを担当していた小島さんと原さんに本番の15分前にお願いして、参加させていただきました。小島さんの言われるパラダイムシフトを借りて、私なりに自分の考えをまとめたいと思います。
最初に手続型のパラダイムは、以下のように表現することができます。
手続型でプログラミングする時は、頭の中が手続(手順)で一杯になっています。これが、現在の主流であるオブジェクト指向では、以下のようになります。
頭の中はオブジェクトで占められて、少しだけ手続のことが残っています。オブジェクトの占める割合で、オブジェクト脳の完成度が決まるといわれるのかな?ここで、パラダイム論でオブジェクト指向を説明するとすれば、オブジェクト+手続という組み合わせた考え方ということになります。なぜなら、オブジェクト指向で実現するオブジェクトには、メソッドやプロパティがあります。メソッドなどの実装は、手続で行いますので手続を忘れたわけじゃないのです。
ここで唐突ですが、チューリングマシンを思い出してください。別の言葉でいえば、ノイマン型コンピュータです。これらの動作原理を単純に説明すれば、以下のようなものになります。
- プログラムカウンタを使って命令を読み込む。
- 命令をデコードして実行。
- プログラムカウンタをカウントして、1へ戻る。
単純化していますが、ノイマン型コンピュータは手続きを使ってプログラムを実行していることが理解できると思います。次にジョン・バッカスという人の話をします。ジョン・バッカスは著名な数学者であり論理学者で、FORTRANを設計した人物です。これ以外にもBNF記法などを考案した人です(BNFは、標準規格など様々なところで使われています)。彼は、その功績によりチューリング賞を受賞します。この受賞講演で「Can Programming be Liberated from the von Neumann Style?"(プログラミングはフォン・ノイマン的スタイルから解放されるか?)」という内容を話しています。内容はともかく、この講演がきっかけで様々な関数型プログラミングの研究が行われていったのです。つまり、ノイマン型プログラムではないというのが、関数型言語の源流だったと考えることもできます。
話を元に戻すとノイマン型コンピュータである限り、手続型から逃れることはできないということを私は言いたかっただけです。CPUがムーアの法則に従って高速化やメニーコア化したとしても、CPU自体がノイマン型ですから実行は手続に則って行われているということです。よって、処理速度の速いプログラムになるような最適化は、手続に従って行われていくということです。逆に非ノイマン型コンピュータは、一般的に使われているのかという疑問が出ると思います。もちろん存在はしますが、一般的になっていないということができると思います。ここで最近、話題に聞く関数型言語はどうなんでしょうか。HaskellやErlangなどの話題を良く聞くと思います。BOFの中で小島さんは、Haskellを使って、ソートなどのアルゴリズムを実装するプログラムを見せてくれました。Haskellと同等なプログラムをC#で記述した場合も見せてくれました。この時のプログラムの脳内模型がどのようになっているかと言えば、以下のようなものになります。
言い方が良いかどうか私にはわかりませんが、良く「本物のプログラマは関数型言語を使う」というような話を耳にします。手続型言語を使うよりも短く、簡潔に記述できるのが関数型言語だという話です。この手のプログラムを読むと、関数型パラダイムが理解できない自分には、なかなかプログラムの意図を理解できなかったりしますが...関数型プログラムの特徴は色々とありますが、代表的なものを以下に列挙します。
- 宣言型である。
- ラムダ式を記述する。
- 不変(Immutable)。
- パターンマッチ
- 遅延評価がある。
- 副作用がない。
小島さんが説明してくれたHaskellの例では、式(厳密にはラムダ式)を宣言していくだけプログラムが記述されるため、宣言した順序が実行される順序ではないということです。つまり.NETのLINQのように遅延実行が行われるということです。こうなると、ソースコードを読むときに遅延実行を考慮しなければ、実行順序がわからなくなるということです。このような言語に取り組むには、パラダイムシフト(考え方を変える)が必要になるのではないうかということです。
関数型言語を考えた時に、「副作用が無い」という特徴から並列実行に適しているのではないかと良く考えられています。このことを私は、「その通り」と考えていまして、宣言した関数が参照透過性(同じ引数を与えれば、同じ値が返るという性質)を持つのであれば、参照透過性を持つ単位で並列実行するタスクとして切り出すことが可能だと考えています。このことにプラスアルファで、「不変」という特徴を組み合わせると、共有メモリのロックの問題が解消されると考えています。これらの事から私が説明したいのは、参照透過性などを意識せずに獲得しやすい言語を使うことで並列化を考えやすくなるのではないかということです(並列化を意識した言語として、GoogleさんからGo言語が発表されています。このような新しい並列対応の言語が作成されることもあるでしょう)。また私は例としてお話させていただいたのが、JavaScriptは関数型言語だという話です。もっとも関数型としてよりも手続型としてJavaScriptを使われている方のほうが多いですが...
今度はオブジェクトというものを考えてみたいと思います。一般的にオブジェクトは、その状態をカプセル化して、振る舞いをメソッドなどで公開します。このオブジェクトを複数のタスクが同時にアクセスすることは易しいでしょうか。同期化プリミティブなどを使ってスレッドセーフティにしなければ、並列操作することは難しいと言えるでしょう。極論すれば、並列操作とオブジェクトは相性が悪いということができます。
次に関数型言語はオブジェクトも使用できますので、どうなるかと言えば、標準が不変なオブジェクトになります。どういうことかと言えば、オブジェクトの状態をインスタンスを作成してからは変更することができなくなります(厳密には、プロパティに対する代入ができないということです)。F#で.NETのオブジェクトを使用する場合に、この問題が発生します。このためF#では、.NETのオブジェクトを束縛する場合に書き換え可能(Mutable)と宣言して、この制約を回避します(この性質から非純粋関数型言語と呼ばれます)。では純粋関数型言語(不変しか取り扱えない)であるHaskellなどでは、どうしたら良いでしょうか。この時に行う方法が、関数が新しい状態を持ったオブジェクトを生成して値として返すという方法になります。このように言語が変われば、考え方を変えるとことで同じようなことを実現できるようになります。これには、パラダイムを変える必要があります。このようにBOFで説明して、参加者の方からメモリが大量に必要になりガベージコレクタが活躍しますよねという質問がありました。答えは「その通り」ということなのですが、JavaにしろCLRにしろGCを備えた実行環境が現実的に使用できているのが現在のコンピュータですよね。さらにメニーコア化し、ギガ単位のメモリを搭載している現在のPCでは、関数型言語も実用域に入っていると考えても良いのではないでしょうか。逆説的に大量のCPUコアやメモリリソースを使い切って高速化するというパラダイムがあっても良いように思います。
このBOFで簡単なF#のサンプルを私がお見せしました。このサンプルは、並列タスクの例として用意したものです。並列化したF#のクラスとC#のクラスで、F#が130行前後でC#の166行前後となっており、実現したいことに適したパラダイムを使うことでプログラム自体が簡潔になりますという話をさせていただきました(自分のセッションでは、時間が足りなくなってお見せできなかったデモなんですが)。
全ての場面で関数型プログラミングを使用する必要はなく、適材適所で言語を使い分けることで少ないコードで目的を達成できる可能性を持たせるために、Visual Stduio 2010 に関数型言語である Visual F#が入ったと考えることができるのではないでしょうか。もちろん関数型言語としてF#を使って、WindowsアプリやASP.NET Webアプリ、或いはSilverlightアプリまで作成していらっしゃる強者もいらっしゃいます。私自身は、そこまでの関数型脳になっていないので、並列化や大量の集合を扱った計算を行う場面で F# を使用するだけです。このためには、自分の関数脳をもっと鍛える必要があるのは言うまでもありません。
皆さんも、今迄と違うパラダイムを使ってみませんか。
PS.脳内モデルは、BOFで説明された小島さんに引用を快く承諾していただいて感謝しています。おもしろいモデルだと私は思っています。関数型パラダイムですが、GoogleさんのMap Reduceフレームワークが関数型のMap関数とReduce関数をC++で実装したというのは有名な話ですよね。Map Reduceフレームワークから私が学んだのは、関数型パラダイムから他の言語へ応用できる考え方が沢山あるということです。
Comments
Anonymous
March 01, 2010
毎回勉強させて頂いております。 関数脳を鍛えるトレーニング方法で、何か良いものを御存知でしょうか? ヒントでも構いません。 御教授下さい。Anonymous
March 01, 2010
たとえば、プログラミングClojureという書籍とかは如何ですか。 http://ssl.ohmsha.co.jp/cgi-bin/menu.cgi?ISBN=978-4-274-06789-1 単純な話で申し訳ないですが、1)再帰関数、2)パターンマッチ、3)パイプライン、4)タプルなどの考えを勉強しても良いのではないでしょうか。以下がF#におけるパターンマッチの例になります。 let rec sumOfSqure nums = match nums with |[]->0 |n::rest-> (sqr n) + (sumOfSqure rest) この中で「[]」と「n::rest」がパターンマッチ(厳密にはリストパターン)を表していて、その意味が理解できるようならループなどを再帰で表現していけば、関数脳が発達していくかも知れません。文中でも記述させていただきましたが、私の関数脳の発達程度はまだ子供という程度なので。Anonymous
March 01, 2010
御教授頂き有難うございました。 何から手をつけて良いか分からず、困っておりました。 薦めて頂いた書籍を取っ掛かりとして、関数脳のトレーニングに励みます。Anonymous
March 03, 2010
プログラミングClojureの訳者川合史朗さんによるSchemeのお話になりますが、 何でもλ http://practical-scheme.net/docs/lambda-j.html 何でも再帰 http://practical-scheme.net/docs/tailcall-j.html 何でも継続 http://practical-scheme.net/docs/cont-j.html これらの記事が関数脳入門としては良かったと思います。Anonymous
March 04, 2010
lambda 様 貴重な情報を提供して頂き、有難うございます。 勉強させて頂きます。