プログラミング言語 Go を読みながらメモ。
第一章 : https://qiita.com/Nabetani/items/077c6b4d3d1ce0a2c3fd
第二章 : https://qiita.com/Nabetani/items/d053304698dfa3601116
第三章 : https://qiita.com/Nabetani/items/2fd9c372fcd8383955a5
第四章 : https://qiita.com/Nabetani/items/59bfd00dc3323883a07f
で。
引数リスト
package main
import "fmt"
func first(x int, _ int) int { return x }
func second(x int, y int) int { return y } // y が未使用だけどエラーにならない
func main() {
fmt.Println(first(1, 2))
fmt.Println(second(1, 2))
}
上記の second
は、未使用のローカル変数でエラーになるかと思っていたんだけど、ならない模様。未使用のローカル変数はエラーだけど、未使用の関数引数は OK らしい。
名前付き引数がないのはちょっと残念というか意外。
非標準パッケージの利用
5.2 で、golang.org/x/net/html
という非標準パッケージを利用しているが、利用する方法が書いていない。まったくよろしくない。
利用するためには
go get golang.org/x/net/html
を実行する必要がある。書いておいてほしい。
複数戻り値
多値呼び出しは、複数のパラメータを持つ関数呼び出しの唯一の引数として書くことが出来ます。
とあるので、多分駄目だろうと思いつつ試してみたのはこんなコード:
package main
import (
"fmt"
)
func main() {
fmt.Printf(fmtAndValue()) //=> okay
fmt.Printf("%d %s", intAndString()) //=> multiple-value intAndString() in single-value context
}
func fmtAndValue() (format string, value int) {
return "%d", 123
}
func intAndString() (int, string) {
return 456, "hoge"
}
やはり、唯一であることが重要。
ruby の splat 展開みたいなのがあると便利そうなんだけど、ないのかな。
あと空リターン。なんかパスカルっぽい。でも
空リターンは控えめに使うのが最善でしょう。
と、やや非推奨らしい。
エラー処理戦略
まあ確かに C++やRubyの例外は難しい部分もあるけど、みんながちゃんと使えば幸せだと思うんだよなぁ。
という私の思いとは関係なく、 Go のエラー処理戦略は第二のリターンバリュー。
握りつぶしやすすぎるのが気になるけど、そこも狙いのひとつなんだと思ったり。
log.Fatal
シリーズが面白い。
package main
import (
"fmt"
"log"
)
func main() {
log.Fatalln("hoge", "fuga", "piyo")
fmt.Println("unreachable")
}
を実行すると
2018/03/17 20:43:16 hoge fuga piyo
exit status 1
と出力される。
関数値
関数値はマップのキーとして使ってはいけません。
「いけません」と言われると試したくなる。
package main
import "fmt"
func foo(int) int { return 1 }
func bar(int) int { return 2 }
func baz(int) int { return 3 }
func main() {
a := make(map[func(int) int]int)
fmt.Println(a) //=> invalid map key type func(int) int
fmt.Println(foo == bar) //=> invalid operation: foo == bar (func can only be compared to nil)
}
エラーなので、いけませんというよりも「エラーになります」とかのほうが誤解がなくて良いと思った。
無名関数
package main
import (
"fmt"
)
func squares() func() int {
x := 0
return func() int {
x++
return x * x
}
}
func main() {
f := squares()
g := squares()
fmt.Println("f#1", f()) //=> f#1 1
fmt.Println("f#2", f()) //=> f#2 4
fmt.Println("f#3", f()) //=> f#3 9
fmt.Println("g#1", g()) //=> g#1 1
fmt.Println("g#2", g()) //=> g#2 4
fmt.Println("g#3", g()) //=> g#4 9
}
f
に閉じ込められている x
と、g
に閉じ込められている x
では値が異なることを確認。当たり前だけど。
ループ変数の補足
変数の寿命を捉えないと、クロージャを作るときに間違えやすい。
package main
import "fmt"
var names = []string{"foo", "bar", "baz"}
func main() {
var funcs []func()
for _, n := range names {
funcs = append(funcs, func() {
fmt.Println(n)
})
}
for _, f := range funcs {
f()
}
}
は、
baz
baz
baz
を出力する。
python も同様だ。
names = ["foo", "bar", "baz"]
funcs=[]
for n in names:
funcs.append( lambda: print(n))
for f in funcs:
f()
は、
baz
baz
baz
を出力する。
一方 ruby は each 内で変数が毎回作られるので
names = ["foo", "bar", "baz"]
funcs = []
names.each do |n|
funcs.push(->(){ puts(n) })
end
for f in funcs do
f[]
end
は
foo
bar
baz
を出力する。
Go の場合は、スコープの狭い変数を作ることで解決する。
こんな具合:
package main
import "fmt"
var names = []string{"foo", "bar", "baz"}
func main() {
var funcs []func()
for _, n := range names {
n := n // 同名でも良い
funcs = append(funcs, func() {
fmt.Println(n)
})
}
for _, f := range funcs {
f()
}
}
python の場合は、for
ループの内側に変数を作ってもうまくいかない。
以下のように
names = ["foo", "bar", "baz"]
funcs=[]
for n in names:
funcs.append(lambda n=n: print(n))
for f in funcs:
f()
lambda
のデフォルト引数を悪用して束縛するとうまくいく。
可変長引数
sum
とか max
を見ると、C++ のようなテンプレートが欲しくなるね。我慢我慢。
遅延関数呼び出し
ようやく出てきた defer
。
関数と引数の式は defer 文が実行されるときに評価されますが、実際の呼び出しは«中略»遅延されます。
すぐに評価されるってどういうこと?
package main
import "fmt"
func foo() string {
fmt.Print("<foo>")
return "FOO"
}
func main() {
defer fmt.Println(foo())
fmt.Print("bar")
}
は、
<foo>barFOO
を出力した。defer
文で fmt.Println
は遅延されるけど、 foo()
は遅延されない。そういうことか。
もうひとつ例を。
package main
import "fmt"
func foo(s string) func() {
fmt.Print("foo")
return func() {
fmt.Print(s)
}
}
func main() {
defer foo("bar\n")()
fmt.Print("baz")
}
これは
foobazbar
を出力する。
defer
文は、ブロックの最後ではなく、関数の最後に実行されるので
package main
import "fmt"
func main() {
fmt.Print("begin ")
for i := 0; i < 3; i++ {
// do something
defer fmt.Printf("%d ", i)
}
fmt.Print("end ")
}
は
begin end 2 1 0
を出力する。
なるほど。長い関数を書くなというメッセージに見える。
まあ、無名関数を使って
package main
import "fmt"
func main() {
fmt.Print("begin ")
for i := 0; i < 3; i++ {
func() {
// do something
defer fmt.Printf("%d ", i)
}()
}
fmt.Print("end ")
}
としてもいいんだけど。
あと。
こんな例はどうだろう:
package main
import "fmt"
func main() {
n := 10
a := []byte{1, 2, 3}
s := "hoge"
defer fmt.Println(n, a, s)
n += 100
s = "fuga"
a[0] = 100
}
これは
10 [100 2 3] hoge
を出力した。つまり、defer は変数ではなく値を束縛しているように見える。
クロージャとは動きが違うところが面白い。クロージャの場合は変数を束縛するので下記の通りになる:
package main
import "fmt"
func main() {
n := 10
a := []byte{1, 2, 3}
s := "hoge"
f := func() { fmt.Println(n, a, s) }
n += 100
s = "fuga"
a[0] = 100
f() //=>110 [100 2 3] fuga
}
defer
とは動きが異なる。
panic
と recover
まずは普通に。
defer
の中に書くのが筋。
defer
の外に書いても:
package main
import "fmt"
func main() {
fmt.Println("panic?", recover()) //=> panic? <nil>
panic("hoge")
fmt.Println("panic?", recover()) // 呼ばれない
}
意味がない。
ともあれ。
panic
を自分で呼ぶことはあるかもしれないとおもう。
でも、(ある種の冗談や練習以外の理由で) recover
を自分で呼ぶことはあまりなさそうな感じ。
次はオブジェクト指向の第六章 https://qiita.com/Nabetani/items/1c100394a65af6506187