今日から始める Go 言語
HAKODATE Developers Conference 2014
25 Oct 2014
Yoshifumi Yamaguchi
Gopher
25 Oct 2014
Yoshifumi Yamaguchi
Gopher
オライリー 1,404円 ePub/mobi
www.oreilly.co.jp/books/9784873115405/
オーム社 4,100円 印刷/PDF/mobi
www.amazon.co.jp/dp/4274069125
Googleでは多くのプロダクトがC++やJavaを使って開発されているが、もろもろの問題点があった。それらを解決するために新しい言語が必要だと考えた。
簡単に並行なプログラムが書けて、GCがあって、ビルドが速い言語を作ろう
簡単に並行なプログラムが書けて、GCがあって、ビルドが速いコンパイラ言語
goroutine
と channel
(簡単に並行)さらに
encoding/json
のパフォーマンスが30%向上)どんどん新規の企業ユーザが増えている
ビルドするとネイティブバイナリが生成されるので直接実行
$ go bulid -o test $ ./test
クロスコンパイルしたい場合には環境変数を変更するだけ
(host)$ GOOS=linux GOARCH=i386 go build -o test (host)$ scp test foo@hoge.net:~ (hoge)% ~/test
依存する外部パッケージは直接ソースにレポジトリに指定して、ビルドコマンドを打つだけ。Makefileはいらない。レポジトリはGit、Mercurial、Subversion、Bazaarに対応。
package main import ( "fmt" "code.google.com/p/go.text/encoding/japanese" // 日本語コーデック解決用のパッケージ ) func main() {...}
これでコマンドを入力
$ cd $GOPATH $ go get $ go build -o test $ ./test
強い型付け。暗黙のキャストは行われないので、明示しない限りコンパイル時エラーとなる。
type Celsius float64 type Fahrenheit float64 // cとfの型が違うのでコンパイル時エラー func SomeFunc() Fahrenheit { c := Celsius(100) f := Fahrenheit(20) return c + f }
type Celsius float64 type Fahrenheit float64 func (c Celsius) ToF() Fahrenheit { return Fahrenheit((9*c/5)+32) } // cとfの型が合ってるのでOK func SomeFunc() Fahrenheit { c := Celsius(100) f := Fahrenheit(20) return c.ToF() + f }
ビルドが通った時点で通常は型チェックがすべて通っている。
変数は var
を用いて変数名と型を宣言。 =
演算子で代入。
var c1 Celsius var c2 Celsius c1 = Celsius(100) c2 = Celsius(2) var c Celsius c = c1 + c2
:=
を用いると初期化と同時に代入値の型を元に変数の型も決定。
c1 := Celsius(100) c2 := Celsius(2) c := c1 + c2
関数は func
キーワードで宣言。引数名とその型、返り値の型を記述。
// Goには例外処理が無いので、正常値と異常値のタプルを返す。異常値はerrorの実装。 func PickByte(s string, n int) (byte, error) { if n >= len(s) { return byte(0), errors.New("out of range") } return s[n], nil }
また関数は第1級オブジェクトなので変数に渡すことも可能。
var f func(string, int) (byte, error) f = PickByte
Goにはクラスが無く、データを struct
と interface
で定義します。
struct
ではフィールドとメソッドを定義できる。メソッドの定義は関数の定義と同様でレシーバを記述が追加されただけ。
type Rectangle struct { Width float64 // フィールドにはフィールド名とその型を記述 Length float64 } // メソッドはレシーバの記述がある func (r Rectangle) Area() float64 { return r.Width * r.Length }
interface
ではメソッドシグネチャの集合を定義できます。
シグネチャをすべて実装しているstructは自動的にそのinterfaceの実装と判断されます。(Javaのような明示的な宣言が要らない)
type Shaper interface { Area() float64 } type Rectangle struct { Width float64 Length float64 } func (r Rectangle) Area() float64 { // Shaperで定義されているメソッド return r.Width * r.Length }
同様にArea()を実装していればShaperを実装していることになる。
type Square struct { Length float64 } func (sq Square) Area() float64 { // Shaperで定義されているメソッド return sq.Length * sq.Length } type Circle struct { Radius float64 } func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }
func main() { r := Rectangle{Width: 3, Length: 5} sq := Square{Length: 2} c := Circle{Radius: 3} // 全部Shapeにキャストできる shapes := []Shaper{Shaper(r), Shaper(sq), Shaper(c)} for _, s := range shapes { fmt.Println(s.Area()) } }
実行結果
$ go run main.go 15 4 28.274333882308138
制御構文は以下の3つが基本。超簡単。
if...else...
for
switch...case...
あと並行プログラミングをするときによく使うのが select
select...case...
いわゆる if
と else
で、 elseif
のようなものはない。
if x.IsValid() { doSomething() } else { doSomethingElse() }
値の取得とエラーの検査を同時に行うこともできる。
if data, err := FetchData(...); err != nil { doErrorProcess() }
ループ制御はすべて for
で、 while
は存在しない。
普通にある範囲をループする場合
for i := 0; i < MaxNum; i++ { doSomethingWith(i) }
slice
や map
からすべて値を取り出す場合
for i, n := range []int{100, 200, 300} { // slice fmt.Printf("%vth value: %v\n", i, n) } for k, v := range map[string]string{"a": "foo", "b": "bar", "c": "buz"} { // map fmt.Printf("%v -> %v\n", k, v) }
while
のように使うには条件のみを書く
i := 0 for i < 10 { fmt.Println(i, " is less than 10") i++ }
あるいは条件を書かない。
for { // while(1) と同じ ... }
普通の switch
だけど、フォールスルーがない。
switch x { case a: // xの値をcase文に書く doA() case b: doB() default: doDefalut() }
あるいはこうも書けます
switch { case x == a: // case文のところで審議判定 doA() case x == b: doB() default: doDefault() }
goroutineとchannelを使うことで並行プログラミングを簡単に書くことができる。
func SomeProcess(t Task) {} fmt.Println("process task...") // goキーワードでgoroutineを起動 go func() { DoTask(t) } fmt.Println("process started...") } func DoTask(t Task) { ... fmt.Println("task done!!") }
実行結果
$ go run test.go process task... process started... task done!!
channelは特定の型のデータのみを扱うキューのようなもの。channelを介して異なるgoroutine間でデータのやり取りが可能になる。
timerChan := make(chan time.Time) go func() { time.Sleep(deltaT) timerChan <- time.Now() // timerChanに時刻を入れる <-演算子でchannelへ代入 }() // この隙に何か別のことができる。 // channelからのデータの受け取りはブロックする。 completedAt := <-timerChan // <-でchannelから取り出し
Effective Goより抜粋
Do not communicate by sharing memory; instead, share memory by communicating.
メモリを共有することで通信しようとしないこと。代わりに通信することでメモリを共有すること。
複数のチャンネルから来る値を非同期に処理をしたい場合は select
構文を使う。
特に常にchannelを監視し続けるようなブロックでは for-select
構文を使う。
たとえば次のコードは ch1
と ch2
から来るメッセージを非同期に処理する。
for { select { case msg1 := <-ch1: fmt.Printf("received message from channel1: %v", msg1) case msg2 := <-ch2: fmt.Printf("received message from channel2: %v", msg2) } }
Goではパッケージ単位で機能を分ける(名前空間を作る)と同時に、アクセス制御も行っている。
package hello import "fmt" type Person struct { name string // 小文字始まりのものはパッケージ外からはアクセスできない age int } func NewPerson(name string, age int) Person { return Person{name, age} } func (p Person) Introduction() string { // 大文字始まりのものはパッケージ外からアクセス可能 return fmt.Sprintf("私の名前は%vです。%v歳です。", p.name, p.age) }
何を公開するか、何をprivateにするかを考える。
package main import ( "fmt" // 標準パッケージ "hello" // 自分で作ったパッケージ ) func main() { p := NewPerson("ごーふぁー", 4) fmt.Println("これから自己紹介があります") p.Introduction() // これはできない // fmt.Printf("わたしは%vです、%v歳です", p.name, p.age) }
project/ <----- $GOPATH bin/ todo # 実行可能コマンド pkg/ linux_amd64/ code.google.com/p/goauth2/ oauth.a # パッケージオブジェクト github.com/ymotongpoo/todo/ task.a # パッケージオブジェクト src/ code.google.com/p/goauth2/ oauth/ oauth.go # パッケージソース oauth_test.go # テストソース github.com/ymotongpoo/ todo/ task/ oauth.go task.go # パッケージソース todo.go # 実行ファイルソース
Goには中央パッケージ管理レポジトリはない。VCSホスティングサービスあるいは自前サーバで公開する。
外部パッケージを利用する際は、URIを指定する。
パッケージは標準パッケージ、自作パッケージ、3rd partyを分けて書くのがおすすめ。
import ( "fmt" // 標準パッケージ "encoding/json" "net/http" "foo" // 自作パッケージ "bar" "code.google.com/p/go.text/encoding/japanese" // 3rd party "github.com/ymotongpoo/mailutil" )
Goは文法がプリミティブにも関わらず書きやすい理由の一つに標準パッケージが豊富なことが挙げられる。
Goでコマンドラインツールを書く例
var ( input = flag.String("input", "input.txt", "input filename") output = flag.String("output", "output.txt", "output filename") ) func main() { infile, err := os.Open(*input) if err != nil { panic(err) } outfile, err := os.Create(*output) if err != nil { panic(err) } n, err := io.Copy(outfile, infile) if err != nil { panic(err) } fmt.Printf("Copied %v bytes from %v to %v", n, *input, *output) }
GoでHTTPサーバを書く例。そこそこ速い。
package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", handler) // URLディスパッチ http.ListenAndServe(":8080", nil) // サーバ起動 } func handler(w http.ResponseWriter, r *http.Requset) { // インターフェースは決まっている fmt.Fprintf(w, "こんにちは、世界") }
Goにはパッケージの中央管理レポジトリがないので、自分で探す必要がある。
次の方法で探すのが良い。
よく3rd partyが使われるのは
Goの利点には標準パッケージの充実度の他に標準ツールが高機能であることが挙げられる。
標準ツールは go
というアーミーナイフコマンドを利用する。サブコマンドには以下がある。
GoではMakefileは通常必要ない。 get
が依存するパッケージをすべて取得し、 build
で対象環境のネイティブバイナリを生成。
$ go get $ go build
実行ファイル名はデフォルトは main
パッケージがあるディレクトリの名前。 -o
オプションで指定することも可能。
$ pwd /path/to/project/hello $ go build $ ls hello hello.go
言えんの?
でもいちいち単体のテストするのにツールを入れたりするのはめんどくさい → 標準であります
単体テストは test
コマンドを使って行う。テストは xxx_test.go
というファイルに次のように書く。
import ( "reflect" "testing" ) func TestFoo(t *testing.T) { in := []Hoge{...} wants := []Bar{...} outs := make([]Bar, len(wants)) for i, h := range in { outs[i] = Process(h) } if !reflect.DeepEqual(wants, outs) { t.Errorf("wants: %v, out: %v", wants, outs) } }
成功すると
$ go test PASS ok /path/to/project/testing_sample 0.004s
失敗すると
$ go test --- FAIL: TestFoo (0.00s) foo_test.go:16: wants: [{6} {2} {7}], outs: [{6} {8} {7}] FAIL exit status 1 FAIL /path/to/project/testing_sample 0.005s
gofmtは
Goではコードの一貫性を大事にしているが、それをコーディング規約で縛る前に fmt
コマンドでフォーマットするのが決まり。
これによって、細かいルールを敷かずともある程度一貫した見栄えになる。
$ gofmt -w test.go # test.goのフォーマットを整えて上書き $ gofmt -l -w . # カレントディレクトリ以下すべてのGoのソースコードのフォーマット
2つ目は go
コマンドの fmt
サブコマンドと同様です。
$ go fmt
コミットフックにかけるととても幸せ。
Javadocのようにソース内のコメントをドキュメントにしてHTMLやテキストの形式で見ることができます。
Go公式サイトのパッケージ一覧はこれを利用している。
$ godoc -http=:8080 # http://localhot:8080/ にアクセスするとドキュメントが見える。
コマンドラインから使う場合はこんな感じです
$ godoc http NewRequest type Request struct { // Method specifies the HTTP method (GET, POST, PUT, etc.). // For client requests an empty string means GET. Method string ... } A Request represents an HTTP request received by a server or to be sent by a client. ... func NewRequest(method, urlStr string, body io.Reader) (*Request, error) NewRequest returns a new Request given a method, URL, and optional body. If the provided body is also an io.Closer, the returned Request.Body is set to body and will be closed by the Client methods Do, Post, and PostForm, and Transport.RoundTrip.
25 Oct 2014