2024/01/08

golangの無名関数

私が一番なじんでいる開発言語はC言語だ。
今後は変わるかもしれないが、まだ一番だ。

二番をgolangにしようとしている。まあ、C++歴もまあまあ長いのだがしばらく使っていないし、それ以外の言語も C言語っぽいから使えているという程度のように思っている。
golang も C言語っぽい。例外がなかったり、*や&を使ったりするなどバイナリになったときのイメージが C言語に近いのもよろしい。

私が golang を覚えるときは C言語と同じだったり違いだったりという差分でやろうとしている。全部覚えられるほど記憶がよくはないのだよ。。。
golang は新しい言語なので、新しい(というわけでもないだろうが)仕様がある。


Javaにもあったと思うのでそんなに新しい言語仕様ではないと思うのだが、C言語自体にはなかったはずである。型として関数を指定できるという意味では関数ポインタに近いような感じはするのだが、イコールとまではいかない。

関数の引数に func(xxx) があるとする。それだけなら関数ポインタと同じだと思う。C言語でも 同じシグネイチャの関数であれば引数に指定できるからだ。しかしそれは無名関数の機能?ではなく、単に引数として func が使えるというだけだろう。

では func をどうやって定義するかというと、ここら辺が golang の特徴ということになるだろうか。

  • グローバルの func
  • 関数の戻り値の func
  • 無名関数の func

 関数を戻り値として返すことができるということで2番目と3番目は分けたが、C言語では関数を戻り値にできなかったはずだから別にした。いや、たぶん関数ポインタとして返すことはできるのだが、関数そのものを返すということはできないはずだ。そういう意味では3番目も同じく、C言語で関数名のない関数を定義することはできなかったはずだ。

defer に直接 func で中身を書くことができたりするのは無名関数があるからだろう。局所的に関数を設定できるのは便利といえば便利だ。ローカル変数を引数で渡さなくても済むし。


そう、ローカル変数を渡さなくて良いのだ。どういうしくみなんだろうか?
クロージャと呼ぶらしいので go.dev/spec を検索したがここしか出てこなかった。

Function literals
https://go.dev/ref/spec#Function_literals

function literals だったり anonymous function だったり closures だったり、よくわからんものが出てくる。

  • function literals は anonymous function として表される
  • function literals は closures である

anonymous function は「無名関数」のこと。匿名関数と書いてあることもあるがまあいい。名前がないので特定できないから匿名だ、と思っておけば良いだろう。

function literals は「関数リテラル」だ(そのまま)。関数を値にしたものとでも思っておけば良いだろう。ということは、名前がある関数は関数リテラルにはならないということか。
しかし、関数を引数にすることはできるので、名前がある関数でも関数値にはできるはずだ。一般的に「関数を表す式」のことを関数リテラルと呼び、いろいろ言語実装があるのだがたいていの言語では無名関数を呼び出したかったら式にするしかないので、無名関数は関数リテラルが同じものになったということだろう。

クロージャは、関数周辺で定義された変数を参照できる、と書いてある。defer func() を直接書いて、その中にクローズする処理なんかを書くことができるのもクロージャだからだろうが、全然意識したことがなかった。defer だからスコープが関数の中で閉じてるし、そういうものだと思っていたのだ。

が、これは func() を返す関数でも同じことで、返す関数の中でローカル変数を参照していたとしても、それを含めて扱ってくれる。

https://go.dev/play/p/eoGL5r405rI
package main

import "fmt"

func count() func() uint8 {
	var cnt uint8
	return func() uint8 {
		cnt++
		return cnt
	}
}

func main() {
	a := count()
	b := count()
	fmt.Printf("count A: %d\n", a())
	fmt.Printf("count B: %d\n", b())
	fmt.Printf("count A: %d\n", a())
	fmt.Printf("count A: %d\n", a())
	fmt.Printf("count B: %d\n", b())
}


a と b それぞれで cnt を別々に保持してくれるので、外部でグローバル変数を用意して渡す、みたいなことをしなくてよい。不安になってしまうのだが、これは慣れるしかないだろう。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。

注: コメントを投稿できるのは、このブログのメンバーだけです。