Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15367] ファイルの概要

このコミットは、Go言語の標準ライブラリbufioパッケージにおけるScanner型の利用促進を目的としています。具体的には、Scannerの新しい使用例をexample_test.goファイルとして追加し、既存のReader型のReadLineReadBytesReadStringメソッドのドキュメントにScannerの使用を推奨する記述を追記しています。これにより、開発者がより簡単にScannerの利便性を理解し、適切な場面で利用できるようになります。

コミット

  • コミットハッシュ: dbd409afb58b8fcd315afedd097a85b65f80387d
  • Author: Rob Pike r@golang.org
  • Date: Thu Feb 21 15:55:40 2013 -0800

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/dbd409afb58b8fcd315afedd097a85b65f80387d

元コミット内容

bufio: add examples for Scanner
Mention Scanner in docs for ReadLine etc.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/7375045

変更の背景

bufioパッケージのScanner型は、入力ストリームをトークン(行、単語など)に分割して読み取るための強力で便利なインターフェースを提供します。しかし、既存のReader型のメソッド(ReadLine, ReadBytes, ReadString)と比較して、その利便性や適切な使用方法が十分に認知されていない可能性がありました。

このコミットの背景には、以下の目的があったと考えられます。

  1. Scannerの利用促進: ReadLineなどの低レベルなメソッドよりも、多くの一般的なテキスト処理タスクにおいてScannerがより安全で効率的であることを開発者に示す。
  2. ドキュメントの改善: 既存のドキュメントにScannerへの言及を追加することで、開発者が適切なツールを選択する際のガイダンスを強化する。
  3. 具体的な使用例の提供: Scannerの基本的な使い方から、カスタムの分割関数(Split function)を用いた高度な使い方まで、具体的なコード例を示すことで、開発者が自身のプロジェクトにScannerを導入しやすくする。

特に、ReadLineはバッファの長さに応じて部分的な行を返す可能性があるなど、扱いが難しい側面があります。Scannerはこのような複雑さを抽象化し、よりシンプルで堅牢な入力処理を可能にします。このコミットは、その優位性を明確にし、開発者がより良いプラクティスを採用するよう促すものです。

前提知識の解説

このコミットを理解するためには、Go言語のioパッケージとbufioパッケージに関する基本的な知識が必要です。

ioパッケージ

Go言語における入出力操作の基本的なインターフェースを提供します。

  • io.Reader: データを読み取るためのインターフェース。Read(p []byte) (n int, err error)メソッドを持ちます。
  • io.Writer: データを書き込むためのインターフェース。Write(p []byte) (n int, err error)メソッドを持ちます。

bufioパッケージ

bufioパッケージは、io.Readerio.Writerインターフェースをラップし、バッファリングされたI/Oを提供することで、効率的な読み書きを可能にします。これにより、システムコール(ディスクI/Oなど)の回数を減らし、パフォーマンスを向上させます。

bufio.Reader

bufio.Readerは、バッファリングされた読み取り機能を提供します。主なメソッドには以下のようなものがあります。

  • ReadLine() (line []byte, isPrefix bool, err error): 1行を読み取ります。行がバッファに収まらない場合、isPrefixtrueになり、行が分割されて返されることがあります。これは低レベルなプリミティブであり、通常はより高レベルなメソッドの使用が推奨されます。
  • ReadBytes(delim byte) (line []byte, err error): 指定された区切り文字(delim)が出現するまでデータを読み取り、バイトスライスとして返します。
  • ReadString(delim byte) (line string, err error): ReadBytesと同様ですが、結果を文字列として返します。

これらのメソッドは、区切り文字の処理やエラーハンドリングを呼び出し側で適切に行う必要があります。特にReadLineisPrefixの扱いは複雑になりがちです。

bufio.Scanner

bufio.Scannerは、io.Readerからデータを読み取り、定義されたルール(分割関数)に基づいてトークン(行、単語など)に分割するための高レベルなインターフェースです。Scannerは、ReadLineなどの低レベルなメソッドが持つ複雑さを抽象化し、よりシンプルでイディオマティックな方法で入力処理を行うことを可能にします。

Scannerの主要なメソッドと概念:

  • bufio.NewScanner(r io.Reader): 新しいScannerを作成します。
  • scanner.Scan() bool: 次のトークンを読み取ります。読み取りが成功した場合はtrueを、入力の終端に達したかエラーが発生した場合はfalseを返します。通常、for scanner.Scan() { ... } のループで使用されます。
  • scanner.Text() string: 現在のトークンを文字列として返します。
  • scanner.Bytes() []byte: 現在のトークンをバイトスライスとして返します。
  • scanner.Err() error: Scan()falseを返した後に、エラーが発生した場合はそのエラーを返します。
  • scanner.Split(splitFunc ScanFunc): トークンの分割方法を定義する分割関数を設定します。bufioパッケージには、いくつかの組み込みの分割関数が用意されています。
    • bufio.ScanLines: 入力を改行文字で分割し、各行をトークンとします。
    • bufio.ScanWords: 入力を空白文字で分割し、各単語をトークンとします。
    • bufio.ScanBytes: 入力を1バイトずつ分割し、各バイトをトークンとします。
    • bufio.ScanRunes: 入力をUTF-8エンコードされたルーン(Unicodeコードポイント)で分割し、各ルーンをトークンとします。 また、カスタムのScanFuncを実装することで、独自の分割ロジックを定義することも可能です。

Scannerは、内部でバッファリングとエラーハンドリングを適切に行うため、開発者は入力処理のロジックに集中できます。

技術的詳細

このコミットの技術的詳細は、主にbufio.goのドキュメント更新と、example_test.goでのScannerの具体的な使用例の追加に集約されます。

bufio.goの変更

bufio.goでは、Reader型のReadLineReadBytesReadStringメソッドのコメントが更新されました。これらのメソッドは低レベルな操作を提供しますが、多くの場合、Scannerがより適切な選択肢であることを示唆する文言が追加されています。

  • ReadLineのコメントに「Most callers should use ReadBytes('\\n') or ReadString('\\n') instead or use a Scanner.」という記述が追加されました。これは、ReadLineが低レベルであり、より高レベルなReadBytesReadString、あるいはScannerの使用が推奨されることを明確にしています。
  • ReadBytesReadStringのコメントに「For simple uses, a Scanner may be more convenient.」という記述が追加されました。これにより、単純なユースケースではScannerがより便利であることを示唆しています。

これらの変更は、開発者がこれらのメソッドのドキュメントを参照した際に、Scannerというより良い選択肢があることを認識させるためのものです。

example_test.goの追加

example_test.goは、bufio.Scannerの様々な使用方法を示すための新しいテストファイルです。Go言語の_test.goファイルにExample関数を記述することで、go testコマンド実行時にその例が実行され、出力が検証されます。これは、ライブラリの機能を示すための非常に効果的な方法です。

追加された例は以下の3つです。

  1. ExampleScanner_lines():

    • 標準入力(os.Stdin)から行を読み取る最も基本的なScannerの使用例です。
    • bufio.NewScanner(os.Stdin)Scannerを初期化し、for scanner.Scan() { ... } ループで各行を処理します。
    • scanner.Text()で現在の行の内容を取得し、fmt.Printlnで出力します。
    • エラーハンドリングとしてscanner.Err()を使用しています。
    • これは、ファイルや標準入力から行単位でデータを処理する際の典型的なパターンを示しています。
  2. ExampleScanner_words():

    • 入力ストリームを単語に分割し、単語数をカウントする例です。
    • strings.NewReader(input)を使用して人工的な入力ソースを作成しています。
    • scanner.Split(bufio.ScanWords)を呼び出すことで、Scannerが空白文字で入力を分割するように設定しています。
    • for scanner.Scan() { ... } ループで各単語を処理し、カウントを増やしています。
    • この例は、ScannerSplitメソッドを使って、組み込みの分割関数(ScanWords)を適用する方法を示しています。
  3. ExampleScanner_custom():

    • カスタムの分割関数を使用して、32ビットの10進数入力を検証する高度な例です。
    • bufio.ScanWordsをラップする形でカスタムのsplit関数を定義しています。
    • このカスタム関数は、まずbufio.ScanWordsで単語を抽出し、その後strconv.ParseIntを使ってその単語が32ビット整数としてパース可能かどうかを検証しています。パースに失敗した場合は、エラーを返します。
    • scanner.Split(split)でこのカスタム関数を設定しています。
    • この例は、Scannerの柔軟性を示しており、開発者が独自の複雑な解析ロジックをSplit関数として組み込むことができることを実演しています。特に、エラーを返すことで、不正な入力があった場合にScannerが処理を停止し、エラーを報告できる点が重要です。

これらの例は、Scannerが提供するシンプルさ、柔軟性、そして堅牢性を具体的に示しており、開発者が自身のニーズに合わせてScannerを効果的に利用するための強力なリファレンスとなります。

コアとなるコードの変更箇所

このコミットによるコードの変更は、以下の2つのファイルにわたります。

  1. src/pkg/bufio/bufio.go:

    • ReadLineメソッドのコメントの変更:
      --- a/src/pkg/bufio/bufio.go
      +++ b/src/pkg/bufio/bufio.go
      @@ -278,7 +278,7 @@ func (b *Reader) ReadSlice(delim byte) (line []byte, err error) {
       }
      
       // ReadLine is a low-level line-reading primitive. Most callers should use
      -// ReadBytes('\n') or ReadString('\n') instead.
      +// ReadBytes('\n') or ReadString('\n') instead or use a Scanner.
       //
       // ReadLine tries to return a single line, not including the end-of-line bytes.
       // If the line was too long for the buffer then isPrefix is set and the
      
    • ReadBytesメソッドのコメントの変更:
      --- a/src/pkg/bufio/bufio.go
      +++ b/src/pkg/bufio/bufio.go
      @@ -331,6 +331,7 @@ func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) {
       // it returns the data read before the error and the error itself (often io.EOF).
       // ReadBytes returns err != nil if and only if the returned data does not end in
       // delim.
      +// For simple uses, a Scanner may be more convenient.
       func (b *Reader) ReadBytes(delim byte) (line []byte, err error) {
        // Use ReadSlice to look for array,
        // accumulating full buffers.
      
    • ReadStringメソッドのコメントの変更:
      --- a/src/pkg/bufio/bufio.go
      +++ b/src/pkg/bufio/bufio.go
      @@ -378,6 +379,7 @@ func (b *Reader) ReadBytes(delim byte) (line []byte, err error) {
       // it returns the data read before the error and the error itself (often io.EOF).
       // ReadString returns err != nil if and only if the returned data does not end in
       // delim.
      +// For simple uses, a Scanner may be more convenient.
       func (b *Reader) ReadString(delim byte) (line string, err error) {
        bytes, err := b.ReadBytes(delim)
        return string(bytes), err
      
  2. src/pkg/bufio/example_test.go:

    • このファイルは新規作成され、bufio.Scannerの3つの使用例(ExampleScanner_lines, ExampleScanner_words, ExampleScanner_custom)が追加されました。
    • ファイル全体が新規追加されたため、差分は全行の追加となります。

コアとなるコードの解説

bufio.goのドキュメント変更

これらの変更は、既存のReaderメソッドのドキュメントにScannerへの参照を追加することで、開発者がより適切なツールを選択できるようにするためのものです。

  • ReadLineは「低レベルなプリミティブ」であり、ほとんどの呼び出し元はReadBytes('\n')ReadString('\n')、あるいはScannerを使用すべきであると明記されました。これは、ReadLineisPrefixフラグの処理を必要とするなど、複雑な側面を持つためです。
  • ReadBytesReadStringについては、「単純な使用例では、Scannerの方がより便利かもしれない」と追記されました。これは、これらのメソッドが特定の区切り文字までの読み取りには適しているものの、行や単語といった論理的なトークン単位での処理にはScannerがより適していることを示唆しています。

これらの変更は、コードの動作自体には影響を与えませんが、Goのドキュメントがコードの利用方法に関する重要なガイダンスを提供するという哲学に基づいています。

example_test.goの新規追加

このファイルは、bufio.Scannerの具体的な使用方法をコード例で示すことで、開発者がScannerの機能を理解し、自身のアプリケーションに適用するのを助けます。

ExampleScanner_lines()

func ExampleScanner_lines() {
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		fmt.Println(scanner.Text()) // Println will add back the final '\n'
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stdout, "reading standard input:", err)
	}
}

この例は、Scannerの最も一般的な使用パターンを示しています。os.Stdin(標準入力)を行単位で読み取り、各行をfmt.Printlnで出力します。scanner.Scan()が次のトークン(この場合は行)を読み取り、成功すればtrueを返します。ループ内でscanner.Text()を呼び出すことで、読み取られた行の内容を文字列として取得できます。ループ終了後、scanner.Err()をチェックすることで、読み取り中に発生したエラーを検出できます。

ExampleScanner_words()

func ExampleScanner_words() {
	// An artificial input source.
	const input = "Now is the winter of our discontent,\nMade glorious summer by this sun of York.\n"
	scanner := bufio.NewScanner(strings.NewReader(input))
	// Set the split function for the scanning operation.
	scanner.Split(bufio.ScanWords)
	// Count the words.
	count := 0
	for scanner.Scan() {
		count++
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stdout, "reading input:", err)
	}
	fmt.Printf("%d\n", count)
	// Output: 15
}

この例では、ScannerSplitメソッドの利用方法を示しています。scanner.Split(bufio.ScanWords)を呼び出すことで、Scannerは入力を空白文字で区切られた単語として認識するようになります。これにより、単語数を簡単にカウントするような処理が実現できます。strings.NewReader(input)は、文字列をio.Readerとして扱うための便利な方法です。

ExampleScanner_custom()

func ExampleScanner_custom() {
	// An artificial input source.
	const input = "1234 5678 1234567901234567890"
	scanner := bufio.NewScanner(strings.NewReader(input))
	// Create a custom split function by wrapping the existing ScanWords function.
	split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		advance, token, err = bufio.ScanWords(data, atEOF)
		if err == nil && token != nil {
			_, err = strconv.ParseInt(string(token), 10, 32)
		}
		return
	}
	// Set the split function for the scanning operation.
	scanner.Split(split)
	// Validate the input
	for scanner.Scan() {
		fmt.Printf("%s\n", scanner.Text())
	}

	if err := scanner.Err(); err != nil {
		fmt.Printf("Invalid input: %s", err)
	}
	// Output:
	// 1234
	// 5678
	// Invalid input: strconv.ParseInt: parsing "1234567901234567890": value out of range
}

この例は、Scannerの最も強力な機能の一つであるカスタム分割関数の使用を示しています。splitという匿名関数が定義されており、これはbufio.ScanFuncインターフェースを満たします。このカスタム関数は、まずbufio.ScanWordsを使って単語を抽出し、その後strconv.ParseIntでその単語が32ビット整数として有効かどうかを検証します。もしパースに失敗した場合(例:数値が32ビットの範囲を超える場合)、エラーを返します。scanner.Split(split)でこのカスタム関数を設定することで、Scannerは単語の抽出と同時にその単語のバリデーションも行うようになります。これにより、不正な入力があった場合にscanner.Err()を通じてエラーが報告され、処理が適切に停止します。この例は、複雑な入力フォーマットの解析やバリデーションにScannerをどのように活用できるかを示しています。

関連リンク

参考にした情報源リンク