この記事は Goクイズアドベントカレンダー2020の記事です。
問題
次のコードを実行するとどうなるでしょうか?
package main
import "fmt"
type S []int
func main() {
var a, b interface{}
a, b = []int{}, S{}
fmt.Print(a == b)
}
選択肢
-
true
と表示される -
false
と表示される build error
panic
正解: 2. 本問に関係する主なGo言語仕様: 比較演算子 https://golang.org/ref/spec#Comparison_operators 出題ポイントと直接関係ない部分を含めて、仕様書に基づいて順番に読んでいきます。適宜仕様書の該当箇所へのリンクを貼っているので、興味に応じて参照していただければと思います。 まず最初の行です。 これは変数宣言で、 次の行に進みます。 これは代入文で、変数 T is an interface type and x implements T. Tがinterface型であり、値xがTを実装するとき(には、xをT型の変数に代入可能である) 「実装する(implement)」の定義はこちらにあります: A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface. interface型の変数は、そのmethod setを含むmethod setを持つ型の値を保持することができます。そのような型は、そのinterfaceを実装する、と言い表します。 問題は次の行です。 Comparison operators 比較演算子は、2つのオペランドを比較して、型なしboolean値を返します。 In any comparison, the first operand must be assignable to the type of the second operand, or vice versa. どのような比較においても、1つめのオペランドは2つめのオペランドの型に代入可能でなければならず、2つめのオペランドも1つめのオペランドの型に代入可能でなければなりません。 この問題では The equality operators == and != apply to operands that are comparable. The ordering operators <, <=, >, and >= apply to operands that are ordered. These terms and the result of the comparisons are defined as follows: 等値演算子 Interface values are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil. interfaceの値は比較可能です。2つのinterfaceの値が等しいのは、それらが同一の動的型を持ち、かつ、等しい動的な値をもつときか、あるいは両方が A comparison of two interface values with identical dynamic types causes a run-time panic if values of that type are not comparable. 2つのinterfaceの値であって同一の動的型をもつものを比較するとき、もしその型の値が比較可能でなければ、run-time panicを引き起こします。 Slice, map, and function values are not comparable. However, as a special case, a slice, map, or function value may be compared to the predeclared identifier nil. Slice, map, functionの値は比較可能ではありません。ただし、例外として、slice、map、functionの値は、事前宣言された識別子のnilと比較することができます。 これを踏まえて、問題に戻ります。 というようになっていましたから、 そうではありません。defined typeである 以上から、正解は2. の (※1): https://golang.org/ref/spec#Type_identity を見ると、"A defined type is always different from any other type." という記述があります。 (※2): 動的型が同一でないときにnot equalであるということは実は明示的には書かれていないのですが、これは未定義というわけではなくて、反対解釈的にnot equalと定義されている、というのが筆者の解釈です。
解答と解説
false
TL;DR
function
型、 slice
型、および map
型の値は比較可能ではなく、比較演算子==
および!=
のオペランドになることができない。
nil
とは比較することができる。a == b
は次のようにふるまう。
a
とb
の動的な型と動的な値とが両方等しいときにのみtrueとなる。a
とb
の動的型が等しくないときは、falseとなる。a
とb
の動的型が等しく、かつその型の値が比較可能でないときには、run-time panicを引き起こす。
細かい解説
var a, b interface{}
interface{}
という型を持つ変数a, b
を宣言しています。interface{}
は「何でも代入できる型」としてお馴染みだと思いますが、これをあえて仕様に基づいて説明すると、メソッドセットが空集合であるようなinterface型を表す型リテラルです。a, b = []int{}, S{}
a, b
に[]int
およびS
のComposite literal
を代入しています。この代入文が合法的であることは、次の代入可能性の仕様からわかります:
interface{}
型は、「空の」method setを表すinterface型です。どんな型のmethod setであれ、「空の」method setを含みますから、どんな型もinterface{}
型を実装すると言えます。したがって、どんな型の値も、interface{}
型の変数に代入可能です。これでこの行も問題ないことがわかりました。fmt.Print(a==b)
fmt.Print
は、引数を(デフォルトのフォーマットで)標準出力に出力します。問題はa==b
という式です。これについては比較演算子のセクションに説明されています。少し長くなりますが、問題に関係する部分を抜き出して訳してみます。
Comparison operators compare two operands and yield an untyped boolean value.
a
とb
どちらの型もinterface{}
型なので、お互いに代入可能になっており、問題ありません。
==, !=
は、比較可能であるようなオペランドに適用されます。順序演算子 <, <=, >, >=
は順序付けされたオペランドに適用されます。これらの用語と各種の比較演算の結果は次のように定義されます:
nil
であるときです。
type S []int
// ...省略
var a, b interface{}
a, b = []int{}, S{}
fmt.Print(a == b)
a
の動的型は[]int
, b
の動的型はS
です。そしてS
の基底型は[]int
ですので、これらの値はいずれも比較可能ではありません。それではpanicするのでしょうか?S
は、他のいかなる型とも異なる型であり(※1)、[]int
とS
とはあくまでも異なる型なのです。a==b
がpanicを引き起こすのは、あくまでも動的型が「同一」である場合に限ります。よって、この問題のように動的型が同一でない場合には、その型の値が比較可能かどうかを判断するまでもなく、a
とb
は等しくないと判断できて、a==b
はfalse
となります(※2)。false
となります。