高階関数
高階関数(こうかいかんすう、英: higher-order function)とは、第一級関数をサポートしているプログラミング言語において少なくとも以下のうち1つを満たす関数である。
- 関数(手続き)を引数に取る
- 関数を返す
概要
高階関数は厳密には第一級関数をサポートしているプログラミング言語において定義される。C言語やPascalでは、関数へのポインタを利用して高階関数を模倣することができるが、関数ポインタによって第一級関数をサポートしているとみなされてはいない。高階関数は主に関数型言語やその背景理論であるラムダ計算において多用される。
また、ある関数(手続き)の引数となる関数(手続き)のことを関数引数[1]や手続き引数[2]と呼ぶこともある。
関数を呼び出す関数
以下に示すPythonの関数apply_2_3
は、与えられた関数に引数2と3を渡して呼び出し、その返り値を返す高階関数である。関数定義であるために、f
は評価はされても、式は呼び出されず、apply_2_3(add)
や apply_2_3(mul)
のように関数そのものを引数として渡して呼び出すことで評価される。
add = lambda x, y: x + y
mul = lambda x, y: x * y
apply_2_3 = lambda f: f(2, 3)
apply_2_3(add) # 5
apply_2_3(mul) # 6
カリー化
複数の引数をとる関数を1変数関数に置き換えることをカリー化と呼ぶ。例えば、Pythonでは二つの数を足し合わせる関数 add
は以下のようになる。
# 通常の定義と呼び出し
add = lambda x, y: x + y
add(2, 3) # 5
# カリー化を施した定義と呼び出し
add = lambda x: lambda y: x + y
add(2)(3) # 5
組み込みの関数が最初からカリー化されている言語がある。これは、関数呼び出しが常に1引数を取る関数を返すと定義するのと同じである(つまりn引数関数を、n個の引数の直積を取る関数とするのではなく、1引数の高階関数をn個並べたような型で定義する)。例えばHaskellにおいて、(2 +)
と記述すると、これは先ほどの add(2)
と同じく、2を足す関数になる。関数 +
が既にカリー化されているため、1引数を適用するだけで関数として機能する。2 + 3
も(2 +) 3
もどちらも5になる。
コレクションの高階関数
ここでは処理系に実装されていることが多いものだけをあげているが、高階関数も普通の関数と同様に、プログラマが自由に定義して利用できるということを特記しておく。
map
map関数はリスト構造の各要素に対して順々に与えられた関数を処理していくものである。ほとんどのプログラミング言語で実装されている。例えば、Pythonでリスト[1, 2, 3]の各要素に対して1を足したリストを得たい場合は以下のようにすれば良い。
list(map(lambda x: x + 1, [1, 2, 3])) # [2, 3, 4]
for-each
mapと同じように動くが、結果を返さない。つまり、出力など、副作用を期待して利用する。
filter
リスト中の各要素をおのおの引数として渡し、引数として渡された関数を呼び出し、その返り値が真なら返り値のリストに残し、偽なら排除される。排除したい要素に対して偽値を返す、述語のような役割の関数を与えて利用する。例えば、Pythonでリスト[1, 2, -3, -4, 5]から、正の数のみを抽出するには以下のようにする。
list(filter(lambda x: x > 0, [1, 2, -3, -4, 5])) # [1, 2, 5]
filterの並列アルゴリズムに関しては並列アルゴリズムを参照。
fold
fold 関数は重畳、堆積、畳み込みや折り畳みなどと呼ばれ、英語ではreduce、injectとも表現される。foldは逐次処理を表現するためにあり、初期状態から始めて、funcは (状態, 要素) → 次の状態 という関数で、foldは最終状態を返す。状態遷移列を返すものがscanである。
関数型言語では、参照透過性を重んじていて、変数は書き換えてはならないが、下記のPythonの実装は変数stateを書き換えていて、変数書き換え無しで実装するには関数の再帰を使用する形となるが、読みやすくするための糖衣構文としてfoldがある。
Pythonでの実装例。
def fold_left(iterable, func, initial):
state = initial
for x in iterable:
state = func(state, x)
return state
def fold_right(iterable, func, initial):
state = initial
for x in reversed(iterable):
state = func(x, state)
return state
例えば、Pythonの場合、リスト[1, 2, 3, 4]の各要素の総和を取るには以下のようにできる。functools.reduceはfold_left相当である。初期状態を省略するとリストの先頭の要素が初期状態になる。
from functools import reduce
reduce(lambda x, y: x + y, [1, 2, 3, 4]) # (((1 + 2) + 3) + 4) = 10
左方向の畳み込み(foldl)と右方向の畳み込み(foldr)とがあり、言語によっては最初から同様のものが標準ライブラリに含まれていることがある(詳しくはfoldの英語版記事の該当項目を参照)。
foldは二項演算子
- 高階関数のページへのリンク