ゼロから学ぶGo言語プログラミング(10) A Tour of Go 40~48
A Tour of Goの続き。 mapの途中から。
40.Mutating Maps
m := make(map[string]int)
という風に、mをstringがキーでintが値のmapとして初期化したとして、
m["foo"] = 100 //挿入・更新 v := m["foo"] //取得 delete(m, "foo") //削除
という操作が出来る。
キーの確認は
v, ok := m["foo"] //取得
という風に、取得時の書き方に2番めの値を付けて真偽を受け取る。
辞書的なデータ構造の扱いは、非常に多用しそうなので、自然と書けるようになっておきたいです。
41.Exercise: Maps
mapを使った課題。
ヒント通り、strings.Fieldsのリファレンスを見ると、与えられた文字列に含まれる単語を、空白文字で分割したsliceを返す関数だった。
切り出したsliceを受けて、後はforでmapに単語単位でカウントしていけば、テストは全てパスできた。
package main import ( "code.google.com/p/go-tour/wc" "strings" ) func WordCount(s string) map[string]int { fs := strings.Fields(s) ret := make(map[string]int) for i := 0; i < len(fs); i++ { ret[fs[i]] = ret[fs[i]] + 1 } return ret } func main() { wc.Test(WordCount) }
日本語利用者としては、strings.Fields()の挙動が気になったので、日本語を与えた場合も試してみた。
package main import ( "code.google.com/p/go-tour/wc" "strings" "fmt" ) func WordCount(s string) map[string]int { st := "私はGo言語を学習している者です。" fs := strings.Fields(st) ret := make(map[string]int) for i := 0; i < len(fs); i++ { ret[fs[i]] = ret[fs[i]] + 1 } fmt.Printf("%s", ret) return ret } func main() { wc.Test(WordCount) }
結果は、単語単位でsliceされず、人連なりの文字列が単独の要素になったsliceを得られただけだった。 UCAや漢字コードなどを元に、単語の自動分かちを期待したが、stringsという文字列処理の基本的パッケージらしいものがカバーすべきとは思わないので、当然かも知れない。
ちなみに、strings.Fields()は、全角の空白文字であっても分割対象にしている。 基本がUnicodeベースになっている恩恵で、楽で嬉しい。
42.Function values
関数の値渡しをサポートしている、という話。 これは少し前、関数を引数に取る画像生成の課題があったため、予測していた。
43.Function closures
クロージャは、JavasScriptでしっかり踏み込まずにやり過ごしてきた概念。 せっかくなのでGoできちんと学ぼう。
サンプルコードと、ツアーからもリンクされていたWikipediaの解説を見ながら進めます。
まずサンプルコード。
package main import "fmt" func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } func main() { pos, neg := adder(), adder() for i := 0; i < 10; i++ { fmt.Println( pos(i), neg(-2*i), ) } }
adder()がクロージャを実現している関数。 戻り値としてintを引数に取る関数を使うことが宣言されている。
adder()の中では変数sumが0で初期化されている。 さらにadder()は、returnで無名関数を返している。 adder()から返す無名関数は、adder()が持つ変数sumに引数xを加算し、変数sumを返すもの。
main()の中で変数posとnegは、"adder()"で初期化されている。 つまりadder()の戻り値である無名関数で初期化されていることになる。 この2つの変数それぞれをforループの中でintを渡しながら呼び出すと、次の結果が出力される。
0 0 1 -2 3 -6 6 -12 10 -20 15 -30 21 -42 28 -56 36 -72 45 -90
関数adderがもつsumの値は、0で初期化されるだけで、後は無名関数で加算されるだけ。 なのでposの出力は 0,1,2,3,4... となっていく筈。 しかし実際にはsumの値を保持しており、0,1,3,6,10... となっている。 また、negの値はposの場合と異なる結果を返しており、posとnegそれぞれが独立したsumを保持していることになる。
これがGoのクロージャのようである。
漠然とした印象では、オブジェクト指向におけるインスタンスの挙動のように見える。 これが関数のスコープで実現できるメリットは、まだいまいち気付けていない。 クロージャとオブジェクト指向の関係を探すと、MDNの以下の記述があった。
感じた類似性そのものは誤りでないらしい。 そしてここにあるように
メソッドを 1 つだけ持つオブジェクトを使いたくなるような状況ならば、どんな時でもクロージャを使う事ができます。 という点も、returnを使ってクロージャが実装されている点から、理解できた気がする。
44.Exercise: Fibonacci closure
早速クロージャを使った課題。 フィボナッチ数をクロージャで返そう、というもの。
初期のコードはこれ。
package main import "fmt" // fibonacci is a function that returns // a function that returns an int. func fibonacci() func() int { } func main() { f := fibonacci() for i := 0; i < 10; i++ { fmt.Println(f()) } }
fibonacci()は関数を返すようになっている。 ここで返す関数が、クロージャということだろう。 Wikipediaを見ると、こういったクロージャの外側のスコープをエンクロージャと呼ぶらしい。
エンクロージャがクロージャを実現するためのスコープをまず用意し、クロージャを無名関数として実装してエンクロージャから返すことで、あたかもオブジェクト指向のインスタンスのように、返された無名関数ごとに状況を引き継ぐ、独立した環境が手に入る、という理解を現時点ではしておく。
最終的に課題はこう書いた。
package main import "fmt" // fibonacci is a function that returns // a function that returns an int. func fibonacci() func() int { a := 0 b := 1 return func() int { fib := a + b a = b b = fib return fib } } func main() { f := fibonacci() for i := 0; i < 10; i++ { fmt.Println(f()) } }
期待通りの結果にはなるが、これがクロージャの用途として適しているのか…。 扱っている関数がクロージャかどうか、注意が必要そうだという事は理解できたし、シンプルにインスタンスっぽいものを持ちたい場合、確かに楽な状況もありそう。
クロージャが活躍する状況は大いにあるのだろうから、もっと積極的に使って慣れておきたい。
45.Switch
ifやforからしばらく経ったここにきて、制御構造switch。
pythonにはswitchが無く、欲しいなあと思っていたけど、breakを忘れてバグに繋がりやすいというのが理由らしい。 goではどうなんだろう。
switch部分だけ抜き出すと、こうなっている。
switch os := runtime.GOOS; os { case "darwin": fmt.Println("OS X.") case "linux": fmt.Println("Linux.") default: // freebsd, openbsd, // plan9, windows... fmt.Printf("%s.", os) }
重要なのは以下の解説。
Goでのswitchは、 case の最後で自動的にbreakします。 もし、brakeせずに通したい場合は、 fallthrough 文をcaseの最後に記述します。
つまりバグの温床になりやすいbreakを自動化し、むしろ意図的な特殊な制御の方をfallthroughという文で実現したらしい。 非常に明確で素晴らしい仕様だと思う。
46.Switch evaluation order
caseは上から順に評価、一致で停止、全てのcaseで一致しなければdefault。 うん明確。
Goのswitchは、評価に式が書けるらしい。 あっちにfallthroughは無いけれど、FileMakerのCase()に似ている。
47.Switch with no condition
switchの条件文を省略することで、caseが比較すべき対象が無くなり、caseの式の真偽で評価される。 確かに条件の多いifを連ねるなら、こちらの方が見通しが良さそう。
48.Advanced Exercise: Complex cube roots
complexに関するAdvancedな課題。
complex型は、複素数だったらしい。 しかし複素数や虚数の素養が、せいぜい概念程度の理解であり、この課題そのものを大きく誤解してしまいそう。 ということで、ひとまずここはスキップする。
次はメソッドから。