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

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

このコミットは、Go言語の標準ライブラリである fmt パッケージにおけるポリモーフィックな(多態的な)印刷機能の導入に関するものです。具体的には、src/lib/fmt/print.go ファイルが変更され、カスタムフォーマッタをサポートするための新しいインターフェースとロジックが追加されました。これにより、開発者は自身の型が fmt パッケージによってどのように印刷されるかを制御できるようになります。

コミット

commit f15dfa7802a9ba59406a476f999071149470257b
Author: Rob Pike <r@golang.org>
Date:   Thu Nov 6 10:40:57 2008 -0800

    Polymorphic printing.
    
    R=rsc
    DELTA=100  (70 added, 10 deleted, 20 changed)
    OCL=18663
    CL=18669

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

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

元コミット内容

Polymorphic printing.

R=rsc
DELTA=100  (70 added, 10 deleted, 20 changed)
OCL=18663
CL=18669

変更の背景

Go言語の初期段階において、fmt パッケージは基本的な型(整数、文字列、浮動小数点数など)の印刷をサポートしていましたが、ユーザー定義型が自身の表示形式をカスタマイズするメカニズムは限られていました。このコミット以前は、fmt パッケージはリフレクションを使用して型の値を検査し、組み込みのルールに基づいて文字列に変換していました。しかし、これは複雑なデータ構造や特定の表示要件を持つ型に対しては柔軟性に欠けていました。

この変更の背景には、Go言語の設計思想である「インターフェースによる多態性」を fmt パッケージにも適用し、より拡張性の高い印刷システムを構築するという意図があります。これにより、ユーザー定義型が特定のインターフェースを実装することで、fmt.Printfmt.Sprintf といった関数がその型のカスタム印刷ロジックを呼び出すことが可能になります。これは、Goの Stringer インターフェース(String() string メソッドを持つ型が fmt パッケージでカスタム表示される)の先駆けとなる、より汎用的なメカニズムの導入と言えます。

前提知識の解説

Go言語のインターフェース

Go言語におけるインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースは、JavaやC++のような明示的な implements キーワードを必要とせず、型がインターフェースで定義されたすべてのメソッドを実装していれば、そのインターフェースを「暗黙的に」実装していると見なされます。これは「ダックタイピング」の一種であり、Goの多態性の主要なメカニズムです。

リフレクション

リフレクションは、プログラムの実行中にその構造(型、メソッド、フィールドなど)を検査し、操作する機能です。Go言語の reflect パッケージは、この機能を提供します。fmt パッケージは、リフレクションを使用して引数の型を動的に調べ、適切な印刷ロジックを適用します。このコミットでは、リフレクションとインターフェースの組み合わせにより、より高度な印刷制御を実現しています。

フォーマット動詞

fmt パッケージでは、%d(10進数)、%s(文字列)、%t(真偽値)などの「フォーマット動詞」を使用して、値の表示形式を指定します。また、%w(幅)や %.p(精度)といったフラグを動詞と組み合わせて、出力の整形を制御できます。このコミットは、これらのフォーマット動詞とフラグの解釈ロジックを拡張し、カスタムフォーマッタにこれらの情報を提供する仕組みを導入しています。

io.Writer インターフェース

Go言語の io パッケージには、バイトスライスを書き込むための Writer インターフェースが定義されています。fmt パッケージの内部では、最終的な出力は Writer インターフェースを実装するオブジェクト(例: os.Stdout)に書き込まれます。このコミットでは、fmt パッケージ内部で利用される Writer インターフェースの定義が変更され、より汎用的な FormatHelper インターフェースの一部として再利用されています。

技術的詳細

このコミットの核心は、fmt パッケージが値の印刷を処理する方法に、インターフェースベースの多態性を導入した点にあります。

  1. Writer インターフェースの再定義と移動: export type Writer interface { Write(b *[]byte) (ret int, err *os.Error); } このインターフェースは、以前は doprintf 関数の近くに定義されていましたが、ファイルのより上部に移動され、FormatHelper インターフェースの基盤として再利用されるようになりました。これは、fmt パッケージが内部的にバイトを書き込むための基本的な契約を明確にするものです。

  2. FormatHelper インターフェースの導入:

    export type FormatHelper interface {
    	Write(b *[]byte) (ret int, err *os.Error);
    	Width()	(wid int, ok bool);
    	Precision()	(prec int, ok bool);
    }
    

    この新しいインターフェースは、カスタムフォーマッタに渡されるコンテキストを提供します。Write メソッドを通じて出力バッファへの書き込みを可能にし、Width()Precision() メソッドを通じて、現在適用されているフォーマット幅と精度に関する情報を提供します。これにより、カスタムフォーマッタは、fmt パッケージの標準的なフォーマットルールに従って自身の出力を整形できます。

  3. Formatter インターフェースの導入:

    export type Formatter interface {
    	Format(f FormatHelper, c int);
    }
    

    これがポリモーフィック印刷の鍵となるインターフェースです。任意の型がこの Formatter インターフェースを実装し、Format メソッドを提供することで、fmt パッケージはその型のカスタム印刷ロジックを呼び出すことができます。Format メソッドは FormatHelper インスタンス(出力先とフォーマット情報を提供)と、現在処理中のフォーマット動詞(%dd など)を表す文字 c を受け取ります。

  4. P struct の拡張: type P structwid, wid_ok, prec, prec_ok フィールドが追加されました。これらは、parsenum 関数によって解析されたフォーマット幅と精度の値を一時的に保持するために使用されます。wid_okprec_ok は、それぞれ幅と精度が指定されたかどうかを示す真偽値です。また、P struct は FormatHelper インターフェースを実装するように変更され、Width()Precision() メソッドが追加されました。

  5. doprintf 関数の変更:

    • フォーマット引数の解析の改善: parsenum 関数からの戻り値が直接 p.wid, p.wid_ok, p.prec, p.prec_ok に格納されるようになりました。これにより、フォーマット幅と精度が P struct の状態として保持され、カスタムフォーマッタからアクセス可能になります。
    • Formatter インターフェースのチェック: doprintf 関数内で、引数の値が Formatter インターフェースを実装しているかどうかがチェックされるようになりました。
      if formatter, ok := field.Interface().(Formatter); ok {
      	formatter.Format(p, c);
      	continue;
      }
      
      もし実装していれば、その型の Format メソッドが呼び出され、標準の印刷ロジックはスキップされます。これがポリモーフィック印刷の実現点です。
    • 型ミスマッチエラーの改善: 以前は型ミスマッチの場合に %b% のような汎用的な文字列が出力されていましたが、goto badtype を使用して、より詳細なエラーメッセージ(例: %(int)%)を生成するようになりました。これにより、デバッグが容易になります。
    • 余分な引数の表示: フォーマット文字列で指定された引数よりも多くの引数が渡された場合に、それらを ?(extra ...) の形式で表示するロジックが追加されました。
  6. getBool 関数の追加: reflect.Value から bool 型の値を取得するためのヘルパー関数 getBool が追加されました。これは、%t フォーマット動詞の処理をより堅牢にするために使用されます。

これらの変更により、Goの fmt パッケージは、ユーザー定義型が自身の印刷ロジックをカプセル化し、fmt パッケージの既存のフォーマット機能とシームレスに統合できる、強力で柔軟なシステムへと進化しました。

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

変更の中心は src/lib/fmt/print.go ファイルです。

  • 新しいインターフェースの定義: Writer, FormatHelper, Formatter インターフェースが追加されました。
  • P struct の変更: フォーマット幅と精度を保持するフィールド (wid, wid_ok, prec, prec_ok) と、それらを取得するメソッド (Width, Precision) が追加されました。
  • doprintf 関数のロジック変更:
    • フォーマット引数の解析と P struct への格納方法の変更。
    • Formatter インターフェースの実装チェックと、実装されている場合のカスタム Format メソッドの呼び出し。
    • 型ミスマッチ時のエラーメッセージの改善。
    • 余分な引数の表示ロジックの追加。
  • getBool 関数の追加: reflect.Value から bool を安全に取得するためのヘルパー関数。

コアとなるコードの解説

新しいインターフェース

// src/lib/fmt/print.go
export type Writer interface {
	Write(b *[]byte) (ret int, err *os.Error);
}

// Representation of printer state passed to custom formatters.
// Provides access to the Writer interface plus information about
// the active formatting verb.
export type FormatHelper interface {
	Write(b *[]byte) (ret int, err *os.Error);
	Width()	(wid int, ok bool);
	Precision()	(prec int, ok bool);
}

export type Formatter interface {
	Format(f FormatHelper, c int);
}

Writer は基本的な書き込み操作を定義します。FormatHelperWriter の機能に加え、フォーマットの幅と精度に関する情報を提供します。Formatter は、カスタム印刷ロジックを持つ型が実装すべきインターフェースです。Format メソッドは、FormatHelper を通じて出力を行い、現在のフォーマット動詞 c を受け取ります。

P struct の変更と FormatHelper の実装

// src/lib/fmt/print.go
type P struct {
	n	int;
	buf	*[]byte;
	fmt	*Fmt;
	wid	int;
	wid_ok	bool;
	prec	int;
	prec_ok	bool;
}

func (p *P) Width() (wid int, ok bool) {
	return p.wid, p.wid_ok
}

func (p *P) Precision() (prec int, ok bool) {
	return p.prec, p.prec_ok
}

P struct は fmt パッケージの内部プリンタの状態を保持します。新しく追加された wid, wid_ok, prec, prec_ok フィールドは、フォーマット文字列から解析された幅と精度を格納します。Width()Precision() メソッドは、これらの値を外部(特に Formatter インターフェースを実装するカスタム型)に公開し、P struct が FormatHelper インターフェースを実装することを可能にします。

doprintf におけるポリモーフィック印刷のロジック

// src/lib/fmt/print.go (doprintf 関数内)
// ...
		// saw % - do we have %20 (width)?
		p.wid, p.wid_ok, i = parsenum(format, i+1, end);
		p.prec_ok = false; // Reset precision flag for new format specifier
		// do we have %.20 (precision)?
		if i < end && format[i] == '.' {
			p.prec, p.prec_ok, i = parsenum(format, i+1, end);
		}
// ...
		field := v.Field(fieldnum);
		fieldnum++;
		if formatter, ok := field.Interface().(Formatter); ok {
			formatter.Format(p, c); // Call custom formatter if available
			continue;
		}
		s := "";
		if p.wid_ok {
			p.fmt.w(p.wid);
		}
		if p.prec_ok {
			p.fmt.p(p.prec);
		}
		switch c {
		// ... 各種フォーマット動詞の処理 ...
		case 't':
			if v, ok := getBool(field); ok {
				if v {
					s = "true";
				} else {
					s = "false";
				}
			} else {
				goto badtype; // Type mismatch, jump to error handling
			}
// ...
		default:
		badtype: // Label for type mismatch error handling
			s = "%" + string(c) + "(" + field.Type().String() + ")%";
		}
		p.addstr(s);
// ...
	if fieldnum < v.Len() { // Handle extra arguments
		p.addstr("?(extra ");
		for ; fieldnum < v.Len(); fieldnum++ {
			p.addstr(v.Field(fieldnum).Type().String());
			if fieldnum + 1 < v.Len() {
				p.addstr(", ");
			}
		}
		p.addstr(")");
	}

このスニペットは、doprintf 関数がどのようにフォーマット文字列を解析し、引数を処理するかを示しています。

  1. parsenum を使用して幅と精度を解析し、P struct のフィールドに格納します。
  2. field.Interface().(Formatter) という型アサーションと型スイッチ (ok 変数) を使用して、現在の引数が Formatter インターフェースを実装しているかをチェックします。
  3. もし実装していれば、その型の Format メソッドを呼び出し、P struct 自体を FormatHelper として渡します。これにより、カスタムフォーマッタは fmt パッケージの内部状態にアクセスし、自身の出力を制御できます。
  4. カスタムフォーマッタが呼び出された場合、continue で次のフォーマット引数に進み、標準の印刷ロジックはスキップされます。
  5. カスタムフォーマッタがない場合、またはカスタムフォーマッタが処理しないフォーマット動詞の場合、fmt パッケージは p.fmt.wp.fmt.p を使用して幅と精度を適用し、switch c ブロックで各フォーマット動詞に応じた標準の印刷ロジックを実行します。
  6. goto badtype は、型ミスマッチが発生した場合に、より詳細なエラーメッセージを生成するための共通の処理ブロックにジャンプするために使用されます。
  7. 最後に、フォーマット文字列で消費されなかった余分な引数がある場合、それらを ?(extra ...) の形式で出力するロジックが追加されています。

これらの変更により、Goの fmt パッケージは、ユーザー定義型が自身の表示形式を完全に制御できる、強力で柔軟な印刷システムへと進化しました。

関連リンク

参考にした情報源リンク

  • Go言語の歴史に関する情報: Wikipedia, TechTarget, dev.to, GeeksforGeeks, released.info
  • Go言語の多態性(インターフェース)に関する情報: Medium, Stack Overflow
  • Go 1.18 でのジェネリクスの追加に関する情報: go.dev
  • コミット情報: commit_data/1072.txt
  • Go言語のソースコード (Go 1.0以前の初期コミット): https://github.com/golang/go/commit/f15dfa7802a9ba59406a476f999071149470257b