今日から始める Go 言語

HAKODATE Developers Conference 2014

25 Oct 2014

Yoshifumi Yamaguchi

Gopher

こんにちは!

自己紹介

Java開発者のための関数プログラミング

オライリー 1,404円 ePub/mobi

すごいErlangゆかいに学ぼう!

オーム社 4,100円 印刷/PDF/mobi

Ingress

おしながき

Goの紹介

Goがなぜ開発されたか

Googleでは多くのプロダクトがC++やJavaを使って開発されているが、もろもろの問題点があった。それらを解決するために新しい言語が必要だと考えた。

簡単に並行なプログラムが書けて、GCがあって、ビルドが速い言語を作ろう

Goはどんな言語か

簡単に並行なプログラムが書けて、GCがあって、ビルドが速いコンパイラ言語

さらに

Goの開発経緯

Goの採用実績

どんどん新規の企業ユーザが増えている

実行環境

実行環境(概要)

ビルドするとネイティブバイナリが生成されるので直接実行

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

型システム (cont'd)

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

struct

Goにはクラスが無く、データを structinterface で定義します。
struct ではフィールドとメソッドを定義できる。メソッドの定義は関数の定義と同様でレシーバを記述が追加されただけ。

type Rectangle struct {
    Width  float64 // フィールドにはフィールド名とその型を記述
    Length float64
}

// メソッドはレシーバの記述がある
func (r Rectangle) Area() float64 {
    return r.Width * r.Length
}

interface

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
}

interface (cont'd)

同様に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
}

interface (cont'd)

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つが基本。超簡単。

あと並行プログラミングをするときによく使うのが select

if と else

いわゆる ifelse で、 elseif のようなものはない。

if x.IsValid() {
    doSomething()
} else {
    doSomethingElse()
}

値の取得とエラーの検査を同時に行うこともできる。

if data, err := FetchData(...); err != nil {
    doErrorProcess()
}

for

ループ制御はすべて for で、 while は存在しない。

普通にある範囲をループする場合

for i := 0; i < MaxNum; i++ {
    doSomethingWith(i)
}

slicemap からすべて値を取り出す場合

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

for (cont'd)

while のように使うには条件のみを書く

i := 0
for i < 10 {
    fmt.Println(i, " is less than 10")
    i++
}

あるいは条件を書かない。

for { // while(1) と同じ
    ...
}

switch

普通の 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

goroutineとchannelを使うことで並行プログラミングを簡単に書くことができる。

goroutine

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は特定の型のデータのみを扱うキューのようなもの。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

複数のチャンネルから来る値を非同期に処理をしたい場合は select 構文を使う。
特に常にchannelを監視し続けるようなブロックでは for-select 構文を使う。

たとえば次のコードは ch1ch2 から来るメッセージを非同期に処理する。

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

パッケージ (cont'd)

何を公開するか、何を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は文法がプリミティブにも関わらず書きやすい理由の一つに標準パッケージが豊富なことが挙げられる。

標準パッケージ (cont'd)

flag

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

net/http

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, "こんにちは、世界")
}

3rd partyパッケージの検索

Goにはパッケージの中央管理レポジトリがないので、自分で探す必要がある。
次の方法で探すのが良い。

よく3rd partyが使われるのは

標準ツール

標準ツール

Goの利点には標準パッケージの充実度の他に標準ツールが高機能であることが挙げられる。
標準ツールは go というアーミーナイフコマンドを利用する。サブコマンドには以下がある。

getとbuild

GoではMakefileは通常必要ない。 get が依存するパッケージをすべて取得し、 build で対象環境のネイティブバイナリを生成。

$ go get
$ go build

実行ファイル名はデフォルトは main パッケージがあるディレクトリの名前。 -o オプションで指定することも可能。

$ pwd
/path/to/project/hello
$ go build
$ ls
hello hello.go

test

言えんの?

でもいちいち単体のテストするのにツールを入れたりするのはめんどくさい → 標準であります

test

単体テストは 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)
    }
}

test (cont'd)

成功すると

$ 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

fmt (gofmt)

gofmtは

Goではコードの一貫性を大事にしているが、それをコーディング規約で縛る前に fmt コマンドでフォーマットするのが決まり。
これによって、細かいルールを敷かずともある程度一貫した見栄えになる。

$ gofmt -w test.go  # test.goのフォーマットを整えて上書き
$ gofmt -l -w .     # カレントディレクトリ以下すべてのGoのソースコードのフォーマット

2つ目は go コマンドの fmt サブコマンドと同様です。

$ go fmt

コミットフックにかけるととても幸せ。

godoc

Javadocのようにソース内のコメントをドキュメントにしてHTMLやテキストの形式で見ることができます。
Go公式サイトのパッケージ一覧はこれを利用している。

$ godoc -http=:8080    # http://localhot:8080/ にアクセスするとドキュメントが見える。

godoc (cont'd)

コマンドラインから使う場合はこんな感じです

$ 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.

参考

参考サイトなど

Thank you

25 Oct 2014

Yoshifumi Yamaguchi

Gopher