この章では,
命令プログラミングと関数プログラミング
当たり前過ぎて意識されていないかもしれませんが,
- 命令を列挙する
(典型的には命令である文をセミコロンで区切って並べる) - 状態がある
(状態とは再代入可能な変数のこと) - 再代入を使って状態を変化させる
一方,
- 関数を引数に適用する
- 状態はない
- (値を破壊したくなったら)
新たな値を作る
状態がないので,
パラダイムの違いがわかる例題
次のような問題を解く関数calcを定義することを考えます。
- 入力として整数の配列あるいはリストが与えられる
(たとえば[10,20,30,40,50]) - 0から数えてn番目の要素にはnを掛ける
- それらすべてを足し合わせて出力として返す
つまり,
10 * 0 + 20 * 1 + 30 * 2 + 40 * 3 + 50 * 4
簡単ですね。
命令的なfor文による問題解決
命令プログラマであれば,
calc.
function calc(ar) {
var ret = 0;
for (var i=0; i < ar.length; i++) {
ret = ret + ar[i]*i;
}
return ret;
}
このcalcは,
- セミコロンで区切って命令を列挙している
- 状態retとiがある
- retとiはループが回るたびに再代入されている
実際に動かしてみましょう。次のコードはRhinoを使い,
% rhino
js> load("calc.js")
js> calc([10,20,30,40,50]);
400
正しい答え400が出てきました。
命令プログラマであれば,
少し話がそれますが,
関数的なMapReduceによる問題解決
関数プログラミングでは,
リストをまとめる
Haskellでは,Prelude>
は>
と略記します)。
> [10,20,30,40,50]
[10,20,30,40,50]
整数のリストそのものが返ってきました。次に,[はじめ..おわり]
と書くと,
> [0..4]
[0,1,2,3,4]
リストが2つあると扱いづらいので,
Haskellでは,
> zip [0..] [10,20,30,40,50]
[(0,10),(1,20),(2,30),(3,40),(4,50)]
リストが1つ新たに生成されました。この要素は(0,10)のように,
zipの第一引数であるリストには
あとで利用しやすいように,
> let ret1 = zip [0..] [10,20,30,40,50]
念のため,
> ret1
[(0,10),(1,20),(2,30),(3,40),(4,50)]
mapを使う
いよいよmapを使ってみましょう。mapとは,
今やりたいことは,
let mul (i,x) = x * i
この定義には,
そして,
> map mul ret1
[0,20,60,120,200]
整数のリストが新たに生成されました。だんだん答えに近づいてきましたね。これにもret2という名前を付けておきましょう。
> let ret2 = map mul ret1
- 注1)
- 「(i,x)」
「x * i」 と順番が逆なのは, zipにカウンタ[1..]を部分適用 (後述) して簡潔に書くためです。