関数型プログラミングでは、非常に頻繁に発生するパターンがあり、それらは独自の名前とサポート機能に値します。最も広く使用されているものの2つはとmap
(fold
時々reduce
)です。これらの2つは、リスト操作の基本的な構成要素を形成し、多くの場合、専用の再帰関数を作成する必要がなくなります。
地図
関数はmap
リストを順番に繰り返し、各要素が元のリストの対応する要素に関数を適用した結果である新しいリストを生成します。典型的なmap
実装方法は次のとおりです。
map(Fun, [H|T]) -> % recursive case
[Fun(H)|map(Fun, T)];
map(_Fun, []) -> % base case
[].
これは、再帰関数の完全な入門例です。大まかに言えば、関数句は再帰的なケース(問題のインスタンスが小さいiselfへの呼び出しになります)または基本的なケース(再帰的な呼び出しは行われません)のいずれかです。
では、どのように使用しますmap
か?最初の引数、Fun
は関数であることに注意してください。Erlangでは、無名関数(ラムダと呼ばれることもあります)をインラインで宣言することができます。たとえば、リスト内の各数値を2乗して、2乗のリストを生成するには、次のようにします。
map(fun(X) -> X*X end, [1,2,3]). % => [1,4,9]
これは高次プログラミングの例です。
map
はErlang標準ライブラリの一部であることに注意してくださいlists:map/2
。
折り畳み
map
あるリストと別のリストの間に1:1の要素マッピングを作成するのに対し、目的はfold
、合計などの単一の結果を累積しながら、リストの各要素に何らかの関数を適用することです。右の折り目(「右に行く」と考えると役立ちます)は次のようになります。
foldr(Fun, Acc, [H|T]) -> % recursive case
foldr(Fun, Fun(H, Acc), T);
foldr(_Fun, Acc, []) -> % base case
Acc.
この関数を使用して、リストの要素を合計できます。
foldr(fun(X, Sum) -> Sum + X, 0, [1,2,3,4,5]). %% => 15
モジュール内のfoldr
とfoldl
は両方ともErlang標準ライブラリの一部であることに注意してください。lists
map
すぐには明らかではないかもしれませんが、非常に大きなクラスの一般的なリスト操作の問題は、fold
単独で使用して解決できます。
再帰的に考える
再帰的アルゴリズムを書くことは最初は気が遠くなるように思えるかもしれませんが、それに慣れるにつれて、それは非常に自然であることがわかります。問題が発生した場合は、次の2つのことを特定する必要があります。
- 問題をより小さなインスタンスに分解するにはどうすればよいですか?再帰が役立つためには、再帰呼び出しはその引数としてより小さな問題をとらなければなりません。さもないと、関数は決して終了しません。
- 基本的なケース、つまり終了基準は何ですか?
1)については、リストの要素を数える問題を考えてみましょう。これをどのようにして小さなサブ問題に分解できるでしょうか。さて、次のように考えてください。最初の要素(ヘッド)がXで、余り(テール)がYである空でないリストが与えられた場合、その長さは1+Yの長さです。Yはリストよりも小さいため[X | Y]、問題を減らすことに成功しました。
リストの例を続けると、いつ停止しますか?まあ、最終的には、尻尾は空になります。空のリストの長さがゼロであるという定義である基本ケースにフォールバックします。さまざまな場合の関数句の記述は、辞書の定義の記述と非常によく似ていることがわかります。
%% Definition:
%% The length of a list whose head is H and whose tail is T is
%% 1 + the length of T.
length([H|T]) ->
1 + length(T);
%% Definition: The length of the empty list ([]) is zero.
length([]) ->
0.