A Tour of Go を見て、演習問題やっていく。

Hello World

Go のプログラムは パッケージ(package) で構成される。プログラムは main パッケージから開始される。

パッケージは、他のパッケージを インポート(import) することもできる。下記のプログラムは fmt をインポートしている。 fmt は標準入出力に対する操作を提供する。これを使って文字列 Hello World を出力している。

package main

import "fmt"

func main() {
	fmt.Println("Hello, World")
}

1行目にあるパッケージの宣言を package hogehoge と書き換えて hogehoge パッケージに変えることもできる。ただし hogehoge は実行できないパッケージとなる。それでも実行しようとすると cannot run non-main package のようなエラーを出して停止する。

Packages

次は math/rand パッケージを使ってみる。これはランダム値を取り扱うパッケージである。

パッケージをインポートしたあとの参照方法は最後の / よりあとの名前を使う。たとえば、 math/rand パッケージをインポートしたあとこれを参照するには rand という名前を使う。実際、下のプログラムでは rand.Intn(10) のように呼び出している。

package main

import "fmt"
import "math/rand"

func main() {
	fmt.Println("My favorite number is", rand.Intn(10))
}

関数 Intn(n) は引数 n に対して 0-n の範囲にあるランダムな数値を返す。ただし、このランダム値は擬似乱数で seed 値を必要とする。seed 値が与えられない場合は seed = 1 として疑似乱数を作る。

Imports

複数のモジュールをインポートする場合は factored import statement を使うこともできる。下記のプログラムは factored import statement を使って fmtmath パッケージの二つをインポートしている。

package main

import (
	"fmt"
	"math"
)

func main() {
	fmt.Printf("Now you have %g problems.\n", math.Sqrt(7))
}

関数 math.Sqrt(n) は引数 n の平方根を返す。

Exported names

モジュールの中で関数や定数を定義する時、大文字で始めると、外部から利用することができる。たとえば、これまでのプログラムで利用した関数 fmt.Printf()math.Sqrt() はどちらも大文字で始まるため、そのルールを満たしていることがわかる。大文字で始まる名前を exported name とよぶ。

関数ではない例としては円周率を表す定数 math.Pi がある。

package main

import (
	"fmt"
	"math"
)

func main() {
	fmt.Println(math.Pi)
}

exported name ではない関数や定数は外部から利用できない。

Functions

これまでは既存のモジュールの関数を利用してきた。次は独自の関数を定義して利用するサンプルを与える。

package main

import "fmt"

func add(x int, y int) int {
	return x + y
}

func main() {
	fmt.Println(add(42, 13))
}

関数を定義するには func を使うは仮引数の後ろに型を書く。戻り値がある場合はカッコの仮引数を閉じるカッコの後ろに戻り値の型を書く。引数の型が同じである場合には、最後の型以外を省略しても良い。つまり上の例は次のように書きかえることができる。

package main

import "fmt"

func add(x, y int) int {
	return x + y
}

func main() {
	fmt.Println(add(42, 13))
}

Multiple results

関数は複数の戻り値を持つことができる。

package main

import "fmt"

func swap(x, y string) (string, string) {
	return y, x
}

func main() {
	a, b := swap("hello", "world")
	fmt.Println(a, b)
}

ここで、 := は変数に対する宣言と代入を同時に行う演算子。この記法を short variable declaration と呼ぶ。 := は型宣言を省略することができる。あとで詳しい話がでてくるだろう。

Named return values

戻り値には、名前を与えることもできる。 named return value を使うときは関数の引数の宣言より前に、戻り値の名前と型を宣言する。

package main

import "fmt"

func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}

func main() {
	fmt.Println(split(17))
}

関数の意味をわかりやすくするうえで named return value は役立つ。関数の最後では return だけが書かれているが、これは冒頭で宣言した戻り値 sum を返す。これを naked return と呼ぶ。

関数がよほど簡単でないかぎり named return value を使うのが良さそう。

Variables

変数を宣言するには var 文を使う。変数は一つずつ定義することもできるし、複数個定義することもできる。var 文はパッケージの内部か、関数の内部で使うことができる。

package main

import "fmt"

var c, python, java bool

func main() {
	var i int
	fmt.Println(i, c, python, java)
}

変数は、数値なら 0 、真偽値なら false 、文字列なら空文字列で初期化される。

Variables with initializers

変数に初期値を与えることもできる。初期値を与えている場合は、その型を省略することができる。

package main

import "fmt"

var i, j int = 1, 2

func main() {
	var c, python, java = true, false, "no!"
	fmt.Println(i, j, c, python, java)
}

初期値が与えられているとき、変数は初期値の型と同じ型になる。たとえば上のプログラムでは i, j は整数型で、 c, python は真偽値型、 java は文字列型である。

Short variable declarations

関数の中では、var 文の代わりに := 代入文を使って暗黙的な型宣言を行うことができる。

package main

import "fmt"

func main() {
	i, j, k := 1, 2, 3
	c, python, java := true, false, "no!"

	fmt.Println(i, j, k, c, python, java)
}

:= は宣言したあとの再代入には使うことはできない。ただし、例外的に新しい変数を初期化しながら、既存の変数を再代入するときには利用することができる。あまり重要ではないと思うが、具体例は effectivego#redeclaration にある。

Basic types

インポート無しで利用できる基本の型は下記の通り。

bool真偽値
string文字列
int32bit または 64bit 整数(OSによって定まる)
int8, int16, int32, int64それぞれ 8bit, 16bit, 32bit, 64bit 整数
uint32bit または 64bit 非負整数(OSによって定まる)
uint8, uint16, uint32, uint64それぞれ 8bit, 16bit, 32bit, 64bit 非負整数
byteuint8 の別名
runeint32 の別名で Unicode コードポイントを指す
float32 float6432bit, 64bit 浮動小数点数
complex64 complex12864bit, 128bit 複素数

fmt.Printf で %T を使うと型を出力できる。

package main

import (
	"fmt"
	"math/cmplx"
)

var (
	ToBe   bool       = false
	MaxInt uint64     = 1<<64 - 1
	z      complex128 = cmplx.Sqrt(-5 + 12i)
)

func main() {
	fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
	fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
	fmt.Printf("Type: %T Value: %v\n", z, z)
}

Zero values

初期値が与えられない変数は 0 や false, 空文字列で初期化される。

package main

import "fmt"

func main() {
	var i int
	var f float64
	var b bool
	var s string
	fmt.Printf("%v %v %v %q\n", i, f, b, s)
}

Type conversions

変数を型変換をするには下記のようにする。

下の例は x の2乗と y の2乗の和を 64bit 浮動小数点数に変換する。math.Sqrt は float64 しか引数に取ることができないのでこの型変換が必要。型変換をしなかった場合はエラーになる。

package main

import (
	"fmt"
	"math"
)

func main() {
	var x, y int = 3, 4
	var f float64 = math.Sqrt(float64(x*x + y*y))
	var z uint = uint(f)
	fmt.Println(x, y, z)
}

Type inference

明示的な型を指定せずに変数を宣言する場合( := や var = のいずれか)、変数の型は右側の変数から型推論される。

var x = "hogehoge" // string
var y = x          // string
i := 42            // int
f := 3.142         // float64
g := 0.867 + 0.5i  // complex128

とても良い機能だと思うが変数どうしの代入の場合はわかりにくくなるかもしれない。

Constants

定数は const キーワードを使って変数と同じように宣言する。型推論も働く。定数は基本の型しか代入できない。また、:= を使うこともできない。

下の例は関数の外で定数 Pi を宣言している。これは型を明言してないが float64 になる。関数の中で二つの定数を宣言している。これらに対しても型推論が働く。

package main

import "fmt"

const Pi = 3.14

func main() {
	const World = "世界"
	fmt.Println("Hello", World)
	fmt.Println("Happy", Pi, "Day")

	const Truth = true
	fmt.Println("Go rules?", Truth)
}

Numeric Constants

数値の定数は型推論されるが、値そのものとして扱われる。(おそらく、変数のようにメモリを確保しない)そのために変数とは違う振る舞いをすることもある。

下の例は、非常に巨大な定数 Big が正常に利用できることを表している。また定数 Small が整数としても浮動小数点数としても利用できることを表している。

package main

import "fmt"

const (
	// Create a huge number by shifting a 1 bit left 100 places.
	// In other words, the binary number that is 1 followed by 100 zeroes.
	Big = 1 << 100
	// Shift it right again 99 places, so we end up with 1<<1, or 2.
	Small = Big >> 99
)

func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
	return x * 0.1
}

func main() {
	fmt.Println(needInt(Small))
	fmt.Println(needFloat(Small))
	fmt.Println(needFloat(Big))
}

ここで const を var に書き換えると三つのエラーが発生する。

./prog.go:8:2: constant 1267650600228229401496703205376 overflows int ./prog.go:20:23: cannot use Small (type int) as type float64 in argument to needFloat ./prog.go:21:23: cannot use Big (type int) as type float64 in argument to needFloat

1つ目のエラーは Big が型推論により int として計算されたがビット数が足りないことを表している。int は最大で 64 bit だから 100 ビットシフトした値は格納できない。

2つ目、3つ目のエラーは関数で宣言した引数の型が不一致であるために発生している。Small は型推論により int と判断されたため needFloat の引数にはできない。同様に Big も needFloat の引数にはできない。

おそらく、定数の場合はコンパイルされた時点でただの値と置き換えられるので needInt や needFloat の呼び出しのときに適切に変換されるのだと思う。

たとえ定数であってもプログラムを書き換えて const Small float64 = 2 のように型を明示すると needInt を呼び出すことができなくなる。

For

for 文は、初期化、繰り返し条件、後処理をまとめて一行に書く。初期化で変数を宣言すると、for 文のスコープ内でだけ利用できる。

package main

import "fmt"

func main() {
	sum := 0
	for i := 0; i < 10; i++ {
		sum += i
	}
	fmt.Println(sum)
}

For continued

初期化と後処理ステートメントは省略できる。下のプログラムは変数 sum が 2048 になるまで sum を2倍にしつづける。

package main

import "fmt"

func main() {
	sum := 1
	for ; sum < 2048; {
		sum += sum
	}
	fmt.Println(sum)
}

For is Go's "while"

セミコロンも省略することもできる。下のプログラムはさっきのプログラムのセミコロン省略したバージョン。この記法があるため、while キーワードは存在しない。

package main

import "fmt"

func main() {
	sum := 1
	for sum < 1000 {
		sum += sum
	}
	fmt.Println(sum)
}

Forever

終了条件も省略することができる。無限ループを書くときに使う。下記のプログラムは終わらないので実行しては駄目。

package main

func main() {
	for {
	}
}

If

if 文はよくあるやつでカッコは不要。下のプログラムは関数 sqrt を定義している。引数が正の数なら math ライブラリに任せる。引数が負の数なら、虚数風に表現した文字列を返す。

package main

import (
	"fmt"
	"math"
)

func sqrt(x float64) string {
	if x < 0 {
		return sqrt(-x) + "i"
	}
	return fmt.Sprint(math.Sqrt(x))
}

func main() {
	fmt.Println(sqrt(2), sqrt(-4))
}

この if 文を入れなかった場合はプログラムは `Program exited: status 137.` を出力して終了する。Sqrt 関数に負の数値を与えると NaN になってしまい、それを出力しようとして、エラーになるようだ。

If with a short statement

go では if 文の条件式の前に、途中計算式を書くこともできる。下のプログラムは関数 pow を定義している。引数 x を n 乗した数を計算して、 lim を超えているなら lim を返す。

if 文の条件式の前で v を初期化していることに注目。

package main

import (
	"fmt"
	"math"
)

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	}
	return lim
}

func main() {
	fmt.Println(
		pow(3, 2, 10),
		pow(3, 3, 20),
	)
}

If and else

if 文に対して else 文もある。if 文の冒頭で宣言した変数は else 文の中でも使うことができる。 下のプログラムは if 文の冒頭で v を宣言しているが else の中でもその変数を利用している。

package main

import (
	"fmt"
	"math"
)

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	} else {
		fmt.Printf("%g >= %g\n", v, lim)
	}
	// can't use v here, though
	return lim
}

func main() {
	fmt.Println(
		pow(3, 2, 10),
		pow(3, 3, 20),
	)
}

実行すると9と20を出力。33は27で、20より大きいので切り下げされる。

Exercise: Loops and Functions

任意の数値 x に対する、その平方根を z とする。 平方根をニュートン法で計算する Sqrt 関数を自分で作ってみる。

package main
import "fmt"

// 平方根を求めるための関数
func fx(x, z float64) float64 {
	return z * z - x
}

// 導関数
func fdashx(x, z float64) float64 {
	return x * z
}

func Sqrt(x float64) float64 {
	result := 1.0

	for i := 0; i<60; i++ {
		fmt.Println(result)
		result = result - fx(x, result) / fdashx(x, result)
	}

	return result
}

func main() {
	fmt.Println(Sqrt(4))
}

2の平方根はすぐに収束する

1
1.5
1.4166666666666667
1.4142156862745099
1.4142135623746899
1.4142135623730951
1.414213562373095
1.4142135623730951
1.414213562373095
1.4142135623730951
1.414213562373095

4 の平方根はそれよりも時間がかかる

1
1.75
1.8839285714285714
1.94375211577522
1.972282980207345
1.9862388687752293
1.9931432694780917
1.996577531800508
1.9982902325711434
1.9991454820113073
1.9995728323197925
1.9997864389737974
1.9998932251885466
1.999946614019457
1.999973307365996
1.9999866537720612
1.999993326908296
1.9999966634597142
1.9999983317312486
1.9999991658659722
1.999999582933073
1.9999997914665582
1.9999998957332845
1.9999999478666437
1.9999999739333223
1.9999999869666611
1.9999999934833306
1.9999999967416653
1.9999999983708328
1.9999999991854165
1.9999999995927082
1.9999999997963542
1.9999999998981772
1.9999999999490887
1.9999999999745444
1.9999999999872722
1.9999999999936362
1.999999999996818
1.999999999998409
1.9999999999992046
1.9999999999996023
1.9999999999998013
1.9999999999999007
1.9999999999999505
1.9999999999999754
1.9999999999999878
1.999999999999994
1.9999999999999971
1.9999999999999987
1.9999999999999993
1.9999999999999998
2

初期値を変えてもそれほど大きな変化はないが、2 にたどりつかず、微妙な誤差が修正できないケースも有るようだ。

Switch

go の switch は純粋な if ... else if ... の省略形としてはたらく。 他の言語と違って、ケースに含まれたプログラムを実行し終わったあと、次のケースを実行するということはない。

package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Print("Go runs on ")
	switch os := runtime.GOOS; os {
	case "darwin":
		fmt.Println("OS X.")
	case "linux":
		fmt.Println("Linux.")
	default:
		// freebsd, openbsd,
		// plan9, windows...
		fmt.Printf("%s.\n", os)
	}
}

ここで初めて登場した runtime パッケージはシステム情報を得る機能を持っている。 GOOS は、GO を実行している OS 名を表す文字列定数である。

Switch evaluation order

switch の実行順はやはり if ... else if ... と同じく、上から下に評価する。

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("When's Saturday?")
	today := time.Now().Weekday()
	switch time.Saturday {
	case today + 0:
		fmt.Println("Today.")
	case today + 1:
		fmt.Println("Tomorrow.")
	case today + 2:
		fmt.Println("In two days.")
	default:
		fmt.Println("Too far away.")
	}
}

time パッケージは時間に関する操作を行う。 time.Now() は現在時刻を time.Time 型の値として返す。 Weekday() は time.Weekday 型の値で、曜日を表す。 数値としては日曜日を0とする列挙型となっている。 上のプログラムでは土曜日が、今日の曜日なら Today. と出力する。 そうでない場合は1を足して比較する。以下省略。

Switch with no condition

switch に条件式を与えない場合は true が入ったのと同じように動く。 この暗黙的な動作を利用すると、if ... else if ... をより短い形で書くことができる。

package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("Good morning!")
	case t.Hour() < 17:
		fmt.Println("Good afternoon.")
	default:
		fmt.Println("Good evening.")
	}
}

上のプログラムは再び time パッケージを利用している。 午前中、夕方、それ以外の条件分岐を短く表現している。

Defer

defer 文は後ろに続く関数の実行を遅らせる機能を持っている。

package main

import "fmt"

func main() {
	defer fmt.Println("world")

	fmt.Println("hello")
}

上のプログラムはまず defer 文により "world" の出力は保留する。そのあと "hello" を出力する。プログラムの制御が main 関数の末尾まで来たとき、保留していた関数を実行する。つまり "world" を出力する。

Stacking defers

defer 文が複数回使われた時、それはスタックで処理される。

package main

import "fmt"

func main() {
	fmt.Println("counting")

	for i := 0; i < 10; i++ {
		defer fmt.Println(i)
	}

	fmt.Println("done")
}

上のプログラムは 0 の出力を保留、1 の出力を保留…と動く。ループを抜け出して "done" を実行した後はスタックされた関数を取り出していく。つまり、 9, 8, 7,... の順で出力し、0 を出力した時プログラムは終了する。

もっと現実的な defer の使用例は ここ で紹介されている。そこでは、defer を使って、ファイルを扱っているときストリームを開く関数と、ストリームを閉じる関数を近くに宣言している。

Pointers

ポインタは値のメモリアドレスを指す。 変数 x のポインタを取り出すには &x と書く。 ポインタ p から変数を取り出すには *p と書く。この操作を dereference と呼ぶ。

package main

import "fmt"

func main() {
	i, j := 42, 2701

	p := &i         // point to i
	fmt.Println(*p) // read i through the pointer
	*p = 21         // set i through the pointer
	fmt.Println(i)  // see the new value of i

	p = &j         // point to j
	*p = *p / 37   // divide j through the pointer
	fmt.Println(j) // see the new value of j
}

上のプログラムは適当な数値 42 のポインタを p に代入する。その後 p の値を読み出しているので 42 を出力する。次の行で 21 をポインタ p の値としてセットしている。セクションの最後で変数 i の中身を出力しているが、途中計算でポインタの向いている先の値を変更したので 21 を出力する。…以下省略。

Struct

struct で構造体を宣言することができる。

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	fmt.Println(Vertex{1, 2})
}

上のプログラムはフィールド X,Y を持つ点を表す構造体を宣言する。 型名の後ろに {} で囲んだ値を書いているのは、構造体リテラルで、その場で構造体を作ることができる。あとでもう一度説明する。

Println は構造体をそれらしい形式で出力できる。

Struct Fields

構造体のフィールドには . を使ってアクセスする。

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	v.X = 4
	fmt.Println(v.X)
}

上のプログラムは点 1,2 を変数 v に代入する。そしてフィールド X を書き換えた上で、取り出している。

Pointers to structs

構造体に対するポインタも、これまでと同様 &* を使って操作することができる。 構造体のポインタ p からフィールド X を読み出すときには (*p).X と正確に書くこともできるが、短く p.X と書いてもよい。

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	p := &v
	p.X = 1e9
	fmt.Println(v)
}

上のプログラムは構造体のポインタを取り出した後、そのフィールド X を大きな値で更新している。 1e9 は指数表記のリテラルで、 11091*{10}^9 した値を表す。

Struct Literals

構造体リテラルは、フィールドの値を列挙することで初期値を示している。 JSON と似たスタイルで、フィールド名を明示的に与えることもできる。 宣言されなかったフィールドは変数の初期化ルールと同じように 0 や空文字列で初期化される。

package main

import "fmt"

type Vertex struct {
	X, Y int
}

var (
	v1 = Vertex{1, 2}  // has type Vertex
	v2 = Vertex{X: 1}  // Y:0 is implicit
	v3 = Vertex{}      // X:0 and Y:0
	p  = &Vertex{1, 2} // has type *Vertex
)

func main() {
	fmt.Println(v1, p, v2, v3)
}

上のプログラムは構造体リテラルを使って、X, Y を初期化している。値を与えなかった場合は 0 がセットされていることがわかる。ポインタを使う場合でも Println は自動的に & 記号とともに、その値を出力する。この例では &{1 2} を出力する。

Arrays

配列の宣言はブラケットを使って [配列の長さ]配列の型名 の形式で宣言する。 配列の長さは変更できない。

package main

import "fmt"

func main() {
	var a [2]string
	a[0] = "Hello"
	a[1] = "World"
	fmt.Println(a[0], a[1])
	fmt.Println(a)

	primes := [6]int{2, 3, 5, 7, 11, 13}
	fmt.Println(primes)
}

上のプログラムは長さ2の文字列の配列を定義して "Hello" "World" を代入している。Println は配列もブラケットで囲んだ形で [Hello World] のように出力する。そのあとに宣言している primes は長さ6の整数配列である。ここでは初期化に配列リテラルを使っている。

Slices

配列に対して、ブラケットとコロンを使った演算で一部を取り出す事ができる。これをスライスと呼ぶ。スライスの長さは不定なので []int のように型表記する。

package main

import "fmt"

func main() {
	primes := [6]int{2, 3, 5, 7, 11, 13}

	var s []int = primes[1:4]
	fmt.Println(s)
}

上のプログラムは、まず固定長配列を定義した後、ブラケットの演算 [1:4] によりスライスを取り出している。コロンの前の数は始点インデックスで、コロンの後ろの数は終点のインデックスである。ただし、このスライスには終点自体は含まれない(半開区間)。よって出力されるのは [3 5 7] である。

Slices are like references to arrays

スライスは配列の部分列を指している参照であり、それ自体が値を保持しているわけではない。 よってスライスの値を変更すると、その参照先の配列の値も変化する。

package main

import "fmt"

func main() {
	names := [4]string{
		"John",
		"Paul",
		"George",
		"Ringo",
	}
	fmt.Println(names)

	a := names[0:2]
	b := names[1:3]
	fmt.Println(a, b)

	b[0] = "XXX"
	fmt.Println(a, b)
	fmt.Println(names)
}

上のプログラムは長さ4の人名配列を用意する。そして、先頭2人の名前を持つスライス a と、先頭と末尾以外の名前を持つスライス b を宣言する。そして、 b の先頭要素を XXX に変更している。この変更は b のみならず a にも影響し、もとの配列にも影響する。Paul という名前が XXX に塗りつぶされている。

Slice literals

配列リテラルと似た宣言方法にスライスリテラルがある。 スライスリテラルは配列リテラルの長さを消したものと同じである。 このリテラルは暗黙的に配列を初期化し、そのすべての要素を取り出したスライスを作る。

package main

import "fmt"

func main() {
	q := []int{2, 3, 5, 7, 11, 13}
	fmt.Println(q)

	r := []bool{true, false, true, true, false, true}
	fmt.Println(r)

	s := []struct {
		i int
		b bool
	}{
		{2, true},
		{3, false},
		{5, true},
		{7, true},
		{11, false},
		{13, true},
	}
	fmt.Println(s)
}

上のプログラムは、スライスリテラルを使って、色々な型を持つ配列を作り、そのスライスを作っている。 最後のリテラルでは名前のない即席の構造体を宣言しつつ、そのスライスを作成している。

Slice defaults

ブラケットとコロンの演算子によりスライスするとき、始点や終点を省略できる。 始点を省略した場合は先頭がスライスの始点となり、終点を省略した場合は末尾がスライスの終点となる。

package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}

	s = s[1:4]
	fmt.Println(s)

	s = s[:2]
	fmt.Println(s)

	s = s[1:]
	fmt.Println(s)
}

上のプログラムは、スライスリテラルにより s を初期化している。 その後、始点終点を変えながらスライスしている。最初のスライスで 3,5,7 が取り出される。 次のスライスで 3,5 が取り出される。最後のスライスで 5 が取り出される。

Slice length and capacity

スライスには長さ(length)と容量(capacity)がある。 それぞれ関数 len()cap() で取得することができる。

package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s)

	// Slice the slice to give it zero length.
	s = s[:0]
	printSlice(s)

	// Extend its length.
	s = s[:4]
	printSlice(s)

	// Drop its first two values.
	s = s[2:]
	printSlice(s)
}

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

最初のスライスでは終端に 0 を選ぶことで長さ 0 のスライスを生成している。 しかしスライスの始点は、参照先の配列の先頭を指しているので容量は 6 である。 次のスライスでは終点に 4 を選ぶことで長さ 4 のスライスを生成している。 スライスの始点は変化していないので容量は 6 のままである。 最後のスライスでは始点に 2 を選んでいる。 もとの配列の3番目の要素が始点になっているので容量は 4 に減少する。

Nil slices

スライスを初期化しなかった場合の値は nil となる。 このスライスは長さも容量も 0 で参照先の配列も存在しない。

package main

import "fmt"

func main() {
	var s []int
	fmt.Println(s, len(s), cap(s))
	if s == nil {
		fmt.Println("nil!")
	}
}

Creating a slice with make

ビルドイン関数 make() を使ってスライスを作ることもできる。 make は第一引数に型、第二引数に長さ、第三引数に容量をとる。

package main

import "fmt"

func main() {
	a := make([]int, 5)
	printSlice("a", a)

	b := make([]int, 0, 5)
	printSlice("b", b)

	c := b[:2]
	printSlice("c", c)

	d := c[2:5]
	printSlice("d", d)
}

func printSlice(s string, x []int) {
	fmt.Printf("%s len=%d cap=%d %v\n",
		s, len(x), cap(x), x)
}

プログラムはあまり意味のあるものではないが

  • a に長さ5の整数スライスをセットする。

  • b に長さ0、容量5のスライスをセットする。

  • cb の長さを二つ拡張したスライスをセットする。

  • dc の3, 4, 5番目の要素を持つスライスをセットする。

Slices of slices

スライスからなるスライスを作ることもできる。

package main

import (
	"fmt"
	"strings"
)

func main() {
	// Create a tic-tac-toe board.
	board := [][]string{
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
	}

	// The players take turns.
	board[0][0] = "X"
	board[2][2] = "O"
	board[1][2] = "X"
	board[1][0] = "O"
	board[0][2] = "X"

	for i := 0; i < len(board); i++ {
		fmt.Printf("%s\n", strings.Join(board[i], " "))
	}
}

strings パッケージは、文字列操作の機能を提供するパッケージ。 Join 関数は第一引数でスライスを受け取り、第二引数にセパレータを受け取る。 戻り値は、セパレータに結合した文字列となる。

Appending to a slice

スライスに要素を追加するにはビルドイン関数の append を使う。 append は第一引数にスライスをとり、残りの引数は追加したい要素をとる。 可変長引数なので、複数の要素をまとめて追加することもできる。

スライスの容量が足りない場合は、暗黙的により大きいサイズの配列を生成する。

package main

import "fmt"

func main() {
	var s []int
	printSlice(s)

	// append works on nil slices.
	s = append(s, 0)
	printSlice(s)

	// The slice grows as needed.
	s = append(s, 1)
	printSlice(s)

	// We can add more than one element at a time.
	s = append(s, 2, 3, 4)
	printSlice(s)
}

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

上のプログラムでは、まず空のスライスを作成している。この容量は0である。そのスライスに対して1要素を追加する append を実行すると、新たな長さ1の配列をつくりそのスライスを返す。append は副作用のない関数なので戻り値を代入しないと効果がないことに注意。その後は、もう一度同じことが繰り返される。複数まとめて追加した場合も、問題なく動作する。

容量がどれくらい拡張されるかは明示されていない。上のプログラムを実行すると、最後は追加した要素よりも大きい容量に拡張されている。なぜぴったりの容量ではないのかはわからない。effective go を読めば詳しいことがわかるかもしれない。

関数 append は容量が不足しているときは、暗黙的にコピーをしているということに注意。大きなスライスであればあるほど、コピーのコストは大きくなる。ただ、普段遣いには便利なので、性能面の問題が現れるまでは、積極的に使って良いと思う。

append は引数の渡し方によってはスライスの結合にも使うことができる。

a := []string{"John", "Paul"}
b := []string{"George", "Ringo", "Pete"}
a = append(a, b...)
// => []string{"John", "Paul", "George", "Ringo", "Pete"}

Range

range はイテレーションのためのキーワードで for 文と同時に利用する。 range の後ろにはイテレーションする対象の配列やスライスをとる。 range の評価値はインデックス、イテレーション要素の二つである。

package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
	for i, v := range pow {
		fmt.Printf("2**%d = %d\n", i, v)
	}
}

pow は 2n の値を持っているスライス。 このスライスに対して range をとり、各要素を出力している。

Range continued

不要なインデックスや値は _ に代入することで捨てることができる。 range でインデックスだけが必要なときは第二引数を省略してもよい。

package main

import "fmt"

func main() {
	pow := make([]int, 10)
	for i := range pow {
		pow[i] = 1 << uint(i) // == 2**i
	}
	for _, value := range pow {
		fmt.Printf("%d\n", value)
	}
}

pow は長さ10のスライスとして初期化する。 ビットシフト演算子を使って 1 を n 乗し、その値を pow に代入する。 最後にそのスライスの値だけを取り出して出力する。

Exercise: Slices

画像を生成するパッケージを使って、適当な二次元配列(スライス)を描画してみよう。

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
	result := make([][]uint8, dy)
	for i := range result {
		result[i] = make([]uint8, dx)
		for j := range result[i] {
			result[i][j] = uint8(i*j)
		}
	}
	return result
}

func main() {
	pic.Show(Pic)
}

二重ループの中でセットしている値を変えると、不思議な模様がでてくる。

Maps

マップはキーとバリューのペアからなる。 マップを初期化するには make 関数を使う。

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m map[string]Vertex

func main() {
	m = make(map[string]Vertex)
	m["Bell Labs"] = Vertex{
		40.68433, -74.39967,
	}
	fmt.Println(m["Bell Labs"])
}

このプログラムは文字列をキーとし、値を緯度経度の点とするようなマップを定義する。そしてベル研究所の座標をセットして、その後取り出せることを確認している。

Map literals

マップを直ちに生成するにはマップリテラルを使う。

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m = map[string]Vertex{
	"Bell Labs": Vertex{
		40.68433, -74.39967,
	},
	"Google": Vertex{
		37.42202, -122.08408,
	},
}

func main() {
	fmt.Println(m)
}

プログラムは、前の例と同じ型を持つマップを取り扱う。ただしマップリテラルによりベル研究所と、グーグルの座標を持つようなマップを初期化している。Println はマップを出力することもできる。マップリテラルは json と少し似ているかもしれない。

Map literals continued

マップリテラルでは、型を省略することもできる。

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}

func main() {
	fmt.Println(m)
}

ただし、マップが複雑な構造を持つ型の場合は省略できないことがあるようだ。

Mutating Maps

マップは代入文を使って要素を追加する。参照するときは、配列アクセスのように行う。 要素を削除したいときはビルドイン関数の delete を使う。参照したが要素が見つからなかった場合には、その値の型の初期値を返す。たとえば int なら 0 を返す。0 が代入されたものか、それとも代入されていない初期値かどうかを判断するには、参照したときにふたつ目の戻り値を利用する。そこには代入された値のとき true そうでないとき false を返すようになっている。

package main

import "fmt"

func main() {
	m := make(map[string]int)

	m["Answer"] = 42
	fmt.Println("The value:", m["Answer"])

	m["Answer"] = 48
	fmt.Println("The value:", m["Answer"])

	delete(m, "Answer")
	fmt.Println("The value:", m["Answer"])

	v, ok := m["Answer"]
	fmt.Println("The value:", v, "Present?", ok)
}

Exercise: Maps

与えられた文字列の、各単語の出現回数を計算してみよう。

package main

import (
	"fmt"
	"strings"
)

func WordCount(s string) map[string]int {
	result := make(map[string]int)
	words := strings.Fields(s)

	for _, word := range words {
		result[word] += 1
	}
	return result
}

func main() {
	fmt.Println(WordCount("this is a pen. this is an apple."))
}

Function values

関数も変数として渡すことができる。

package main

import (
	"fmt"
	"math"
)

func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}

func main() {
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x*x + y*y)
	}
	fmt.Println(hypot(5, 12))

	fmt.Println(compute(hypot))
	fmt.Println(compute(math.Pow))
}

プログラムは、関数 compute を定義している。compute は、2引数関数を受け取り、その関数に引数3, 4を与えた結果を返す。メイン関数では hypot という関数を定義して三角形の底辺と高さを与えると斜辺の長さを返す関数を定義している。そして compute で呼び出せることを確かめている。math.Pow もまた同じ型の引数を取るので、同じように compute を呼び出すことができる。 このような関数の取り扱いは javascript と少し似ているかもしれない。

Function closures

go の関数はクロージャ(closure)である。

package main

import "fmt"

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

func main() {
	pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println(
			pos(i),
			neg(-2*i),
		)
	}
}

このプログラムは adder 関数は関数を返している。そして、関数を返す前に変数 sum を定義して、返却する関数の中で sum を利用している。これによって adder を呼び出したときカウントを初期化し、戻り値の関数を呼び出したときに加算されていくカウンタを実現している。

メイン関数では二つのカウント pos, neg を用意し pos は 0,...,9 の和を計算している。neg には 0,-2,-4,...-18 の和を計算している。このとき、メイン関数の内部では += のような加算演算子を一切利用していないことに注目する。メイン関数では状態を保持しておらず、関数自体が状態を持っている。

Exercise: Fibonacci closure

連続するフィボナッチ数(0,1,1,2,3,5,8,13,...)を計算する。

// i 番目のフィボナッチ数を見つける
func fibonacci_r(i int) int {
	switch {
	case i == 0: return 0
	case i == 1: return 1
	default: return fibonacci_r(i-2) + fibonacci_r(i-1)
	}
}

このように再帰的に定義することもできるが、クロージャを使って、繰り返し呼び出すごとに次のフィボナッチ数を見つけるような実装をしてみよう。

package main

import "fmt"

func fibonacci() func() int {
	a := -1
	b := -1

	return func() int {
		switch {
		case a == -1:
			a = 0
			return 0
		case b == -1:
			b = 1
			return 1
		default:
			c := a + b
			a = b
			b = c
			return c
		}
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}

一つ前の数と、二つ前の数という2つの状態を覚える必要があるので、このような実装にした。クロージャを使う方法では、再帰的バージョンと比べて、計算に無駄がない。ときには、クロージャが有益に働くことを示している。

Methods

Go にはクラスがない。その代わりに型にメソッドを定義できる。 メソッドの定義は関数の定義とよく似ているが、関数名の前にレシーバー(レシーバー型)を書く。

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
}

このプログラムは、Vertex 型に対して、メソッド Abs を定義している。

Methods are functions

メソッドと関数は違うので注意。下記のプログラムは、メソッドを関数で書き直したもの。レシーバー型が無いことに注目する。

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func Abs(v Vertex) float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(Abs(v))
}

Methods continued

これまでは struct に対してメソッドを定義してきたが、それ以外の任意型にメソッドを定義できる。メソッドの宣言をするときのレシーバー型は、同じパッケージでなければならない。つまり、プリミティブ型(int, stringなど)に対してはメソッドを定義することはできない。その代わりに別の型として宣言すればメソッドを定義できる。

package main

import (
	"fmt"
	"math"
)

type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

func main() {
	f := MyFloat(-math.Sqrt2)
	fmt.Println(f.Abs())
}

上の例では MyFloat に対してメソッドを定義している。たとえば float64 に対して同じことを試みると、下記のエラーが出力される。

> cannot define new methods on non-local type float64

Pointer receivers

レシーバー型にポインタ型をとることもできる。

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(10)
	fmt.Println(v.Abs())
}

呼び出し方はまったく同じなのでわかりにくい。 Abs メソッドは構造体を引数にとっているので、値渡しとなる。 Scale メソッドはポインタを引数にとっているので、参照渡しとなる。 引数で受け取った構造体自身を加工した場合には、呼び出しもとに副作用がある。

Pointers and functions

メソッドの実装を関数で書き直してみよう。

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func Abs(v Vertex) float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func Scale(v *Vertex, f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	Scale(&v, 10)
	fmt.Println(Abs(v))
}

Methods and pointer indirection

package main

import "fmt"

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func ScaleFunc(v *Vertex, f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(2)
	ScaleFunc(&v, 10)

	p := &Vertex{4, 3}
	p.Scale(3)
	ScaleFunc(p, 8)

	fmt.Println(v, p)
}

プログラムは座標に対するメソッド scale と、座標に対する関数 ScaleFunc の二つを定義している。 メソッドの場合は、構造体でも、構造体ポインタでも同じように呼び出せる。 関数の場合は、構造体と、構造体ポインタではほんの少しだけ呼び出し方が違っている。

Methods and pointer indirection (2)

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func AbsFunc(v Vertex) float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
	fmt.Println(AbsFunc(v))

	p := &Vertex{4, 3}
	fmt.Println(p.Abs())
	fmt.Println(AbsFunc(*p))
}

上のプログラムはやはりこれまでと似ていてメソッドとして Abs を定義し、関数として AbsFunc を定義する。 これらの振る舞いは同じように見えるが、関数は構造体を引数に取ることにしているので、構造体ポインタを受け取ることはできない。

Choosing a value or pointer receiver

ポインタレシーバを使うべき場面は、二つある。副作用を与えたい場合と、値渡しによるコピーをしたくない場合。 一般的に、値渡しと参照渡しを混在させるべきではない。

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := &Vertex{3, 4}
	fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
	v.Scale(5)
	fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}

この例では、Scale, Abs メソッドはともにポインタレシーバを使っている。 Printf で %+v は構造体の属性名を出力するためのオプション。 %v の場合は構造体の値だけを出力する。

Interfaces

インターフェース(interface)型は、メソッドのシグニチャ(型情報)の集まり。 インタフェース型を定義した上で、その型の変数を宣言すると、そのシグニチャに一致する構造体なら何でも格納することができる。

package main

import (
	"fmt"
	"math"
)

type Abser interface {
	Abs() float64
}

func main() {
	var a Abser
	f := MyFloat(-math.Sqrt2)
	v := Vertex{3, 4}

	a = f  // a MyFloat implements Abser
	a = &v // a *Vertex implements Abser

	// In the following line, v is a Vertex (not *Vertex)
	// and does NOT implement Abser.
	a = v

	fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

このプログラムは Abser というインターフェースを定義している。これは Abs メソッドを持っていることを定めている。そして、メイン関数では Abser 型の変数 a を宣言している。a はカスタムな型 Myfloat をセットすることもできるし、Vertex 型のポインタをセットする事もできる。Vertex 型のポインタである理由は Abs の引数がポインタレシーバであるため。そして a にポインタではなく構造体そのものを代入しようとするとエラーが発生する。

> cannot use v (type Vertex) as type Abser in assignment: Vertex does not implement Abser (Abs method has pointer receiver)

なぜなら、Abs メソッドはポインタレシーバのみに定義されているため。

Interfaces are implemented implicitly

go におけるインターフェースは、暗黙的に実装するものであって、明示的に実装するようなキーワードは準備されていない。 逆に言うと、ある型が、あるインターフェース型を満たしているかどうかは、明示されない。

package main

import "fmt"

type I interface {
	M()
}

type T struct {
	S string
}

// This method means type T implements the interface I,
// but we don't need to explicitly declare that it does so.
func (t T) M() {
	fmt.Println(t.S)
}

func main() {
	var i I = T{"hello"}
	i.M()
}

上のプログラムでは、構造体 T はインターフェース I を満たしているので、I 型の変数に代入可能である。 ここで I になにかのメソッドを追加すると、T はそのインターフェースを満たせなくなるので、エラーが発生する。

Interface values

インターフェース型を宣言した変数がどのように扱われるかを見る。

package main

import (
	"fmt"
	"math"
)

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	fmt.Println(t.S)
}

type F float64

func (f F) M() {
	fmt.Println(f)
}

func main() {
	var i I

	i = &T{"Hello"}
	describe(i)
	i.M()

	i = F(math.Pi)
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}

上のプログラムではインターフェース型 I の変数 i を宣言している。 そして、そこに格納されている値を観察するための関数 describe を定義している。 構造体 T のポインタを代入している時 i は

  • 値は 構造体のポインタアドレス

  • 型は 構造体Tのポインタ である

という二つの情報を持っている。float64 の拡張型 F を代入しているときも同様に

  • 値は math.Pi

  • 型は F

という二つの情報を持っている。このように考えるとインターフェース型の変数に格納するのは (値, 型) のタプルであるとも言える。

Interface values with nil underlying values

インターフェース型の中にセットされている値が nil のとき、メソッドは nil をレシーバーとして呼び出す。多くの言語では nil をレシーバーとするメソッドはエラーを発生させるが、go では nil レシーバーを許容するような実装をする事が一般的らしい。

package main

import "fmt"

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}

func main() {
	var i I

	var t *T
	i = t
	describe(i)
	i.M()

	i = &T{"hello"}
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}

上のプログラムでは T のポインタをレシーバーとするメソッド M は nil を受け取ったときはエラーを出さず "<nil>" を出力するように実装されている。

メイン関数を見る。変数 t は初期値を与えられないので nil で初期化される。それを i にセットすると、やはり値は nil となる。このときに型情報は正しくセットされているので i はメソッド M を正しく呼び出すことができる。

Nil interface values

インターフェース型の変数を初期化したときは、nil がセットされる。この状態では実装を呼び出すための型情報が一切与えられないので、エラーとなる。

package main

import "fmt"

type I interface {
	M()
}

func main() {
	var i I
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}

The empty interface

空のインターフェースを定義することもできる。 空のインターフェースは、どのようなメソッドの実装も求めない。 空のインターフェース型を宣言した変数には、あらゆる値を代入することができる。 これを使うことで、任意の型を引数に取るような関数を実装できる。

package main

import "fmt"

func main() {
	var i interface{}
	describe(i)

	i = 42
	describe(i)

	i = "hello"
	describe(i)
}

func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}

上のプログラムでは describe 関数はどのような値でも受け取ることができることを表している。Printf も同様である。

Type assertions

type assertion はインターフェースを宣言した変数から、値を取り出すための演算で t, ok = i.(T) の形で記述する。 type assertion は値と、その値の取り出しに成功したかどうか、の二つの値を返す。2つ目の戻り値は省略できる。

省略した場合は成功したときのみ正しく動作し、失敗した場合にはエラーを発生させてプログラムは停止する。

package main

import "fmt"

func main() {
	var i interface{} = "hello"

	s := i.(string)
	fmt.Println(s)

	s, ok := i.(string)
	fmt.Println(s, ok)

	f, ok := i.(float64)
	fmt.Println(f, ok)

	f = i.(float64) // panic
	fmt.Println(f)
}

上のプログラムは、インターフェースを宣言した変数 i に文字列を代入している。 そして、i から文字列型を取り出している。これは成功する。 次に、i から浮動小数点数を取り出そうとしている。これは失敗する。 最後は戻り値を省略している場合で、これはエラーが発生してプログラムは停止する。

> panic: interface conversion: interface {} is string, not float64

Type switches

type switch は switch 文を型により分岐させるパターン。 type assertion の構文と似ているが特別のキーワード type を使う。 この type は type switch 以外に使うことはできない。

package main

import "fmt"

func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}

func main() {
	do(21)
	do("hello")
	do(true)
}

上のプログラムは関数 do で type switch を行う。

Stringers

もっともよく使われている interface の一つに fmt パッケージ に定義されている Stringer がある。 Stringer インタフェースは文字列を返す String() メソッドの実装を求める。

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func (p Person) String() string {
	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
	a := Person{"Arthur Dent", 42}
	z := Person{"Zaphod Beeblebrox", 9001}
	fmt.Println(a, z)
}

上のプログラムでは Stringer インターフェースを満たす構造体 Person を作成している。 そのおかげで fmt.Println の引数に Person 型の変数を与えると String() メソッドの結果が出力される。

Exercise: Stringers

IPAddr 型を実装してみよう。

package main

import (
	"fmt"
	"strings"
)

type IPAddr [4]byte

func (ipAddrs IPAddr) String() string {
	var ipAddrStrings [4]string

	for i, ip := range ipAddrs {
		ipAddrStrings[i] = fmt.Sprintf("%d", ip)
	}

	return strings.Join(ipAddrStrings[:], ".")
}

func main() {
	hosts := map[string]IPAddr{
		"loopback":  {127, 0, 0, 1},
		"googleDNS": {8, 8, 8, 8},
	}
	for name, ip := range hosts {
		fmt.Printf("%v: %v\n", name, ip)
	}

}

Errors

error 型はインターフェースの一つで Error() メソッドの実装を要求する。 これは string を返さなければならない。ある種の関数は、第二の戻り値に error 型を返す。 エラーが起きていれば何らかのエラー値が格納されていて、エラーが無いときは nil を返す。 たとえば、引数の文字列を整数に変換して返す関数 strconv.Atoi() は、上に書いた通りの振る舞いをする。

package main

import (
	"fmt"
	"time"
)

type MyError struct {
	When time.Time
	What string
}

func (e *MyError) Error() string {
	return fmt.Sprintf("at %v, %s",
		e.When, e.What)
}

func run() error {
	return &MyError{
		time.Now(),
		"it didn't work",
	}
}

func main() {
	if err := run(); err != nil {
		fmt.Println(err)
	}
}

上のプログラムは型 MyError を宣言している。そして、MyError は error インターフェースを満たしている。関数 run はその場で MyError 構造体を初期化して返す。メイン関数では、この run 関数を呼び出した後、エラーが存在するならその内容を出力するというものになっている。

Exercise: Errors

以前 Sqrt 関数を実装した。これを改良して、実行に失敗したときはエラーを返すようにしてみよう。

package main

import (
	"fmt"
)

type ErrNegativeSqrt float64

func (self ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %f", self)
}

func fx(x, z float64) float64 {
	return z * z - x
}

func fdashx(x, z float64) float64 {
	return x * z
}

func Sqrt(x float64) (float64, error) {
	if x > 0 {
		result := 1.0

		for i := 0; i<10; i++ {
			fmt.Println(result)
			result = result - fx(x, result) / fdashx(x, result)
		}
		return result, nil
	} else {
		return 0, ErrNegativeSqrt(x)
	}

}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}

Readers

io パッケージには Reader というインターフェースが定義されている。 Reader が要求するのは Read() というメソッドで、バイトのスライスを受け取り、 戻り値として読み込んだバイト数と、エラーが有る場合はエラーの二つを返す。 読み込みがストリームの終端に来たときは、定数 io.EOF を返す。

package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	r := strings.NewReader("Hello, Reader!")

	b := make([]byte, 8)
	for {
		n, err := r.Read(b)
		fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
		fmt.Printf("b[:n] = %q\n", b[:n])
		if err == io.EOF {
			break
		}
	}
}

上のプログラムは strings のメソッド NewReader を呼び出している。 このメソッドは与えられた文字列の Reader を返す。 そして、容量8のバイトスライスに、Reader から読み取った値をセットしている。

  • 一回目のループで "Hello, R" の8バイトが読み込まれて n にセットされる。

  • 二回目のループで "eader!" が読み込まれて n にセットされる。

  • 三回目のループでは n はセットされず err に io.EOF がセットされる。

そして、プログラムは終了する。

Exercise: Readers

ASCII 文字の "A" を無限に出力する Reader を作ってみよう。

package main

import "golang.org/x/tour/reader"

const a_char byte = 65

type MyReader struct{}

func (m MyReader) Read(b []byte) (int, error) {
	for i := range b {
		b[i] = a_char
	}

	return len(b), nil
}

func main() {
	reader.Validate(MyReader{})
}

Exercise: rot13Reader

ROT13 換字式暗号をすべてのアルファベットの文字に適用して読み出す rot13Reader を実装してみよう。

package main

import (
	"io"
	"os"
	"strings"
)

type rot13Reader struct {
	r io.Reader
}

func (reader rot13Reader) Read(bytes []byte) (int, error) {
	n, err := reader.r.Read(bytes)

	for i := 0; i<n; i++ {
	    b := bytes[i]
		switch {
		case b > 'a' && b < 'z':
			bytes[i] = ((bytes[i] - 'a' + 13) % 26) + 'a'
		case b > 'A' && b < 'Z':
			bytes[i] = ((bytes[i] - 'A' + 13) % 26) + 'A'
		default:
		}
	}

	return n, err
}

func main() {
	s := strings.NewReader("Lbh penpxrq gur pbqr!")
	r := rot13Reader{s}
	io.Copy(os.Stdout, &r)
}

Images

image パッケージは下記の Image インターフェースを定義している。

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}

Rectangle は image で定義された型で、color.Model と color.Color は image/color パッケージで定義されたインターフェース型である。これらの実装型としては color.RGBAModel, color.RGBA がある。

package main

import (
	"fmt"
	"image"
)

func main() {
	m := image.NewRGBA(image.Rect(0, 0, 100, 100))
	fmt.Println(m.Bounds())
	fmt.Println(m.At(0, 0).RGBA())
}

メイン関数の一行目は、長さ100の正方形のRGBA画像をつくっている。初期化時点ではすべての色が 0 (黒)である。 二行目ではBounds()メソッドにより画像mを囲う点を出力する。Boundsの戻り値はRectangleである。 三行目では画像の点(0,0)の位置における色を出力している。

Exercise: Images

Image インターフェースを満たすような型を実装して遊んでみよう。

package main

import "image"
import "image/color"
import "golang.org/x/tour/pic"

type Image struct{}

func (img Image) ColorModel() color.Model {
	return color.RGBAModel
}

func (img Image) Bounds() image.Rectangle {
	return image.Rect(0, 0, 100, 100)
}

func (img Image) At(x, y int) color.Color {
	return color.RGBA{uint8(x*y), uint8(x*y), uint8(x*y), 255}
}

func main() {
	m := Image{}
	pic.ShowImage(m)
}

At で取り出せる画像の色を変化させることで、色々な模様を作ることができる。

Goroutine

goroutine は軽量なスレッド。キーワード go の後に関数を続けて書くだけでゴルーチンを実行できる。 ゴルーチンの引数はスレッドで評価されず、関数だけがスレッドで評価される。

package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
}

このプログラムは0.1秒ごとに与えた文字列を5回出力する say 関数を定義している。 そしてゴルーチンを使って "world" を出力させながら "hello" を出力している。 スレッド実行されている間もプログラムは進むのでおおよそ "hello world" が出力される。 スレッドが早く起動した場合は "world hello" になることもあり、安定しない。

Channel

チャネルを用いてゴルーチンとメインプログラムの間で、値の送受信ができる。 チャネルは make 関数を使って生成する。 チャネルにはそのチャネルが受け取ることのできる型を宣言する必要がある。 チャネルに値を渡したり、受け取ったりするにはチャネルオペレータ <- を使う。

package main

import "fmt"

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // send sum to c
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // receive from c

	fmt.Println(x, y, x+y)
}

関数sumは与えられた配列の総和をチャネルに送信する。 メイン関数では適当な配列とチャネルを用意した後、 配列を二つに分割してそれぞれの総和をチャネルに送信している。 それらの計算はゴルーチンにより並列実行される。 メイン関数はゴルーチンから計算結果が与えられるまで待機したあと、 最後にそれぞれの結果と、和を出力して終了する。

Buffered Channels

チャネルはバッファとして使うこともできる。 make 関数でチャネルを生成するとき、第二引数に長さを与えるとそれはバッファとなる。 バッファは長さのぶんだけ値を受け取り、貯めておくことができる。 バッファの長さ以上に値を送信した場合、そのスレッドはバッファが空くまで待ち状態になる。 バッファは first in first out で動作する。

package main

import "fmt"

func main() {
	ch := make(chan int, 2)
	ch <- 1
	ch <- 2
	ch <- 3
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}

上のプログラムは長さ2のバッファに3つの値を送信している。 その結果、3つ目の値は受け取ることができないので待ち状態となる。 プログラムは下記のエラーを出して終了する。

> fatal error: all goroutines are asleep - deadlock!

Range and Close

チャネルにそれ以上の値を送ることができない場合にはチャネルをクローズする。 チャネルがクローズされたかどうかを判断するには値を受け取るときに第二引数をとればよい。 受信する値がなく、チャネルが閉じているときは false が与えられる。 また、for i := range c の形式でチャネルに対するループを書くこともできる。 この場合もチャネルがクローズされるまでループは継続する。

チャネルがクローズされているのに値が送信され場合はpanicになるので実行してはいけない。 また、チャネルは必ずしもクローズしなければならないわけではないということに注意する。 必要があるときにだけクローズするべきである。

package main

import (
	"fmt"
)

func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
	}
	close(c)
}

func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	for i := range c {
		fmt.Println(i)
	}
}

n番目のフィボナッチ数を計算してチャネルcにその結果を送信し続ける関数fibonacciを持つ。 メイン関数では10個のフィボナッチ数をゴルーチンに計算させている。 チャネルに値が送られるたびにメインスレッドはその値を出力する。

Select

select 文はゴルーチンを待ち状態にする。 そして select 文に続く case 文のいずれかの条件を満たすとプログラムが動き始める。 複数の case 文が同時に条件を満たした場合、そのうちのいずれかが実行される。 実行順序は保証されない。

package main

import "fmt"

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}

このプログラムは関数fibonacciを持つ。これは、チャネルcに値を送信可能なら次のフィボナッチ数を計算する。 またチャネルquitから値を受信可能なら"quit"を出力してプログラムを停止する。 メイン関数では、その場で名前のない関数を作りゴルーチンを起動している。 内容は10回チャネルcから値を受信して標準出力に送ったのちに、quit に 0 を送信するというものである。 その後fibonacci を呼び出している。実際にこのプログラムを実行すると

  1. まずゴルーチンが起動しチャネルcから値を取り出そうとして待ち状態に入る

  2. メイン関数がfibonacciを実行し最初のcase文に条件一致してc <- 0 が実行される

  3. ゴルーチンはcを受け取りx,yを更新する。再び待ち状態に入る

  4. メイン関数はfibonacciの続きを実行するチャネルcは受信可能なので c <- 1 が実行される

  5. 以下10回繰り返される。

  6. メイン関数はfibonacciの中でcを送信しようとするがcはバッファを持たないので2度目のselectで待ち状態に入る

  7. ゴルーチンはfor文を終了してquit <- 0を実行する。

  8. メイン関数はfibonacciの中でquitが受信可能になったことを察知して終了する

Default Selection

select文にはdefaultを定義することもできる。 どのcaseにもマッチしなかった場合は待ち状態に入らずdefaultの中を実行する。

package main

import (
	"fmt"
	"time"
)

func main() {
	tick := time.Tick(100 * time.Millisecond)
	boom := time.After(500 * time.Millisecond)
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:
			fmt.Println("    .")
			time.Sleep(50 * time.Millisecond)
		}
	}
}

このプログラムはチャネルtickとboomを初期化している。 tickは100ミリ秒ごとに値を送信するチャネルで、 boomは500ミリ秒あとに値を送信するチャネルである。 select文の中でtick,boom が値を返すときにはそれぞれに対応した文字を出力する。 どちらも値を返さないときはdefaultの中に入り、" ."を出力したあと少しプログラムを停止させる。 このプログラムを実行すると" ."が2回のあと"tick."を5回繰り返し、最後に"BOOM!"を出力して終了する。

Exercise: Equivalent Binary Trees

ある数列を二分木の深さ優先で探索で表現することにしよう。 このとき、1つの数列を表現する二分木は複数存在する。(→) ゴルーチンを使って、これを計算してみよう。

package main

import (
	"fmt"
	"golang.org/x/tour/tree"
)


func walk2(t *tree.Tree, ch chan int) {
	if t.Left != nil {
		walk2(t.Left, ch)
	}

	ch <- t.Value

	if t.Right != nil {
		walk2(t.Right, ch)
	}
}

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
	walk2(t, ch)
	close(ch)
}

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go Walk(t1, ch1)
	go Walk(t2, ch2)

      // t1, t2の長さが違う場合、下記のループは正しく動作しない
	for x := range ch1 {
		if x != <-ch2 {
			return false
		}
	}

	return true
}

func testWalk() {
	k := 10
	ch := make(chan int)
	go Walk(tree.New(k), ch)
	for i := range ch {
		fmt.Println(i)
	}
}

func testSame() {
	result := Same(tree.New(1), tree.New(1))
	fmt.Println("Same(tree.New(1), tree.New(1)) => ", result)

	result = Same(tree.New(1), tree.New(2))
	fmt.Println("Same(tree.New(1), tree.New(2)) => ", result)
}

func main() {
	testWalk()
	testSame()
}

sync.Mutex

ゴルーチンで排他制御が必要な場合は、syncパッケージを使うとよい。 syncパッケージは型sync.Mutexを提供する。MutexはLock,Unlockメソッドを持つ。 Lockはそのミューテックスをロックする。 すでにロックされているミューテックスに対してLockを呼び出すとアンロックされるまで、ゴルーチンは停止する。

package main

import (
	"fmt"
	"sync"
	"time"
)

// SafeCounter is safe to use concurrently.
type SafeCounter struct {
	mu sync.Mutex
	v  map[string]int
}

// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
	c.mu.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	c.v[key]++
	c.mu.Unlock()
}

// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
	c.mu.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	defer c.mu.Unlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
}

SafeCounterはメソッドIncを持つ。Incはカウンタを1加算するが、その計算の間はロックする。 Valueメソッドは値を取り出す間はロックする。 メイン関数では、そのような機能を備えたSafeCounterを初期化してキー"somekey"を1000個のゴルーチンでカウンタをすすめる。 1秒後にカウンタの値を出力する。 このプログラムを実際に動かすと1000個のゴルーチンが一斉にカウントアップしようとするが、 それらは正しく排他制御されているので最後の出力結果は1000となる。

試しにメソッドIncの排他制御をコメントアウトしてプログラムを実行すると下記のエラーが発生してプログラムは停止した。

> fatal error: concurrent map writes

Exercise: Web Crawler

Web Clawler を作ってみよう。

package main

import (
	"fmt"
	"sync"
)

type Fetcher interface {
	// Fetch returns the body of URL and
	// a slice of URLs found on that page.
	Fetch(url string) (body string, urls []string, err error)
}

type urlHistory struct {
	mu sync.Mutex
	urls map[string]bool
	channel chan string
}

func (u *urlHistory) isVisit(url string) bool {
	u.mu.Lock()
	defer u.mu.Unlock()

	visit, ok := u.urls[url]
	return ok && visit
}

func (u *urlHistory) visit(url string) {
	u.mu.Lock()
	defer u.mu.Unlock()

	u.urls[url] = true
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher, urlHistory *urlHistory) {
	// TODO: Fetch URLs in parallel.
	// TODO: Don't fetch the same URL twice.
	// This implementation doesn't do either:
	if depth <= 0 {
		return
	}

	body, urls, err := fetcher.Fetch(url)
	urlHistory.visit(url)

	if err != nil {
		fmt.Println(err)
		return
	}

	str := fmt.Sprint("found: %s %q\n", url, body)
	urlHistory.channel <- str

	for _, u := range urls {
		if urlHistory.isVisit(u) {
			// fmt.Printf("already visited! %s\n", u)
		} else {
			go Crawl(u, depth-1, fetcher, urlHistory)
		}
	}
	return
}

func main() {
	urlHistory := urlHistory{
		urls: make(map[string]bool),
		channel: make(chan string),
	}

	go Crawl("golang.org", 4, fetcher, &urlHistory)

	for str := range urlHistory.channel {
		fmt.Printf(str)
	}
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
	body string
	urls []string
}

func (f fakeFetcher) Fetch(url string) (string, []string, error) {
	if res, ok := f[url]; ok {
		return res.body, res.urls, nil
	}
	return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
	"golang.org": &fakeResult{
		"The Go Programming Language",
		[]string{
			"golang.org/pkg",
			"golang.org/cmd",
		},
	},
	"golang.org/pkg": &fakeResult{
		"Packages",
		[]string{
			"golang.org",
			"golang.org/cmd",
			"golang.org/pkg/fmt",
			"golang.org/pkg/os",
		},
	},
	"golang.org/pkg/fmt": &fakeResult{
		"Package fmt",
		[]string{
			"golang.org",
			"golang.org/pkg",
		},
	},
	"golang.org/pkg/os": &fakeResult{
		"Package os",
		[]string{
			"golang.org",
			"golang.org/pkg",
		},
	},
}

すべてのゴルーチンが終了したときに、チャネルを閉じられればよいのだが、木の探索がいつ終わるかわからない。 プログラムは最後にエラーを出して停止する。

> osfatal error: all goroutines are asleep - deadlock!