A Tour of Go の Exercise: Readers 解答例

はじめに

Go 言語基礎文法最速マスターにて出題されている演習課題「Exercise: Readers(リーダー)」の答えに辿り着くまでの手順と解答例の解説を記事にしてみました。無限ストリーム、reader.Validate 関数、Read メソッドなどについても簡単に説明しています。

課題

課題の目的は ASCII 文字 'A' の無限ストリームを出力する Reader 型を実装することです。

package main

import "golang.org/x/tour/reader"

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.

func main() {
	reader.Validate(MyReader{})
}

解答例

package main

import "golang.org/x/tour/reader"

type MyReader struct{}

func (mr MyReader) Read(p []byte) (n int, e error) {
	for i := range p {
		p[i] = 'A';
	}
	return len(p), nil
}

func main() {
    reader.Validate(MyReader{})
}

解説

初めに、課題のソースコードを読み解きます。

3 行目、golang.org/x/tour/reader パッケージをインポートしています。main 関数で呼び出されている reader.Validate 関数が実装されていますね。

import "golang.org/x/tour/reader"

5 行目、typestruct{} 型を MyReader という名前で 型宣言 していますね。

type MyReader struct{}

7 行目、TODO コメントで MyReader 型に Read([]byte) (int, error) メソッドを実装してねと書いてあります。これは Reader インターフェースを暗黙的に実装します。

// TODO: Add a Read([]byte) (int, error) method to MyReader.

9 〜 11 行目、main 関数で reader.Validate 関数を呼び出して MyReader{} を渡しています。

func main() {
	reader.Validate(MyReader{})
}

さて、実装を進める前に reader.Validate 関数の ソースコード を確認しましょう。

func Validate(r io.Reader) {
	b := make([]byte, 1024, 2048)
	i, o := 0, 0
	for ; i < 1<<20 && o < 1<<20; i++ { // test 1mb
		n, err := r.Read(b)
		for i, v := range b[:n] {
			if v != 'A' {
				fmt.Fprintf(os.Stderr, "got byte %x at offset %v, want 'A'\n", v, o+i)
				return
			}
		}
		o += n
		if err != nil {
			fmt.Fprintf(os.Stderr, "read error: %v\n", err)
			return
		}
	}
	if o == 0 {
		fmt.Fprintf(os.Stderr, "read zero bytes after %d Read calls\n", i)
		return
	}
	fmt.Println("OK!")
}

ざっくりとした流れは下記のような感じですね。

  1. 変数 bmake 関数で byte 型の スライス を格納します、要素数は 1028 個、容量は 2048 です。
  2. for 文で <<(ビット演算子)を使って 1<<20(1048576)と変数 i, o を比較してループします。コメントにもあるように 1 メガバイト(1048576 バイト)の検証を行います。
  3. Read メソッドで変数 b 読み取り、バイト数とエラーを変数 n, err に格納しています。
  4. : と変数 n で変数 b の範囲を指定して for range でループします。
  5. 'A' でない場合は fmt.Fprintf 関数と os.Stderr で標準エラー出力をします。
  6. 変数 o に変数 n を加算して格納する、つまり変数 o は総バイト数ですね。
  7. Read メソッドでエラーが発生している場合は標準エラー出力をします。
  8. ループ終了後に総バイト数が 0 の場合は標準エラー出力をします。
  9. 正常終了した場合は fmt.Println 関数で OK! と出力します。

なるほど OK! が出力されれば課題クリアってことがわかります。

次は Read メソッドの振る舞いについて理解が必要ですね。

type Reader interface {
	Read(p []byte) (n int, err error)
}

Reader インターフェースの仕様説明から Read メソッドについての要点を簡単にまとめると下記の 4 点になります。

  1. 最大 len(p) バイトを p に読み取り、読み取られたバイト数 0 <= n <= len(p) とエラーを返す。
  2. n < len(p) だと err != nil になる場合がある。
  3. 呼び出し元は n > 0 を常に処理する必要がある。
  4. len(p) == 0 の場合を除いて (0, nil) を返すのは非推奨です。

まさに reader.Validate 関数のやっていることが参考になりますね。

さて、ASCII 文字 'A' の無限ストリームを出力する Reader 型を実装していきましょう。

まずは、TODO コメントに従って MyReader 型に Read([]byte) (int, error) メソッドを実装します。

package main

import "golang.org/x/tour/reader"

type MyReader struct{}

func (mr MyReader) Read(p []byte) (n int, e error) {}

func main() {
    reader.Validate(MyReader{})
}

次は、'A' の無限ストリームの実装です。これの意味はスライス p'A' で埋めるループのことですね。

例えば、下記のようなループを実装しlen(p)nil を返せば良いです。

package main

import "golang.org/x/tour/reader"

type MyReader struct{}

func (mr MyReader) Read(p []byte) (n int, e error) {
	for i := range p {
		p[i] = 'A';
	}
	return len(p), nil
}

func main() {
    reader.Validate(MyReader{})
}

実行すると下記のように出力されます。

OK!

大丈夫だ、問題ない。

以上です。

おわりに

サガフロンティアの連携技に「無限ストリーム」ってありそう。