[インデックス 1839] ファイルの概要
このコミットは、Go言語の仕様書 doc/go_spec.html
に対する変更であり、Go言語に「型スイッチ (type switches)」の概念と構文を導入するものです。これにより、インターフェース型の変数が保持する具体的な型に基づいて異なる処理を行うことが可能になります。
コミット
commit 5a5784977aad171b9c877f81245bb532ea9367ba
Author: Rob Pike <r@golang.org>
Date: Tue Mar 17 16:48:35 2009 -0700
type switches
R=rsc,gri
DELTA=107 (90 added, 1 deleted, 16 changed)
OCL=26420
CL=26427
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5a5784977aad171b9c877f81245bb532ea9367ba
元コミット内容
type switches
R=rsc,gri
DELTA=107 (90 added, 1 deleted, 16 changed)
OCL=26420
CL=26427
変更の背景
Go言語は静的型付け言語ですが、柔軟性を高めるためにインターフェース型を導入しています。インターフェース型は、そのインターフェースを実装する任意の具体的な型の値を保持できます。しかし、プログラムの実行時にインターフェース変数が実際にどの具体的な型の値を保持しているかを知り、それに基づいて異なるロジックを実行する必要がある場面が頻繁に発生します。
このコミット以前は、インターフェース変数の具体的な型を判別し、それに応じた処理を行うための直接的な構文が存在しませんでした。おそらく、型アサーション(value.(Type)
)とif-else if
の組み合わせで型チェックを行っていたと考えられますが、これは冗長で読みにくいコードになりがちでした。
このコミットは、このような型による分岐処理をより簡潔かつ安全に記述するための「型スイッチ」構文をGo言語の仕様に導入することを目的としています。コミットメッセージの[x] type switch or some form of type test needed - duplicate entry
という記述からも、型テストの必要性が以前から認識されており、この機能が待望されていたことが伺えます。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の基本的な概念を理解しておく必要があります。
-
インターフェース (Interfaces): Go言語におけるインターフェースは、メソッドのシグネチャの集まりを定義する型です。具体的な型がインターフェースで定義されたすべてのメソッドを実装していれば、その具体的な型は自動的にそのインターフェースを「実装」していると見なされます。インターフェース型の変数は、そのインターフェースを実装する任意の具体的な型の値を保持できます。これにより、ポリモーフィズムを実現し、柔軟なコード設計が可能になります。
例:
type Shape interface { Area() float64 } type Circle struct { Radius float64 } func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius } type Rectangle struct { Width, Height float64 } func (r Rectangle) Area() float64 { return r.Width * r.Height } func main() { var s Shape s = Circle{Radius: 5} fmt.Println(s.Area()) // CircleのArea()が呼ばれる s = Rectangle{Width: 3, Height: 4} fmt.Println(s.Area()) // RectangleのArea()が呼ばれる }
-
型アサーション (Type Assertions): 型アサーションは、インターフェース型の変数が保持している具体的な値の型を、実行時にチェックし、その型に変換(アサート)するための構文です。
構文:
value.(Type)
value
がType
型の値を保持している場合、その値とtrue
が返されます。value
がType
型の値を保持していない場合、その型のゼロ値とfalse
が返されます。- 単一の戻り値で型アサーションを使用し、型が一致しない場合はパニックが発生します。
例:
var i interface{} = "hello" s := i.(string) // sは"hello" fmt.Println(s) f, ok := i.(float64) // fは0.0, okはfalse fmt.Println(f, ok) // g := i.(int) // パニック: interface conversion: interface {} is string, not int
-
switch
ステートメント (Switch Statements): Go言語のswitch
ステートメントは、他の言語のそれと似ていますが、いくつかの特徴があります。case
式は定数である必要はありません。fallthrough
キーワードがない限り、マッチしたcase
ブロックの実行後、自動的にswitch
ステートメントを抜けます。- 式を省略した場合、
switch true
と同じ意味になります。
例:
x := 10 switch { case x < 0: fmt.Println("Negative") case x == 0: fmt.Println("Zero") default: fmt.Println("Positive") }
これらの概念を組み合わせることで、型スイッチがどのように機能し、どのような問題を解決するのかが明確になります。
技術的詳細
このコミットは、Go言語の仕様書に「型スイッチ (Type Switch)」の構文とセマンティクスを追加しています。型スイッチは、インターフェース変数が保持する具体的な型に基づいて、異なるコードブロックを実行するための特殊なswitch
ステートメントです。
型スイッチの構文
コミットによって追加された文法定義は以下の通りです。
SwitchStat = ExprSwitchStat | TypeSwitchStat .
ExprSwitchStat = "switch" [ [ SimpleStat ] ";" ] [ Expression ] "{" { CaseClause } "}" .
TypeSwitchStat = "switch" [ [ SimpleStat ] ";" ] TypeSwitchExpression "{" { CaseClause } "}" .
TypeSwitchExpression = identifier ":=" Expression "." "(" "type" ")" .
CaseClause = SwitchCase ":" [ StatementList ] .
SwitchCase = "case" ExpressionList | SwitchAssignment | Type | "default" .
SwitchAssignment = Expression ( "=" | ":=" ) Expression .
SwitchExpression = Expression.
ここで重要なのは TypeSwitchStat
と TypeSwitchExpression
です。
TypeSwitchStat
は、switch
キーワードの後にTypeSwitchExpression
が続く形式です。TypeSwitchExpression
は、identifier := Expression.(type)
という特殊な形式を取ります。ここで、Expression
はインターフェース型の値を評価する式であり、.
の後に続く(
type
)
は、これが型スイッチであることを示す特別な構文です。identifier
は、各case
ブロック内で、マッチした型にアサートされた値を受け取るための変数です。
型スイッチの動作
型スイッチは、以下のように動作します。
TypeSwitchExpression
のExpression
が評価され、インターフェース値が取得されます。- 各
case
節の型が、このインターフェース値の動的な型と比較されます。 - 上から順に
case
節が評価され、最初にマッチしたcase
節のステートメントが実行されます。 default
節がある場合、どのcase
節にもマッチしなかった場合に実行されます。default
節はswitch
ステートメント内のどこにでも配置できます。case
節内で宣言された変数(例:i := f().(type)
のi
)は、そのcase
ブロック内でのみ有効であり、マッチした型にアサートされた値が代入されます。これにより、型アサーションを別途記述することなく、安全に型変換された値を使用できます。
表現スイッチとの類似性
コミットの変更内容では、型スイッチが「値ではなく型を比較する」点を除けば、表現スイッチ(通常のswitch
)と同じ特性を持つと説明されています。さらに、型スイッチは型ガード(type guards)を使用した表現スイッチとして書き換えることができるとも述べられています。
例として、コミットで示されている以下の2つのswitch
ステートメントは同等であると説明されています。
型スイッチの例:
switch i := f().(type) {
case int:
printInt(i); // i is an int
case float:
printFloat(i); // i is a float
default:
printString("don't know the type");
}
型ガードを使用した表現スイッチの例:
switch val := f(); true {
case i := val.(int):
printInt(i); // i is an int
case i := val.(float):
printFloat(i); // i is a float
default:
printString("don't know the type");
}
この比較は、型スイッチが単なる糖衣構文(syntactic sugar)ではなく、Go言語の型システムと実行時の型情報に基づいて効率的に動作するように設計されていることを示唆しています。特に、val.(int)
のような型アサーションがcase
節の条件として直接使用できる点が重要です。これは、型アサーションが成功したかどうかを示す2番目の戻り値(ok
)を暗黙的に利用し、true
と比較することで、そのcase
がマッチするかどうかを判断していることを意味します。
bool_expr
を使用した表現スイッチの拡張
コミットでは、bool_expr
(ブール式)をswitch
の式として使用した場合の特別な形式についても言及しています。これは、型ガード、マップのインデックス操作、チャネル操作の結果をテストし、その値を変数に格納するケースを扱います。
例:
switch bool_expr {
case x0:
f0();
case x1 := y1.(T1):
f1();
case x2 := y2[z2]:
f2();
case x3 := <-y3:
f3();
default:
f4();
}
これは以下のif
ステートメントと類似しています。
if x0 == bool_expr {
f0();
} else if x1, ok1 := y1.(T1); ok1 == bool_expr {
f1();
} else if x2, ok2 := y2[z2]; ok2 == bool_expr {
f2();
} else if x3, ok3 := <-y3; ok3 == bool_expr {
f3();
} else {
f4();
}
この拡張は、型スイッチの導入と同時に、switch
ステートメントの柔軟性を高め、より表現力豊かな条件分岐を可能にすることを目指しています。特に、型アサーションやチャネル受信などの操作が成功したかどうか(ok
値)を直接case
の条件として利用できる点が強力です。
コアとなるコードの変更箇所
このコミットは、Go言語の仕様書である doc/go_spec.html
ファイルのみを変更しています。具体的な変更箇所は以下の通りです。
--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -68,7 +68,6 @@ Closed:
- added to wish list
[x] convert should not be used for composite literals anymore,
in fact, convert() should go away - made a todo
-[x] type switch or some form of type test needed - duplicate entry
[x] provide composite literal notation to address array indices: []int{ 0: x1, 1: x2, ... }
and struct field names (both seem easy to do). - under "Missing" list
[x] passing a "..." arg to another "..." parameter doesn't wrap the argument again
@@ -3121,24 +3120,42 @@ if x := f(); x < y {\n \n <p>\n "Switch" statements provide multi-way execution.\n-An expression is evaluated and compared to the "case"\n-expressions inside the "switch" to determine which branch\n-of the "switch" to execute.\n-A missing expression is equivalent to <code>true</code>.\n+An expression or type specifier is compared to the "cases"\n+inside the "switch" to determine which branch\n+to execute.\n+A missing expression or type specifier is equivalent to\n+the expression <code>true</code>.\n+There are two forms: expression switches and type switches.\n </p>\n \n-<pre class=\"grammar\">\n-SwitchStat = "switch" [ [ SimpleStat ] ";" ] [ Expression ] "{" { CaseClause } "}" .\n-CaseClause = SwitchCase ":" StatementList .\n-SwitchCase = "case" ExpressionList | "default" .\n-</pre>\n-\n <p>\n-The case expressions, which need not be constants,\n-are evaluated top-to-bottom; the first one that matches\n+In an expression switch, the cases contain expressions that are compared\n+against the value of the switch expression.\n+In a type switch, the cases contain types that are compared against the\n+type of a specially annotated switch expression.\n+</p>\n+\n+<pre class=\"grammar\">\n+SwitchStat = ExprSwitchStat | TypeSwitchStat .\n+ExprSwitchStat = "switch" [ [ SimpleStat ] ";" ] [ Expression ] "{" { CaseClause } "}" .\n+TypeSwitchStat = "switch" [ [ SimpleStat ] ";" ] TypeSwitchExpression "{" { CaseClause } "}" .\n+TypeSwitchExpression = identifier ":=" Expression "." "(" "type" ")" .\n+CaseClause = SwitchCase ":" [ StatementList ] .\n+SwitchCase = "case" ExpressionList | SwitchAssignment | Type | "default" .\n+SwitchAssignment = Expression ( "=" | ":=" ) Expression .\n+SwitchExpression = Expression.\n+</pre>\n+\n+<p>\n+In an expression switch,\n+the switch expression is evaluated and\n+the case expressions, which need not be constants,\n+are evaluated top-to-bottom; the first one that equals the\n+switch expression\n triggers execution of the statements of the associated case;\n the other cases are skipped.\n-If no case matches and there is a "default" case, its statements are executed.\n+If no case matches and there is a "default" case,\n+its statements are executed.\n There can be at most one default case and it may appear anywhere in the\n "switch" statement.\n </p>\n@@ -3181,6 +3198,78 @@ case x == 4: f3();\n }\n </pre>\n \n+<p>\n+If the expression in an expression switch is a boolean, the cases\n+may take a special form that tests a type guard, map index, or\n+channel operation and stores the value in a variable, which may\n+be declared using a simple variable declaration. The success\n+of the case's operation is compared against the value of the boolean.\n+A switch of the form:\n+</p>\n+\n+<pre>\n+switch bool_expr {\n+case x0:\n+\tf0();\n+case x1 := y1.(T1):\n+\tf1();\n+case x2 := y2[z2]:\n+\tf2();\n+case x3 := <-y3:\n+\tf3();\n+default:\n+\tf4();\n+}\n+</pre>\n+\n+<p>\n+is therefore analogous to the "if" statement\n+</p>\n+\n+<pre>\n+if x0 == bool_expr {\n+\tf0();\n+} else if x1, ok1 := y1.(T1); ok1 == bool_expr {\n+\tf1();\n+} else if x2, ok2 := y2[z2]; ok2 == bool_expr {\n+\tf2();\n+} else if x3, ok3 := <-y3; ok3 == bool_expr {\n+\tf3();\n+} else {\n+\tf4();\n+}\n</pre>\n+\n+<p>\n+A type switch compares types rather than values. In other respects it has\n+the same properties as an expression switch and may in fact be rewritten\n+as an expression switch using type guards. It is introduced by special\n+notation in the form of a generic type guard using the reserved word\n+<code>type</code> rather than an actual type.\n+Given a function <code>f</code>\n+that returns a value of interface type,\n+the following two "switch" statements are analogous:\n+</p>\n+\n+<pre>\n+switch i := f().(type) {\n+case int:\n+\tprintInt(i);\t// i is an int\n+case float:\n+\tprintFloat(i);\t// i is a float\n+default:\n+\tprintString("don't know the type");\n+}\n+\n+switch val := f(); true {\n+case i := val.(int):\n+\tprintInt(i);\t// i is an int\n+case i := val.(float):\n+\tprintFloat(i);\t// i is a float\n+default:\n+\tprintString("don't know the type");\n+}\n</pre>\n \n <h3>For statements</h3>\n \n```
## コアとなるコードの解説
変更の中心は、Go言語の仕様書における`switch`ステートメントのセクションです。
1. **既存の記述の修正**:
- 従来の`switch`ステートメントの説明が、「式が評価され、`case`式と比較される」という一般的なものから、「式または型指定子が`case`と比較される」という、型スイッチを包含する表現に修正されています。
- 「式が省略された場合は`true`に等しい」という記述も、「式または型指定子が省略された場合は`true`に等しい」と拡張されています。
- そして、「表現スイッチと型スイッチの2つの形式がある」と明記されています。
2. **文法定義の拡張**:
- `SwitchStat`の定義が`ExprSwitchStat | TypeSwitchStat`となり、表現スイッチと型スイッチの両方を許容するように変更されています。
- `TypeSwitchStat`と`TypeSwitchExpression`の新しい文法規則が追加されています。これにより、`switch`の式部分で`identifier := Expression.(type)`という構文が正式に導入されます。
- `CaseClause`と`SwitchCase`の定義も更新され、`SwitchAssignment`(ブール式スイッチのケースで使用される)や`Type`(型スイッチのケースで使用される)を`case`として許容するようになっています。
3. **型スイッチの詳細な説明の追加**:
- 型スイッチが値ではなく型を比較すること、そして表現スイッチと同じ特性を持つことが説明されています。
- `type`キーワードを使った特殊な型ガードの形式で導入されることが強調されています。
- インターフェースを返す関数`f`を例に、型スイッチと、型ガードを使用した表現スイッチが同等であることを示すコード例が追加されています。これにより、型スイッチがどのように機能し、どのような利便性を提供するかが具体的に示されています。
4. **ブール式スイッチの特別な形式の追加**:
- `switch`の式がブール式である場合に、型ガード、マップインデックス、チャネル操作をテストし、その値を変数に格納できる特別な形式が導入されています。
- この形式が対応する`if-else if`ステートメントにどのように変換されるかを示すコード例が提供されており、そのセマンティクスが明確にされています。これは、型スイッチとは直接関係ないものの、`switch`ステートメントの柔軟性を高める重要な変更です。
これらの変更により、Go言語の`switch`ステートメントは、単なる値の比較だけでなく、実行時の型に基づく強力な分岐メカニズムを提供するようになりました。特に、インターフェースの動的な型を安全かつ簡潔に処理できるようになったことは、Go言語の表現力を大きく向上させました。
## 関連リンク
- Go言語の仕様書 (現在のバージョン): [https://go.dev/ref/spec](https://go.dev/ref/spec)
- Go言語の型スイッチに関する公式ドキュメント: [https://go.dev/tour/methods/16](https://go.dev/tour/methods/16) (Go Tourの該当セクション)
## 参考にした情報源リンク
- コミット情報: [https://github.com/golang/go/commit/5a5784977aad171b9c877f81245bb532ea9367ba](https://github.com/golang/go/commit/5a5784977aad171b9c877f81245bb532ea9367ba)
- Go言語の公式ドキュメントおよび仕様書
- Go言語のインターフェースと型アサーションに関する一般的な知識
- Go言語の`switch`ステートメントに関する一般的な知識
- Go言語の歴史と機能追加に関する情報 (2009年当時のGo言語の状況を考慮)
- Go言語の初期の設計思想や、型システムに関する議論を理解するために、当時のGo言語コミュニティの議論やブログ記事などを参照しました。特に、型アサーションの存在と、それだけでは不十分であった背景を理解することが重要でした。
- Go言語の進化の過程で、どのように言語機能が追加・改善されてきたかを把握するために、Go言語のリリースノートや設計ドキュメントも参考にしました。