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

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

このコミットは、Go言語の標準ライブラリである reflect パッケージにおける配列コピーのメカニズムに関する変更です。具体的には、reflect.CopyArray という独立した関数を、ArrayValue インターフェースのメソッドである CopyFrom へと移行しています。これにより、配列のコピー操作がよりオブジェクト指向的なアプローチで提供されるようになります。

変更されたファイルは以下の通りです。

  • src/lib/json/struct.go: reflect.CopyArray の呼び出しが ArrayValue.CopyFrom に変更されています。
  • src/lib/reflect/all_test.go: テストコード内で reflect.CopyArray の呼び出しが ArrayValue.CopyFrom に変更されています。
  • src/lib/reflect/value.go: ArrayValue インターフェースに CopyFrom メソッドが追加され、copyArray 関数が内部ヘルパー関数として定義し直されています。

コミット

commit c5f99ccbdd3f8d0fcef53ff5e3f46076ed4a5740
Author: Rob Pike <r@golang.org>
Date:   Wed Jan 21 15:45:54 2009 -0800

    change reflect.CopyArray into a method on ArrayValue called CopyFrom
    
    R=rsc
    DELTA=16  (12 added, 0 deleted, 4 changed)
    OCL=23242
    CL=23242

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

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

元コミット内容

reflect.CopyArrayArrayValue のメソッド CopyFrom に変更。

変更の背景

この変更の背景には、Go言語の reflect パッケージにおけるAPI設計の改善があります。初期のGo言語では、リフレクション操作のための関数が独立して提供されることがありましたが、時間の経過とともに、関連する操作を特定の型(インターフェース)のメソッドとしてカプセル化する方が、より一貫性があり、使いやすく、オブジェクト指向的な設計であるという認識が深まりました。

reflect.CopyArray は、配列の値をコピーするための汎用関数でしたが、この操作は ArrayValue という特定の型(インターフェース)に密接に関連しています。ArrayValue は、Goの配列やスライスをリフレクションで操作するためのインターフェースであり、配列の長さや要素へのアクセスなどの機能を提供します。配列のコピーもまた、この ArrayValue が持つべき自然な振る舞いの一部と見なされました。

このリファクタリングにより、ArrayValue を扱うコードは、そのインスタンスに対して直接 CopyFrom メソッドを呼び出すことで配列のコピーを行えるようになり、APIの discoverability(発見しやすさ)と使いやすさが向上します。また、reflect パッケージ全体のAPIの一貫性を高めることにも寄与しています。

前提知識の解説

Go言語の reflect パッケージ

Go言語の reflect パッケージは、実行時にプログラムの構造を検査・操作するための機能を提供します。これにより、型情報(Type)、値情報(Value)、構造体のフィールド、メソッドなどを動的に取得・設定できます。主に、汎用的なデータ処理ライブラリ(例: JSONエンコーダ/デコーダ、ORM)や、テストフレームワークなどで利用されます。

  • reflect.Type: Goの型の静的な情報(名前、カテゴリ、メソッドなど)を表します。
  • reflect.Value: Goの値の動的な情報(実際のデータ、設定可能性など)を表します。
  • reflect.ArrayValue インターフェース: reflect.Value の一種で、特に配列やスライスを表すために使用されます。このインターフェースは、配列の長さ (Len()) や容量 (Cap())、特定のインデックスの要素へのアクセス (Elem(i int))、長さの設定 (SetLen(len int)) などのメソッドを定義します。
  • reflect.PtrValue: ポインタの値を表す reflect.Value の一種です。ポインタが指す先の値には Sub() メソッドでアクセスできます。

オープン配列と固定配列 (Open Arrays vs. Fixed Arrays)

Goの reflect パッケージの内部では、配列は「オープン配列 (open arrays)」と「固定配列 (fixed arrays)」という概念で扱われることがあります。

  • 固定配列 (Fixed Arrays): Goの通常の配列 [N]T に対応します。コンパイル時にサイズが固定されます。
  • オープン配列 (Open Arrays): Goのスライス []T に対応します。実行時にサイズが動的に変更される可能性があります。

reflect パッケージは、これらの異なる種類の配列を抽象化して ArrayValue インターフェースとして扱います。

メソッドと関数の違い

  • 関数 (Function): 独立したコードブロックで、特定のタスクを実行します。引数としてデータを受け取り、結果を返します。
  • メソッド (Method): 特定の型に関連付けられた関数です。レシーバ引数(func (r ReceiverType) MethodName(...)r ReceiverType 部分)を持ち、その型のインスタンスのデータにアクセスしたり、そのインスタンスの振る舞いを定義したりします。メソッドは、オブジェクト指向プログラミングにおける「オブジェクトの振る舞い」を表現するのに適しています。

このコミットでは、reflect.CopyArray という独立した関数を、ArrayValue インターフェースのメソッド CopyFrom に変更することで、配列のコピー操作を ArrayValue 型の「振る舞い」として定義し直しています。

技術的詳細

このコミットの主要な技術的変更点は、reflect パッケージにおける配列コピーのAPIを、独立した関数から ArrayValue インターフェースのメソッドへと移行したことです。

  1. ArrayValue インターフェースへの CopyFrom メソッドの追加: src/lib/reflect/value.go において、ArrayValue インターフェースに以下のメソッドが追加されました。

    type ArrayValue interface {
        // ... 既存のメソッド ...
        CopyFrom(src ArrayValue, n int)
    }
    

    これにより、ArrayValue を実装するすべての型は CopyFrom メソッドを提供する必要があるという契約が確立されます。

  2. copyArray ヘルパー関数の導入と実装の移動: 元の reflect.CopyArray 関数の実装は、copyArray という名前の非エクスポート(パッケージプライベート)関数として再定義されました。

    // Works on both fixed and open arrays.
    func copyArray(dst ArrayValue, src ArrayValue, n int) {
        if n == 0 {
            return
        }
        // ... 実際のコピーロジック ...
    }
    

    この copyArray 関数は、引き続き配列コピーの具体的なロジックをカプセル化しますが、外部からは直接呼び出されなくなります。

  3. openArrayValueStructfixedArrayValueStruct への CopyFrom メソッドの実装: ArrayValue インターフェースを実装する具体的な型である openArrayValueStruct (スライスに対応) と fixedArrayValueStruct (固定長配列に対応) に、新しく追加された CopyFrom メソッドが実装されました。これらの実装は、内部の copyArray ヘルパー関数を呼び出すだけです。

    func (v *openArrayValueStruct) CopyFrom(src ArrayValue, n int) {
        copyArray(v, src, n);
    }
    
    func (v *fixedArrayValueStruct) CopyFrom(src ArrayValue, n int) {
        copyArray(v, src, n);
    }
    

    これにより、ArrayValue インターフェースを介して、オープン配列と固定配列の両方に対して一貫した方法で CopyFrom メソッドを呼び出すことができるようになります。

  4. 既存の呼び出し箇所の更新:

    • src/lib/json/struct.go: JSONエンコーダ/デコーダの内部で、動的に配列のサイズを調整する際に reflect.CopyArray が使用されていましたが、これが av1.CopyFrom(av, av.Len()) という形式に変更されました。avav1ArrayValue 型の変数です。
    • src/lib/reflect/all_test.go: reflect パッケージのテストコード内で、TestCopyArray 関数における CopyArray の呼び出しが、vb.(PtrValue).Sub().(ArrayValue).CopyFrom(va.(PtrValue).Sub(), tocopy) という形式に変更されました。これは、ポインタの値をアンラップし、さらに ArrayValue に型アサーションしてから CopyFrom を呼び出すという、リフレクションAPIの典型的な使用パターンを示しています。

この変更は、APIのセマンティクスをより明確にし、ArrayValue が配列操作の中心的なエンティティであることを強調します。また、将来的に ArrayValue の実装が追加された場合でも、CopyFrom メソッドを実装するだけでよく、既存の copyArray 関数を変更する必要がないため、拡張性も向上します。

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

src/lib/json/struct.go

--- a/src/lib/json/struct.go
+++ b/src/lib/json/struct.go
@@ -149,7 +149,7 @@ func (b *_StructBuilder) Elem(i int) Builder {
 					n *= 2
 				}
 				av1 := reflect.NewOpenArrayValue(av.Type(), av.Len(), n);
-				reflect.CopyArray(av1, av, av.Len());
+				av1.CopyFrom(av, av.Len());
 				pv.SetSub(av1);
 				av = av1;
 			}

src/lib/reflect/all_test.go

--- a/src/lib/reflect/all_test.go
+++ b/src/lib/reflect/all_test.go
@@ -308,7 +308,7 @@ func TestCopyArray(t *testing.T) {
 		}
 	}
 	for tocopy := 1; tocopy <= 7; tocopy++ {
-		CopyArray(vb.(PtrValue).Sub(), va.(PtrValue).Sub(), tocopy);
+		vb.(PtrValue).Sub().(ArrayValue).CopyFrom(va.(PtrValue).Sub(), tocopy);
 		for i := 0; i < tocopy; i++ {
 			if a[i] != b[i] {
 				t.Errorf("1 tocopy=%d a[%d]=%d, b[%d]=%d",

src/lib/reflect/value.go

--- a/src/lib/reflect/value.go
+++ b/src/lib/reflect/value.go
@@ -553,8 +553,11 @@ type ArrayValue interface {
 	Cap() int;
 	Elem(i int)	Value;
 	SetLen(len int);
+	CopyFrom(src ArrayValue, n int)
 }
 
+func copyArray(dst ArrayValue, src ArrayValue, n int);
+
 /*
 	Run-time representation of open arrays looks like this:
 	        struct  Array {
@@ -600,6 +603,10 @@ func (v *openArrayValueStruct) Elem(i int) Value {
 	return newValueAddr(v.elemtype, Addr(data_uint));
 }
 
+func (v *openArrayValueStruct) CopyFrom(src ArrayValue, n int) {
+	copyArray(v, src, n);
+}
+
 type fixedArrayValueStruct struct {
 	commonValue;
 	elemtype	Type;
@@ -628,6 +635,10 @@ func (v *fixedArrayValueStruct) Elem(i int) Value {
 	return nil
 }
 
+func (v *fixedArrayValueStruct) CopyFrom(src ArrayValue, n int) {
+	copyArray(v, src, n);
+}
+
 func arrayCreator(typ Type, addr Addr) Value {\
 	arraytype := typ.(ArrayType);\
 	if arraytype.Open() {
@@ -843,7 +854,8 @@ func NewOpenArrayValue(typ ArrayType, len, cap int) ArrayValue {
 	return newValueAddr(typ, Addr(array));
 }
 
-func CopyArray(dst ArrayValue, src ArrayValue, n int) {
+// Works on both fixed and open arrays.
+func copyArray(dst ArrayValue, src ArrayValue, n int) {
 	if n == 0 {
 		return
 	}

コアとなるコードの解説

src/lib/reflect/value.go の変更

このファイルは reflect パッケージの核心部分であり、Goの型と値をリフレクションで表現するための構造体とインターフェースが定義されています。

  1. ArrayValue インターフェースの変更: ArrayValue インターフェースに CopyFrom(src ArrayValue, n int) メソッドが追加されました。これは、ArrayValue を実装するすべての型が、別の ArrayValue から n 個の要素をコピーする機能を提供する必要があることを意味します。これにより、配列のコピー操作が ArrayValue の「振る舞い」として正式に定義されました。

  2. copyArray 関数の導入と CopyArray のリネーム: 以前はエクスポートされていた CopyArray 関数が、非エクスポートの copyArray 関数にリネームされました。この copyArray 関数は、引き続き配列の要素を実際にコピーする低レベルなロジックを保持しています。この変更により、copyArrayreflect パッケージ内部でのみ使用されるヘルパー関数となり、外部からは ArrayValue インターフェースの CopyFrom メソッドを介してのみ配列コピー機能にアクセスできるようになりました。

  3. openArrayValueStructfixedArrayValueStruct への CopyFrom メソッドの実装: openArrayValueStruct (スライスを表す内部構造体) と fixedArrayValueStruct (固定長配列を表す内部構造体) の両方に、新しく追加された CopyFrom メソッドが実装されました。これらのメソッドは、単に内部の copyArray ヘルパー関数を呼び出すことで、実際のコピー処理を委譲しています。これにより、ArrayValue インターフェースを介して、スライスと固定長配列の両方に対して統一されたコピー操作が可能になります。

src/lib/json/struct.go の変更

このファイルは、Goの json パッケージの内部実装の一部です。JSONデータをGoの構造体にデコードする際に、動的に配列のサイズを調整する必要がある場合があります。

  • reflect.CopyArray から av1.CopyFrom への変更: 以前は reflect.CopyArray(av1, av, av.Len()) という形で独立した関数を呼び出していましたが、このコミットにより av1.CopyFrom(av, av.Len()) と変更されました。ここで avav1reflect.ArrayValue 型の変数です。この変更は、json パッケージが reflect パッケージの新しいAPI設計に準拠したことを示しています。配列のコピー操作が、コピー先の ArrayValue インスタンスのメソッドとして呼び出されるようになりました。

src/lib/reflect/all_test.go の変更

このファイルは reflect パッケージのテストコードです。

  • CopyArray から CopyFrom への変更: TestCopyArray 関数内で、配列コピーのテストを行う際に CopyArray 関数を直接呼び出す代わりに、vb.(PtrValue).Sub().(ArrayValue).CopyFrom(...) という形式で CopyFrom メソッドを呼び出すように変更されました。これは、テストコードが新しいAPIを使用するように更新されたことを示しており、PtrValue から ArrayValue への型アサーションを含む、リフレクションAPIの典型的な使用例を反映しています。

これらの変更は全体として、Goの reflect パッケージのAPIをより一貫性があり、オブジェクト指向的なものにするためのリファクタリングの一環です。配列のコピー操作が、その操作の対象となる ArrayValue インスタンスに密接に関連付けられることで、コードの可読性と保守性が向上します。

関連リンク

参考にした情報源リンク