LoginSignup
87
82

More than 5 years have passed since last update.

忙しい人のためのA Tour of Go

Last updated at Posted at 2013-04-15

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

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 {}
  • iffor のように条件の前にステートメントを記述できる 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) 関数をつかって調べられる
  • 容量を指定するには make3 番目の引数を使う 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

  • switchcase の最後で自動的にbreakする
  • case には式も書けて上から下へ case を評価する
  • brakeせずに次の case のブロックへ通したい場合はfallthroughを入れる
   case "linux":
        fmt.Println("Linux.")
        fallthrough

Switch with no condition

  • 条件のない switchswitch 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.Colorcolor.Model は共にインターフェースで、実装としては定義済みの color.RGBAcolor.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を複数の通信操作で待たせる
  • selectcase の条件で通信が実行できるまでブロックし、条件が一致すれば 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)
        }
    }
}
87
82
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
87
82