最近、Go言語を勉強しています。
以前はHyperledger Fabricを触っていて、チェーンコードを書くためだけに使っていましたが、並列・分散処理に向いているGo言語を真面目にやってみようと思い立ち、競プロ問題やらなんやらをGo言語で書いて、勉強しています。
そんな時に、友人に「Go言語で高階関数を書いてみて」と言われたので、関数を自作していじいじしてみました。以下はそのまとめです。
#1. 高階関数とは
高階関数(higher order function)とは、第一級関数をサポートしているプログラミング言語において、関数を引数にしたり、あるいは関数を戻り値とするような関数のことである。
(Wikiより)
Go言語もLispやHaskellといった関数型言語のように関数が第一級のオブジェクトの言語なので、高階関数を扱うことができます。
##1-1. 関数を引数に取る
package main
import "fmt"
func square(x int) int {
return x * x
}
func cube(x int) int {
return x * x * x
}
func sumOf(f func(int) int, n, m int) int {
a := 0
for ; n <= m; n++ {
a += f(n)
}
return a
}
func main() {
fmt.Println(sumOf(square, 1, 10))
fmt.Println(sumOf(cube, 1, 10))
}
2乗、もしくは3乗した値の和を求めるsumOfという関数を定義しています。
sumOfの引数fに関数を渡します。関数の型宣言は以下の通りです。
func ([引数のリスト]) [戻り値型]
このプログラムの実行結果は以下の通りです。
$ go run kokaiSample.go
385
3025
##1-2. 無名関数
無名関数は以下のように定義できます。
func ([引数のリスト]) [戻り値型] { [無名関数の本体] }
1-1のsquareやcubeといった関数は高階関数でしか使っていない関数で、その都度定義するのは面倒です。そこで、無名関数を使って書き直してみます。
package main
import "fmt"
func sumOf(f func(int) int, n, m int) int {
a := 0
for ; n <= m; n++ {
a += f(n)
}
return a
}
func main() {
square := func(x int) int {
return x * x
}
cube := func(x int) int {
return x * x * x
}
fmt.Println(sumOf(square, 1, 10))
fmt.Println(sumOf(cube, 1, 10))
}
squareやcubeを無名関数として定義して、sumOfに渡しています。
$ go run mumeiSample.go
385
3025
#1-3. 戻り値が関数の場合
今度は戻り値を関数としてみます。
package main
import (
"fmt"
)
func tenTimes() func(int) int {
return func(x int) int { return 10 * x }
}
func main() {
f := tenTimes()
result := f(10)
fmt.Println(result)
}
tenTimesという与えられた値を10倍して返すという関数の戻り値に関数が定義されています。
main内でこの関数をfが受け取り、その引数に10を代入して、最終的にfに渡された値を10倍して結果を出力しています。
$ go run modoriSaple.go
100
##1-4. 課題
最後は以下の条件を満たすプログラムを書いてみます。
(友人のエンジニアに出された課題)
・引数に関数と配列と関数
・処理は各要素に対して関数を適用
・戻り値は上記の"処理は各要素に対して関数を適用"それ自体
・mainのスコープ側でその戻り値の関数を実行
配列は[1,2, 3, 4, 5]を用意して、各要素を任意のべき乗にして、返却するプログラムを書きました。
package main
import "fmt"
func multiplier(target int, n int) int {
res := 1
for i := 1; i <= n; i++ {
res = res * target
}
return res
}
func customMap(target []int, f func(int, int) int) func(int) []int {
b := make([]int, len(target))
return func(n int) []int {
for i := 0; i < len(target); i++ {
b[i] = f(target[i], n)
}
return b
}
}
func main() {
target := []int{1, 2, 3, 4, 5}
var n, m int
fmt.Scan(&m)
for i := 0; i < m; i++ {
fmt.Scan(&n)
f := customMap(target, multiplier)
result := f(n)
fmt.Printf("%d\n", result)
}
}
customMapの戻り値に配列の各要素に対して処理を行う関数(今は各要素に対して、任意のべき乗を実行する関数)が入っています。それをmainで受け取って、n乗して、結果を返しています。
$ go run sample.go
2
2
[1 4 9 16 25]
3
[1 8 27 64 125]
※標準入力の1行目で何回target配列のべき乗を計算するかをしています。
...あれ?multiplierは無名関数でよくね?
という指摘をいただき、修正しました。
package main
import "fmt"
func customMap(target []int, f func(int, int) int) func(int) []int {
b := make([]int, len(target))
return func(n int) []int {
for i := 0; i < len(target); i++ {
b[i] = f(target[i], n)
}
return b
}
}
func main() {
target := []int{1, 2, 3, 4, 5}
var n, m int
multiplier := func(target int, l int) int {
res := 1
for i := 1; i <= l; i++ {
res = res * target
}
return res
}
fmt.Scan(&m)
for i := 0; i < m; i++ {
fmt.Scan(&n)
f := customMap(target, multiplier)
result := f(n)
fmt.Printf("%d\n", result)
}
}
結果はsample.goと同じです。
multiplierってcustomMapの引数に直接書けるんじゃない?
と言われ、さらに書き直しました。
package main
import "fmt"
func customMap(target []int, f func(int, int) int) func(int) []int {
b := make([]int, len(target))
return func(n int) []int {
for i := 0; i < len(target); i++ {
b[i] = f(target[i], n)
}
return b
}
}
func main() {
target := []int{1, 2, 3, 4, 5}
var n, m int
fmt.Scan(&m)
for i := 0; i < m; i++ {
fmt.Scan(&n)
f := customMap(target, func(target int, l int) int {
res := 1
for i := 1; i <= l; i++ {
res = res * target
}
return res
})
result := f(n)
fmt.Printf("%d\n", result)
}
}
結果はもちろん同じです。
#2. 最後に
今回はGo言語で高階関数を書いてみましたが、
言語の学習のために関数を自作していじいじしてみるのって、やっぱ楽しいですね(笑)