高階関数とは「関数を引数に取る関数」のことです。関数や手続きを引数として渡すことで、実装の詳細を見ずに、「やりたいこと」に集中できます。例えば、Lispの方言であるSchemeにはmapという高階関数があり、リストの各要素に対して関数を実行できます(リスト1)。この例ではmapに関数を渡すことで、リストの各要素へのアクセス方法など実装の詳細については隠したまま、要素1つずつに対して行うべき処理だけに集中できます。実装の詳細を隠蔽するのは、「抽象化」といってプログラミングの基本的なテクニックです。
(define (print-elem n) (* n 2))
↑ 引数に受け取った値を2倍する関数print-elemを定義
(map print-elem '(1 2 3))
↑ 高階関数mapを使って、3つの数値(1、2、3)からなるリストそれぞれに、print-elem関数を適用
# => (2 4 6)
↑ リストの値がそれぞれ2倍された
高階関数を実現するためには、プログラム言語は関数あるいは手続きをデータとして取り扱うことができる必要があります。
Rubyのブロックは、文法上の見かけこそSchemeと少々異なっていますが、やっているのは本質的に高階関数と同じことです。リスト1のプログラムをRubyのブロックを用いて表現したものがリスト2です。Rubyのブロックは名前のないのがデフォルトなので、Schemeと比べてプログラムがややシンプルになっています。
[1,2,3].map{|n| n * 2}
# => [2, 4, 6]
もう1つ別の例としてソートを考えてみましょう。配列要素のソートを行う場合、比較する条件を変更したいことはよくあります。そのようなときにも高階関数を用いて解決できます。リスト3はRubyで配列をソートするsort_byメソッドの利用例です。リスト3のソートは、「奇数を降順に、次に偶数を昇順に並べる」という一風変わった基準でソートしていますが、複雑な処理の割に非常に簡潔に記述できています。
ソートについては、Cでも関数を引数に取るqsort関数が提供されています(リスト4)。これも高階関数ですね。
p [4,2,3,6,1].sort_by{|x| if x % 2 == 0 then x else -x end}
↑奇数を降順に、次に偶数を昇順に並べるソート
# => [3, 1, 2, 4, 6]
高階関数があるとクロージャも欲しくなります。クロージャとは、スコープの外側の変数への参照を「閉じ込めた」関数オブジェクトです。リスト5にクロージャと高階関数を組み合わせたサンプルを示します。
(define (xtimes x) (lambda (n) (* n x)))
↑ 「引数に受け取った値をx倍する関数」を返す関数xtimesを定義
(map (xtimes 5) '(1 2 3))
↑(xtimes 5)で、「引数に受け取った値を5倍する関数」を生成。
高階関数mapを使って、それを3つの数値(1、2、3)からなるリストそれぞれに適用
# => (5 10 15)
↑ リストの値がそれぞれ5倍された
「xtimes」関数は、整数を引数として受けて、それを整数倍する関数を返す関数です。ちょっと分かりにくいかもしれませんが、
(xtimes 5)
の値をfとすると、
(f 4)
は「20」を返します。関数オブジェクトは「lambda*」式によって作成し、「(n)」の部分がlambdaの返す関数の引数で、「(* n x)」の部分が関数定義になります。ここでnは引数ですが、xはlambdaの外側の変数(xtimes関数の引数)です。lambdaが作り出す関数オブジェクトは、変数xを自分の中に取り込んでいるわけです。
クロージャがあると、高階関数の使い勝手が非常に向上します。例えばクロージャがなければ、せっかく高階関数を使ってループを抽象化しても、ループの外側の変数を参照できないことになってしまいます。それでは高階関数による抽象化のメリットがだいなしです。
最近の動的言語の多くは何らかのクロージャ機能を提供していますし、Javaでさえ、2008年リリース予定のJDK7でクロージャを提供するという話が聞こえてきています。
「らむだ」と読む。Lispの数学的背景となる「ラムダ算法」からきている。関数を定義する構文。
Copyright © ITmedia, Inc. All Rights Reserved.