はじめに
プログラミング言語 Go の研修を受講しているので学びをメモしていきます。
プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- 作者:Alan A.A. Donovan,Brian W. Kernighan
- 発売日: 2016/06/20
- メディア: 単行本(ソフトカバー)
Go ではどのような問題解決をしようとしたか
Google は C++を利用して、検索エンジンなどパフォーマンスが求められるサービスを作成していました。 C++の問題点として以下があるそうです。
- コンパイルが遅い
- 言語機能が多い(各々が色々なサブセットを使っている)
Go では、複雑で不安定なコードをもたらしてきた機能は避けています。 Go はポインタについてあえて制約があります。 C などではポインタのインクリメントができます。 これにより、メモリ破壊を簡単にできてしまいます。 また、Go は機能が少なく作られています。
GC と並列プログラミング
GC がないと大変です。
GC がないと自分でメモリをfree
しないといけません。
例えば、チャネルごしにデータを渡した時は渡した側と受け取り側でどちらが削除する責務を追うのかみたいな問題が発生します。
並行処理では、誰がどれを削除する責務を決めるのが難しいです。
そこで、GC があると簡単になります。
Java を書いているとメモリが開放されているつもりでも実は開放されていなかったみたいなことが起こりうるので、GC があったらあったで GC の挙動を把握する必要があります。
ジェネクリクス
Go ではあと数年でジェネリクスが追加されるようです。 分かりやすい例だと、sort パッケージです。 型ごとにメソッドがあり最初見た時違和感を感じました。 ジェネリクスが解決してくれるでしょう。
文字列連結
本の練習問題で、文字列連結をする場合に+
を使う時とstrings.Join
を使う時でパフォーマンスを比較するものがありました。
想像通り、strings.Join
を使った方が早かったです。
strings.Join
では、strings.Builder
を使って全てバイトで処理されるため早いようです。
func Join(a []string, sep string) string { switch len(a) { case 0: return "" case 1: return a[0] } n := len(sep) * (len(a) - 1) for i := 0; i < len(a); i++ { n += len(a[i]) } var b Builder b.Grow(n) b.WriteString(a[0]) for _, s := range a[1:] { b.WriteString(sep) b.WriteString(s) } return b.String() }
mapで順序保証されない
言語仕様上定義されていないという意味ではなく,意図的に乱択されています。
func main() { m := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4} for k, v := range m { fmt.Print(k, v, " ") } }
例えば、上のコードでは出力はa1 b2 c3 d4
, b2 c3 d4 a1
, c3 d4 a1 b2
, d4 a1 b2 c3
のいずれかになります。
スタートの要素がずれるだけで、ランダムの順序になるわけではなさそうです。
ちなみに、map の比較ではreflect.DeepEqual
メソッドを使われることが多いです。
この中の実装としては以下のようになっていました。
case Map: if v1.IsNil() != v2.IsNil() { return false } if v1.Len() != v2.Len() { return false } if v1.Pointer() == v2.Pointer() { return true } for _, k := range v1.MapKeys() { val1 := v1.MapIndex(k) val2 := v2.MapIndex(k) if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(val1, val2, visited, depth+1) { return false } } return true
untyped value
Go では定数が untyped value として扱われます。 定数の型は決まっておらず、変数に代入するときや式の中で決まります。
func main() { x := 1 y := 0.1 fmt.Println(x + y) // invalid operation: x + y (mismatched types int and float64) fmt.Println(1 + 0.1) // 1.1 fmt.Println(1 + y) // 1.1 fmt.Println(x + 0.1) // constant 0.1 truncated to integer type MyInt int var myInt MyInt = 42 fmt.Println(10 + myInt) // 52 fmt.Println(x + myInt) // invalid operation: mismatched types int and MyInt }
fmt.Println(10 + myInt)
のように自分で定義した MyInt 型と定数 10 が計算できるのは戸惑いました。
それは、自分の頭の中で勝手に 10 は Int 型と変換していたからであって、untyped value の概念を知ると納得しました。
おわりに
今回は最初の章を読んだメモでしたので浅い掘り具合になりましたが、今後の章ではより深掘りしていけると思います。