🤖

🤖

:gijutsu_burogu:

Goを体系的に学んでいるメモ その1

はじめに

プログラミング言語 Go の研修を受講しているので学びをメモしていきます。

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

Go ではどのような問題解決をしようとしたか

GoogleC++を利用して、検索エンジンなどパフォーマンスが求められるサービスを作成していました。 C++の問題点として以下があるそうです。

  • コンパイルが遅い
  • 言語機能が多い(各々が色々なサブセットを使っている)

Go では、複雑で不安定なコードをもたらしてきた機能は避けています。 Go はポインタについてあえて制約があります。 C などではポインタのインクリメントができます。 これにより、メモリ破壊を簡単にできてしまいます。 また、Go は機能が少なく作られています。

GC と並列プログラミング

GC がないと大変です。 GC がないと自分でメモリをfreeしないといけません。 例えば、チャネルごしにデータを渡した時は渡した側と受け取り側でどちらが削除する責務を追うのかみたいな問題が発生します。 並行処理では、誰がどれを削除する責務を決めるのが難しいです。 そこで、GC があると簡単になります。

Java を書いているとメモリが開放されているつもりでも実は開放されていなかったみたいなことが起こりうるので、GC があったらあったで GC の挙動を把握する必要があります。

ジェネクリクス

Go ではあと数年でジェネリクスが追加されるようです。 分かりやすい例だと、sort パッケージです。 型ごとにメソッドがあり最初見た時違和感を感じました。 ジェネリクスが解決してくれるでしょう。

golang.org

文字列連結

本の練習問題で、文字列連結をする場合に+を使う時と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 の概念を知ると納得しました。

おわりに

今回は最初の章を読んだメモでしたので浅い掘り具合になりましたが、今後の章ではより深掘りしていけると思います。