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

Archive

Tag: チュートリアル

チュートリアルの翻訳、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]; }

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


catコマンドの実装例

前回作成したfileパッケージを基にして、Unixユーティリティのcat(1)の簡易バージョンを作成します。
このファイルはprogs/cat.goにあります。

05    package main

07    import (
08        "./file";
09        "flag";
10        "fmt";
11        "os";
12    )

14    func cat(f *file.File) {
15        const NBUF = 512;
16        var buf [NBUF]byte;
17        for {
18            switch nr, er := f.Read(&buf); true {
19            case nr < 0:
20                fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", f.String(), er.String());
21                os.Exit(1);
22            case nr == 0:  // EOF
23                return;
24            case nr > 0:
25                if nw, ew := file.Stdout.Write(buf[0:nr]); nw != nr {
26                    fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", f.String(), ew.String());
27                }
28            }
29        }
30    }

32    func main() {
33        flag.Parse();   // Scans the arg list and sets up flags
34        if flag.NArg() == 0 {
35            cat(file.Stdin);
36        }
37        for i := 0; i < flag.NArg(); i++ {
38            f, err := file.Open(flag.Arg(i), 0, 0);
39            if f == nil {
40                fmt.Fprintf(os.Stderr, "cat: can't open %s: error %s\n", flag.Arg(i), err);
41                os.Exit(1);
42            }
43            cat(f);
44            f.Close();
45        }
46    }

すでにこのコードをすぐに理解できるようになっていると思いますが、switch文には説明すべき箇所があります。
forループと同じく、ifやswitchには初期化文を含めることができます。18行目のswitchではf.Read()の戻り値をnrとer変数に、一度に代入しています。(25行目のifも同様です)

swtich文では通常、値と一致するまでcaseを先頭ひとつずつ順に評価していきます。caseに記述する式はswitchで指定した値と同じ型でありさえすれば定数や数値である必要はありません。

このswitchの値にはtrueが指定されています。このように値がtrueのときだけは値の記述は省略可能です。(for文と同じです)
値が記述されていないときはtrueとして扱われます。このようなswitch文は、if – else文を連ねたのと同じです。
またswtich文のcaseには暗黙的なbreakがあることをここに明記しておきます。

26行目では、入力バッファをスライスしてWrite()を呼び出しています。
Go言語ではI/Oバッファを取り扱うときは通常スライスを使用します。

これから入力をrot13変換するオプション機能をもったcatの亜種を作成してみましょう。rot13とはアルファベットを一文字毎に13文字後のアルファベットに置き換えてつくる簡単な暗号です。(参考文献:Wikipedia)
これはバイト処理で行えば簡単ですが、すこし回り道してGo言語のインタフェース(interface)を理解していきましょう。

cat()サブルーチンはf.Read()とString()の2つのメソッドしか使っていませんでした。
まずは、それらの2つのメソッドとまったく同じメソッド持つインタフェースを定義することから始めましょう。
このコードは、progs/cat_rot13.goファイルにあります。 

26    type reader interface {
27        Read(b []byte) (ret int, err os.Error);
28        String() string;
29    }

readerインタフェースの2つのメソッドを持つ型であればすべて(たとえ他のメソッドを有していても)このインタフェースを実装していることになります。すなわちfile.Fileはこの2つのメソッドをすでに実装しているので、readerインタフェースを実装していることになります。

catサブルーチンを少し変更し、引数を*file.Fileの代わりにreaderを受け取るようにするだけでもちゃんと動作しますが、せっかくなのでreaderを実装する別の型を記述してみましょう。

新しく作成する型では別のreaderをラップし、そのreaderから取り出したデータにrot13変換を行います。
型を定義しreaderインタフェースの2つのメソッドを記述すると、readerインタフェースの2個目の実装ができあがります。

31    type rotate13 struct {
32        source    reader;
33    }

35    func newRotate13(source reader) *rotate13 {
36        return &rotate13{source}
37    }

39    func (r13 *rotate13) Read(b []byte) (ret int, err os.Error) {
40        r, e := r13.source.Read(b);
41        for i := 0; i < r; i++ {
42            b[i] = rot13(b[i])
43        }
44        return r, e
45    }

47    func (r13 *rotate13) String() string {
48        return r13.source.String()
49    }
50    // end of rotate13 implementation

(42行目で呼び出しているrot13関数は、取るに足らないので省略しています)

新しく追加した機能を利用するためにflagを定義します。

14    var rot13Flag = flag.Bool("rot13", false, "rot13 the input")

さらに、これを使うためにcat()関数を少しだけ変更します。 

52    func cat(r reader) {
53        const NBUF = 512;
54        var buf [NBUF]byte;

56        if *rot13Flag {
57            r = newRotate13(r)
58        }
59        for {
60            switch nr, er := r.Read(&buf); {
61            case nr < 0:
62                fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", r.String(), er.String());
63                os.Exit(1);
64            case nr == 0:  // EOF
65                return;
66            case nr > 0:
67                nw, ew := file.Stdout.Write(buf[0:nr]);
68                if nw != nr {
69                    fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", r.String(), ew.String());
70                }
71            }
72        }
73    }

56~58行目でrot13フラグがtrueのとき、引数で受け取ったreaderを、rotate13内にラップさせて入れ替えています。
このラッピングさせる処理をmain関数内で行えば、cat関数は引数の型以外は変更しなくてもよかったのですが、これも学習の一部と思ってください。

注意してもらいたいのは、インタフェース変数がポインタではなく値であることです。
渡された値は構造体のポインタではありますが、受け取る引数の型はreaderであり*readerではありません。

実行してみます。

    % echo abcdefghijklmnopqrstuvwxyz | ./cat
    abcdefghijklmnopqrstuvwxyz
    % echo abcdefghijklmnopqrstuvwxyz | ./cat --rot13
    nopqrstuvwxyzabcdefghijklm
    %

このようにインタフェースを使ってファイルディスクリプタの実装が入れ替えられるので、依存性注入(dependency injection)も簡単に行えます。

このインタフェースは他の言語にない、Go言語の特徴です。
インタフェース内に定義されている全てのメソッドを実装した型は、自動的にそのインタフェースを実装したことになります。
すなわち、型は複数の異なるインタフェースを実装可能ということです。

Go言語の型には階層はありませんが、その代わりインタフェースを使用することでrot13で行ったようにプログラムの部分的な変更が容易に行えます。
file.File型はreaderインタフェースを実装していますが、writerインタフェース、その他のインタフェースを必要に応じて実装可能です。

空インタフェースについて考えてみましょう。

    type Empty interface {}

すべての型は空インタフェース(interface {})を実装しています。
そのため、空インタフェースはコンテナの類を作るときに役立ちます。