はじめに
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 行目、type
で struct{}
型を 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!")
}
ざっくりとした流れは下記のような感じですね。
- 変数
b
にmake
関数でbyte
型の スライス を格納します、要素数は 1028 個、容量は 2048 です。 - for 文で
<<
(ビット演算子)を使って1<<20
(1048576)と変数i
,o
を比較してループします。コメントにもあるように 1 メガバイト(1048576 バイト)の検証を行います。 Read
メソッドで変数b
読み取り、バイト数とエラーを変数n
,err
に格納しています。:
と変数n
で変数b
の範囲を指定してfor range
でループします。'A'
でない場合はfmt.Fprintf
関数とos.Stderr
で標準エラー出力をします。- 変数
o
に変数n
を加算して格納する、つまり変数o
は総バイト数ですね。 Read
メソッドでエラーが発生している場合は標準エラー出力をします。- ループ終了後に総バイト数が 0 の場合は標準エラー出力をします。
- 正常終了した場合は
fmt.Println
関数でOK!
と出力します。
なるほど OK!
が出力されれば課題クリアってことがわかります。
次は Read
メソッドの振る舞いについて理解が必要ですね。
type Reader interface {
Read(p []byte) (n int, err error)
}
Reader
インターフェースの仕様説明から Read
メソッドについての要点を簡単にまとめると下記の 4 点になります。
- 最大
len(p)
バイトをp
に読み取り、読み取られたバイト数0 <= n <= len(p)
とエラーを返す。 n < len(p)
だとerr != nil
になる場合がある。- 呼び出し元は
n > 0
を常に処理する必要がある。 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!
大丈夫だ、問題ない。
以上です。
おわりに
サガフロンティアの連携技に「無限ストリーム」ってありそう。