[インデックス 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以前では、関数値とマップ値の等価性比較が可能でしたが、これらの比較には以下の根本的な問題がありました:
- セマンティクスの曖昧性: 関数値の等価性、特にクロージャが関係する場合において、「等しい」とは何を意味するかが明確でない
- パフォーマンス上の問題: 関数比較を許可すると、コンパイラが最適化を行う際の制約になる
- 実装の複雑性: マップの等価性比較は内容ではなくポインタベースで行われ、直感的でない結果を招く
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>
仕様変更の影響
- 関数比較の削除: 関数値同士の等価性比較が完全に禁止
- マップ比較の削除: マップ値同士の等価性比較が完全に禁止
- 統一的な表現: スライス、マップ、関数を同じ制約グループに統合
- チャネル比較の保持: チャネル値同士の比較は引き続き許可
コアとなるコードの変更箇所
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)
この設計により、開発者は比較のセマンティクスを明確に理解し、適切な比較手段を選択することが強制されます。
関連リンク
- Go Programming Language Specification - Comparison operators
- Go Blog - All your comparable types
- Go 1 Release Notes
- Go FAQ - Why can't I compare slices?
- Go Blog - Maps in action
参考にした情報源リンク
- Go Programming Language Specification
- Stack Overflow - How do I compare two functions for pointer equality in Go?
- Medium - Comparison operators in Go
- Medium - Equality in Golang
- GeeksforGeeks - How to Compare Equality of Struct, Slice and Map in Golang
- GitHub Issues - proposal: Go 2: define slice equality
- YourBasic Go - 3 ways to compare slices