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

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

このコミットは、Go言語の実験的な型システムパッケージ exp/types における、型の文字列表現生成メカニズムの重要な変更を導入しています。具体的には、Type インターフェースに定義されていた String() メソッドを削除し、代わりに TypeString(Type) string という独立した関数と、その内部で利用される writeType(*bytes.Buffer, Type) というヘルパー関数を導入しています。これにより、パッケージ内の他の型操作関数(例: DerefUnderlying)との一貫性を高めるとともに、文字列結合の効率を向上させています。

コミット

commit 152279f203e75f923c510e4cabae6405046ba7f6
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Jul 31 19:30:18 2012 -0700

    exp/types: Replace String method with TypeString function
    
    This is more in sync with the rest of the package;
    for instance, we have functions (not methods) to
    deref or find the underlying type of a Type.
    
    In the process use a single bytes.Buffer to create
    the string representation for a type rather than
    the (occasional) string concatenation.
    
    R=r
    CC=golang-dev
    https://golang.org/cl/6458057

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

https://github.com/golang/go/commit/152279f203e75f923c510e4cabae6405046ba7f6

元コミット内容

exp/types: StringメソッドをTypeString関数に置き換え

これはパッケージの他の部分との同期をより良くするためです。例えば、Typeの参照解除や基底型を見つけるための関数(メソッドではない)があります。

この過程で、型を文字列で表現するために、(時折発生する)文字列結合ではなく、単一のbytes.Bufferを使用するようにしました。

R=r CC=golang-dev https://golang.org/cl/6458057

変更の背景

この変更の背景には、主に以下の2つの理由があります。

  1. パッケージ内の一貫性の向上: exp/types パッケージには、Type インターフェースを操作する多くのヘルパー関数が存在していました。例えば、ポインタの基底型を取得する Deref や、型の基底型を見つける Underlying などは、Type インターフェースのメソッドとしてではなく、独立した関数として提供されていました。型の文字列表現を取得する String() メソッドが Type インターフェースの一部であることは、この設計パターンと一貫性がありませんでした。このコミットは、文字列表現の生成も独立した関数 TypeString として提供することで、パッケージ全体のAPI設計の一貫性を高めることを目的としています。

  2. 文字列生成のパフォーマンス改善: 従来の String() メソッドの実装では、特に構造体や関数型など、複数の要素から文字列表現を構築する際に、頻繁な文字列結合が行われていました。Go言語において、+ 演算子による文字列結合は、新しい文字列が生成されるたびにメモリの再割り当てとコピーが発生するため、特にループ内で多数回実行されるとパフォーマンスが低下する可能性があります。bytes.Buffer は、可変長のバイトシーケンスを効率的に構築するための型であり、内部でバッファを拡張しながらデータを追加していくため、頻繁なメモリ再割り当てを避けることができます。このコミットは、bytes.Buffer を利用することで、型の文字列表現生成におけるパフォーマンスを向上させることを目指しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と、Goの型システムに関する基本的な知識が必要です。

  • インターフェース (Interfaces): Go言語のインターフェースは、メソッドのシグネチャの集合を定義します。ある型がインターフェースのすべてのメソッドを実装していれば、その型はそのインターフェースを満たしていると見なされます。このコミットでは、Type インターフェースから String() メソッドが削除され、その実装が各具象型から独立した関数に移行しています。

  • メソッド (Methods) と関数 (Functions): Goでは、特定の型に関連付けられた関数をメソッドと呼びます。メソッドはレシーバ(func (t *MyType) MyMethod()) を持ちます。一方、関数はレシーバを持ちません。この変更は、Type インターフェースの具象型に紐づいていた String() メソッドを、レシーバを持たない独立した TypeString 関数に置き換えるものです。

  • fmt.Stringer インターフェース: Go言語の標準ライブラリには fmt.Stringer というインターフェースが定義されており、type Stringer interface { String() string } というシグネチャを持ちます。このインターフェースを実装する型は、fmt.Print 系の関数で自動的に String() メソッドが呼び出され、その戻り値が文字列表現として使用されます。このコミットは、exp/types パッケージ内の Type インターフェースから String() メソッドを削除することで、Typefmt.Stringer インターフェースを満たさなくなることを意味します。これは、exp/types が実験的なパッケージであり、その内部的な表現と外部への公開APIの分離を意図している可能性があります。

  • bytes.Buffer: bytes パッケージに含まれる Buffer 型は、バイトスライスを効率的に操作するための構造体です。特に、文字列を少しずつ構築していくようなシナリオで、頻繁な文字列結合によるパフォーマンスオーバーヘッドを避けるために使用されます。WriteStringWriteByte などのメソッドを提供し、最後に String() メソッドで構築された文字列を取得できます。

  • Goの型システム (Go Type System): Go言語の型システムは、プログラムの構造と振る舞いを定義します。exp/types パッケージは、GoのコンパイラやツールがGoのソースコードを解析し、その型情報を表現・操作するために使用される、Go言語の型システムをモデル化したものです。このパッケージには、Basic (基本型)、Array (配列型)、Slice (スライス型)、Struct (構造体型)、Pointer (ポインタ型)、Func (関数型)、Interface (インターフェース型)、Map (マップ型)、Chan (チャネル型)、Name (名前付き型) など、様々なGoの型を表す構造体が定義されています。

  • 型アサーション (Type Assertion): t.(Type) のような構文は型アサーションと呼ばれ、インターフェース値が特定の具象型であるかどうかをチェックし、その具象型の値を取得するために使用されます。このコミットでは、writeType 関数内で switch t := typ.(type) を使用して、渡された Type インターフェース値の実際の具象型を判別し、それぞれの型に応じた文字列表現を生成しています。

技術的詳細

このコミットの技術的な変更は、主に以下の3つの側面から構成されます。

  1. Type インターフェースからの String() メソッドの削除: src/pkg/exp/types/types.goType インターフェース定義から String() string が削除されました。これにより、Type インターフェースを実装するすべての具象型(*Bad, *Basic, *Array など)は、もはや String() メソッドを実装する必要がなくなりました。

    type Type interface {
    	isType()
    	// -String() string // 削除された行
    }
    
  2. 各具象型からの String() メソッドの実装の削除: *Bad, *Basic, *Array, *Slice, *Struct, *Pointer, *Func, *Interface, *Map, *Chan, *Name といった Type インターフェースの各具象型から、それぞれに定義されていた String() メソッドがすべて削除されました。これらのメソッドが持っていた文字列表現生成のロジックは、後述の writeType 関数に集約されます。

    例: *Bad 型の String() メソッドの削除

    // func (t *Bad) String() string { // 削除されたメソッド
    // 	return fmt.Sprintf("badType(%s)", t.Msg)
    // }
    
  3. writeType 関数と TypeString 関数の導入:

    • writeType(buf *bytes.Buffer, typ Type) 関数: この関数は、Goの様々な型(Type インターフェースを実装する具象型)の文字列表現を、引数として渡された bytes.Buffer に書き込む役割を担います。内部では、switch t := typ.(type) を用いて、渡された Type の実際の具象型を判別し、それぞれの型に応じた文字列表現を buf に追加します。 この関数は再帰的に呼び出されることで、Array の要素型、Pointer の基底型、Func のパラメータや結果型、Map のキー型と要素型、Chan の要素型など、ネストされた型の文字列表現も適切に処理します。これにより、すべての型の文字列表現生成ロジックが一元化されました。

    • TypeString(typ Type) string 関数: この関数は、外部から型の文字列表現を取得するための新しい公開APIです。内部で新しい bytes.Buffer を作成し、そのバッファを writeType 関数に渡して型の文字列表現を構築させます。最後に、buf.String() を呼び出して、構築された文字列を返します。これにより、ユーザーは typ.String() の代わりに TypeString(typ) を呼び出すことで、型の文字列表現を取得できるようになりました。

    • 既存のヘルパー関数の更新: writeParamswriteSignature といった、型情報を文字列に変換する既存のヘルパー関数も、内部で Type の文字列表現が必要な箇所で、従来の Type.(Type).String() の呼び出しを writeType(buf, Type.(Type)) に変更しています。これにより、新しい一元化された文字列表現生成メカニズムが全体に適用されます。

これらの変更により、型の文字列表現生成のロジックが Type インターフェースの具象型から切り離され、独立した関数に集約されました。これにより、コードの保守性が向上し、bytes.Buffer の一貫した使用によりパフォーマンスも改善されました。

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

src/pkg/exp/types/types.go

  1. Type インターフェースからの String() メソッドの削除:

    --- a/src/pkg/exp/types/types.go
    +++ b/src/pkg/exp/types/types.go
    @@ -17,7 +17,6 @@ import (
     // All types implement the Type interface.
     type Type interface {
     	isType()
    -	String() string // この行が削除された
     }
    
  2. 各具象型から String() メソッドの削除: *Bad, *Basic, *Array, *Slice, *Struct, *Pointer, *Func, *Interface, *Map, *Chan, *Name の各型から、それぞれの String() メソッドが削除されています。

    例: *Bad 型の変更

    --- a/src/pkg/exp/types/types.go
    +++ b/src/pkg/exp/types/types.go
    @@ -32,9 +31,6 @@ type Bad struct {
     	Msg string // for better error reporting/debugging
     }
     
    -func (t *Bad) String() string { // このメソッドが削除された
    -	return fmt.Sprintf("badType(%s)", t.Msg)
    -}
    -
     // A Basic represents a (unnamed) basic type.
     type Basic struct {
     	implementsType
    
  3. writeType 関数の追加:

    --- a/src/pkg/exp/types/types.go
    +++ b/src/pkg/exp/types/types.go
    @@ -155,71 +141,88 @@ func writeSignature(buf *bytes.Buffer, t *Func) {
     	writeParams(buf, t.Results, false)
     }
     
    -func (t *Func) String() string { // このメソッドが削除された
    -	buf := bytes.NewBufferString("func")
    -	writeSignature(buf, t)
    -	return buf.String()
    -}
    +func writeType(buf *bytes.Buffer, typ Type) { // この関数が追加された
    +	switch t := typ.(type) {
    +	case *Bad:
    +		fmt.Fprintf(buf, "badType(%s)", t.Msg)
    +
    +	case *Basic:
    +		buf.WriteString("basicType") // TODO(gri) print actual type information
    +
    +	case *Array:
    +		fmt.Fprintf(buf, "[%d]", t.Len)
    +		writeType(buf, t.Elt) // 再帰呼び出し
    +
    +	case *Slice:
    +		buf.WriteString("[]")
    +		writeType(buf, t.Elt) // 再帰呼び出し
    +
    +	case *Struct:
    +		buf.WriteString("struct{")
    +		for i, fld := range t.Fields {
    +			if i > 0 {
    +				buf.WriteString("; ")
    +			}
    +			if fld.Name != "" {
    +				buf.WriteString(fld.Name)
    +				buf.WriteByte(' ')
    +			}
    +			writeType(buf, fld.Type.(Type)) // 再帰呼び出し
    +			if i < len(t.Tags) && t.Tags[i] != "" {
    +				fmt.Fprintf(buf, " %q", t.Tags[i])
    +			}
    +		}
    +		buf.WriteByte('}')
    +
    +	case *Pointer:
    +		buf.WriteByte('*')
    +		writeType(buf, t.Base) // 再帰呼び出し
    +
    +	case *Func:
    +		buf.WriteString("func")
    +		writeSignature(buf, t) // writeSignature内でwriteTypeが使われる
    +
    +	case *Interface:
    +		buf.WriteString("interface{")
    +		for i, m := range t.Methods {
    +			if i > 0 {
    +				buf.WriteString("; ")
    +			}
    +			buf.WriteString(m.Name)
    +			writeSignature(buf, m.Type.(*Func)) // writeSignature内でwriteTypeが使われる
    +		}
    +		buf.WriteByte('}')
    +
    +	case *Map:
    +		buf.WriteString("map[")
    +		writeType(buf, t.Key) // 再帰呼び出し
    +		buf.WriteByte(']')
    +		writeType(buf, t.Elt) // 再帰呼び出し
    +
    +	case *Chan:
    +		var s string
    +		switch t.Dir {
    +		case ast.SEND:
    +			s = "chan<- "
    +		case ast.RECV:
    +			s = "<-chan "
    
  •   default:
    
  •   	s = "chan "
    
  •   }
    
  •   buf.WriteString(s)
    
  •   writeType(buf, t.Elt) // 再帰呼び出し
    
  • case *Name:
  •   buf.WriteString(t.Obj.Name)
    
  • }
  • } ```
  1. TypeString 関数の追加:

    --- a/src/pkg/exp/types/types.go
    +++ b/src/pkg/exp/types/types.go
    @@ -230,9 +224,11 @@ func (t *Name) String() string {
     	return t.Obj.Name
     }
     
    +// TypeString returns a string representation for typ.
    +func TypeString(typ Type) string { // この関数が追加された
    +	var buf bytes.Buffer
    +	writeType(&buf, typ)
    +	return buf.String()
    +}
    
  2. writeParams および writeSignature 関数内の String() 呼び出しの置き換え:

    --- a/src/pkg/exp/types/types.go
    +++ b/src/pkg/exp/types/types.go
    @@ -132,7 +118,7 @@ func writeParams(buf *bytes.Buffer, params ObjList, isVariadic bool) {
     	\tif isVariadic && i == len(params)-1 {
     	\t\tbuf.WriteString("...")
     	\t}
    -\t\tbuf.WriteString(par.Type.(Type).String()) // String()呼び出しが削除
    +\t\twriteType(buf, par.Type.(Type)) // writeType呼び出しに置き換え
     	}
     	buf.WriteByte(')')
     }
    @@ -147,7 +133,7 @@ func writeSignature(buf *bytes.Buffer, t *Func) {
     	buf.WriteByte(' ')
     	if len(t.Results) == 1 && t.Results[0].Name == "" {
     		// single unnamed result
    -\t\tbuf.WriteString(t.Results[0].Type.(Type).String()) // String()呼び出しが削除
    +\t\twriteType(buf, t.Results[0].Type.(Type)) // writeType呼び出しに置き換え
     		return
     	}
    

src/pkg/exp/types/types_test.go

  1. テストコード内の String() 呼び出しの置き換え:
    --- a/src/pkg/exp/types/types_test.go
    +++ b/src/pkg/exp/types/types_test.go
    @@ -120,7 +120,7 @@ func TestTypes(t *testing.T) {
     			continue
     		}
     		typ := Underlying(pkg.Scope.Lookup("T").Type.(Type))
    -\t\tstr := typ.String() // String()呼び出しが削除
    +\t\tstr := TypeString(typ) // TypeString呼び出しに置き換え
     		if str != test.str {
     			t.Errorf("%s: got %s, want %s", test.src, str, test.str)
     		}
    

コアとなるコードの解説

このコミットの核心は、Goの型システムにおける型の文字列表現の生成方法を、オブジェクト指向的なメソッド呼び出しから、より関数型プログラミングに近い独立した関数呼び出しへと変更した点にあります。

従来の設計では、Type インターフェースが String() メソッドを定義し、各具象型(*Bad, *Basic, *Array など)がそのメソッドを実装していました。これは fmt.Stringer インターフェースのパターンに従っており、Goでは一般的な慣習です。しかし、このコミットの作者は、exp/types パッケージ内の他の型操作(例: Deref, Underlying)が関数として提供されていることとの一貫性を重視し、文字列表現の生成も関数として提供する方針に転換しました。

新しいアプローチでは、Type インターフェースから String() メソッドが削除され、各具象型からその実装も削除されました。代わりに、writeType(*bytes.Buffer, Type) という内部ヘルパー関数が導入されました。この関数は、Type インターフェースの具象型に応じて、switch ステートメントを使って適切な文字列表現を bytes.Buffer に書き込みます。特に重要なのは、Array の要素型や Pointer の基底型など、ネストされた型に対しても writeType が再帰的に呼び出される点です。これにより、複雑な型構造を持つ場合でも、一貫した方法で文字列表現が生成されます。

そして、TypeString(Type) string という新しい公開関数が導入されました。この関数は、内部で bytes.Buffer を初期化し、writeType を呼び出してバッファに文字列表現を構築させ、最終的に buf.String() で完成した文字列を返します。これにより、ユーザーは typ.String() の代わりに TypeString(typ) を呼び出すことで、型の文字列表現を取得できるようになりました。

また、この変更はパフォーマンスの最適化も兼ねています。従来の String() メソッドの実装では、特に StructFunc のように複数の部分から文字列を構築する場合に、頻繁な文字列結合(+ 演算子)が行われていました。Goにおいて、文字列は不変であるため、+ 演算子による結合は新しい文字列オブジェクトの生成とデータのコピーを伴い、これが繰り返されると効率が悪くなります。bytes.Buffer は、内部で可変長のバイトスライスを管理し、必要に応じてバッファを拡張しながらデータを追加していくため、このような頻繁なメモリ再割り当てとコピーを避けることができます。これにより、特に大規模な型構造の文字列表現を生成する際のパフォーマンスが向上します。

まとめると、このコミットは、APIの一貫性向上とパフォーマンス最適化という二重の目的を達成するために、Goの型システムにおける文字列表現の生成メカニズムを根本的に再設計したものです。

関連リンク

参考にした情報源リンク

  • コミットメッセージと差分 (git diff)
  • Go言語の公式ドキュメント (bytes パッケージ, fmt パッケージ)
  • Go言語における文字列結合のパフォーマンスに関する一般的な知識
  • Go言語のインターフェースとメソッドに関する一般的な知識