チュートリアルの翻訳、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 {})を実装しています。
そのため、空インタフェースはコンテナの類を作るときに役立ちます。