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

[インデックス 10370] Go言語仕様:関数・マップ比較の制限

コミット

コミットハッシュ: efb74460c366286cf17225874c7d6333542647bc
作者: Russ Cox rsc@golang.org
日付: 2011年11月13日 22:57:45 -0500
タイトル: spec: disallow general func, map comparisons

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

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

元コミット内容

spec: disallow general func, map comparisons

R=golang-dev, gri, r, r
CC=golang-dev
https://golang.org/cl/5369090

変更ファイル: doc/go_spec.html (3行追加、7行削除)

主な変更点:

  • 関数値の等価性比較を削除(nilとの比較を除く)
  • マップ値の等価性比較を削除(nilとの比較を除く)
  • スライス、マップ、関数値はnilとのみ比較可能に統一
  • 仕様書の日付を2011年11月9日から11月13日に更新

変更の背景

この変更は、Go 1.0のリリースに向けた言語仕様の安定化作業の一環として行われました。Go 1.0以前では、関数値とマップ値の等価性比較が可能でしたが、これらの比較には以下の根本的な問題がありました:

  1. セマンティクスの曖昧性: 関数値の等価性、特にクロージャが関係する場合において、「等しい」とは何を意味するかが明確でない
  2. パフォーマンス上の問題: 関数比較を許可すると、コンパイラが最適化を行う際の制約になる
  3. 実装の複雑性: マップの等価性比較は内容ではなくポインタベースで行われ、直感的でない結果を招く

Russ Coxを中心としたGo開発チームは、これらの問題を解決するために、曖昧な操作を言語仕様から除外するという設計判断を下しました。

前提知識の解説

Go言語における比較演算子の分類

Go言語では、型によって比較演算子(==!=)の利用可能性が異なります:

比較可能な型(Comparable Types):

  • 基本型(bool、数値型、string)
  • ポインタ型
  • チャネル型
  • インターフェース型(動的型が比較可能な場合)
  • 構造体型(すべてのフィールドが比較可能な場合)
  • 配列型(要素型が比較可能な場合)

比較不可能な型(Non-comparable Types):

  • スライス型
  • マップ型
  • 関数型

nilとの比較の特殊性

Go言語では、比較不可能な型であっても、事前定義された識別子nilとの比較は特別に許可されています。nilは以下の型のゼロ値を表現します:

  • ポインタ型
  • チャネル型
  • インターフェース型
  • スライス型
  • マップ型
  • 関数型

技術的詳細

変更前の仕様

<!-- 変更前 -->
<li>
Function values are equal if they refer to the same function
or if both are <code>nil</code>.
</li>
<li>
A slice value may only be compared to <code>nil</code>.
</li>
<li>
Channel and map values are equal if they were created by the same call to <code>make</code>
(§<a href="#Making_slices_maps_and_channels">Making slices, maps, and channels</a>)
or if both are <code>nil</code>.
</li>

変更後の仕様

<!-- 変更後 -->
<li>
A slice, map, or function value may be compared only to <code>nil</code>.
</li>
<li>
Channel values are equal if they were created by the same call to <code>make</code>
(§<a href="#Making_slices_maps_and_channels">Making slices, maps, and channels</a>)
or if both are <code>nil</code>.
</li>

仕様変更の影響

  1. 関数比較の削除: 関数値同士の等価性比較が完全に禁止
  2. マップ比較の削除: マップ値同士の等価性比較が完全に禁止
  3. 統一的な表現: スライス、マップ、関数を同じ制約グループに統合
  4. チャネル比較の保持: チャネル値同士の比較は引き続き許可

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

doc/go_spec.htmlの比較演算子セクション(2966行目付近):

-	Function values are equal if they refer to the same function
-	or if both are <code>nil</code>.
-	</li>
-	<li>
-	A slice value may only be compared to <code>nil</code>.
+	A slice, map, or function value may be compared only to <code>nil</code>.
 	</li>
 	<li>
-	Channel and map values are equal if they were created by the same call to <code>make</code>
+	Channel values are equal if they were created by the same call to <code>make</code>

コアとなるコードの解説

1. 関数比較の制限理由

問題1: クロージャの等価性判定

func createIncrementer() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}

f1 := createIncrementer()
f2 := createIncrementer()
// f1 == f2 は何を意味するべきか?

問題2: パフォーマンス上の制約 関数比較を許可すると、コンパイラはクロージャごとに個別の実装を生成する必要があり、最適化の機会が失われます。

2. マップ比較の制限理由

変更前の動作(ポインタベース比較):

m1 := make(map[string]int)
m2 := make(map[string]int)
m1["key"] = 1
m2["key"] = 1
// m1 == m2 は false(同じmake呼び出しでないため)

期待される動作(内容ベース比較):

// 開発者が期待する動作
m1 := map[string]int{"key": 1}
m2 := map[string]int{"key": 1}
// m1 == m2 が true であることを期待

この不一致により、開発者に混乱を与えるため、マップ比較は完全に禁止されました。

3. 統一的な設計原則

変更後の仕様では、以下の一貫した原則が適用されます:

// 許可される比較
var slice []int
var m map[string]int
var f func()

fmt.Println(slice == nil) // true
fmt.Println(m == nil)     // true
fmt.Println(f == nil)     // true

// 禁止される比較
// slice1 == slice2  // コンパイルエラー
// m1 == m2         // コンパイルエラー
// f1 == f2         // コンパイルエラー

4. 代替手段の提供

仕様変更により、開発者は明示的な比較手段を使用する必要があります:

// スライス比較の代替手段
func equalSlices(a, b []int) bool {
    if len(a) != len(b) {
        return false
    }
    for i, v := range a {
        if v != b[i] {
            return false
        }
    }
    return true
}

// マップ比較の代替手段
func equalMaps(a, b map[string]int) bool {
    if len(a) != len(b) {
        return false
    }
    for k, v := range a {
        if b[k] != v {
            return false
        }
    }
    return true
}

// reflect.DeepEqualを使用した汎用比較
import "reflect"
equal := reflect.DeepEqual(obj1, obj2)

この設計により、開発者は比較のセマンティクスを明確に理解し、適切な比較手段を選択することが強制されます。

関連リンク

参考にした情報源リンク