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

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

このコミットは、Go言語の reflect パッケージ内の deepValueEqual 関数から、配列の長さを比較する冗長なチェックを削除するものです。deepValueEqualreflect.DeepEqual の内部で利用され、2つの値が「ディープイコール」であるかを再帰的に判定します。この変更により、コードの簡潔性が向上し、既に他の場所で保証されている条件の重複チェックが排除されます。

コミット

commit c989a0b2f7f7a51edbf4f46ae383ce78047a7500
Author: Shawn Smith <shawn.p.smith@gmail.com>
Date:   Sun Dec 29 11:05:30 2013 -0800

    reflect: remove length check for arrays in deepValueEqual
    
    R=golang-codereviews, iant
    CC=golang-codereviews
    https://golang.org/cl/39910044

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

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

元コミット内容

reflect: remove length check for arrays in deepValueEqual

R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/39910044

変更の背景

reflect.DeepEqual 関数は、Go言語において2つの値が構造的に等しいかどうかを判断するために使用されます。この関数は内部的に deepValueEqual というヘルパー関数を呼び出し、値の型に基づいて異なる比較ロジックを適用します。

以前の deepValueEqual 関数では、配列 (Array Kind) の比較を行う際に、まず2つの配列の長さが等しいかどうかを確認する明示的なチェック (v1.Len() != v2.Len()) が含まれていました。しかし、reflect.DeepEqual の設計上、deepValueEqual が配列の比較を行う段階に到達する前に、既に v1v2 の型が完全に一致していることが保証されています。Go言語の型システムにおいて、配列の型は要素の型と配列の長さの両方によって定義されます。例えば、[3]int[4]int は異なる型と見なされます。

したがって、deepValueEqualArray Kind の比較を行う時点で、v1.Type()v2.Type() は既に等しいことが保証されており、これは v1.Len()v2.Len() も等しいことを意味します。このため、配列の長さを明示的にチェックするコードは冗長であり、削除しても機能的な影響はありません。このコミットは、この冗長なチェックを削除し、コードをより簡潔で効率的にすることを目的としています。

前提知識の解説

Go言語の reflect パッケージ

reflect パッケージは、Goプログラムが実行時に自身の構造を検査(リフレクション)することを可能にします。これにより、変数の型、値、メソッドなどを動的に調べたり、操作したりすることができます。

  • reflect.Value: Goの任意の値を抽象化したものです。この型を通じて、値の操作や情報取得を行います。
  • reflect.Kind: reflect.Value が表す値の基本的なカテゴリ(例: Int, String, Struct, Array, Slice, Map など)を示します。
  • reflect.Type: Goの型システムにおける型そのものを表します。reflect.Type は、その型の名前、基底型、メソッド、フィールドなどの情報を提供します。
  • v.Kind(): reflect.Value の基底型を返します。
  • v.Len(): Array, Slice, Map, String, Chan の長さ(要素数、バイト数、バッファサイズ)を返します。
  • v.Index(i): 配列やスライス、文字列の i 番目の要素を reflect.Value として返します。

reflect.DeepEqual 関数

reflect.DeepEqual(x, y interface{}) bool は、2つの引数 xy が「ディープイコール」であるかどうかを再帰的に判断する関数です。これは、単なる == 演算子では比較できない構造体、配列、スライス、マップなどの複合型に対して特に有用です。

DeepEqual の比較ルールは以下の通りです:

  • 異なる型: xy の型が異なる場合、false を返します。
  • 基本型: 基本型(数値、文字列、真偽値など)は == 演算子で比較されます。
  • 配列: 要素ごとに再帰的に DeepEqual で比較されます。配列の型には長さが含まれるため、長さが異なる配列は型が異なると判断され、この比較ロジックに入る前に false となります。
  • スライス: 長さ、容量、および要素が再帰的に比較されます。nilスライスと空スライスは異なるものとして扱われます。
  • マップ: キーと値のペアが再帰的に比較されます。
  • 構造体: フィールドごとに再帰的に比較されます。
  • ポインタ: 指している値が再帰的に比較されます。
  • 関数、チャネル、インターフェース: 同一性(同じオブジェクトを指しているか)が比較されます。

Go言語における配列の型

Go言語において、配列の型は要素の型と配列の長さの両方によって厳密に定義されます。例えば、[3]int[4]int は全く異なる型です。これは、スライス([]int)が長さを型に含まず、実行時に長さを変更できるのとは対照的です。この型システムの特性が、deepValueEqual における配列の長さチェックが冗長である理由の根幹にあります。

技術的詳細

このコミットの技術的詳細は、reflect.DeepEqual の内部実装、特に deepValueEqual 関数がどのように型チェックと値の比較を分離しているかに集約されます。

reflect.DeepEqual の実装は、まず比較対象の2つの値 xyreflect.Value 型に変換し、それらの型が一致するかどうかを最初に確認します。もし型が一致しない場合、DeepEqual は即座に false を返します。

// DeepEqual reports whether x and y are “deeply equal,” defined as follows.
// ...
func DeepEqual(x, y interface{}) bool {
	if x == nil || y == nil {
		return x == y
	}
	v1 := ValueOf(x)
	v2 := ValueOf(y)
	if v1.Type() != v2.Type() { // ここで型が比較される
		return false
	}
	return deepValueEqual(v1, v2, make(map[visit]bool), 0)
}

上記のコードスニペット(コミット当時の正確なコードではない可能性がありますが、概念は同じです)が示すように、deepValueEqual が呼び出される時点では、v1.Type()v2.Type() は既に等しいことが保証されています。

配列の reflect.Type は、その要素の型と配列の長さの両方を含みます。例えば、reflect.TypeOf([3]int{})reflect.TypeOf([4]int{}) は異なる reflect.Type オブジェクトを返します。したがって、v1.Type() == v2.Type()true であるならば、それは v1v2 が同じ要素型を持ち、かつ同じ長さを持つ配列であることを意味します。

このため、deepValueEqual 関数内の case Array: ブロックで v1.Len() != v2.Len() のチェックを行うことは、既に DeepEqual の初期段階で保証されている条件を再確認するものであり、冗長です。このチェックを削除しても、DeepEqual の正確性や振る舞いに影響はありません。むしろ、わずかながら実行パスが短縮され、コードの意図がより明確になります。

この変更は、Goの reflect パッケージが型システムと密接に連携していること、そしてリフレクションAPIが提供する情報(Kind, Type, Len など)がどのように相互に関連しているかを理解していることを示しています。

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

変更は src/pkg/reflect/deepequal.go ファイルの deepValueEqual 関数内で行われました。

--- a/src/pkg/reflect/deepequal.go
+++ b/src/pkg/reflect/deepequal.go
@@ -62,9 +62,6 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool, depth int) bool {

 	switch v1.Kind() {
 	case Array:
-\t\tif v1.Len() != v2.Len() {
-\t\t\treturn false
-\t\t}\
 		for i := 0; i < v1.Len(); i++ {
 			if !deepValueEqual(v1.Index(i), v2.Index(i), visited, depth+1) {
 				return false

具体的には、case Array: ブロック内の以下の3行が削除されました。

		if v1.Len() != v2.Len() {
			return false
		}

コアとなるコードの解説

削除されたコードは、deepValueEqual 関数が Array 型の値を比較する際の最初のステップでした。このチェックは、2つの reflect.Value オブジェクト v1v2 が表す配列の長さが異なる場合に false を返すことを意図していました。

しかし、前述の通り、reflect.DeepEqual の呼び出し元で既に v1.Type() == v2.Type() のチェックが行われています。Go言語の配列の型は、その長さを含むため、型が同じであれば長さも必ず同じになります。したがって、この if 文は常に true になるか、あるいはこのコードパスに到達する前に既に DeepEqualfalse を返しているかのどちらかであり、実質的に意味のないチェックでした。

この冗長なチェックを削除することで、コードはよりクリーンになり、実行時のオーバーヘッドもわずかながら削減されます。これは、Goの標準ライブラリが継続的に最適化され、不必要なコードが排除されるプロセスの一環です。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語の reflect パッケージのソースコード
  • Go言語の型システムに関する一般的な知識