高階関数
再帰関数の次は、高階関数という関数について解説します。
前回、「関数型言語では関数はファーストクラスオブジェクトである」、と紹介しました。高階関数とは、まさにそのこと、関数をパラメーターとする、あるいは、関数を戻り値として返すような関数のことを表します。F#の優れた点の1つとして、便利な高階関数が多々、標準ライブラリに用意されているという点が上げられます。これらの高階関数を使用することで、C#などの命令型言語のスタイルをF#に取り込まなくても、より簡単に同様の機能を実行することができます。「命令型のスタイルを使用しない」というのは、例えば、関数型言語ではC#などで多様されるループなどのステートメントをあまり使用しません。代わりとなる高階関数が用意されているからです。
ここでいくつかF#の標準関数(高階関数)の使用例を紹介します。
リストを操作する高階関数
F#にはリストというデータ型があります。F#リスト(以下リスト)は同じ型の複数要素からなるコレクションです。各要素は[]内で、;(セミコロン)で区切られます。マイクロソフトでは、「.NET FrameworkのList<T>の拡張版」と宣伝しているようです。F#リストの要素は、配列やGenericのリストと異なり、イミュータブル(作成後にその状態が変更できないオブジェクトのこと)だからです。イミュータブルということは、リストに新たな要素を追加する際には、「毎回そのリストをコピーして新しいリストを作る必要があり、とても非効率的である」と思われるかもしれません。しかし、F#のリストは、要素をすべてコピーするのではなく、新しいリストはオリジナルのリストの要素を参照しています。従って、オリジナルのリストへの変更は新しいリストにも反映されます。
(1)リストの定義
let リスト名 = [1; 2; 3]
(2)リストへの要素の追加
let 新しいリスト名 = 追加する要素の値 :: リスト名
(3)空リストの定義
let 空リスト名 = []
(4)同型のリスト同士の連結
let 新しいリスト名 = 連結するリスト名 @ 連結するリスト名
(5)ある範囲内で増加・減少する整数の指定
let 新しいリスト名 = [最小値 .. 最高値]
let listtest = [1; 2; 3] //(1) let listtest0 = [] //(3) let listtest1 = 0 :: listtest0 //(2) let listtest2 = listtest @ listtest1 //(4) let listtest3 = [1 .. 5] //(5)
▼
val listtest : int list = [1; 2; 3] val listtest0 : 'a list val listtest1 : int list = [0] val listtest2 : int list = [1; 2; 3; 0] val listtest3 : int list = [1; 2; 3; 4; 5]
リストを操作するF#の標準関数(高階関数)には次のようなものがあります。
| リストを操作する高階関数 | 役割 |
| List.iter | リストの各要素ごとに引数として渡された関数を呼ぶ |
| List.map | 関数をパラメーターとして受け取り、その関数をリストの各要素に適用し、新しいリストを返す |
| List.filter | リスト内の要素が命題に対してtrueかfalseか判断し、trueのものだけを返す |
1,2,3という要素からなるリストの各要素を2倍して、一つずつ表示させてみましょう。
C#の命令型のアプローチで実行するとこんな感じでしょうか。
List<int> list0 = new List<int>{ 1, 2, 3};
List<int> list = new List<int>();
for(int i = 0; i < list0.Count; i++)
{
list.Add(list0[i] * 2);
}
list.ForEach(Console.WriteLine);
C#でも下記のように、関数型プログラミングのアプローチを使うと
> new List<int>(new[] { 1, 2, 3 }.Select(x => 2 * x)).ForEach(Console.WriteLine);
など、若干すっきりして見えますね。では、F#ではどんなコードになるのでしょうか。
//F#でリストを操作し、各要素の2倍を表示する > [1..3] |> List.map (fun x -> 2 *x ) |> List.iter (printfn "%d");;
▼
2 4 6 val testList : unit = ()
命令型アプローチのC#のコードより、関数型のアプローチの方がはるかにシンプルにリストの操作を記述できることが分かります。中でも、型推論機能により、型指定が省かれているのがF#の特徴です。List.mapに(fun x -> x * 2)という関数とリスト[1; 2; 3]を引数として渡し、List.iter にその結果(リスト)と(printfn "%d")を引数として渡しています。 “|>”(パイプライン記号)を使うことで、よりシンプルで、今行われていることが分かりやすい書式になっているように思います。パイプラインはインプット用データとして(主に引数として)記号の左辺の式の値を後続の関数に渡します。
