Go入門
ver. 2017/04
The Go gopher was designed by Renee French.
The gopher stickers was made by Takuya Ueda.
Licensed under the Creative Commons 3.0
Attributions license.
アジェンダ
■ 自己紹介
■ Goの紹介
■ Goの基本
■ 型・メソッド・インタフェース
■ ゴールーチン・チャネル
■ ネットワークプログラミング
■ go test と testingパッケージ
■ ハンズオン
2
自己紹介
上田拓也
twitter: @tenntenn
■ コミュニティ活動
Google Cloud Platform User Group (GCPUG) Tokyo
Goビギナーズ
golang.tokyo
Go Conference
3
Goの紹介
● Goとは?
● Goの特徴
● Goを勉強するには?
4
Goとは?
Googleが開発しているプログラミング言語
■ 特徴
● シングルバイナリ・クロスコンパイル
● 強力でシンプルな言語設計と文法
● 並行プログラミング
● 豊富な標準ライブラリ群
● 周辺ツールの充実
Goの紹介/Goとは?
5
Goの特徴 − シングルバイナリ・クロスコンパイル −
■ 環境変数のGOOSとGOARCHを指定する
開発環境とは違うOSやアーキテクチャ向けに
クロスコンパイルできる
Goの紹介/Goの特徴 − シングルバイナリ・クロスコンパイル −
6
# Windows(32ビット)向けにコンパイル
$ GOOS=windows GOARCH=386 go build
# Linux(64ビット)向けにコンパイル
$ GOOS=linux GOARCH=amd64 go build
シングルバイナリになるので
動作環境を用意しなくてよい
go buildはコンパイルするコマンド
Goの特徴 − 強力でシンプルな言語設計と文法 −
■ スクリプト言語の書きやすさ
● 冗長な記述は必要ない
■ 型のある言語の厳密さ
● 曖昧な記述はできない
■ 考えられたシンプルさ
● 機能を増やすことで言語を拡張していくこと
はしない
Goの紹介/Goの特徴 − 強力でシンプルな言語設計と文法 −
7
Goに入ってはGoに従え
= 言語の思想を理解しよう
Goの特徴 − 並行プログラミング −
■ ゴールーチン
● 軽量なスレッドに近いもの
● goキーワードをつけて関数呼び出し
■ チャネル
● ゴールーチン間のデータのやり取り
● 安全にデータをやり取りできる
Goの紹介/Goの特徴 − 並行プログラミング −
8
チャネル
ゴールーチン
A
ゴールーチン
B
データ
データ
go f()
Goの特徴 − 周辺ツールの充実 −
■ go tool として標準/準標準で提供
■ サードパーティ製のツールも充実
■ IDEによらない独立したツールとして提供
Goの紹介/Goの特徴 − 周辺ツールの充実 −
9
go build ビルドを行うコマンド
go test
xxxx_test.goに書かれたテスト
コードの実行
go doc, godoc ドキュメント生成
gofmt, goimports コードフォーマッター
golint コードチェッカー、リンター
gocode コード補完
Goの特徴 − 豊富な標準ライブラリ −
■ 標準ライブラリ一覧
 https://golang.org/pkg/
Goの紹介/Goの特徴 − 豊富な標準ライブラリ −
10
net/http HTTPサーバなど
archive, compress zipやgzipなど
crypto 暗号化
encoding JSON, XML, CSVなど
html/template HTMLテンプレート
os, path/filepath ファイル操作など
Goの勉強するには?
■ コミュニティ
● gophers-slack
○ 世界中のGopherが集まるチャット
● Qiita #Go
○ Go言語の初心者が見ると幸せになれる場所
■ 書籍
● The Go Programing Language(日本語)
● みんなのGo言語
Goの紹介/Goの勉強するには?
11
Goの基本
● A Tour of Goをやろう
● for, if, switch
● Goのインストール
● GOPATH
● go tool
12
A Tour of Goをやろう
■ A Tour of Go
● Goのチュートリアル
● Web上で実行できる
● Basicsにチャレンジしてみよう
Goの基本/A Tour of Goをやろう
13
繰り返し:for
■ Goの繰り返しはforのみ
Goの基本/繰り返しfor
14
// いつものfor
for i := 0; i <= 100; i++ {
}
// while的な使い方
for i <= 100 {
}
// 無限ループ
for {
}
()はいらない
分岐:if
■ 条件式の前に代入文などが書ける
Goの基本/分岐:if
15
// いつものif
if a == 0 {
}
// 代入文を書く
if a := f(); a > 0 {
fmt.Println(a)
} else {
fmt.Println(2*a)
}
()はいらない
分岐:switch
■ caseに式が書ける
■ breakは書かなくてよい
Goの基本/分岐:switch
16
switch a {
case 1:
fmt.Println("a is 1")
default:
fmt.Println("default")
}
swtich {
case a == 1:
fmt.Println("a is 1")
}
caseをまたぐ際には、
fallthroughを使う
何もしないと
breakになる
Goのインストール
■ インストール方法
 Goの公式サイトからダウンロード
■ ソースコードからビルドする
 Goの公式サイトを参考にする。
Goの基本/Goのインストール
17
Go1.5以上はビルドにGoが必要
そのため1.4のバイナリを入れておく
GOPATH
■ GOPATHとは?
 Goのソースコードやビルドされたファイルが入るパス。
importされるパッケージもここから検索される。
Goの基本/GOPATH
18
$GOPATH
├── bin
│ └── fuga
├── pkg
│ └── darwin_amd64
│ └── hoge.a
└── src
├── fuga
│ └── main.go
└── hoge
└── hoge.go
ビルドされた実行可能ファイルが入る
ビルドされたパッケージが入る。
pkg/GOARCH/pkgname.a
mainパッケージのGoソース。
src/cmdname/*.go
自作パッケージのGoソース。
src/pkgname/*.go
go tool − go install −
■ go install
 ビルドして、GOPATH以下に配置するコマンド。
Goの基本/go tool − go install −
19
$ export GOPATH=`pwd`
$ go install fuga
$GOPATH
├── bin
│ └── fuga
├── pkg
│ └── darwin_amd64
│ └── hoge.a
└── src
├── fuga
│ └── main.go
└── hoge
└── hoge.go
go install によって生成されたファイル
手元で試してみよう!
(src/fugaとsrc/hogeだけを使用)
go tool − go get −
■ go get
 パッケージをダウンロードしてビルドしてGOPATH以下に配置
するコマンド。
Goの基本/go tool − go install −
20
$ export GOPATH=`pwd`
$ go get github.com/nsf/termbox-go
└── src
└── github.com
├── mattn
│ └── go-runewidth
│ ...
│ └── runewidth_windows.go
└── nsf
└── termbox-go
...
└── terminfo_builtin.go
依存するパッケージも
インストールされる
.
├── pkg
│ └── darwin_amd64
│ └── github.com
│ ├── mattn
│ │ └── go-runewidth.a
│ └── nsf
│ └── termbox-go.a
手元で試してみよう!
型・メソッド・インタフェース
● 型の種類
● 配列・スライス・マップ・構造体
● type
● メソッド・インタフェース
● 埋め込み
21
型の種類
型・メソッド・インタフェース/型の種類
22
組み込み型 int, float64, string など
配列
[100]int など
要素の型と要素数は固定
スライス
[]int など
要素の型、要素数は可変。
マップ
map[string]int など
連想配列。
構造体
struct { a int } など
フィールドのリストを持つ
インタフェース
interface { m() int } など
メソッドのリストを持つ
配列 −1−
[要素数]要素の型
型・メソッド・インタフェース/配列 −1−
23
var a [3]int
a[1] = 10
b := [...]int{1, 2, 3}
for i, n := range b {
fmt.Println(i,"/",len(b),"=>", n)
}
Playgroundで動かす
要素数が違えば別の型
...で初期値の要素に合わせる
添字と値で繰り返せる
配列 −2−
値でコピーされる
型・メソッド・インタフェース/配列 −2−
24
a := [3]int{1, 2, 3}
b := a
a[0] = 10
for i := range a {
fmt.Println(a[i], b[i])
}
参照がコピーされる
わけではない
10, 1
2, 2
3, 3
関数に渡した場合も
同様に値がコピーされる
Playgroundで動かす
2つ目は省略可
スライス −1−
[]要素の型
make([]要素の型, 要素数[, キャパシティ])
型・メソッド・インタフェース/スライス −1−
25
a := make([]int, 3, 10)
fmt.Println(a, len(a), cap(a))
b := []int{1, 2, 3}
for i, n := range b {
fmt.Println(i,"/",len(b),"=>", n)
}
Playgroundで動かす
スライスでもrangeは使える
スライス −2−
スライスの背後には配列がある
型・メソッド・インタフェース/スライス −2−
26
a := [...]int{1, 2, 3, 4}
b := a[1:3]
fmt.Println(b, len(b), cap(b))
Playgroundで動かす
1 2 3 4
a[0] a[1] a[2] a[3]
b[0] b[1]
len(b) = 2
cap(b) = 3配列
スライス
スライス −3−
append(スライス, 要素...) スライス
型・メソッド・インタフェース/スライス −3−
27
var a []int // nil
for i := 0; i <= 10; i++ {
a = append(a, i * 10)
fmt.Println(len(a), cap(a), a)
}
Playgroundで動かす
appendした際にcapを
超えた場合は
新しく配列が確保される
スライス −4−
■ 課題1
配列とスライスをそれぞれ関数の引数や戻り値に
した場合の挙動の違いを考えてみよう。
■ 課題2
スライスへの任意位置への挿入、削除を実装して
みよう。
型・メソッド・インタフェース/スライス −4−
28
// スライスaとbを結合
c := append(a, b...)
Playgroundで動かす
可変長引数に
スライスを展開
マップ
map[キーの型]値の型
make(map[キーの型]値の型[, キャパシティ])
型・メソッド・インタフェース/マップ
29
a := make(map[string]int)
a["c"] = 100
n, ok := a["c"]
fmt.Println(n, ok)
b := map[string]int{"c":2, "d":4}
for k, v := range b {
fmt.Println(k, v)
}
Playgroundで動かす
値が存在すればnはその値、okはtrue
存在しなければ、nはゼロ値、okはfalse
キーと値で繰り返せる
構造体
フィールドのリストを持つデータ構造。
フィールドの型は任意の型を指定できる。
構造体のゼロ値は、フィールドすべてがゼロ値の構造体。
型・メソッド・インタフェース/構造体
30
a := struct{
N int
s string
}{
N: 100,
s: "hoge",
}
fmt.Printf("%#vn", a)
fmt.Println(a.N, a.s)
Playgroundで動かす
構造体リテラル
型情報
フィールド
typeを使った型の作成
type <型名> <型リテラル>|<既存の型>
型・メソッド・インタフェース/typeを使った型の作成
31
// 組み込み型に名前をつける
type Int int
// 他のパッケージの型に名前をつける
type MyWriter io.Writer
// 型リテラルに名前をつける
type Person struct {
Name string
}
intとIntは別の型として扱われる
typeで名前が付けれるもの
■ 組み込み型
int, float64, string など
■ 型リテラル
構造体、インタフェース、
マップ、スライス、チャネル、関数 など
■ 名前付きの型
パッケージの内外で作った型
型・メソッド・インタフェース/typeで名前を付けれるもの
32
別の型として再定義できる
メソッド −1−
type で定義した型はメソッドのレシーバにできる
型・メソッド・インタフェース/メソッド −1−
33
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
// 100をHex型として代入
var hex Hex = 100
// Stringメソッドを呼び出す
fmt.Println(hex.String())
Playgroundで動かす
メソッド −2−
■ レシーバにできるもの
● 名前の付いた型
typeで定義したもの
● パッケージ内の型のみ
パッケージ外の型はtypeで再定義する
● ポインタ型も含む
レシーバに変更を与えたい場合
レシーバも引数と同じ扱い
型・メソッド・インタフェース/メソッド −2−
34
type Hex int など
type S bufio.Scanner
など
func (p *Hoge) M() {
...
} など
メソッド −3−
■ 課題3
関数にメソッドを設けてみよう。
var f func(string) int
■ 課題4
レシーバをポインタにしてみてレシーバに変更を与
えてみよう。構造体以外も試してみよう。
■ 課題5
レシーバがnilの場合の挙動を試してみよう。
型・メソッド・インタフェース/メソッド −3−
35
インタフェース
● メソッドのリストを持つ
● メソッドのリストがインタフェースで規定しているものと一致
する型はインタフェースを実装していることになる
型・メソッド・インタフェース/インタフェース
36
var s interface {
String() string
}
s = Hex(100)
fmt.Println(s.String())
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
Playgroundで動かす
インタフェースを実装していることになる
型とメソッドとインタフェース
■ 既存の型にもインタフェースを実装
● 後づけで実装させる
● メソッドリストさえ一致してればよい
■ 構造体以外も実装可能
● typeで宣言すればメソッドが設けられる
● メソッドリストさえ一致してればよい
型・メソッド・インタフェース/型とメソッドとインタフェース
37
インタフェースの活用
■ 1つのメソッドしか持たない
● io.Writer: Writeメソッド
● io.Reader: Readメソッド
■ 標準パッケージで多く使われている
● fmt, net, bytes, encoding, bufio, os ...
● ファイルやネットワークのコネクション
● 抽象度の高いインタフェース
型・メソッド・インタフェース/インタフェースの活用
38
インタフェースは
Goの良い言語機能の1つ
インタフェース
■ 課題6
interface{} という型はどういう特徴を持つ型
か説明してください。
■ 課題7
インタフェース型を1つ作り、組み込み型、構造体
型、関数型にそれぞれ実装させてみましょう。ま
た、作ったインタフェース型を引数に取る関数を
作ってみましょう。
型・メソッド・インタフェース/インタフェース
39
型アサーション
インタフェース.(型)
インタフェース型の値を任意の型にキャストする。第2戻り値に
キャストできるかどうかが返る。
型・メソッド・インタフェース/型アサーション
40
var v interface{}
v = 100
n,ok := v.(int)
fmt.Println(n, ok)
s,ok := v.(string)
fmt.Println(s, ok)
Playgroundで動かす
型スイッチ
型によって処理をスイッチする
型・メソッド・インタフェース/型スイッチ
41
var i interface{}
i = 100
switch v := i.(type) {
case int:
fmt.Println(v*2)
case string:
fmt.Println(v+"hoge")
default:
fmt.Println("default")
}
Playgroundで動かす
インタフェース
i = "hoge"
も試してみよう
埋め込み −1−
構造体に匿名フィールドを埋め込む機能
型・メソッド・インタフェース/埋め込み −1−
42
type Hoge struct {
N int
}
// Fuga型にHoge型を埋め込む
type Fuga struct {
Hoge // 名前のないフィールドになる
}
埋め込み −2−
埋め込んだ値に移譲(継承とは違う)
型・メソッド・インタフェース/埋め込み −2−
43
type Hoge struct {N int}
type Fuga struct {Hoge}
f := Fuga{Hoge{N:100}}
// Hoge型のフィールドにアクセスできる
fmt.Println(f.N)
// 型名を指定してアクセスできる
fmt.Println(f.Hoge.N)
Playgroundで動かす
埋め込みの特徴
■ 型リテラルでなければ埋め込められる
● typeで定義したものや組み込み型
● インタフェースも埋め込められる
■ インタフェースの実装
埋め込んだ値のメソッドもカウント
型・メソッド・インタフェース/埋め込みの特徴
44
// Stringerを実装
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
// Hex2もStringerを実装
type Hex2 struct {Hex}
type Stringer interface {
String() string
}
Playgroundで動かす
インタフェースと埋め込み
■ 既存のインタフェースの振る舞いを変える
型・メソッド・インタフェース/インタフェースと埋め込み
45
type Hoge interface{M();N()}
type fuga struct {Hoge}
func (f fuga) M() {
fmt.Println("Hi")
f.Hoge.M() // 元のメソッドを呼ぶ
}
func HiHoge(h Hoge) Hoge {
return fuga{h} // 構造体作る
}
Mの振る舞いを変える
インタフェースと埋め込み
■ 課題8
以下のコードは有効でしょうか?Playgroundで動
かして確認しましょう。
■ 課題9
前のスライドの例を実際にPlaygroundで動かして
挙動を確認しよう。
型・メソッド・インタフェース/インタフェースと埋め込み
46
type Hoge struct {N int}
type Fuga struct {Hoge}
f := Fuga{Hoge{100}}
var _ Hoge = f _は変数を使用しないときに使う記法
HiHogeの戻り値の型が
Hogeにできる理由は?
参考:インタフェースの実装パターン
ゴールーチン・チャネル
● 並行と並列
● ゴールーチン
● チャネル
● チャネルを使うパターン
47
Concurrency is not Parallelism
■ 並行と並列は別ものである by RobPike
● 並行:Concurrency
● 並列:Parallelism
■ Concurrency
● 同時にいくつかの質の異なることを扱う
■ Parallelism
● 同時にいくつかの質の同じことを扱う
ゴールーチン・チャネル/Concurrency is not Parallelism
48
並列と並行の違い
■ Concurrency
 同時にいくつかの質の異なることを扱う
■ Parallelism
 同時にいくつかの質の同じことを扱う
ゴールーチン・チャネル/並行と並列の違い
49
本を運ぶ
本を燃やす
台車を戻す
本を積む
本を燃やす 本を燃やす 本を燃やす
ゴールーチンとConcurrency
■ ゴールーチンでConcurrencyを実現
● 複数のゴールーチンで同時に複数のタスクをこなす
● 各ゴールーチンに役割を与えて分業する
■ 軽量なスレッドのようなもの
● LinuxやUnixのスレッドよりコストが低い
● 1つのスレッドの上で複数のゴールーチンが動く
■ ゴールーチンの作り方
● goキーワードをつけて関数を呼び出す
ゴールーチン・チャネル/ゴールーチンとConcurrency
50
複数のコアで動くとは限らない
go f()
無名関数とゴールーチン
ゴールーチン・チャネル/無名関数とゴールーチン
51
package main
import "fmt"
import "time"
func main() {
go func() {
fmt.Println("別のゴールーチン")
}()
fmt.Println("mainゴールーチン")
time.Sleep(50*time.Millisecond)
}
Sleepしないとすぐに終了する
http://play.golang.org/p/jy1HWriRTS
ゴールーチン間のデータのやりとり −1−
ゴールーチン・チャネル/ゴールーチン間のデータのやりとり −1−
52
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
ゴールーチン間のデータのやりとり −2−
ゴールーチン・チャネル/ゴールーチン間のデータのやりとり −2−
53
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
変数v
print(v) v = 100
共有の変数を使う?
ゴールーチン間で共有の変数を使う
ゴールーチン・チャネル/ゴールーチン間で共有の変数を使う
54
func main() {
done := false
go func() {
time.Sleep(3 * time.Second)
done = true
}()
for !done {
time.Sleep(time.Millisecond)
}
fmt.Println("done!")
}
共有の変数を使う
http://play.golang.org/p/mGSOaq4mcr
ゴールーチン間のデータ競合 −1−
ゴールーチン・チャネル/ゴールーチン間のデータ競合 −1−
55
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
変数v
print(v) v = 100
処理順序が保証されない
競合
ゴールーチン間のデータ競合 −2−
ゴールーチン・チャネル/ゴールーチン間のデータ競合 −2−
56
n := 1
go func() {
for i := 2; i <= 5; i++ {
fmt.Println(n, "*", i)
n *= i
time.Sleep(100)
}
}()
http://play.golang.org/p/yqk82u0E4V
for i := 1; i <= 10; i++ {
fmt.Println(n, "+", i)
n += 1
time.Sleep(100)
}
競合
データ競合の解決
■ 問題点
● どのゴールーチンが先にアクセスするか分からない
● 値の変更や参照が競合する
■ 解決方法
● 1つの変数には1つのゴールーチンからアクセスする
● チャネルを使ってゴールーチン間で通信をする
● またはロックをとる(syncパッケージ)
ゴールーチン・チャネル/データ競合の解決
57
"Do not communicate by sharing memory; instead,
share memory by communicating"
チャネルとは?
ゴールーチン・チャネル/チャネルとは?
58
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
ch<-100<-ch
チャネル
100
チャネルの特徴
■ 送受信できる型
● チャネルを定義する際に型を指定する
■ バッファ
● チャネルにバッファを持たせることができる
● 初期化時に指定できる
● 指定しないと容量0となる
■ 送受信時の処理のブロック
● 送信時にチャネルのバッファが一杯だとブロック
● 受信時にチャネル内が空だとブロック
ゴールーチン・チャネル/チャネルの特徴
59
送信時のブロック
ゴールーチン・チャネル/送信時のブロック
60
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
受信してくれるまでブロック
ch<-100
チャネル
100
ブロック
受信時のブロック
ゴールーチン・チャネル/受信時のブロック
61
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
送信されるまでブロック
<-ch
チャネル
100
ブロック
チャネルの基本 −1−
ゴールーチン・チャネル/チャネルの基本 −1−
62
■ 初期化
■ 送信
■ 受信
ch1 := make(chan int)
ch2 := make(chan int, 10)
n1 := <-ch1
n2 := <-ch2 + 100
容量を指定
ch1 <- 10
ch2 <- 10 + 20
受け取られるまでブロック
一杯であればブロック
送信されまでブロック
空であればブロック
make(chan int, 0)と同じ
チャネルの基本 −2−
ゴールーチン・チャネル/チャネルの基本 −2−
63
func main() {
done := make(chan bool) // 容量0
go func() {
time.Sleep(time.Second * 3)
done <- true
}()
<-done
fmt.Println("done")
}
送信されるまでブロック
http://play.golang.org/p/k0sMCYe4PA
複数のチャネルから同時に受信
ゴールーチン・チャネル/複数のチャネルから同時に受信
64
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
チャネル-1 チャネル-2
ブロック
ブロックされるので
同時に送受信出来ない?
select - case −1−
ゴールーチン・チャネル/select-case −1−
65
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
チャネル-1 チャネル-2
ブロックされるので
同時に送受信出来ない?
select
select - case −2−
ゴールーチン・チャネル/select - case −2−
66
func main() {
ch1 := make(chan int)
ch2 := make(chan string)
go func() { ch1<-100 }()
go func() { ch2<-"hi" }()
select {
case v1 := <-ch1:
fmt.Println(v1)
case v2 := <-ch2:
fmt.Println(v2)
}
}
先に受信した方を処理
http://play.golang.org/p/moVwtEdQIv
nilチャネル
ゴールーチン・チャネル/nilチャネル
67
func main() {
ch1 := make(chan int)
var ch2 chan string
go func() { ch1<-100 }()
go func() { ch2<-"hi" }()
select {
case v1 := <-ch1:
fmt.Println(v1)
case v2 := <-ch2:
fmt.Println(v2)
}
}
nilの場合は無視される
ゼロ値はnil
http://play.golang.org/p/UcqW6WH0XT
ファーストクラスオブジェクト
■ チャネルはファーストクラスオブジェクト
● 変数に入れれる
● 引数に渡す
● 戻り値で返す
● チャネルのチャネル
■ timeパッケージ
ゴールーチン・チャネル/ファーストクラスオブジェクト
68
http://golang.org/pkg/time/#After
chan chan int など
// 5分間待つ
<-time.After(5 * time.Minute)
5分たったら現在時刻が
送られてくるチャネルを返す
チャネルを引数や戻り値にする
ゴールーチン・チャネル/チャネルを引数や戻り値にする
69
func makeCh() chan int {
return make(chan int)
}
func recvCh(recv chan int) int {
return <-recv
}
func main() {
ch := makeCh()
go func() { ch <- 100 }
fmt.Println(recvCh(ch))
}
http://play.golang.org/p/UcqW6WH0XT
双方向チャネル
ゴールーチン・チャネル/双方向チャネル
70
func makeCh() chan int {
return make(chan int)
}
func recvCh(recv chan int) int {
go func() { recv <- 200 }()
return <-recv
}
func main() {
ch := makeCh()
go func() { ch <- 100 }()
fmt.Println(recvCh(ch))
}
http://play.golang.org/p/6gU92C6Q2v
間違った使い方ができる
単方向チャネル
ゴールーチン・チャネル/単方向チャネル
71
func makeCh() chan int {
return make(chan int)
}
func recvCh(recv <-chan int) int {
return <-recv
}
func main() {
ch := makeCh()
go func(ch chan<- int) {
ch <- 100
}(ch)
fmt.Println(recvCh(ch))
}
http://play.golang.org/p/pY4u1PU3SU
受信専用のチャネル
送信専用のチャネル
タイピングゲーム
■ 課題10
90秒以内に予め用意された5つの文章を入力させ、すべて入力
できた場合は"OK"と出力し、タイムオーバーの場合は"Time
Over"と表示するプログラムを作ってください。なお、入力ミスし
た場合は正解するまで同じ文章を入力させてください。
ヒント:
time.After, bufio.Scanner
ゴールーチン・チャネル/タイピングゲーム
72
Concurrencyの実現
■ 複数のゴールーチンで分業する
● タスクの種類によってゴールーチンを作る
● Concurrencyを実現
■ チャネルでやりとりする
● ゴールーチン間はチャネルで値を共有する
● 複雑すぎる場合はロックを使うことも
■ for-selectパターン
● ゴールーチンごとに無限ループを作る
● メインのゴールーチンはselectで結果を受信
ゴールーチン・チャネル/Concurrencyの実現
73
for-selectパターン −1−
ゴールーチン・チャネル/for-selectパターン −1−
74
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
チャネル-1 チャネル-2
select
for{}for{}
各ゴールーチンで
無限ループを作る
for-selectパターン −2−
ゴールーチン・チャネル/for-selectパターン −2−
75
ゴルーチン-1
for{}
ゴルーチン-2
for{}
ゴルーチン-3
for{}
ゴルーチン-4
for{}
チャネル
チャネル
チャネル
チャネル
GopherでConcurrency
■ 課題11
前のスライドのGopherたちが本を燃やす様子をプログラムで表
現してみてください。なお、1冊の本を燃やしたり、本を積んだ
り、台車を運んだりするのに、それなりに時間がかかることを想
定し、適度にtime.Sleepで処理を止めて見ましょう。
■ 課題12
課題11で燃やす本の量をどんどん増やした場合に、どうス
ケールすれば処理速度を落とさずに本を燃やせるでしょうか?
ゴールーチン・チャネル/GopherでConcurrency
76
# データ競合のチェック
go run -race main.go
ネットワークプログラミング
● netパッケージ
● net/httpパッケージ
77
netパッケージ −サーバ−
ネットワークプログラミング/netパッケージ −サーバー−
78
p, a := "tcp", ":8080"
ln, err := net.Listen(p, a)
if err != nil {...}
for {
conn, err := ln.Accept()
if err != nil {...}
go handle(conn)
}
エラー処理
エラー処理
netパッケージ −クライアント−
ネットワークプログラミング/netパッケージ −クライアント−
79
p, a := "tcp", ":8080"
conn, err := net.Dial(p, a)
if err != nil {...}
// 書き込み
fmt.Fprintln(conn, "hello")
エラー処理
手元で試してみよう!
(クライアント、サーバ)
グループチャットも
作ってみよう!
(プログラミング言語Goの8.10も参考になります。)
net/httpパッケージ
ネットワークプログラミング/net/httpパッケージ
80
h := func(
w http.ReponseWriter,
r *http.Request) {
fmt.Fprintf(w, "hello")
}
http.HandleFunc("/hello", h)
const a = ":8080"
http.ListenAndServe(a, nil)
手元で試してみよう!
Android上でサーバを動かす
81
ネットワークプログラミング/Android上でサーバを動かす
Youtubeで見る コード
母艦のシェル
adb shell
端末
タイピングゲーム2
■ 課題13
課題11のタイピングゲームを改造し、netパッケージを使って
立てたtcpのサーバから問題となる文章と制限時間を取得し、そ
の時間内にタイピングできるかを競うゲームにしてみよう。
■ 課題14
上記のプログラムをサーバとクライアントで実行可能ファイルを
分けずに、1つの実行ファイルでサーバもクライアントも実現しよ
う。また、問題の出題を交互に行えるようにしよう。
ネットワークプログラミング/タイピングゲーム2
82
go test とtestingパッケージ
● go test
● testingパッケージ
● コードの品質とGo
83
go test
go test と testingパッケージ/go test
84
■ testを行うためコマンド
 _testというサフィックスの付いた
 goファイルを対象にしてテストを実行
# mypkgのテスト行う
$ go test mypkg
ok mypkg 0.007s
# 失敗する場合
$ go test mypkg
--- FAIL: TestHex_String (0.00s)
hex_test.go:11: expect="a"
actual="A"
FAIL
FAIL mypkg 0.008s
hex.go ⇒ hex_test.go
go testのオプション(一部)
■ -v
詳細を表示する。
■ -cpu
実行する並列度を指定する。
複数のコアを使ったテストができる。
■ -race
データの競合が起きないかテストする。
■ -cover
カバレッジを取得する。
go test と testingパッケージ/go testのオプション(一部)
85
testingパッケージ
■ テストを行うため機能を提供するパッケージ
 *testing.T型のメソッド使う。
go test と testingパッケージ/testingパッケージ
86
package mypkg_test
import "testing"
import "mypkg"
func TestHex_String(t *testing.T) {
expect := "a"
actual := mypkg.Hex(10).String()
if actual != expect {
t.Errorf(`expect="%s" actual="%s"`, expect, actual)
}
}
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
mypkg.go
mypkg_test.go
testingパッケージでできること
■ 失敗理由を出力してテストを失敗させる
 Error(), Errorf(),
 Fatal(), Fatalf()
■ テストの並列実行
Parallel()
go testの-parallelオプションで並列数を指定
■ ベンチマーク
 *testing.B型を使う
■ ブラックボックステスト
testing/quickパッケージ
go test と testingパッケージ/testingパッケージでできること
87
testingパッケージでできないこと
■ アサーションはない
自動でエラーメッセージを作るのではなく、
ひとつずつErrorfを使って自前で作る
■ テストはGoで書く
テストのための新しいミニ言語を作らない
■ 比較演算子だけはツライのでは?
reflect.DeepEqualを使うと良い
go test と testingパッケージ/testingパッケージでできないこと
88
かなり薄いテストパッケージ
Go Mock
■ インタフェースのモックを作るツール
github.com/golang/mock/gomock
メソッドが呼ばれているかなどがテストできる。
標準パッケージではなく、サブプロジェクト。
func TestSample(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := mock.NewMockSample(ctrl)
m.EXPECT().Method("hoge").Return(1)
t.Log("result:", m.Method("hoge"))
}
参考:Go Mockでインタフェースのモックを作ってテストする
go test と testingパッケージ/Go Mock
89
Goのコンセプト
■ 実現する手段は少ないほうが良い
 機能が増えると複雑さが増える。
■ 暗黙的で曖昧な記述をさせない
 エラーにつながる,暗黙の型変換や
 不使用の変数等の宣言は許さない。
■ 不要なものは避ける
 不要な型の宣言の排除など,
 タイプ数をできるだけ減らすように。
■ コンセプトに一貫性を持たせる
 コンセプトにずれる言語仕様は入れない。
go test と testingパッケージ/Goのコンセプト
90
“Simplicity is Complicated” by Rob Pike
シンプルさと強力さ
■ シンプルな機能を組み合わせる
 シンプルな機能を組み合わせて,複雑な問題に対処する
  ⇒シンプルだが強力さも十分ある
go test と testingパッケージ/シンプルさと強力さ
91
シンプル
簡潔さ
強力さ
表現力
品質の良いコードが作りやすい
Goのコード品質を高める要素
■ シンプルな文法と言語設計
 可読性の高い文法と複雑になりにくい言語仕様。
■ 型階層がない
 複雑な型の階層が存在せず不要な型ができにくい。
■ コンパイルによるエラー検出
 静的型付け言語なのでバグがコンパイル時に分かる。
 バグになり得る箇所がコンパイルエラーになる。
 (型不一致,未使用の変数など)
■ コードフォーマッタ
 標準のコードフォーマッタ(gofmt)がある。
■ テスト
 標準のテストツール(go test)がある
go test と testingパッケージ/Goのコード品質を高める要素
92
コンパイルエラーになるもの −1−
■ 型の不一致
go test と testingパッケージ/コンパイルエラーになるもの −1−
93
var n int = 100
var m float64 = 1.5
// エラー
var a int = n + m
// OK
var b int = n + int(m)
コンパイルエラーになるもの −2−
■ 未使用の変数/パッケージ
go test と testingパッケージ/コンパイルエラーになるもの −2−
94
import (
"fmt" // エラー
_ "io" // OK
)
func main() {
var n int = 100 // エラー
_ = 200 // OK
}
コンパイルエラーになるもの −3−
■ インタフェースの未実装(型の不一致)
go test と testingパッケージ/コンパイルエラーになるもの −3−
95
type Hex int
func (h Hex) Str() string {
return fmt.Sprintf("%x", int(h))
}
// エラー
var _ fmt.Stringer = Hex(100)
type Stringer
interface {
String() string
}
コンパイルエラーになるもの −4−
■ 曖昧な記述
go test と testingパッケージ/コンパイルエラーになるもの −4−
96
type Hoge struct{ N int }
type Piyo struct{ N int }
type Foo struct {
Hoge
Piyo
}
func main() {
f := Foo{Hoge{100}, Piyo{200}}
fmt.Println(f.N) // エラー
fmt.Println(f.Hoge.N) // OK
}
Goとテスト
■ 言語のコンセプトを守る
● 実現する手段は少なく
⇒ テストの為のミニ言語を入れない。
● 暗黙的で曖昧な記述をさせない
⇒ アサーションで自動でエラーメッセージを作らない。
 コンテキストにあったエラーメッセージを作る。
■ コンパイルエラーで検出できる
コンパイルで検出できるものはテストは不要。
コンパイルでは検出できないものに集中できる。
■ テストが良いサンプル
テストがGoで書かれてるので良いサンプルになる。
go test と testingパッケージ/Goとテスト
97
FAQを読もう!
ドキュメントとテスト
■ テストされたサンプル
func ExampleHex_String() {
fmt.Println(mypkg.Hex(10))
// Output: a
}
テストファイルにExampleで始まる
関数を書くとサンプルとして扱われる。
// Output:を書くとテスト対象になる。
go test と testingパッケージ/ドキュメントとテスト
98
言語標準ツールの強み
■ ツール間で連携が取りやすい
標準ツールなので、他のツールと連携が取りやすい
■ メンテが保証される
 バグが放置されたり、メンテされなかったりしない
■ みんなが共通に使う
その言語を使っているユーザ間で、共通の知識になる
テストツールも
言語標準のメリットは大きい
go test と testingパッケージ/言語標準ツールの強み
99
課題
■ 課題15
hogeパッケージのテスト書いてみましょう。
■ 課題16
Exapleテストを書いてみましょう。
また、godocを使ってドキュメント生成してみましょう。
go test と testingパッケージ/課題
100
# godocをインストールしよう
$ go get golang.org/x/tools/cmd/godoc
$ $GOPATH/bin/godoc --http=":8080"
リフレクション
● reflectパッケージとは
● リフレクションの基本
101
reflectパッケージとは?
■ 何ができるのか?
● 実行時に型情報を取得
● 任意の型の変数に値を入れる
● 構造体のフィールドのタグを取得する
■ どこで使われてるの?
● encodingパッケージ
● ORマッパーなど
リフレクション/reflectパッケージとは?
102
encodingパッケージでの利用
● JSONなどのシリアライズされたものを構造体に
変換する際に使用される
type Person struct {
Name string `json:"name"`
Aget int `json:"age"`
}
{
"name": "Gopher",
"age": 4
}
Goの構造体:
JSON例:
structタグで対応付ける
リフレクション/encodingパッケージでの利用
templateパッケージでの利用
● HTMLなどに任意の型の値を埋め込むために
使われる
Hello, {{.Name}}!! Hello, Gopher!!
Person {
Name: “Gopher”,
Age: 4,
}
テンプレート 出力
データの埋込み
Execute
リフレクション/templateパッケージでの利用
Value型とType型
■ Value型
● 任意の値を表す型
● 値への操作をメソッドで提供
● reflect.ValueOf()で取得できる
■ Type型
● 任意の型を表す型
● 型に関する操作をメソッドで提供
● reflect.TypeOf()で取得できる
105
リフレクション/Value型とType型
変数に値を入れる
106
var n int
fmt.Println(n) // 0
vp := reflect.ValueOf(&n)
v := vp.Elem()
if v.CanSet() {
v.SetInt(100)
}
fmt.Println(n) // 100
http://play.golang.org/p/HkJPjQsP8o
リフレクション/変数に値を入れる
変数に値を入れる(図解)
100n: &n
Value
(ポインタ)
ValueOf()
Value
(Int)
Elem()
Set
リフレクション/変数に値を入れる(図解)
interface{}としてポインタを渡す
■ interface{}として、任意の型のポインタを受
け取る
func set(p, v interface{}) {
pv := reflect.ValueOf(p) // ポインタ
vv := reflect.ValueOf(v) // 設定する値
pv.Elem().Set(vv)
}
リフレクション/interface{}としてポインタを渡す
構造体のリフレクション
109
s := struct{
A string `k:"v"`; b int
}{"a", 1}
v := reflect.ValueOf(&s).Elem()
println(v.FieldByName("A").CanSet())
println(v.FieldByName("b").CanSet())
f1, ok := v.Type().FieldByName("A")
println(ok, f1.PkgPath, f1.Tag.Get("k"))
f2, _ := v.Type().FieldByName("b")
println(f2.PkgPath)
http://play.golang.org/p/NkwP3KSjDu
リフレクション/構造体のリフレクション
リフレクションの注意点
■ 容易にpanicが起きる
● ValueとTypeで実体によって対応していな
いメソッドを呼ぶとpanicになる
○ 例:int型の値のValueに対してLen()を呼ぶ
● Kindで適切に分岐してpanicを避ける
■ 実行時間がかかる
● 実行時に解析するのでコストが大きい
● 静的解析を用いる選択肢もある
リフレクション/リフレクションの注意点
静的解析
● 静的解析とは?
● Goにおける静的解析
● 静的解析の基本
111
ソースコードの静的解析とは?
112
■ ソースコードを実行せずに解析すること
● ソースコードから抽象構文木(AST)などを取
得して解析する
● 静的型付け言語だと、静的解析で型情報が
取得できる
● 逆は実行して解析する動的解析
静的解析/ソースコードの静的解析とは?
Goで静的解析をすると何が嬉しいのか?
● リファクタリングツール
○ 変数の宣言位置や使用箇所の抽出
○ パッケージの解析
● コードジェネレーター
○ コメントによるアノテーションの抽出
○ コードフォーマッタ
● 処理系
○ 抽象構文木(AST)の解析
○ 定数の扱い
113
静的型付け言語なので
静的解析でも多くの事が知れる
静的解析/Goで静的解析をすると何が嬉しいのか?
開発ツールとソースコードの静的解析
114
■ 開発ツールの多くは静的解析を行っている
● gofmt/goimports
○ コードフォーマッター
● go vet/golint
○ コードチェッカー、リンター
● guru
○ 静的解析
● gocode
○ コード補完
● errcheck
○ エラー処理のチェック
● gorename/gomvpkg
○ リファクタリングツール
静的解析/開発ツールとソースコードの静的解析
■ 標準パッケージで静的解析の機能を提供
goパッケージ
115
go/ast 抽象構文木(AST)を提供
go/build パッケージに関する情報を集める
go/constant 定数に関する型を提供
go/doc ドキュメントをASTから取り出す
go/format コードフォーマッタの機能を提供
go/importer コンパイラに適したImporterを提供
go/parser 構文解析の機能を提供
go/printer ASTの表示機能を提供
go/scanner 字句解析の機能を提供
go/token トークンに関する型を提供
go/types 型チェックに関する機能を提供
静的解析/goパッケージ
静的解析の流れ
116
ソースコード
トークン
抽象構文木(AST)
型情報
構文解析
字句解析
型チェック
go/scanner
go/token
go/parser
go/ast
go/types
go/constant
静的解析/静的解析の流れ
字句解析 - go/scanner,go/token
■ 文字列をトークンにしていく
● 空白などを取り除き、意味のある単位=トー
クンにしていく作業
117
v + 1
IDENT ADD INT
トークン
ソースコード
静的解析/字句解析
構文解析 - go/parser,go/ast
■ トークンを抽象構文木(AST)にしていく
● プログラムの構造を持たせる
118
v + 1
IDENT ADD INT
ソースコード
+
v 1
BinaryExpr
Ident BasicLit
トークン
抽象構文木(AST)
静的解析/構文解析
型チェック - go/types,go/constant
■ 型チェックを行う
● 識別子の解決
● 型の推論
● 定数の評価
119
n := 100 + 200
fmt.Println(n)
定数の評価
=300
型の推論
-> int
識別子の解決
識別子の解決
-> fmtパッケージ
静的解析/型チェック
抽象構文木(ASt)の取得
■ go/parserパッケージの関数を使う
● ParseExpr,ParseExprFrom
○ 式をパースする
○ ParseExprはParseExprFromを簡易版
● ParseFile
○ ファイル単位でパースする
● ParseDir
○ ディレクトリ単位でパースする
○ 中でParseFileを呼んでいる
120
静的解析/抽象構文木(AST)の取得
式のASTを取得する
■ 式を構文解析する
■ ParseExprFromでも書ける
121
expr, err := parser.ParseExpr(`v + 1`)
if err != nil {
/* エラー処理 */
}
/* exprを解析する処理 */
fset := token.NewFileSet() // ファイル情報
src := []byte(`v + 1`)
f := "" // ファイル名(式なので不要)
m := 0 // モード(式なので不要)
expr, err := parser.ParseExprFrom(fset, f, s, m)
静的解析/式のASTを取得する
token.FileSetとは?
■ ファイル中の位置情報を記録する為の型
● 位置情報は数値で表される
● 複数のファイル間で一意の値
● 各ファイルのoffsetが記録されている
● パースする際に記録されていく
122
token.FileSetは出力引数として
Parse系の関数に渡す
静的解析/token.FileSetとは?
ファイルからASTを取得する
■ 完全なGoのソースコードを構文解析する
123
const src = `
package main
var v = 100
func main() {
fmt.Println(v+1)
}`
fs := token.NewFileSet()
f, err := parser.ParseFile(fs, "my.go", src, 0)
if err != nil {
/* エラー処理 */
}
/* f を解析する処理
引数はparse.ExprFromと
同じ構成
srcがnilだとファイル名
でファイルを開く
解析するファイルの中身
静的解析/ファイルからASTを取得する
Hello, WorldのASTの構成
124
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
ast.File
ast.File
ast.GenDecl
ast.FuncDecl
ast.CallExpr
Goの抽象構文木(AST)を手入力してHello, Worldを作る
http://qiita.com/tenntenn/items/0cbc6f1f00dc579fcd8c
Playgroundで動かす
静的解析/Hello, WorldのASTの構成
ASTをトラバースする
■ ast.Inspectを使う
125
n, _ := parser.ParseExpr(`v + 1`)
ast.Inspect(n, func(n ast.Node) bool {
if n != nil { fmt.Printf("%Tn", n) }
return true
})
printer.Fprint(os.Stdout, token.NewFileSet(), n)
*ast.BinaryExpr
*ast.Ident
*ast.BasicLit
v + 1
+
v 1
構文解析
抽象構文木(AST)を探索
抽象構文木(AST)を出力
BinaryExpr
Ident BasicLit
Playgroundで動かす
ast.Walkというのもある
静的解析/ASTをトラバースする
ASTをトラバースする
■ 再帰を使ってトラバースする
126
func traverse(n ast.Node) {
switch n := n.(type) {
case *ast.Indent:
fmt.Println(n.Name)
case *ast.BinaryExpr:
traverse(n.X)
traverse(n.Y)
case *ast.UnaryExpr:
traverse(n.X)
}
}
識別子の場合は名前を出力
二項演算式の場合は
各項を探索
単項演算式の場合は
項を探索
型でswitchする
https://play.golang.org/p/5SOdiy420p
静的解析/ASTをトラバースする
参考資料
■ goパッケージで簡単に静的解析して世界を広げよう
● コードジェネレータ
○ ASTを取得する方法を調べる
○ 抽象構文木(AST)をトラバースする
○ 抽象構文木(AST)をいじってフォーマットをかける
○ Goの抽象構文木(AST)を手入力してHello, Worldを作る
○ go-app-builderのソースコードを読む
● リファクタリングツール
○ gorenameをライブラリとして使う
○ Goのスコープについて考えてみよう
○ go/typesパッケージを使い変数名をリネームしてみる
● 処理系
○ 簡単な式の評価機を作ってみる
○ 【実践goパッケージ】文字列から複素数型の値をパースする
○ もっと楽して式の評価器を作る
127
静的解析/参考資料
ハンズオン
● ハンズオンの説明
● ドキュメントとPlayground
128
ハンズオンの説明
■ リポジトリ
● https://github.com/tenntenn/gohandson/tree/master/i
mgconv/ja
■ コマンドラインツール
● ターミナルで動くプログラム
● 画像を変換するツール
# 50%に縮小して、JPEGにする
$ imgconv -resize 50% a.png b.jpg
ハンズオン/ハンズオンの説明
129
ドキュメントを読もう
■ パッケージドキュメント
● https://golang.org/pkg
● 標準パッケージの使い方が書いてある
■ FAQ
● https://golang.org/doc/faq
● なぜGoに◯◯がないのか?など
■ 言語仕様
● https://golang.org/ref/spec
公式ドキュメントを読もう!!
ハンズオン/ドキュメントを読もう
130
Go Playground
■ Go Playground
● http://play.golang.org/
● Web上でGoを実行できる
● Share機能で、SNSで共有したり質問する
ハンズオン/Go Playground
131

Go入門