Effective Go 野良翻訳(2)
続き。関数とデータ。これで半分ぐらい。
関数
複数返り値
Go特有の機能として関数やメソッドが、複数の値を返せることが挙げられる。この機能を使うと、Cでよく出てくるカッコ悪い書き方を改善することができる。エラーステータスを(-1やEOFなどの)特殊な返り値で表現したり、引数を変更してエラーを返したりする方法だ。
Cでは、write でエラーが生じると負の値が返され、エラーコードはすぐ消えてしまう場所に暗黙に置かれる。GoではWrite関数は、書き出したバイト数とエラーを返す。「xxxバイトは書き出したが全ては書けなかった。デバイスがいっぱいになったので。」というようなことが表現できる。osパッケージの *File.Write は次のようなシグネチャを持つ。
func (file *File) Write(b []byte) (n int, err Error)
ドキュメントに書かれているように、この関数は書き出したバイト数と(n != len(b)の場合には)nilでない Error を返す。この書き方は一般的な書き方だ。エラー処理セクションにもう少し例を載せてある。
この方法を使うと、返り値をポインタ渡しして参照渡し引数の代わりにする必要もなくなる。次の関数は、バイト配列の指定位置から次の数値を読み出して返す関数だ。数値と次の位置を返す。
func nextInt(b []byte, i int) (int, int) { for ; i < len(b) && !isDigit(b[i]); i++ { } x := 0 for ; i < len(b) && isDigit(b[i]); i++ { x = x*10 + int(b[i])-'0' } return x, i }
この関数を使って、入力配列の中から数字を取り出すことができる。
for i := 0; i < len(a); { x, i = nextInt(a, i) fmt.Println(x) }
名前付き結果引数(result parameter)
Go関数の返り値、もしくは結果引数(result parameter)には名前を付けることができ、入力引数と同様に通常の変数として使用することができる。名前が与えられた結果引数は、関数開始時にその型のゼロ値で初期化される。関数が引数なしでreturnした場合には、結果引数のその時点での値が返り値となる。
結果引数に名前を付ける必要はないが、場合によってはコードがより短く、明確になることもある。名前がドキュメントとなるからだ。例えば下のnextIntの返り値に名前をつければ、返り値の意味が分かりやすくなるだろう。
func nextInt(b []byte, pos int) (value, nextPos int) {
名前付き結果引数は初期化され、引数なしのreturnと結び付けられているため、コードがより簡潔になる場合がある。そのような例として io.ReadFullを見てみよう。
func ReadFull(r Reader, buf []byte) (n int, err os.Error) { for len(buf) > 0 && err == nil { var nr int nr, err = r.Read(buf) n += nr buf = buf[nr:len(buf)] } return }
Defer
Goのdefer文は、関数の呼び出しをそのdefer文が含まれる関数のリターンの直前まで遅延する。遅延される関数をdefer関数と呼ぶ。この方法は少し変わってはいるが、どのリターンパスをとおる場合でも資源を開放したいような場合に有効な方法だ。よくある使用例としては、排他ロックの解放や、ファイルのクローズがある。
// Contents returns the file's contents as a string. func Contents(filename string) (string, os.Error) { f, err := os.Open(filename, os.O_RDONLY, 0) if err != nil { return "", err } defer f.Close() // f.Close will run when we're finished. var result []byte buf := make([]byte, 100) for { n, err := f.Read(buf[0:]) result = bytes.Add(result, buf[0:n]) if err != nil { if err == os.EOF { break } return "", err // f will be closed if we return here. } } return string(result), nil // f will be closed if we return here. }
defer 関数には、2つのメリットがある。ひとつは、ファイルをクローズすることを忘れる心配がないこと。これは、関数を編集して新しいリターンパスを追加した場合によく起こるミスだ。もうひとつは、closeをopenのすぐそばに書く事ができることだ。関数の終りにcloseを書くよりもはるかに明確になる。
defer関数の引数は、関数呼び出し時ではなくdeferが呼び出された時点で評価される。defer関数がメソッド呼び出しの場合はメソッド呼び出しのレシーバも同様だ。このため、呼び出したあとで変数の値が変わってしまうことを心配する必要はない。さらに、一つのdefer 文で複数の関数を呼び出すこともできるわけだ。馬鹿げた例を示そう。
for i := 0; i < 5; i++ { defer fmt.Printf("%d ", i) }
defer関数は、LIFO順で実行される。したがってこのプログラムを実行すると、この関数の終了時に 4 3 2 1 0 が出力される。もっと説得力のある例として、プログラムを通じて関数の実行を簡単にトレースすることを考えてみよう。次のようにトレース関数を定義する事ができる。
func trace(s string) { fmt.Println("entering:", s) } func untrace(s string) { fmt.Println("leaving:", s) } // Use them like this: func a() { trace("a") defer untrace("a") // do something.... }
defer関数の引数が、deferが呼び出された時点で評価されることを利用してもっと簡単にすることもできる。traceでuntraceのための引数をセットアップするのだ。
func trace(s string) string { fmt.Println("entering:", s) return s } func un(s string) { fmt.Println("leaving:", s) } func a() { defer un(trace("a")) fmt.Println("in a") } func b() { defer un(trace("b")) fmt.Println("in b") a() } func main() { b() }
これを実行すると、次のように出力される。
entering: b in b entering: a in a leaving: a leaving: b
他の言語でよくあるブロックレベルでのリソース管理になれたプログラマにとっては、deferは妙な感じがするかもしれない。しかしこの機能の最も興味深く最も強力な利用方法は、この機能がまさにブロックベースではなく関数ベースであるからこそ可能になっているのだ。panicとrecoverの節で詳しく述べる。
Data
new()によるメモリ確保
Goにはnew()とmake()の2つのメモリ確保方法がある。これらの動作は異なるし、異なる型に適用される。紛らわしいかもしれないいが、ルールは簡単だ。まずはnew()について説明しよう。この組み込み関数は、他の言語の同じ名前の関数と本質的には同じことを行う。つまり new(T) は、型Tの新しいアイテムのためにメモリを確保し、ゼロでクリアして、そのアドレスを *T 型の値として返す。Go用語でいえば、新たに確保したT型のゼロ値へのポインタを返す。
new()では、メモリ領域はゼロ値で初期化されるので、ゼロ値のオブジェクトに対してそれ以上の初期化が必要ないようにしておくと便利だ。そうすれば、ユーザはnew()するだけでそのまま使うことができる。たとえば、bytes.Bufferのドキュメントには、「Bufferのゼロ値は空のバッファで、すぐに使うことができる」と書かれている。同様に、sync.Mutexにも明示的なコストラクタやInitメソッドがない。sync.Mutexのゼロ値は、ロックされていないmutexとしてそのまま利用できるようになっているのだ。
この「ゼロ値が便利」という性質は推移する。次の宣言を見てみよう。
type SyncedBuffer struct { lock sync.Mutex buffer bytes.Buffer }
このSyncedBuffer型の値は、確保したらすぐに利用出来る。次のコードのpやvは、この後なにもしなくてもそのまま使うことができる。
p := new(SyncedBuffer) // type *SyncedBuffer var v SyncedBuffer // type SyncedBuffer
コンストラクタと複合リテラル
ゼロ値では十分でない場合には初期化を行うコンストラクタが必要となる。次の例は、os パッケージからとってきたものだ。
func NewFile(fd int, name string) *File { if fd < 0 { return nil } f := new(File) f.fd = fd f.name = name f.dirinfo = nil f.nepipe = 0 return f }
ここには同じような書き方がたくさん出てくる。これを複合リテラルを用いて単純化することができる。複合リテラルは評価されるたびに新しいインスタンスを作る式だ。
func NewFile(fd int, name string) *File { if fd < 0 { return nil } f := File{fd, name, nil, 0} return &f }
この関数はローカル変数のアドレスを返しているが、これはGoでは問題ないことに注意しよう。変数に関連したメモリ領域は、関数がリータンしたあとも生き残る。実際、複合リテラルのアドレスを返すコードは、評価されるたびに新たなインスタンスを確保するので、2行を1行にまとめて次のように書く事もできる。
return &File{fd, name, nil, 0}
複合リテラルでは、すべてのフィールドを宣言した順番で書かなければならない。しかし、フィールド名:値 のペアで明示的にラベルをつけると、順番は関係なくなり、指定されていないフィールドはその型のゼロ値となる。したがって次のように書きなおすこともできる。
return &File{fd: fd, name: name}
これを極端にすると、フィールドが全く含まれていない複合リテラルになる。その場合、その型のゼロ値を作ることになる。つまり、new(File)と&File{}は等価だ。
複合リテラルは、配列、スライス、マップに対しても利用出来る。フィールドラベルには、インデックスやマップのキーを用いる。下の例は、Enone, Eio, Einvalの値がなんであれ相互に異なる値で有りさえすれば、有効な初期化である。
a := [...]string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"} s := []string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"} m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
make()によるメモリ確保
メモリ確保の話に戻ろう。組み込み関数 make(T, args) は、new(T)とは違う目的に用いる。この関数は、スライス、マップ、チャンネルを作るのにのみ用いる。ゼロ値ではなく初期化された値を返す。また型*T ではなく 型Tを返す。このような相違があるのはこの3つの型が実態としては、初期化を必要とするデータ構造への参照だからだ。例えば、スライスは配列内のデータへのポインタ、長さ、容量という3つのアイテムを指す。これらのアイテムが初期化されるまではスライスは空(nil)だ。makeは、スライス、マップ、チャンネルの内部のデータ構造を初期化して、利用出来るようにする。例えば、次のコードを見てみよう。
make([]int, 10, 100)
このコードは、長さ100のint配列をつくり、その上に、配列の最初の10要素を指す、長さ10、容量100のスライス構造体を作る(スライスを作る際の容量は省略できる。次節を参照)。これに対してnew([]int)は、ゼロで初期化されたスライス構造体をつくり、そのポインタを返す。すなわち空のスライス値へのポインタを返す。
下の例をみると、new()とmake()の違いがわかるだろう。
var p *[]int = new([]int) // *p == nil のスライスを確保。使う機会はあまりない。 var v []int = make([]int, 100) // スライス v は100個のint配列を参照している // 不必要に複雑 var p *[]int = new([]int) *p = make([]int, 100, 100) // 普通はこう書く v := make([]int, 100)
make()は、マップ、スライス、チャンネルにしか使わず、ポインタを返すわけではないことを覚えておこう。明示的にポインタを作る場合にはnew()を用いる。
配列
配列はメモリ上のレイアウトを詳細に計画し、余分なメモリ確保を避けるために使うこともできるが、基本的には次節で述べるスライスの部品である。スライスを説明する基礎とするために、ここで配列について説明する。
Goの配列とCの配列は大きく異なる。Goでは配列は値だ。配列を別の配列に代入すると、要素がすべてコピーされる。特に、関数に配列を渡すと、関数には配列のポインタではなく配列のコピーが渡される。
配列のサイズは、配列の型の一部となっている。 [10]int と [20]int は別の型だ。
配列が値であるということは便利ではあるが高価でもある。Cのような動作と効率を求めるなら配列のポインタを渡すこともできる。
func Sum(a *[3]float) (sum float) { for _, v := range *a { sum += v } return } array := [...]float{7.0, 8.5, 9.1} x := Sum(&array) // Note the explicit address-of operator
この書き方もGoではあまり使わない。普通はスライスを使う。
スライス
スライスは、配列をラップし、一連のデータに対する、より汎用で強力で便利なインターフェイスを提供する。Goでは、例えば変換行列のような、明示的に次元があるようなデータ構造を扱う場合を除き、ほとんどの配列プログラムは、配列そのものではなくスライスを用いて記述する。
スライスは、参照型である。つまり、あるスライスを他のスライスに代入すると、両者は同じ内部アレイを参照することになる。例えば、スライスを引数とする関数がスライスの内容を変更すると、関数の呼び出し側からも変更されたことが確認できる。内部の配列へのポインタを渡したのと同じだ。Read関数は、ポインタとカウントではなく、スライスを引数とする。スライスの長さを読み出しバイト数の上限値とする。osパッケージのFile型のReadメソッドのシグネチャは次のようになっている。
func (file *File) Read(buf []byte) (n int, err os.Error)
このメソッドは読み出したバイト数と、エラー時にはエラー値を返す。大きいバッファに、最初の32バイトだけデータを読み込むには、次のようにバッファをスライスして使う。
n, err := f.Read(buf[0:32])
このようにスライスする書き方は一般的で効率的だ。実際、効率を考えなければ、次のコードも同じように最初の32バイトを読み込んでいる。
var n int var err os.Error for i := 0; i < 32; i++ { nbytes, e := f.Read(buf[i:i+1]) // Read one byte. if nbytes == 0 || e != nil { err = e break } n += nbytes }
スライスの長さは、内部配列の中に収まっている限り変更することができる。新しいスライスを作って自分に代入すればいい。組み込み関数capで参照できるスライスの容量は、スライスの最大長を与える。次の関数はスライスにデータを追加する関数だ。データ量が容量を超えると、スライスを再度確保して、更新されたスライスを返す。この関数は、空のスライスに対しても関数lenと関数capを使うことができ、それぞれ0を返すことを利用している。
func Append(slice, data[]byte) []byte { l := len(slice) if l + len(data) > cap(slice) { // reallocate // 将来のため、必要量の2倍を確保する newSlice := make([]byte, (l+len(data))*2) // copy関数は任意の型のスライスに対して機能する copy(newSlice, slice) slice = newSlice } slice = slice[0:l+len(data)] for i, c := range data { slice[l+i] = c } return slice }
Append関数はスライスを返さなければならない。この関数ではスライスの要素を変更しているが、スライスそのもの(ポインタや長さや容量を確保している実行時データ構造)は、値でこの関数に渡されているからだ。
Map
Mapは、便利で強力な組み込みデータ構造で、異なる型の値を関連付ける。キーとしては、等価比較演算子が定義されている型であればなんでもよい。整数、浮動小数点数、文字列、ポインタなど。動的型が等価比較をサポートしていればインターフェイスでもよい。構造体、配列、スライスは、等価比較が定義されていないのでMapのキーとしては使えない。スライスと同様Mapも参照型である。Mapを関数に渡し、関数がMapの内容を変更すると、呼び出し側にその変更が反映される。
Mapは、通常の複合リテラルのシンタックスで作ることができるので、初期化の際に構築するのも簡単だ。キーとバリューのペアはコロンで区切る。
var timeZone = map[string] int { "UTC": 0*60*60, "EST": -5*60*60, "CST": -6*60*60, "MST": -7*60*60, "PST": -8*60*60, }
Mapに値を設定したり、値を取り出したりするのは、文法的には配列と似ている。ただ、インデックスが整数でなくてもいいというだけだ。
offset := timeZone["EST"]
Mapから存在しないキーを使って値を取りだそうとすると、mapのエントリの型のゼロ値が返される。例えば、整数を収めたmapであれば、0が返される。
エントリが存在しない場合とゼロ値が入っている場合を区別したい場合もあるだろう。"UTC" に対するエントリがあるのか、ないからゼロ値として0が返って来てるのだろうか?これを区別するには複数代入を用いる。
var seconds int var ok bool seconds, ok = timeZone[tz]
理由は明白だろうが、この書き方は"カンマOK"イディオムと呼ばれている。この例では、tzが存在すれば、seconds は相応に設定され、okにはtrueがセットされる。tzがなければ、secondsはゼロになり、okはfalseとなる。下の関数ではこれを使って、エラーをログに出力している。
func offset(tz string) int { if seconds, ok := timeZone[tz]; ok { return seconds } log.Println("unknown time zone", tz) return 0 }
mapに値が入っているかだけを確認したい場合には、ブランク識別子、つまりアンダースコアを用いる。ブランク識別子は、任意の値、任意の型を代入したり宣言したりすることができる。代入された値は無害に捨てられる。値の存在を確認するには、次のように値を受ける変数にブランク識別子を使って次のように書く。
_, present := timeZone[tz]
mapのエントリを削除するには、複数代入の向きを変えて、余分なbooleanを右側につける。booleanがfalseの場合には、エントリが削除される。キーがマップにない場合にもこの方法は利用出来る。
timeZone["PDT"] = 0, false // Now on Standard Time
書式付き出力
Goの書式付き出力は、Cのprintfファミリと似たスタイルだが、よりリッチで汎用となっている。出力関数はfmtパッケージに定義されており、関数名はキャピタライズされていて fmt.Printf, fmt.Fprintf, fmt.Sprintf のようになっている。文字列関数群(Sprintfなど)は、バッファを与えてそこに書かせるのではなく文字列を返すようになっている。
いつも書式文字列を与える必要はない。Printf, Fprintf, Sprintf それぞれに対して対応する2つの関数が(Print, Printlnなど)が定義されている。これらの関数は、書式文字列を取らず、与えられた引数のデフォルトのフォーマットで出力する。Printlnのほうは、引数をスペースで区切り最後に改行を挿入するが、Printのほうは、2つの引数のどちらも文字列でない場合にだけスペースを挿入する。下の例はすべて同じ文字列を出力する。
fmt.Printf("Hello %d\n", 23) fmt.Fprint(os.Stdout, "Hello ", 23, "\n") fmt.Println("Hello", 23) fmt.Println(fmt.Sprint("Hello ", 23))
チュートリアルにも書かれているとおり、fmt.Fprintファミリは、最初の引数としてio.Writerインターフェイスを実装した任意のオブジェクトを取る。このインターフェイスを実装したインスタンスでおなじみのものとしては、os.Stdout や os.Stderr がある。
さてこのあたりから、Cとは違ってくる。まず、数値フォーマットの%dなどは、引数が符号付きかどうかやビット長を示すフラグをとらない。出力ルーチンは、引数の型を見て、その属性を判断する。
var x uint64 = 1<<64 - 1 fmt.Printf("%d %x; %d %x\n", x, x, int64(x), int64(x))
上のコードは次のような出力を行う。
18446744073709551615 ffffffffffffffff; -1 -1
デフォルトの出力、たとえば整数には10進表現など、をおこないたい場合には、便利なフォーマット %v (vはvalueを示す)を用いればいい。結果はPrintやPrintlnが出すものとおなじになる。さらに、このフォーマットは、任意の値を出力することができる。配列や、構造体、マップでもだ。前節で定義したタイムゾーンマップを出力する例を見てみよう。
fmt.Printf("%v\n", timeZone) // or just fmt.Println(timeZone)
出力は次のようになる。
map[CST:-21600 PST:-28800 EST:-18000 UTC:0 MST:-25200]
当然だが、マップではキーの出力順序は保証されない。構造体を出力する際には、%+v を用いると構造体のフィールド名も表示される。さらに、%#v とすると、どのような値でも、完全なGoの文法で出力される。
type T struct { a int b float c string } t := &T{ 7, -2.35, "abc\tdef" } fmt.Printf("%v\n", t) fmt.Printf("%+v\n", t) fmt.Printf("%#v\n", t) fmt.Printf("%#v\n", timeZone)
出力は次のようになる。アンパーサンド(&)が付いていることに注意。
&{7 -2.35 abc def} &{a:7 b:-2.35 c:abc def} &main.T{a:7, b:-2.35, c:"abc\tdef"} map[string] int{"CST":-21600, "PST":-28800, "EST":-18000, "UTC":0, "MST":-25200}
%qでは、string型や、[]byte型の値に対してクォートが付く。さらに、%#q では可能な場合にはバッククォートが使われる。%xは、整数値だけでなく文字列やバイト配列にも使うことができ、長い16進文字列を生成する。%とxの間にスペースを入れると(% x)バイトごとにスペースが挿入される。
もうひとつの便利なフォーマットとして %Tがある。これは、値の型を表示する。
fmt.Printf("%T\n", timeZone)
出力は次のようになる。
map[string] int
特定の型のデフォルト出力フォーマットを制御したければ、String() string メソッドをその型に対して定義すればいいい。T型に対しては次のように定義すればいい。
func (t *T) String() string { return fmt.Sprintf("%d/%g/%q", t.a, t.b, t.c) } fmt.Printf("%v\n", t)
出力は次のようになる。
7/-2.35/"abc\tdef"
String() メソッドで、Sprintfを呼んでいる。出力ルーチンはリエントラントに書かれているので、再帰的になっても問題ないのだ。さらに一歩進めて、出力ルーチンの引数を他のルーチンに直接渡すこともできる。Printfのシグネチャは、最後の引数を...interface{}型としている。これは、任意個の任意の型の引数が書式文字列の後ろに来ることをしめしている。
func Printf(format string, v ...interface{}) (n int, errno os.Error) {
関数Printfの中では、vは []interface{} 型の変数のように振舞うが、これを他の可変引数関数に渡すと、通常の引数リストのように処理される。上で使ったlog.Printlnの実装は次のようになっている。引数リストをfmt.Sprintlnに直接引渡して、実際のフォーマッティングをさせている。
// Println prints to the standard logger in the manner of fmt.Println. func Println(v ...interface{}) { std.Output(2, fmt.Sprintln(v...)) // Output takes parameters (int, string) }
書式付き出力にはここでは書ききれなかったことがまだある。詳しくは、パッケージfmtのgodocドキュメントを参照してほしい。
ところで、... を使った引数は、特定の型に対しても利用できる。たとえば、一連の整数から最小のものを選択する関数は...int を使って次のように書く事ができる。
func Min(a ...int) int { min := int(^uint(0) >> 1) // largest int for _, i := range a { if i < min { min = i } } return min }