ループとは大雑把に言うと、似たような作業をある終了条件が満たされるまで行ないたい時に使います。 C 言語などには for 文や while 文がありましたが、 これらは基本的にはある変数を破壊的に変更していくため、 純粋な関数的なプログラミングでは避けたいものです。 ここでは、関数的プログラミングにおいて、繰り返しをどう行なっていけば 良いのかについて紹介していきたいと思います。
全てのループを関数的にするが良い事ではありません。 OCaml では Haskell などの純粋な関数型言語と違い、 for 文や while 文が用意されており、 そちらを使った方が簡単に書ける場合は使ったほうが良いでしょう。 関数型言語的、命令型言語的プログラミングが好きに選べるのが OCaml のウリでしょう。
for 文や while 文を使わずに、どのようにして繰り返し処理をしろと言うのでしょう。 答えは、関数を再帰的、つまり関数の中から自分自身を呼びだせば良いのです。 以下の例を見てみましょう。正の整数を引数に与えられた時、1 からその数までの和を計算する関数です。
# let rec sum n =
if n <= 0 then 0 else n + (sum (n-1));;
val sum : int -> int = <fun>
# sum 10;;
- : int = 55
要は、n と、(n-1) までの和を足したものが、 n まで足したものの和になると言うのを素直にコーディングしただけです。
このままでは末尾再帰ではないので、通常は以下のようにコードする事になります
# let sum' n =
let rec sum_loop n acc =
if n <= 0 then acc else sum_loop (n-1) (n + acc)
in
sum_loop n 0
val sum' : int -> int = <fun>
# sum 10;;
- : int = 55
上の関数は C に翻訳すると以下のような感じでしょうか
int sum_aux (int n, int acc){
if (n <= 0) return (acc);
else return (sum_aux ((n-1), (n + acc)));
}
int sum (int n) { return (sum_aux (n, 0)); }
まあこのような特定の数値列に対して何かをする、というのは for 文のほうがわかりやすくなるでしょう。 以下ではもう少し複雑なデータ構造の上での繰り返し処理で、 関数型プログラミングがどう活きてくるかを見てみたいと思います。
リストの要素に対して繰り返し処理をしたい時にはどうするのでしょう。
パターンマッチを使うと結構簡単に再帰の処理が書けますが、大抵の場合は
繰り返し用の関数を書く必要はありません。List.rev_map
,
や List.fold_left
などを使うと良いでしょう。
以下の関数は、int list の要素の和を計算します。
List.map は末尾再帰でないので、リストの長さが短かいとわかっている場合にだけ使うようにしましょう。
(* 使い型:
List.fold
(fun [前の値] [次の要素] -> [新しい値を計算する])
[初期値]
[対象リスト] *)
# let list_sum l = List.fold_left (fun sum elm -> elm + sum) 0 l
val list_sum : int list -> int = <fun>
# list_sum [0;3;9;2;5];;
- : int = 19
List.fold_left は使い方を覚えると重宝します。 要は、[前の値]と[次の要素]から、[新しい値]を計算する関数を作れば、 リストの全ての要素に対してそれを行なってくれる関数です。 これをうまく使うと色々な事が短かくすっきりと書けるようになります。
配列上の繰り返し処理もリストと同様にいろいろとできます。 違いは幾つかありますが、一つは Array 上の map などの関数は ループとして実装できるので、 末尾再帰かどうか気にする必要が無いと言う事でしょうか。
TODO: もっと詳しく
TODO: もう少し複雑なデータ構造
TODO: 書く