##特殊な関数
前回までは、関数の戻り値を利用していましたが、まずは関数自体を値として変数に代入し、他の関数の戻り値にしてみたいと思います。4つの関数を作り、引数の数とそれぞれのデータ型(int, int)
戻り値の数とそれぞれのデータ型(int, string)
を全て等しくさせデータ型の要素を持つ配列を作成します。
package main
import "fmt"
func addition(a int, b int) (result int, state string) {
result = a + b
state = "a + b"
return
}
func subtraction(a int, b int) (result int, state string) {
result = a - b
state = "a - b"
return
}
func multiply(a int, b int) (result int, state string) {
result = a * b
state = "a * b"
return
}
func division(a int, b int) (result int, state string) {
result = a / b
state = "a / b"
return
}
func main() {
statistics := []func(int, int) (int, string){
addition, subtraction, multiply, division,
}
for i := 0; i < len(statistics); i++ {
result, state := statistics[i](24, 4)
fmt.Printf("%s = %d\n", state, result)
}
}
###ビルドして実行
a + b = 28
a - b = 20
a * b = 96
a / b = 6
次に関数を他の関数の引数に取ってみます。fnにaとbを代入して、その結果をresultの値と比較して一致した場合はtrue、一致しなければfalseを返します。この関数ではある関数が想定した結果を出すかどうかが調べられます。fn→func(int,int)int
a→int
b→int
result→int
println関数でapplicationの戻り値を書き出し、trueとfalseとだけ出力させます。
package main
import "fmt"
func application(fn func(int, int) int, a int, b int, result int) bool {
if fn(a, b) == result {
return true
}
return false
}
func main() {
fmt.Println(application(func(a int, b int) int {
return a + b
}, 6, 14, 20,
))
fmt.Println(application(func(a int, b int) int {
return a * b * b
}, 2, 4, 30,
))
}
###ビルドして実行
true
false
#GO言語のクロージャ
関数は「値」なので戻り値にできます。関数の中に変数の値を閉じ込めるのがクロージャです。下記コードでは、closurefun関数が3回実行されているのがわかると思います。closurefun関数を紐解いていくとmain関数の一番最初に、closurefun変数に上で定義されたclosure関数の戻り値が代入されています。closure関数の戻り値は関数になります。returnの後に記述されいるのは、引数、戻り値がないfunc関数です。しかし、このfunc関数は自分の外側にある変数creの値を扱っています。main関数内のclosure関数は一度だけ呼ばれています。書かれた順序通りメモリのどこかに変数creで、見るべき値の保存場所が作成されそのcreの値を増やす処理を含む関数が戻されて、変数closurefunに代入されます。するとclosurefunは関数名としてしようすることができるのです。代入された関数は引数を取らない型なのでカラの( )をおきます。そしてclosurefunと呼ぶことでメモリ上の同じ箇所に置かれた値が処理され数値が変わっていきます。
package main
import "fmt"
func closure() func() {
cre := 0
return func() {
cre++
fmt.Println(cre)
}
}
func main() {
closurefun := closure()
closurefun()
closurefun()
closurefun()
}
###ビルドして実行
1
2
3
#引数を取るクロージャ
そして上記のclosure関数は引数も戻り値もなくmain関数との値のやり取りがないものに対して、引数を取ったり戻り値を与えるクロージャもあります。形は同じですが、関数や変数名を変えてコードを書いてみます。take関数内で変数takが最初に宣言された直後、数値を初期化しました
と1行追加して挙動を見てみましょう。take関数が呼ばれた最初だけtakの初期化が宣言され、その後は関数を呼んでも宣伝されていないことがわかります。freetake関数自身が取る引数startは数値の初期値になります。そして戻り値の関数の型func(int) int
にも整数の引数を1つ取り、整数の戻り値を1つ返す型にしています。戻り値の関数の引数addは数値に足し込む整数の値になります。ですので、この取得数値は初期値も、カウントするたびに増やす値も、main関数で引数を与えることによって、自由に調節することができます。戻り値の関数が戻す戻り値takは、戻さなくても成立するのですが、戻せるということを示すためにあえて戻してあります。そのため、Printfで埋め込む文字列に\n
を記述していません。ではクロージャにあたるfreetak関数から戻り値を取って、関数Printlnで出力させてみます。
package main
import "fmt"
func take() func() {
tak := 0
fmt.Println("数値を初期化しました")
return func() {
tak++
fmt.Println(tak)
}
}
func freetake(start int) func(int) int {
tak := start
fmt.Printf("取得数値を%dからはじめます\n", tak)
return func(add int) int {
fmt.Printf("%dを足して", add)
tak += add
return tak
}
}
func main() {
takfun := take()
takfun()
takfun()
takfun()
freetak := freetake(20)
fmt.Println(freetak(3))
fmt.Println(freetak(4))
fmt.Println(freetak(5))
}
##ビルドして実行
前回の実行結果に続き、関数freetakからの出力とmain関数からの出力が合わさった形になって表示されました。
数値を初期化しました
1
2
3
取得数値を20からはじめます
3を足して23
4を足して27
5を足して32