パーサーコンビネータについて調べていて、 そもそもその元になる概念としてコンビネータがあることを知る(いまさら?!)。 つまり、パーサーコンビネータはいろいろあるコンビネータの中の一つ。 そして、いろいろあるコンビネータの中でもっとも有名なのが Y コンビネータとか Z コンビネータらしい。 そして、コンビネータを構成するパーツの一つがラムダ(式)とか無名関数であると。
名前付き関数はいつも使っていて、そして名前無し関数(無名関数)も結構使う。 では、ラムダ(式) と無名関数ってイコールじゃないの? と思って調べるもよくわからない。
Wikipedia によると: 「プログラミング言語における無名関数(英語: anonymous functionあるいはnameless function)とは,名前付けされずに定義された関数のことである.無名関数を表現するための方法には様々なものがあるが,近年主流となっているのはラムダ式による記法である.」 ということで、ラムダ式(という概念)をプログラム言語で表現するときに使うのが無名関数(または匿名関数)と考えて問題なさそう。
パーサーコンビネータや Yコンビネータを理解するための最初の一歩としてラムダ式を把握する。
よくありがちな、 succ という数値を与えると +1 した数値を返すという関数を考えてみる。
main.js:
// 1)
const succ = (x)=> x+1;
console.log( succ );
console.log( succ(3) );
// 2)
console.log( ((x)=> x+1) );
console.log( ((x)=> x+1)(3) );
1)が名前付きで、2)が名前無しの例。
実行すると:
$ node main.js
[Function: succ]
4
[Function (anonymous)]
4
Javascritp は型を記述しないので、すっきりは書けるが、複雑になったときに 何がどうなっているかわからなくなりそう。
main.kts:
// 1)
val succ = fun(x: Int): Int { return x+1 }
println( succ )
println( succ(3) )
// 2)
println( fun(x: Int): Int { return x+1 } )
println( fun(x: Int): Int { return x+1 }(3) )
1)が名前付きで、2)が名前無しの例。
実行すると:
$ kotlinc -script main.kts
(kotlin.Int) -> kotlin.Int
4
(kotlin.Int) -> kotlin.Int
4
kotlin は型を明示的に書く必要がある。 複雑になったときに、何がどうなっているかわかりやすい。 ただし、もっと複雑になると、型の記述が溢れてかえって書くのも読むのも辛くなるのかもしれない。
main.go:
package main
import "fmt"
func main() {
// 1)
succ := func(x int64) int64 {
return x + 1
}
fmt.Println( fmt.Sprintf("%#v", succ) )
fmt.Println(succ(3))
// 2)
fmt.Println( fmt.Sprintf("%#v", func(x int64) int64 { return x + 1 }) )
fmt.Println(func(x int64) int64 { return x + 1 }(3))
}
1)が名前付きで、2)が名前無しの例。
実行すると:
$ go run main.go
(func(int64) int64)(0x108b8a0)
4
(func(int64) int64)(0x108b8c0)
4
kotlin とだいたい同じ。
無名関数完全に理解した。 まだ道のりは長い。