無名関数ワカランの状態だったので理解を整理してみました。
用語の理解しよう
無名関数 | func 予約語の後に名前がない関数リテラルが表す関数値 |
---|---|
関数リテラル | 関数値を表す式 |
さらに理解を深めるため無名関数を利用した簡単なコードを書いてみました。
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
f := func() string { return "Hello, 世界" }
fmt.Println(f())
}
https://go.dev/play/p/y-kmWRvL719
無名関数を代入した変数 f
を用いて関数 f()
の戻り値である文字列を fmt.Println()
の引数に渡しています。これだけでは無名関数のメリットは感じられず、ややこしいコードを書いてる感覚になります。なので以下から書籍『プログラミング言語Go』でも使用されている平方数を出力する処理を題材に理解を進めました。
平方数を求めるコードを書く
無名関数の概念を知らない状態であれば、真っ先に思いつく実装コードは以下になります。
package main
import "fmt"
var x int
func squares() int {
x++
return x * x
}
func main() {
fmt.Println(squares())
fmt.Println(squares())
fmt.Println(squares())
fmt.Println(squares())
}
https://go.dev/play/p/E6__IF6-5K1
出力結果は以下になります。結果は期待値通りです。
1
4
9
16
ここでは関数外で変数宣言した x
のスコープを広めて値を保持しています。しかし以下のコードは少しあからさまですが、意図しない変更が加わることがあります。
package main
import "fmt"
var x int
func squares() int {
x++
return x * x
}
func main() {
fmt.Println(squares())
fmt.Println(squares())
detour() //寄り道
fmt.Println(squares())
fmt.Println(squares())
}
func detour() {
bug() //バグ投入
}
func bug() {
x = 10
}
https://go.dev/play/p/oDH8NANjkXo
1
4
121
144
そこで利用できるのが無名変数でした。※以下は『プログラミング言語Go』P.154記載のサンプルコードになります。
package main
import "fmt"
func squares() func() int {
var x int
return func() int {
x++
return x * x
}
}
func main() {
f := squares()
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
}
https://go.dev/play/p/fJ1JKTemXHc
無名関数を利用することで関数外で変数宣言することなくローカル変数 x
の値を保持することができます。当たり前のようにmain関数で x
に値を代入することはできません。
func main() {
x = 10 //コンパイルエラー(undefined: x)
無名関数を利用するメリット
自分は以下と理解しています。
- 無名関数を使用することでローカル変数の状態を維持できる
- 値を保持するための一時変数のスコープを広げなくてよい
先日の疑問
以下はfmt
パッケージの一部です。これはフィールド New
を使用して New()
で無名関数を呼んで構造体 pp
を nil
で初期化していると解釈して、先日より理解が深まりました。
var ppFree = sync.Pool{
New: func() any { return new(pp) },
}
if x == nil && p.New != nil {
x = p.New()
}
最後に
この辺はオブジェクト指向言語っぽいと思いました。完全に無名関数を理解するため、標準パッケージを読みながら少しずつ理解を深めていこうと思います。
参考
プログラミング言語Go