はじめに
なんか、高階関数で、一部界隈が盛り上がっているぽいので、なんとなくエントリを書いてみました。一言で言うと、「関数を引数に取る関数」あるいは「関数を返り値とする関数」が、高階関数と呼ばれます。それだけです。
あとは、各言語で、引数や返り値になれるような「値としての関数=第一級関数」をどのように実現するかが問題になります。ここで、「関数を引数に取る」というのは、「値としての関数」を引数に取る、という意味であることに注意してください。
というわけで、以降、いくつかの言語で、第一級関数をどのようにして表現しているかについて述べてみたいと思います。他にも方式があると思いますが、ツッコミ歓迎。
いわゆる「関数型プログラミング言語」の場合
多くの「関数型プログラミング言語」では、「関数」それ自身がプリミティブでかつ値なので言うまでもないのですが、いくつか例を挙げます。
たとえば、Schemeでは、
(map (lambda(x) (* x 2)) '(1 2 3)) ;; (2 4 6)
のような感じで、lambda
を使うのが基本になります( define
を使った定義も、 lambda
に落とし込めたはずだけど、大昔なので記憶が曖昧)。
あるいは、Standard MLなら次のような感じ。
map (fn x => x * 2) [1, 2, 3, 4, 5];
また、
fun f x = x * 2;
map f [1, 2, 3, 4, 5]
としてもいいです(名前を付ける場合)。fn
使っただけの定義だと再帰関数が(たぶん)書けないのと、多相型の場合に違いがでるような気がしますが、それはおいときます(識者のツッコミ待ち)
オブジェクト指向言語での実現
多くのオブジェクト指向言語では、メソッドが一つだけのオブジェクトを関数とみなすか、関数は特定のクラスに属するオブジェクト、と言う風にみなしています。
たとえば、Java 8以降ではStream APIを使って、
var strings = List.of("A", "B", "C");
var newStrings = strings.stream().map(s -> "A" + s).collect(Collectors.toList());
System.out.println(newStrings);
と書くことができますが(上記コードはJava 11以降でないと動かないのに注意)、ここで、map
は Function
インタフェースを引数に取るメソッドとして定義されているわけです。Function
のような、「抽象メソッドが一つだけ」のインタフェースは、Functional Interfaceと呼ばれます。上で書いた、メソッド一つだけのオブジェクトを関数とみなす考え方といえるかと思います。
なお、別にラムダ式を使わなくても
var strings = List.of("A", "B", "C");
var newStrings = strings.stream().map(new Function<String, String> () {
public String apply(String s) { return "A" + s ;}
}).collect(Collectors.toList());
と書いても動きます(内部の生成コードは、ラムダ式使った方がinvokedynamic使ってくれて効率的ですが、おいときます)。
あるいは、Scalaではもっと極端です。たとえば、
val xs = List(1, 2, 3).map(x => x + x)
は
val xs = List(1, 2, 3).map(new Function1[Int, Int] {
def apply(x: Int): Int = x + x
})
書いても良いです。つまり、実質的に無名関数は Function1
の無名サブクラスのシンタックスシュガーです(ただし、 return
の扱いなど、細かいところで異なる部分があります)。Scalaでは、より一般的には、FunctionN
(N
は引数の数)の無名クラスのインスタンスの簡略記法として、無名関数を位置付けています。これは、特定のクラスのオブジェクトを関数とみなす方法だと言えます。ちなみに、この手法はKotlinにも継承されています。
また、件の記事で挙げられているJavaScriptでも関数はオブジェクトです。たとえば、
function foo() { return "FOO" }
foo.toString()
のように、関数に対してメソッドを呼び出すことができています。
Rubyのブロックは、厳密にいえば、その時点で値になってるとは限らないですが、仮引数に &
を付けると、 Proc
クラスのオブジェクトになります。
def capture(&block)
block
end
x = capture { puts "Foo" }
x.call # Foo
Pythonについては詳しくないのですが、元々がオブジェクト指向言語でなかったせいか、「関数それ自体」は値ではあっても、(メソッドを持つという意味での)オブジェクトではないようです。
def foo(x):
x
print(foo.__name__) # <lambda>
print(foo.__class__) # <type 'function'>
とかがいけるあたり、一応、普通のオブジェクトだといっていい……のでしょうか。
高階関数のメリット
ところで、高階関数が何であるかはおいといて、高階関数のメリットについては、単に
const xs = [1, 2, 3].filter(x => x % 2 != 0)
console.log(xs) // [1, 3]
などの、定義済み高階関数の例を出すか、あるいは、自分で定義するとして、
function filterNot(array, fun) {
return array.filter(x => !fun(x))
}
const xs = filterNot([1, 2, 3], x => x % 2 == 0)
console.log(xs) // [1, 3]
のような例を示せばそれで充分であって、大作記事を作るまでもないと思ったんですが、どうなんでしょうか。