Skip to content
Go言語とは、Googleが開発した新しいプログラミング言語です。
当サイトではこの新しい言語についての情報を集約していきます。
このサイトの更新が滞っており、情報が古くなっておりますのでご注意ください。

Archive

Archive for 11月, 2009

今回からThe Go Programming Language Specificationの翻訳をはじめます。
何回かに分けて公開しますので、おつきあいください。


はじめに

この文書はプログラミング言語Goのリファレンスマニュアルです。その他の情報、文書はhttp://golang.org を参照ください。

Go言語はシステムプログラミングを念頭に置いた多目的言語です。
この言語には、強い型付け、ガーベージコレクション、並列処理のサポート機能があります。
プログラムはパッケージという単位から成り、パッケージはそれを利用するプログラムに管理されたアクセスを提供します。
今現在は、従来からあるコンパイル/リンクモデルによる実行バイナリ生成方式を採っています。

Go言語の文法はコンパクトかつ規則的で、IDE(統合開発環境)のような自動ツールにとって解析しやすい言語となっています。

表記方法

この文書はEBNF(Extended Backus-Naur Form)の記法に準拠します。
下はEBNFをEBNF自身で表したものです。

Production  = production_name "=" Expression "." .
Expression  = Alternative { "|" Alternative } .
Alternative = Term { Term } .
Term        = production_name | token [ "..." token ] | Group | Option | Repetition .
Group       = "(" Expression ")" .
Option      = "[" Expression "]" .
Repetition  = "{" Expression "}" .

EBNFでは語句および、下で優先度順に示された演算子による式を組み合わせて定義を行います。

|   いずれか
()  グルーピング
[]  オプション(0 または 1回)
{}  繰り返し (0 ~ n回)

イコールの左辺の名称が小文字場合は、その名称はトークンを識別するために使われます。
名称を他のEBNF文の右辺で使用する場合は、大文字と小文字を混在させて記述します。
シンボルはダブルクォート""、またはバッククォート``でくくられます。

「a ... b」形式はaからbまでのうちの、いずれかの文字のセットを表します。

チュートリアルの翻訳、11回目(最終回)です。
前回までの訳を含めてチュートリアル[日本語訳]にまとめてありますのでごらんください。


多重化

チャネルを使うことで多重化処理のコードをほとんど書くことなく、複数の独立しているゴルーチンに対してデータ配信することができます。
今回のポイントとしては、サーバーへ送信するメッセージ内にあらかじめ返信用チャネルを含めておき、それを使ってクライアントに返信を行います。
実際のクライアント/サーバプログラムは多くのコードからできていますが、ここでは分かりやすいように単純化した例で説明します。
まずはじめにrequest型を定義し、そこに返信に使用するチャネルを埋め込みます。 

09    type request struct {
10        a, b    int;
11        replyc  chan int;
12    }

サーバ側は大した処理は行いません。整数の二項演算をするだけです。
下のコードは演算を実行し、requestに返信します。

14    type binOp func(a, b int) int

16    func run(op binOp, req *request) {
17        reply := op(req.a, req.b);
18        req.replyc <- reply;
19    }

14行目では関数の「型」を定義しています。この関数はパラメータに2つの整数を取り、整数を返します。

server関数は無限ループしています。
リクエストを受け取るたびに処理を行いますが、処理に時間がかかるとループ内でブロックしてしまうため、ゴルーチンを起動して処理を行わせます。 

21    func server(op binOp, service chan *request) {
22        for {
23            req := <-service;
24            go run(op, req);  // don't wait for it
25        }
26    }

おなじみの方法でserverを作成・起動後、それと接続しているチャネルを返します。

28    func startServer(op binOp) chan *request {
29        req := make(chan *request);
30        go server(op, req);
31        return req;
32    }

次は簡単なテストコードです。
「加算処理を行う関数」をfunc文で作成し、それをパラメータにしてsartServerを呼び出し、サーバを起動します。
つづいてN個のリクエストを連続して送信します。
すべてのリクエストを送信し終えてから、応答を確認します。

34    func main() {
35        adder := startServer(func(a, b int) int { return a + b });
36        const N = 100;
37        var reqs [N]request;
38        for i := 0; i < N; i++ {
39            req := &reqs[i];
40            req.a = i;
41            req.b = i + N;
42            req.replyc = make(chan int);
43            adder <- req;
44        }
45        for i := N-1; i >= 0; i-- {   // doesn't matter what order
46            if <-reqs[i].replyc != N + 2*i {
47                fmt.Println("fail at", i);
48            }
49        }
50        fmt.Println("done");
51    }

このプログラムの問題点は、サーバーがちゃんとシャットダウンしないということです。
mainからリターンするときに、いつかのゴルーチンが処理が長引いてしまうため通信をブロックしてしまいます。
これを解決するために、別のチャネルquitをserver関数に渡します。

32    func startServer(op binOp) (service chan *request, quit chan bool) {
33        service = make(chan *request);
34        quit = make(chan bool);
35        go server(op, service, quit);
36        return service, quit;
37    }

server関数にquitチャネルをを渡します。
server関数では受け取ったチャネルを次のようにして使います。

21    func server(op binOp, service chan *request, quit chan bool) {
22        for {
23            select {
24            case req := <-service:
25                go run(op, req);  // don't wait for it
26            case <-quit:
27                return;
28            }
29        }
30    }

server内のselect文は、caseでリストされた複数の通信のうち処理が続行可能な方を選択します。
すべてがブロックされていれば、どれかが続行可能になるまで待機します。どちらも続行可能ならどちらかがランダムに選択されます。

この場合、selectはquitメッセージを受信しリターンするまでの間、requestメッセージを受信し続けます。

あとは、main関数の最後にquitチャネルにデータを送信するよう変更します。 

40        adder, quit := startServer(func(a, b int) int { return a + b });

55        quit <- true;

まだまだGo言語のプログラミングおよび同期処理について語るところはありますが、このチュートリアルは基本の説明だけにとどめておきましょう。

チュートリアルの翻訳、10回目です。
前回までの訳はチュートリアル[日本語訳]にまとめてありますのでごらんください。


素数

やっと、プロセスと平行通信プログラミングのところまで到達しました。
これは重要なセクションであり、このトピックにはある程度の知識が必要です。

「素数の篩(ふるい)」と呼ばれる古くから存在する素数を取得するプログラムがあります。
(エラトステネスの篩はここで扱うアルゴリズムより効率的ではありますが、このセクションでは並列性を重要視しています)

このアルゴリズムでは、まず2以上の自然数を順番に並べて数列を作成します。そこから素数をひとつづ取り出し、その素数の倍数すべてを数列から除外することを繰り返します。残った数列の先頭から取り出した数が次の素数であり、そこからまた倍数をすべて除外して次の数列を作成します。

下が、これのフロー図です。それぞれの箱は数列の先頭から取り出した素数であり、この箱で数列をフィルタリングして次の数列を作成しています。  sieve

 整数の数列を作成するために、チャネルと呼ばれるGo言語の機能を使います。チャネルの基となったものはCSPであり、チャネルとは2つの並列動作している処理をつなぐ通信チャネルです。
Go言語のチャネル変数は、”通信を調整するランタイムオブジェクト”への参照です。
新しいチャネルをつくるには、マップやスライスと同様にmakeを使用してください。

下はprogs/sieve.go内の関数です。

09    // Send the sequence 2, 3, 4, ... to channel 'ch'.
10    func generate(ch chan int) {
11        for i := 2; ; i++ {
12            ch <- i  // Send 'i' to channel 'ch'.
13        }
14    }

このgenerate関数は、2,3,4,5,…という一連の値をパラメータchに送信します。送信には二項演算子<-を使用します。
チャネルの送信ではch変数を受信する側がいなければブロックされ、誰かが受信状態になるまで送信を保留します。

次のfilter関数では3つのパラメータ、入力チャネル・出力チャネル・素数を受け取り、入力チャネルの値を出力チャネルにコピーします。
その際、素数で割り切れる値は除外します。
受信に使用する単項演算子<-は、チャネル上から次の値を取り出します。 

16    // Copy the values from channel 'in' to channel 'out',
17    // removing those divisible by 'prime'.
18    func filter(in, out chan int, prime int) {
19        for {
20            i := <-in;  // Receive value of new variable 'i' from 'in'.
21            if i % prime != 0 {
22                out <- i  // Send 'i' to channel 'out'.
23            }
24        }
25    }

generator関数とfilters関数は平行動作します。
Go言語は独自のプロセス/スレッド/軽量プロセス/コルーチンモデルを持っていますが、
表記上の混乱を避けるためGo言語の平行実行処理をゴルーチン(goroutines)と呼ぶことにしています。
ゴルーチンを開始するためには、キーワードgoを前に付けて関数を呼び出します。
こうすると呼び出した関数はカレントの処理と同じアドレス空間のまま、平行動作を開始します。

    go sum(hugeArray); // calculate sum in the background

計算が終了したことを検知したいときは、つぎのようにして処理終了を通知するチャネルを関数に渡してください。

    ch := make(chan int);
    go sum(hugeArray, ch);
    // ... do something else for a while
    result := <-ch;  // wait for, and retrieve, result

素数の篩に話を戻しましょう。下のようにして篩パイプラインを組み合わせます。 

28    func main() {
29        ch := make(chan int);  // Create a new channel.
30        go generate(ch);  // Start generate() as a goroutine.
31        for {
32            prime := <-ch;
33            fmt.Println(prime);
34            ch1 := make(chan int);
35            go filter(ch, ch1, prime);
36            ch = ch1
37        }
38    }

まずはじめに、29行目でgenerate関数に渡すチャネルを初期化しています。
チャネルから素数をひとつ取り出し、新たにfilterをパイプラインに加えて、そこからの出力でch変数を置き換えます。

この篩プログラムは、共通プログラミングパターンを使って書き換えられます。
下のコードはgenerate関数を変更したバージョンです。このファイルはprogs/sieve1.goにあります。

10    func generate() chan int {
11        ch := make(chan int);
12        go func(){
13            for i := 2; ; i++ {
14                ch <- i
15            }
16        }();
17        return ch;
18    }

このバージョンでは、関数内ですべてのセットアップを行っています。
まず出力チャネルをつくり、次にリテラル関数をゴルーチンとして起動して、呼出元にチャネルを返します。
これはすなわちゴルーチンを起動しその接続を返すという、並列処理のためのファクトリです。

リテラル関数表記(12-16行目)によって、匿名の関数をつくって即座に呼び出しています。
注意していただきたいのは、ローカル変数chはリテラル関数からもアクセス可能で、generate関数から抜けたあとも残り続けることです。

同じ変更をfilter関数にも適用します。 

21    func filter(in chan int, prime int) chan int {
22        out := make(chan int);
23        go func() {
24            for {
25                if i := <-in; i % prime != 0 {
26                    out <- i
27                }
28            }
29        }();
30        return out;
31    }

その結果、メインループはよりシンプルで処理が明確になりました。
これも同様にファクトリに変更します。 

33    func sieve() chan int {
34        out := make(chan int);
35        go func() {
36            ch := generate();
37            for {
38                prime := <-ch;
39                out <- prime;
40                ch = filter(ch, prime);
41            }
42        }();
43        return out;
44    }

その結果、メイン関数とsieve関数との受け渡しはprimesチャネルだけになりました。

46    func main() {
47        primes := sieve();
48        for {
49            fmt.Println(<-primes);
50        }
51    }

チュートリアルの翻訳、9回目です。
前回までの訳はチュートリアル[日本語訳]にまとめてありますのでごらんください。


文字出力

これまでは文字列のフォーマット出力は、あまり使ってきませんでした。
このセクションではGo言語の文字列フォーマットI/Oがどのように動いているか解説します。

いままでのサンプルコードの中では簡単な使い方しかしていなかったfmtパッケージですが、このパッケージにはPrintf, Fprintfなどの関数が実装されています。fmtパッケージ内ではPrintfが次のシグネチャーで宣言されています。

    Printf(format string, v ...) (n int, errno os.Error)

この…は可変引数リストです。C言語ではstdarg.hのマクロを使ってハンドリングしますが、Go言語では空インタフェース(interface {})変数として引き渡され、それをリフレクションライブラリを使って処理します。
話がそれますが、Printfはリフレクションを使用してパラメータの型を動的に判断しています。リフレクションを使ってみるとGo言語のPrintfの素晴らしさがお分かりいただけると思います。

たとえば、C言語ではフォーマットと受け取ったパラメータとの型が一致している必要がありますが、Go言語ではほとんどの場合もっと簡単です。
フォーマット%lludの代わりに%dとだけ記述すれば、Printfは整数のサイズ・符号を判断して一番適したフォーマットで出力します。

下は簡単な例です。 

10        var u64 uint64 = 1<<64-1;
11        fmt.Printf("%d %d\n", u64, int64(u64));

その出力です。

    18446744073709551615 -1

もしフォーマットについて考えるのが面倒なら%vを使ってください。どんな値(配列や構造体でさえ)であってもそれに最適な書式で出力します。次はその例です。

14        type T struct { a int; b string };
15        t := T{77, "Sunset Strip"};
16        a := []int{1, 2, 3, 4};
17        fmt.Printf("%v %v %v\n", u64, t, a);

その出力です。

    18446744073709551615 {77 Sunset Strip} [1 2 3 4]

もしPrintfの代わりにPrintまたはPrintlnを使用するのであれば、フォーマットの指定は全く不要です。これらのルーチンは完全自動でフォーマットを行います。
Print関数はパラメータで指定された値を%vフォーマットを使ったときと同じように出力します。一方Println関数はパラメータの間にスペースを、そして最後に改行コードを挿入します。
下の2行の出力内容は、前回のPrintfの呼び出しと同じ結果になります。

18        fmt.Print(u64, " ", t, " ", a, "\n");
19        fmt.Println(u64, t, a);

自分で作った型をPrintfまたはPrintでフォーマットしたいときは、文字列を返すString()メソッドを実装してください。
printルーチンは、出力しようとする値がString()メソッドを実装しているか調べ、もし実装しているのであればそれを使います。

ここに分かりやすい例があります。

09    type testType struct { a int; b string }

11    func (t *testType) String() string {
12        return fmt.Sprint(t.a) + " " + t.b
13    }

15    func main() {
16        t := &testType{77, "Sunset Strip"};
17        fmt.Println(t)
18    }

*testTypeはString()メソッドを持っているので、この型のデフォルトのフォーマット処理としてString()メソッドが出力時に使われます。

    77 Sunset Strip

このString()メソッド内でフォーマットのためにSprint関数を呼び出しているように、自作のフォーマット処理から他のフォーマット処理を呼び出すこともできます。

Printfのもう一つの特徴として、フォーマット%Tは値そのものではなく型情報を文字列に変換します。これはポリモーフィックなコードをデバッグするときに役立ちます。

フラグや精度情報などを持った完全自作のフォーマット処理を記述することは可能ですが、このチュートリアルの範囲からはずれているためここでは説明しません。

ある型がString()メソッドを実装しているかどうかをPrintfがどのように判断しているかというと、値が『String()メソッドを実装したインタフェース』に変換可能かを調べることで判定しています。

仮に変数vがあったとして、次のように判断しています。

    type Stringer interface {
        String() string
    }

 

    s, ok := v.(Stringer);  // vが"String()"を実装しているか調べる
    if ok {
        result = s.String()
    } else {
        result = defaultOutput(v)
    }

このコードでは、型アサーション(type assertion)を使用し、v変数の値がStringerインタフェースを実装しているか調べています。v.(Stringer)の箇所が型アサーションです。型アサーションに成功すると、指定したインタフェース型に変換されたインスタンスがs変数に設定され、ok変数にはtrueが設定されます。その後、変換されたs変数を使用してメソッドにアクセスします。
(「カンマ+ok」という書き方は、型変換、マップ更新、通信処理において、処理が成功したかどうかを確認するときによく使われるGo言語のイディオムです。ですがチュートリアル内で使用しているのはここだけです)

値がインタフェースを実装していないときは、ok変数にはfalseが設定されます。

このStringerのコードのように、インタフェースに名前をつける際、メソッド名にer( またはr)をつけるというのがGo言語の習わしです。

最後の話題です。Printf系、Sprintf系がでてきましたが、当然Fprintf系もあります。C言語と異なりFprintfの第1パラメータはファイルではなく、ioパッケージで定義されているio.Writerインタフェースです。

    type Writer interface {
        Write(p []byte) (n int, err os.Error);
    }

このインタフェースは前出の習わしに従った名前です。WriteメソッドからWriterインタフェースと名づけられています。
io.Reader、io.ReadWriterの名前も同じルールで名づけられています。

このように、ファイルだけでなく、ネットワークチャネル、バッファなど標準Writeメソッドを実装していればどんな型を使っても、Fprintfを呼び出すとができます。

チュートリアルの翻訳、8回目です。
前回までの訳はチュートリアル[日本語訳]にまとめてありますのでごらんください。


 ソート

Go言語のインタフェースはポリモーフィズムを実現します。
ポリモーフィズムにより、オブジェクトのふるまいと実際の処理を完全に分離できるので、同一インタフェースを実装していても型が異なるオブジェクトを、ひとつの変数を使って(代入しなおす必要はありますが)アクセスすることができます。

下の簡単なソートアルゴリズムの例をみてください。このファイルはprogs/sort.goにあります。

13    func Sort(data Interface) {
14        for i := 1; i < data.Len(); i++ {
15            for j := i; j > 0 && data.Less(j, j-1); j-- {
16                data.Swap(j, j-1);
17            }
18        }
19    }

Sortに渡されるInterfaceに必要なのは3つのメソッドだけです。 

07    type Interface interface {
08        Len() int;
09        Less(i, j int) bool;
10        Swap(i, j int);
11    }

Sort関数には、このLen,Less,Swapメソッドを実装さえしていればどんな型でも渡すことができます。

sortパッケージには、整数値や文字列の配列をソートするのに必要なメソッドが用意されています。
下は、int型の配列をソートするコードです。 

33    type IntArray []int

35    func (p IntArray) Len() int            { return len(p); }
36    func (p IntArray) Less(i, j int) bool  { return p[i] < p[j]; }
37    func (p IntArray) Swap(i, j int)       { p[i], p[j] = p[j], p[i]; }

構造体ではない型に対しメソッドを定義していることがお分かりいただけるでしょうか。
どんな型であっても、それが定義されたパッケージ内であればメソッドを定義することができます。

これをテストするためのルーチンです。これはprogs/sortmain.goファイルにあります。
ここではコードを省略するため、sortパッケージのIsSortedメソッドを使って実際にソートされているか確認しています。

12    func ints() {
13        data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586};
14        a := sort.IntArray(data);
15        sort.Sort(a);
16        if !sort.IsSorted(a) {
17            panic()
18        }
19    }

新しい型をソート可能にするには、このように3つのメソッドを実装するだけです。

30    type day struct {
31        num        int;
32        shortName  string;
33        longName   string;
34    }

36    type dayArray struct {
37        data []*day;
38    }

40    func (p *dayArray) Len() int            { return len(p.data); }
41    func (p *dayArray) Less(i, j int) bool  { return p.data[i].num < p.data[j].num; }
42    func (p *dayArray) Swap(i, j int)       { p.data[i], p.data[j] = p.data[j], p.data[i]; }