LT第二弾
■関数型プログラミングについて
前にLT第一弾で、尖がった書き方と言いました。
例をあげたいと思います。
もう、ある意味、普通の話になってしまった感がありますが
関数型プログラミングについて、話したいと思います。
(新人の人は、分からない単語がたくさん出ると思いますが
そんなものがあるんだと思って細かいことは気にしないでください
そして余力があれば、クグったり質問したりしてみて調べてください)
唐突ですが、関数型プログラミングで有利な点
(a)並列/並行処理に強い。
(b)構文解析器が簡単に書ける仕組みがある。(parsec)
(c)プロパティベースのテストコードがあること
数学に乗るので、静的型チェックや、証明や検証に乗りやすい。
(d)数式の様な記述になるので、簡潔でコンパクトにロジックが書け
バグが少なくなることが期待できる。
(a)並列/並行処理に強い。
一昔前の話になってしまいますが、cpuのコアがマルチコアに
なっていった頃に、コンピュータ雑誌でよく、
「マルチコアの性能を使い切るには、プログラムで専用の書き方を
しなければならない。」と書かれていました。
その当時は、具体的になにを指すのか分からなかったのですが
関数型で副作用を局所化する書き方が
その答えだと最近気付きました。
(科学技術計算をする人の様な特殊なものは、また別の話になります。)
例えば、hadoopのmap/reduceのmapやreduceは
関数型由来のもので関数型言語で標準装備されるものです。
また、並列分散処理フレームワークのsparkは
scalaのアクターを使用したakkaというライブラリを使用しています。
scalaは、関数型言語で、akkaも関数型の流儀に従って作成されています。
さらにsparkのRDDに装備されているAPIも
関数型で良くある流儀のものが使用されています。
http://www.task-notes.com/entry/20160112/1452525344
http://qiita.com/sotetsuk/items/6e4e2953799078fd6027
sparkの元ネタになった、ヨーロッパの電話交換機用の言語Erlangも1985から
ヨーロッパではエリクソンで実際に使用されてきた言語です。
また、R言語も、一応関数型言語です。
(なので、基本的にforなどのループは使用しないでください。遅いです。
インデックスを指定して一括更新したり(古い方法)、ライブラリのdplyrを使用してください。)
また、トランザクショナルメモリの第一人者となった
サイモンペイトンジョーンズ氏は、初めJAVAの人の
トランザクショナルメモリの講演を聞いて
「これなら関数型言語のHASKELならば、簡単に実現できると思い
試したらカンタンにできた。」の様な旨のことを言っていました。
それから、デファクトとなる様なAPI群を作り、発表したので
「トランザクショナルメモリの第一人者、みたいに言われる様に
なったが、自分がすごいのではなく
HASKELLがこの問題に適合していたからだ。」
とインタビューに答えていました。
つまり、要約すると、関数型プログラミングは
並列/並行処理に強いのです。
なぜ、並列/並行処理いのか ?
端的に言うと次のことが挙げられます。
・イミュータブルなどで、副作用を局所化することを
推奨していることで、分散処理される単位で
共有メモリを複数のスレッドから更新することが無くなること
・制御構造が、原始的な、逐次・分岐・反復ではなく
map、reduce、filterなどの高階関数群が使用できることで
コンパイラが解釈して処理を分散して振り分けることが
可能となること
C# やるなら LINQ を使おう
http://yohshiy.blog.fc2.com/blog-entry-274.html
マルチスレッド、トランザクション処理、並列/並行処理は
一般にプログラム難易度が高いと言われています。
下の書籍では、達人にインタビューしたものですが
「プログラミングで難しいこと?」という問いに
上記の様な分散処理を上げる人がだいたい7割くらいいました。
つまり、これまでの方法論では、本質的に難しいのです。
Coders at Work プログラミングの技をめぐる探求
https://www.amazon.co.jp/Coders-Work-%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%AE%E6%8A%80%E3%82%92%E3%82%81%E3%81%90%E3%82%8B%E6%8E%A2%E6%B1%82-Peter-Seibel/dp/4274068471
察するに、並列/並行処理で、共有メモリを持つ方式では
複数のスレッドで共有メモリを更新するので再現性が
不確実なバグが発生します。
コードの規模が大きくなると、デバッグは中々骨の折れる
作業になるし、テストで完璧に叩くというのも
現実的ではありません。
(組合せの数は簡単に発散します。)
そこで、共有メモリを持たない様に、イミュータブルなどで
副作用を局所化して、複数に分割して並行に走らせる処理では
外部のメモリを共有しない様にする実装が正解となるのです。
初心者のみなさん、無駄に難しいことをして
荒修行をしなくても良いのですよ。
先人の知恵は、パクりましょう。
蛇足
このことから、mapやreduceなどに渡す処理関数は
副作用を持たない様にするべきです。
C#のLINQを使用した写像(map)フィルター(filter)並び替え
(sort)畳み込み(fold)でも同じで、各関数で行う処理は
副作用を持たない様にするべきです。
mapの中でDBを更新したり、ファイルを書換えたりするのは
通常の書き方ではありません。
map等の関数を使用して、データを完成形にしてから、その結果を元に
それぞれで、DBを更新したり、ファイルを書換えたりするのです。
因みに、JAVAでもJAVA8から、この様な高階関数が使える様に
なりました。
これらを使うと、マルチコアを使うプログラミングが、特殊な指定なしに
楽に書けます。
(b)(c)については、マニアックに成り、時間もかかるので省略。
(d)も主観だといわれれば、細かい論証に時間がかかるので省略。
まとめ
並列/並行処理関係のフレームワークなどは
関数型プログラミングの流儀で書くことが多いと思います。
なので、並列/並行処理をするときは、関数型プログラミングの基礎も
学んでみると良いと思います。
以上、発表終わり