A Tour of Goの練習問題を解説するシリーズ(7/11) – Exercise: Readers

みなさん、こんにちは。人類をGopherにしたいと考えているまるりんです。

A Tour of Goはプログラミング言語Goの入門サイトです。 このシリーズではA Tour of Goの練習問題を解説します。

今回は以下の問題を扱います。

問題
Exercise: Readers
解答
https://go.dev/play/p/tVGZy4D70SP


ASCII文字 'A' の無限ストリームを出力する Reader 型を実装してください。

問題の意味がよく分からなかったので、とりあえず'A'を返してみました。

ソース
https://go.dev/play/p/3mkfz0hY94A

実行結果

got byte 0 at offset 0, want 'A'

どうやら変数b[0]'A'を期待しているようです。

ソース
https://go.dev/play/p/tVGZy4D70SP

実行結果

OK!

なぜか「OK!」が出力されました。この問題が問うていることの真意はなんでしょうか。 まずreader.Validate()の中を覗いてみたいと思います。

go getでダウンロードします。

$ go get golang.org/x/tour/reader
$ ls ~/go/pkg/mod/golang.org/x/tour@v0.1.0/reader/validate.go

以下が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!")
}

(よく考えたらGitHubにありました)
https://github.com/golang/tour/blob/master/reader/validate.go#L13

ループの終了条件はループが1048576(1<<20 == 2^20)回実行されるか、r.Read()で読み込んだバイト数の合計が1048576(1<<20 == 2^20)より小さくないか、です。 bの長さは最大で1024あるのでr.Read()がそれを1024回読み出せば、以下の条件が偽になりループを抜けます。

1048576(読み込んだバイト数の合計) < 1048576(読み込めるバイト数の上限)

a = 1024: r.Read()が1回に読み出せるバイト数の上限
b = 1024: ループ実行回数
1048576 = a * b
        = 1024 * 1024
        = 2^10 * 2^10
        = 2^20

これを踏まえた上でr.Read()が最大長を読み出せる解答を作ります。

ソース
https://go.dev/play/p/tVGZy4D70SP

実行結果

OK!

結論として「無限ストリーム」ではなくたかだか1KBのストリームだということが分かりました(関数型言語でこのような概念を無限ストリームというのかどうかは知りません)。