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)
}
}
}