Go Conference 2013 SpringのハンズオンでA Tour of Goやったけど目で読んでも頭に入ってこないのでメモりながらやった。
ちょっとしたまとめページになったのでさらっとよんで感触をつかむときには役に立つかもしれないけど、結局は演習などでコード書かないと身につかないことがわかったので時間をつくってちゃんとやった方がいいと思う。
Packages
- すべてのプログラムは
packageで構成される - プログラムの処理は
mainパッケージのmain関数で始まる - 他のパッケージは
importで取り込める -
import "websocket"のwebsoketはcode.google.com/p/go.net/websocketを指している
Imports
-
importは括弧でグループすることができる
import "fmt"
import "math"
↓
import (
"fmt"
"math"
)
Exported names
- インポートしたパッケージが外部に公開(Export)している名前を参照できる
- 公開する名前は最初の文字が大文字になっている
math.Pi
Functions
- 関数は0個以上の引数を取ることができる
- 変数、戻り値の型は後ろに来る
func add(x int, y int) int - 理由はGo's Declaration Syntaxを参照
Functions continued
- 同じ型の引数の場合は最後の型だけ残して省略できる
func add(x, y int) int
Multiple results
- 関数は複数の戻り値を返すことができる
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
Named results
- 複数の戻り値は名前付きで宣言しておくと
returnだけで返せる
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(split(17))
}
Variables
-
varを変数の宣言に使える - 同じ型が連続する場合は最後以外は省略可能
var x, y, z int
var c, python, java bool
Variables with initializers
-
var宣言では、変数ひとつひとつに初期値を渡せるvar x, y, z int = 1, 2, 3 - 初期値によって型を省略することができそのリテラルの型になる
var c, python, java = true, false, "no!"
Short variable declarations
- 関数内では
var宣言の代わりに、暗黙的な型宣言ができる:=の代入文を使うことができるc, python, java := true, false, "no!"
Basic types
- 基本型(組み込み型)
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 の別名
rune // int32 の別名
// Unicode のコードポイントを表す
float32 float64
complex64 complex128
Constants, Numeric Constants
-
constキーワードを使い定数を宣言できるconst Pi = 3.14 - 型としてはcharacter、string、boolean、数値(numeric)のみで使える
- 数値定数は高精度な値である
- 型のない定数は、その状況によって必要な型を取る
For
- 繰り返し文は
forだけで while文 はない - 括弧
()は不要で中括弧{}は必要
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
For continued, For is Go's "while"
-
forの条件の前後は省略可能for sum < 1000 {} -
;も省略できるのでこれが while の代わりになる - ループ条件も省略して
for {}にすれば無限ループになる
If, If with a short statement, If and else
-
ifは 括弧()は不要で中括弧{}は必要if x < 0 {} -
ifもforのように条件の前にステートメントを記述できるif v := math.Pow(x, n); v < lim {} - そのステートメントで宣言された変数は
ifおよびelseブロック内で有効になる
Structs, Struct Fields
- 構造体
structはフィールドの集まり - フィールドは
.でアクセスするv.X = 4 -
type宣言できる
type Vertex struct {
X int
Y int
}
Vertex{1, 2}
Pointers
- ポインタはあるがポインタ演算はない
- 構造体のポインタを渡して
.でフィールドにアクセスできる
p := Vertex{1, 2}
q := &p
q.X = 1e9
Struct Literals
-
structリテラルはName:構文を使って初期化できるr = Vertex{X: 1} -
Name:構文を使った場合は指定順序は無関係になる - 接頭辞
&はstructリテラルへのポインタを示すq = &Vertex{1, 2}
The new function
-
new(T)という表現は、ゼロ初期化したTの値をメモリに割り当て、そのポインタを返す -
var t *T = new(T)またはt := new(T)と書ける
Slices
- Goの配列表現sliceは値の配列を参照し、長さも含む
-
T型要素のsliceは[]Tとなるp := []int{2, 3, 5, 7, 11, 13}
Slicing slices
-
s[lo:hi]のように範囲を指定してsliceからsliceを作成できる
p == [2 3 5 7 11 13]
p[1:4] == [3 5 7]
p[:3] == [2 3 5]
p[4:] == [11 13]
Making slices
-
make関数はゼロ初期化した配列をメモリに割り当て、その配列を参照したsliceを返すa := make([]int, 5) - sliceは、長さと容量を持っており、容量より長くすることはできない
- 長さは
len(a)容量はcap(a)関数をつかって調べられる - 容量を指定するには
makeの 3 番目の引数を使うb := make([]int, 0, 5) // len(b)=0, cap(b)=5 - 省略した場合、容量は長さと同じになる
- 容量はre-slicingによって縮小することがある
Nil slices
-
var z []intなどで宣言したsliceの初期値はnil -
nilな sliceの長さと容量をlen(z), cap(z)で計算した時の値は 0 である
Range
-
forループのrangeはsliceやmapを1つずつ反復処理する - あとからでてくるチャネルからも1つずつ処理できる
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)
}
}
Range continued
-
rangeで受け取ったインデックスや値を使わない場合は_へ代入することで破棄することができる
for _, value := range pow {
fmt.Printf("%d\n", value)
}
for i, _ := range pow {
fmt.Printf("%d\n", pow[i])
}
Maps
- map はキーと値とを関連付ける
var m map[string]Vertex - mapはsliceでも使った
make関数で作成する - makeで作成するまでの
nilのmapは空なので要素を割り当てられないm = make(map[string]Vertex)
Map literals, Map literals continued
- mapリテラルは、structリテラルに似ているがキーが必要
-
stringがキーで、structが値のmapのリテラルは以下のようになる
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
- リテラルの要素から型が推定できる場合は型名を以下のように省略可能
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
Mutating Maps
- map
mへ要素(elem)の挿入や更新は:m[key] = elem - 要素の取り出しは:
elem = m[key] - 要素の削除は:
delete(m, key) - キーが存在するかどうかは、2つの値の代入で確認する
elem, ok = m[key] - キーが存在しない場合は ok は
falseになり、elemは要素の型のゼロ値になる
Function values
- 関数も変数として扱える
hypot := func(x, y float64) float64 { return math.Sqrt(x*x + y*y) } - 関数そのものを関数の引数とすることもできる
Function closures
- 関数はclosureである
- 関数が変数として代入されたスコープの関数外の変数を使える
Switch, Switch evaluation order
-
switchはcaseの最後で自動的にbreakする -
caseには式も書けて上から下へcaseを評価する - brakeせずに次の
caseのブロックへ通したい場合はfallthroughを入れる
case "linux":
fmt.Println("Linux.")
fallthrough
Switch with no condition
- 条件のない
switchはswitch trueと同じになる - 長いif-then-elseのつながりをシンプルに表現できる
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.")
}
Methods, Methods with pointer receivers
- Goにはクラスはないが
structにメソッドを定義できる - メソッドレシーバを
funcキーワードとメソッド名の間で、それ自身の引数リストで表現する (下記の例では(v *Vertex))
type Vertex struct {
X, Y float64
}
// Vertex.Absメソッド
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())
}
- ポインタのメソッドレシーバを使うのはメソッド呼び出し時に値をコピーによるオーバヘッドを避けるためと
structのフィールドの内容を更新するためがある
Methods continued
-
structだけではなくパッケージ内で任意の型にもメソッドを定義できる - ただし他のパッケージからの型や基本型 以外 に限る
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())
}
Interfaces
- インターフェース型はメソッド群を定義する
- インターフェース型で宣言した変数は、それらのメソッドを実装する型の値を持つことができる
type Abser interface {
Abs() float64
}
Interfaces are satisfied implicitly
- 型は(インターフェース型が定義した)メソッドを実装することでインターフェースを実装したことになり、明示的にインターフェースを持っていることを定義する必要がない
- この暗黙的なインターフェースはインターフェースを定義するパッケージから実装するパッケージを分離することができ、他に依存しない
- インタフェースのすべての実装を見つける必要もないし、新しいインターフェースの名前でそのインタフェースを関連付ける必要もないので、正確なインターフェースの定義を推奨する
Errors
- エラーは文字列でエラーそのものを説明する
-
errorインターフェースのErrorメソッドを定義することで表現する
type error interface {
Error() string
}
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
Web servers
-
httpパッケージはHTTPリクエストの処理を行うためのhttp.Handlerインターフェースを定義する -
http.Handlerインターフェースを実装することで HTTPリクエストの処理機能を提供することができる
type Hello struct{}
func (h Hello) ServeHTTP(
w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "Hello!")
}
func main() {
var h Hello
http.ListenAndServe("localhost:4000", h)
}
Images
-
imageパッケージは、以下のImageインターフェースを定義する
package image
type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}
- 詳細は ドキュメント を参照
-
color.Colorとcolor.Modelは共にインターフェースで、実装としては定義済みのcolor.RGBAとcolor.RGBAModelを使うことができる
Goroutines
- goroutineは、Goのランタイムに管理される軽量なスレッド、
main関数もgoroutineで実行される -
main関数のgoroutineの処理が終わると他のgroutineが実行中でもプロセスが終了する -
go f(x, y, z)と書くと 関数f は新しいgoroutine上で実行される - x,y,zの値の評価は現在のgoroutineで発生しfの実行は、新しいgoroutineで行われる
- goroutineは同じアドレス空間で実行されるため、共有メモリへのアクセスはきちんと同期する必要がある
- 同期の方法にsync パッケージがあるが、Goでは以降の章のものがあるのであまり必要されない
Channels
- チャネルはチャネルオペレータの <- を用いて値の送受信ができる直通ルートの型
-
map,sliceと同じくmake関数で生成するch := make(chan int) - 送受信する値の型の前に
chanをつける - デフォルトでは片方が準備できるまで送受信はブロックする(goroutineの処理がそこで止まる)
- これは明確なロックや条件変数がなくてもgoroutineの同期を許す
- データは矢印の方向に流れる
ch <- v // v をチャネル ch へ送る。
v := <-ch // ch から受信し、
// 変数を v へ割り当てる
func sum(a []int, c chan int) {
sum := 0
for _, v := range a {
sum += v
}
c <- sum // send sum to c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
Buffered Channels
- チャネルはバッファでき、送信した値をためておける
-
make関数の第2引数でバッファの長さを指定できるch := make(chan int, 100) - バッファがいっぱいになったチャネルへの送信はブロックする
- バッファが空のチャネルには受信をブロックする
func main() {
c := make(chan int, 2)
c <- 1
c <- 2 // バッファがいっぱいになる
c <- 3 // バッファがいっぱいなので送信がブロックされmainのgoroutinesが止まりデットロック
fmt.Println(<-c)
fmt.Println(<-c)
}
Range and Close
- 送り手はこれ以上の送信する値がないことを示すため
close(ch)でチャネルを閉じることができる - 受け手は受信の式の2つ目のパラメータが
falseかどうかで送り手が閉じたかどうかをテストできるv, ok := <-ch -
for i := range cはチャネルcが閉じるまで繰り返し値を受信する - 閉じたチャネルに送信するとpanicになるため受け手が勝手にチャネルを閉じると危険である
- チャネルは通常は閉じなくてよい
- チャネルを閉じないといけないの
rangeループなどで受け手が送信側の終了を判断しないといけないとき
Select
-
selectステートメントはgoroutineを複数の通信操作で待たせる -
selectはcaseの条件で通信が実行できるまでブロックし、条件が一致すればcaseの中の処理を実行する - 以下の例では
main関数と無名関数を処理する2つのgoroutineがあり、main側のgoroutineはfunc fibonacci内のselectでブロックする。無名関数を処理するgoroutineがcからの受信、quiteの送信を行うこととmain側のgoroutineでcase内の処理が実行され、quiteを受信した際にreturnし、main関数のgoroutineが処理を終える
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)
}
Default Selection
-
selectを評価したときにどのcaseにも一致しないのであればdefaultが実行される -
selectでブロックしたくないときはdefaultを使うことができる
func main() {
tick := time.Tick(1e8)
boom := time.After(5e8)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(5e7)
}
}
}