Golang Cafe #7 まとめ 1.2での変更ほか

2013/12/08に開催された「Golang Cafe #7」についてのまとめです。


今回は14日(土)の合同勉強会 in 大都会岡山 -2013 Winter-でセッションデビューする+Ryuji Iwata氏の練習と、余った時間で1.2で追加・変更になった機能からのピックアップと、私が気になった部分を+TakashiYokoyama氏に質問するだけで時間が一杯となりました。


+Ryuji Iwata氏の発表内容については勉強会後公開されると思いますので、ここでは触れないでおきます。

私が質問したこと

C#でいうところのdecimal型の扱い

C#では値型としてdecimal型が用意されています。
「財務や金融の計算に適しています」とリンク先にも書かれている通りで、お金関係の計算で丸め誤差を無視できない場合に使用しています。(適当な有効桁で端数処理すればいいだろという話はおいておいて)
ただ実行速度は遅くなってしまうのでそこはトレードオフです。
Go言語でdecimalに相当するものは無いのかというと、無いそうです。JavaBigIntegerBigDecimalに相当するmath/big/Intmath/big/Ratを使用するしかなさそうです。さらにはJavaとはお作法が少し違うようなので注意も必要です。

package main

import (
    "fmt"
    "math/big"
)

func main() {
    i, j, k := big.NewInt(1), big.NewInt(2), big.NewInt(0)

    x := i.Add(i, j)
    k.Add(i, j)

    fmt.Println(i, j, k, x)
}
3 2 5 3
演算子オーバーロード

無いそうです。あれば先のdecimalの話も解決できるのでしょうか。

値の型名の取得

今まではswitch文で判定するかreflectパッケージを使用するかしかなかったようですが、1.2になって別の方法が増えたようです。
switch文での判定、reflectパッケージの使用、およびPrintf("%T", ...)を使用することで取得できます。
後述のThe fmt packageでも書きます。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var i int32 = 1

    // reflectパッケージを使った方法
    typeName1 := reflect.ValueOf(i).Type().Name()
    // 1.2での追加
    typeName2 := fmt.Sprintf("%T", i)
    
    fmt.Println(typeName1, typeName2)
}
int32 int32
C#でいうところのnull許容型

C#ではintに対してint?やNullableと書けばnull許容型になります。JavaではIntegerが使えます。
でもGo言語では用意されていないようです。NullInt64というものがありますが、database/sqlパッケージというのが気になります。

1.2で追加・変更になった機能からピックアップ

先日1.2が正式に発表になりました、詳細はGo 1.2 Release Notesで。
今回は+TakashiYokoyama氏が何点かピックアップしてくれました。

Use of nil

当日説明を聞いただけではよく分からなかったのですが、Go 1.2 Nil Checksの内容を実際に1.1と1.2での結果を比較してみた分かりました。
検証に使用したコードは以下のものでリンク先にあるとおりです。

package main

import (
	"fmt"
)

type T struct {
    Field1 int32
    Field2 int32
}

type T2 struct {
    X [1<<24]byte
    Field int32
}

func main() {
    var x *T
    p1 := &x.Field1
    p2 := &x.Field2
    var x2 *T2
    p3 := &x2.Field

    fmt.Println(p1, p2, p3)
}

1.1.2で実行した場合の結果は以下のとおりです。

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x0 pc=0x401030]

goroutine 1 [running]:
main.main()
        d:/study/golang/src/practice/sample.go:22 +0x30

goroutine 2 [runnable]:
exit status 2

1.2で実行した場合の結果は以下のとおりです。

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x1 addr=0x0 pc=0x4010ee]

goroutine 1 [running]:
runtime.panic(0x4a7760, 0x58536f)
        C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist667667715/go/src/pkg/runtime/panic.c:266 +0xc8

main.main()
        d:/study/golang/src/practice/sample.go:19 +0xee
exit status 2

両方ともpanicが起こっていますが、発生している箇所が違います。
今までは初期化していない構造体のポインタのフィールドにアクセスできていましたが、1.2になってこれがpanicになるようになったようです。

Three-index slices

配列からスライスを作成するときに3つ目のインディックスでキャパシティを指定することで、スライスを変更した際に元の配列に影響する範囲を指定することができます。(逆に言うと影響されないようにできる)

package main

import "fmt"

func main() {
    m1()
    fmt.Println()
    m2()
    fmt.Println()
    m3()
}

func m1() {
    arr := setup()

    slice := arr[2:4]

    appendAndOutput(slice)

    fmt.Println(arr)
}

func m2() {
    arr := setup()

    slice := arr[2:4:7]

    appendAndOutput(slice)

    fmt.Println(arr)
}

func m3() {
    arr := setup()

    slice := arr[2:4:4]

    appendAndOutput(slice)

    fmt.Println(arr)
}

func setup() [10]int {
    return [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
}

func appendAndOutput(slice []int) {
    fmt.Printf("%v, len=%d, cap=%d\n", slice, len(slice), cap(slice))

    for i := 0; i < 10; i++ {
        slice = append(slice, 100)
    }

    fmt.Println(slice)
}
[3 4], len=2, cap=8
[3 4 100 100 100 100 100 100 100 100 100 100]
[1 2 3 4 100 100 100 100 100 100]

[3 4], len=2, cap=5
[3 4 100 100 100 100 100 100 100 100 100 100]
[1 2 3 4 100 100 100 8 9 10]

[3 4], len=2, cap=2
[3 4 100 100 100 100 100 100 100 100 100 100]
[1 2 3 4 5 6 7 8 9 10]
The fmt package

fmtパッケージのPrintfメソッドでのフォーマットの指定方法が追加されました。
一つは先ほどの「私が質問したこと」のところでも少し書きましたが、%Tを指定することで型を表示できるようになりました。
もう一つはフォーマット文字列内で値のインディックスが指定できるようになりました。
ただ気をつけなくてはならないのは、インディックスを指定したした後にインディックスを指定しないものがあると直前のインディックス+1と解釈されるということです。
バグを生み出さないためにもどちらかだけで統一したほうがいいかもしれません。

package main

import "fmt"

func main() {
    //
    // 1.1.2でもこの方法は可能だったようです。
    //v := 1.0
    //fmt.Printf("%T\n", v)

    //
    v1, v2, v3, v4, v5 := "あ", "い", "う", "え", "お"

    fmt.Printf("%v, %v, %v, %v, %v\n", v1, v2, v3, v4, v5)

    fmt.Printf("%[5]v, %[5]v, %[3]v, %[3]v, %[1]v\n", v1, v2, v3, v4, v5)

    fmt.Printf("%[2]v, %v, %[1]v, %v, %v\n", v1, v2, v3, v4, v5)
}
float64
あ, い, う, え, お
お, お, う, う, あ
い, う, あ, い, う

最後に

次回は「今年のGo言語の振り返りと、来年のGo言語の動向」がテーマだそうです。

2013/12/14追記

1.2の機能でTest coverageが気になったのを忘れていました。
一度確かめておかなくてはなりませんね。