体験を伝える―『ガジェット通信』の考え方

面白いものを探しにいこう 本物を体験し体感しよう 会いたい人に会いに行こう 見たことのないものを見に行こう そしてそれをやわらかくみんなに伝えよう [→ガジェ通についてもっと詳しく] [→ガジェット通信フロアについて]

【関数型言語Haskellの基本】高階関数map,reduceを使って遊んでみた

次に、「ヘビがカエルを食べた結果、生き残るカエルの数」を返す関数eatを定義します。
letは、関数定義にも使うことができます。

let eat (s,f) = if f > s then f-s else 0

ここでifが登場しますが、Haskellにおけるifは「if文」ではなく、値を返す「if関数」です。徹底的に関数の組み合わせで処理を書いて行きます。

関数に関数を渡す「高階関数」登場

次に登場するのが、関数プログラミングの花形「高階関数」。
高階関数とは「関数を引数に取る関数」のことで、破壊的代入や、forループ、whileループのような繰り返しの記述なしにさまざまな処理を実現します。
これが、関数プログラミングにおけるもっとも特徴的なメソッドです。

これから使うmapという高階関数は、第一引数に関数を、第二引数にリストを取り、第二引数のリストに対し第一引数の処理を適用します。
少々ややこしいですから、実際にやってみましょう。

第一引数として渡す関数はもちろん、先ほど定義したeatです。
第二引数にはsnakes,flogsの二つのリストを渡したいのですが、mapに渡せるリストは一つだけですから、zipという関数で一つのリストにまとめます。

let snakeFlog = zip snakes flogs

中身は以下のようになっています。

snakeFlog
[(5,7),(4,6),(3,1),(2,2),(1,2)]

いよいよ高階関数mapを使います。
第一引数として関数を、第二引数としてリストを渡しましょう。

結果は生き残った各グループのカエルの数を表すリストになりますから、返り値はsurvivorsというリストを定義して、そこに格納することにしましょう。

let survivors = map eat snakeFlog

リストの中を覗いてみましょう。

survivors
[2,2,0,0,1]

全部で何匹生き残ったか、ここまで来れば一目瞭然ですが、すべての値を足し合わせるメソッドを実装したいですね。

足し算の関数”+”をリストに対し繰り返し適用するために、高階関数foldlを使ってみましょう。
二つの引数しか取る事のできない関数”+”を繰り返し適用したい場合、for文のないHaskellにおいてはreduce(畳み込み)というメソッドを使います。

reduceとはリスト等の引数を一つの値に集約させることを意味する、抽象的な概念で、関数型言語にはこれを実現するための様々な高階関数が存在します。

二つの引数を取る関数+を使ってsurvivorsの全要素を足し合わせるためのアイデアの一つとして、以下のような方法を思いつくでしょう。

まず初期値0に第一要素を足し合わせ、その結果に第二要素を足し合わせ、その結果に対して第三要素を足し合わせ…という以下のような数式です。

((((0 + 2) + 2) + 0) + 0) + 1

foldl関数はこのような、特定の関数を使った繰り返し操作を実現する高階関数です。

foldlという名前は、「畳む」を意味するfoldと、「左」を意味するlの組み合わせです。「畳み込み(reduce)を左から適用していく」ために付きました。

皆さまお察しの通り、「右」、つまりリストの末尾から関数を適用したい場合は、foldrという関数が使えます。

さて、リストsurvivorsに対して、(+)を繰り返し適用してみましょう。foldlは第一引数に関数、第二引数に初期値、第三引数にリストを取ります。

foldl (+) 0 survivors
5

これで「5」という出力が得られ、5匹のカエルが生き残ることが分かりました。

関数型プログラミングの良さ

ここまでの記述を総合すると、Cよりもはるかに短くシンプルに書けていることが分かると思います。

さらに、forループなどは登場せず「関数を関数に渡す」高階関数が設計の基本ですから、一つ一つの関数がどんな機能を持っているか整理しないと、コーディングが成立しません。

結果的に、簡潔かつ保守性の高く、スケーラブルな実装を実現しやすいと言えるでしょう。
なお、スケーラビリティを冠した関数型言語に「Scala」があります。こちらの記事もございますので、ぜひのぞいてみてくださいね。

前のページ 1 2
CodeIQ MAGAZINEの記事一覧をみる
  • 誤字を発見した方はこちらからご連絡ください。
  • ガジェット通信編集部への情報提供はこちらから
  • 記事内の筆者見解は明示のない限りガジェット通信を代表するものではありません。