A Tour of Go vol.8 (Goroutines)
Goroutines
・軽量なスレッド
・goキーワードに続く関数を新しいスレッド(=goroutine)で実行する
・goroutineが実行されていてもmain()が終了するとプロセスが終了する点は注意
・Message-passing communication(各プロセスはメッセージを送り合い、内容は書き変わらない)
Channels
・goroutine 間でのメッセージパッシングをするためのもの
・メッセージの型を指定できる
・bufferで1度に扱えるメッセージの量を指定できる
・送信用チャネルはクローズする必要がある
・後述のselectで複数のチャネルから同時にメッセージを受信できる
// チャネルの生成 make(chan 要素数,キャパシティ) make(chan 要素型) // チャネルへの値の送受信 チャネル <- 送信する値 <- チャネル // バッファの指定 ch := make(chan int, 100)
// 第二引数で送信先であるチャネル(変数:c)を受け取る func sum(s []int, c chan int) { sum := 0 for _, v := range s { sum += v } // send sum to c c <- sum } func main() { s := []int{7, 2, 8, -9, 4, 0} // int型のチャネルを生成 c := make(chan int) go sum(s[:len(s)/2], c) go sum(s[len(s)/2:], c) // receive from c x, y := <-c, <-c fmt.Println(x, y, x+y) }
Select
・複数ある case のいずれかが準備できるようになるまでブロックし、準備ができた case を実行
・複数の case の準備ができている場合、 case はランダムに選択される
func fibonacci(c, quit chan int) { x, y := 0, 1 for { select { // 変数xの値をチャネルcを通して送信 case c <- x: x, y = y, x+y // チャネルquitが準備できたら"quit"を出力 case <-quit: fmt.Println("quit") return } } } func main() { // 1:チャネル作成 c := make(chan int) quit := make(chan int) go func() { // 2:0~9まで10回反復処理 for i := 0; i < 10; i++ { // 受信したメッセージを表示 fmt.Println(<-c) } // 送信用チャネルquitにメッセージ0を送信 quit <- 0 }() // フィボナッチ関数にチャネルを引数として渡して実行 fibonacci(c, quit) }
A Tour of Go vol.7(Interfaces/Type assertions)
Interfaces
・メソッドの型だけを定義した型
・オブジェクトの振る舞いを定義する
下記の例では Abserをインターフェースとする変数aに対し、Abserで定義しているメソッドAbs()を実装していない型の変数を代入しようとしてエラーになる。
// インターフェースAbserはメソッドAbsを振る舞いとして定義 type Abser interface { Abs() float64 } func main() { var a Abser f := MyFloat(-math.Sqrt2) v := Vertex{3, 4} a = f // a MyFloat implements Abser -> 1 a = &v // a *Vertex implements Abser -> 2 // In the following line, v is a Vertex (not *Vertex) // and does NOT implement Abser. a = v fmt.Println(a.Abs()) } type MyFloat float64 // 1: MyFloatはメソッドabs()を持つ func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } type Vertex struct { X, Y float64 } // 2: *Vertex(ポインタ型)はメソッドabs()を持つ func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }
インターフェースを実装する際はimplementsの表記は必要ない。
インターフェースのわかりやすい実装例
dev.classmethod.jp
nil
一旦離脱するがGoではnilは型を持つ
func main() { var i32 *int32 fmt.Println(i32 == nil) // true var arr []int fmt.Println(arr == nil) // true fmt.Println(compare(i32, arr)) // false }
基本的にnilは "==nil" がtrueになるが、インターフェースの時だけ型もnilでないと==nilはfalseとなる。
func main() { var arr []int fmt.Println(arr == nil) // true fmt.Println(reflect.ValueOf(arr).IsNil()) // true fmt.Println(reflect.TypeOf(arr)) // []int var arr2 interface{} = arr fmt.Println(arr2 == nil) // false fmt.Println(reflect.ValueOf(arr2).IsNil()) // true fmt.Println(reflect.TypeOf(arr2)) // []int var arr3 interface{} fmt.Println(arr3 == nil) // true fmt.Println(reflect.TypeOf(arr3)) // <nil> }
arr2はinterfaceとして扱っているため、型がnilでないので== nilはfalseとなる
isNilはあくまでBalueOfの下から値がnilだということを言いたいのか。
== nilは値に限らずnilをチェックしているのだろうが深堀はしない。
Type assertions
インターフェースはどんな型でも受け入れるので引数の型をインターフェース型にすればどんな型の値も受け取る関数をつくることができる。
しかし、この方法で受け渡された値は元の型がなにだったのかということを知ることができない。
この時に型アサーションを使えば動的に元の型がなんであるのかをチェックすることができる。
value, ok := <変数>.(<型>)
valueには型アサーション成功時に実際の値が格納され、2番目の変数okにはアサーションの成功否が格納される。
func main() { printIf(12) printIf("hello") printIf([]string{"cat", "dog"}) printIf([2]string{"hello", "world"}) } func printIf(src interface{}) { if value, ok := src.(int); ok { fmt.Printf("parameter is integer. [value: %d]\n", value) return } if value, ok := src.(string); ok { value = strings.ToUpper(value) // 対象がstring型なのでstringを引数に取る関数が実行できる fmt.Printf("parameter is string. [value: %s]\n", value) return } if value, ok := src.([]string); ok { value = append(value, "unknown") // 対象がsliceなのでAppendができる fmt.Printf("parameter is slice string. [value: %s]\n", value) return } fmt.Printf("parameter is unknown type. [valueType: %T]\n", src) }
Type switches
型アサーションと分岐を組み合わせた処理を手軽に記述することができる。
func printIf(src interface{}) { switch value := src.(type) { case int: fmt.Printf("parameter is integer. [value: %d]\n", value) case string: value = strings.ToUpper(value) // 対象がstring型なのでstringを引数に取る関数が実行できる fmt.Printf("parameter is string. [value: %s]\n", value) case []string: value = append(value, "<不明>") // 対象がsliceなのでAppendができる fmt.Printf("parameter is slice string. [value: %s]\n", value) default: fmt.Printf("parameter is unknown type. [valueType: %T]\n", src) } }
A Tour of Go vol.6 (Methods/Receiver)
Methods
Goはクラスの仕組みを持たないが型にメソッドを持つことができる。
メソッドは引数を
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()を持つ。
レシーバー: メソッドを呼び出される対象
クラスでメソッドを実装してきた身としてはメソッドありきでどの型に属すかを引数で決めるという順番はすごく慣れない。
レシーバーは値型もポインタ型も取ることができる。
ポインタ型
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { fmt.Println(v) //{30 40} return math.Sqrt(v.X*v.X + v.Y*v.Y) } func (v *Vertex) Scale(f float64) { v.X = v.X * f //3*10=30 v.Y = v.Y * f //4*10=40 } func main() { v := Vertex{3, 4} v.Scale(10) fmt.Println(v.Abs()) }
ポインタ型ではmain関数で宣言したVertext変数を操作する
値型
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { fmt.Println(v) //{3 4} 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()) }
値型ではVertext変数のコピーを操作する
type Vertex struct { X, Y float64 } // メソッドがポインタレシーバ func (v *Vertex) Scale(f float64) { fmt.Println(v) v.X = v.X * f v.Y = v.Y * f fmt.Println(v) } func ScaleFunc(v *Vertex, f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} // vは値型 v.Scale(2) // ScaleFuncの第一引数はポインタ型なので&v ScaleFunc(&v, 10) p := &Vertex{4, 3} // pはポインタ型 p.Scale(3) // ScaleFuncの第一引数はポインタ型でpはもともとポインタ型 ScaleFunc(p, 8) fmt.Println(v, p) }
type Vertex struct { X, Y float64 } // 値型のレシーバー func (v Vertex) Abs() float64 { fmt.Println(v) 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はポインタ型 p := &Vertex{4, 3} // Absは値型のレシーバだけどポインタ型も受け取る fmt.Println(p.Abs()) fmt.Println(AbsFunc(*p)) }
主なポインタレシーバを使う2つの理由
- メソッドでレシーバが指す先の変数を変更させたい
- メソッドの呼び出し毎に変数のコピーを避けたい
レシーバーを値型/ポインタ型使い分ける考え
qiita.com
POLARの運動データを取得してかっこいいグラフにしたい
開発動機
私が通っている暗闇ボクシングではPOLARという心拍計測器を使うことができ、心拍数や運動時間から下記の項目を算出してくれます。
計測項目
- 運動時間
- 最高心拍数
- 平均心拍数
- 心拍数推移
- 消費カロリー
2日に1回以上のペースで通って2ヶ月以上経ちましたが、全然動けない時、高い心拍数を叩き出せたときなど差があり、その差をいまいち把握できていません。
実際自分のデータから何か傾向を掴みたいと思い、"マイページに蓄積されているPOLARデータから時間別・パフォーマー別など自分でカスタマイズしたグラフを出す"というものをつくろうと思いました。
いきごみ
本業はアプリ開発でWebに関して知識が乏しいため、できるだけシンプルなプロダクトにしました。
つまずきながらもなんとか自分で進められるようレベル設定したつもりです。
これから分割した作業を少しずつ順番にこなして、目標は2ヶ月以内に完成させたいと思っています。
そして、現在の運動のペースを保ち続けること。
ゆくゆくは朝食食べてから行った時とか前日の睡眠時間とかも記録していきたいです。
A Tour of Go vol.5 (Range / Map)
Range
スライスやマップの要素についてindexとあわせて1つずつ返す。
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} for i, v := range pow { fmt.Printf("index:%d value:%d\n", i, v) }
第一引数のindexについては"_"を使うことで破棄することもできる。
Maps
キーと値とを関連付ける(マップする)。
ゼロ値はnil
type Vertex struct { Min, Max int64 } // キーがString型 値がVertex型のマップ型 var m map[string]Vertex func main() { m = make(map[string]Vertex) m["Size Range"] = Vertex{ 40, 74, } fmt.Println(m["Size Range"]) }
初期化
var g = map[string]Vertex{ "Kids Size Range": {40, 60}, "Teens Size Range": {61, 100}, }
値の確認
elem, ok := m[key]
マップmにkeyがあれば2つ目の返却値でtrueが返ってくる。
クロージャ
クロージャーの説明が1番わかりやすかった記事
なぜクロージャ(Closure)と言うのか?
関数が自信のスコープ外から変数を参照し、変更を与える場合にその関数のことをクロージャーと見なされる。
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関数がクロージャーである無名関数を返却している。
無名関数が格納された変数posとnegはそれぞれadder関数の持つsum変数を参照し、値を操作している。
この時"posとnegは変数sumへバインドされている"と表現する。
A Tour of GoのBasicsが完了しました!!
Next: Methods and interfaces
Last: Concurrency
A Tour of Go vol.4 (Pointer / Array / Slice)
ここから本格的な学び
Pointer
値のメモリのアドレスのこと。
type
既存の型や型リテラルに別名をつけることができる。
type Vertex struct { X int Y int }
上記はstruct型をVertexという別名の型にしている。
typeについては下記の記事が言語仕様に則って説明してくれている。
Goを学びたての人が誤解しがちなtypeと構造体について #golang
Array
[n]T
n:配列の要素数
T:配列の型
配列の長さは変えることができない。
Slice
スライスは配列への参照のようなものであり、スライスの要素変更は元の配列に影響する。
よって同じ元となる配列を共有している他のスライスは、それらの変更が反映される。
ゼロ値はnil。
型
[]T
固定長の配列に対し、スライスは可変長。
primes := [6]int{1,2,3,4,5,6} var s []int = primes[1:4] //2, 3, 4
例)構造体を要素とする配列
複数のフィールドを持つオブジェクトを要素とする配列は下記の用に作る。
s := []struct { i int b bool }{ {2, true}, {3, false}, {5, true}, }
capacity
スライスはスライスの最初の要素から数えて、元となる配列の要素数をcapacityとして持つ。
スライス s の長さと容量は len(s) と cap(s) という式を使用して得る。
a := make([]int, 5) // len=5 cap=5 [0 0 0 0 0] b := make([]int, 0, 5) // len=0 cap=5 [] c := b[:2] // len=2 cap=5 [0 0] d := c[2:5] // len=3 cap=3 [0 0 0]
要素の追加
スライスへの新しい要素の追加はappend関数を使う。
var s []int // cap=0 [] s = append(s, 2, 3) // len=2 cap=2 [2 3]
A Tour of Go vol.3
For Continued
初期化と後処理ステートメントは省略可能。
for ; sum < 1000; { sum += sum }
whileはないので繰り返し処理は全てforで記述が統一される。
if
func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } return lim }
ifステートメントで宣言した変数はifスコープ内のみ有効なので注意する。
Switch
Switch文ではcaseの条件が一致した時点で自動的にbreakされる。
また条件なしで記述することも可能。
switch { case t.Hour() < 12: fmt.Println("Good morning!") case t.Hour() < 17: fmt.Println("Good afternoon.") default: fmt.Println("Good evening.") }
Defer
defer へ渡した関数の実行を、呼び出し元の関数の終わり(returnする)まで遅延させる。
使い所
func main() { file, err := os.Open("hoge.txt") if err != nil { fmt.Println("File open error: ", err) return } buf := make([]byte, 1024) for { n, err := file.Read(buf) if n == 0 { break } if err != nil { fmt.Println("File read error: ", err) return } fmt.Print(buf[:n]) } file.Close() }
上のプログラムの問題はエラーが発生した場合にファイルがCloseされないということ。
こういうところでDeferを使うケースが多いようだ。
func main() { file, err := os.Open("hoge.txt") if err != nil { fmt.Println("File open error: ", err) return } defer file.Close() // <- ここでCloseを遅延実行する buf := make([]byte, 1024) for { // 省略 } }
例外としてos.Exitを使った場合はdeferは実行されない。