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

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

このコミットは、Go言語の仕様書 doc/go_spec.txt に対する重要な更新を含んでいます。主に、型等価性の厳密な定義、型ガードの導入、および break ステートメントの言語仕様の修正に焦点を当てています。また、仕様書全体で一人称の「we」を排除し、より客観的で非人称的な表現に統一するなどの軽微な体裁変更も行われています。

コミット

commit 434c6052d80153ebd7bac3ed83dcad33842fa709
Author: Robert Griesemer <gri@golang.org>
Date:   Fri Nov 7 13:34:37 2008 -0800

    - language to define type equality rigorously
    - language for type guards
    - fixed language for break statements
    
    Also: Removed uses of "we" and replaced by impersonal language.
    Minor cosmetic changes.
    
    DELTA=237  (160 added, 34 deleted, 43 changed)
    OCL=18620
    CL=18800

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

https://github.com/golang/go/commit/434c6052d80153ebd7bac3ed83dcad33842fa709

元コミット内容

このコミットは、Go言語の仕様書 doc/go_spec.txt を更新し、以下の主要な変更を導入しました。

  • 型等価性の厳密な定義: 型の等価性に関するルールをより厳密に定義する言語を追加。
  • 型ガードの導入: 型ガード(Type Guards)に関する言語仕様を導入。
  • break ステートメントの修正: break ステートメントの動作に関する記述を修正。
  • 非人称表現への変更: 仕様書全体で「we」のような一人称表現を削除し、非人称的な表現に置き換え。
  • 軽微な体裁変更: その他の細かな体裁の修正。

変更の背景

Go言語は、その設計段階から型安全性と明確なセマンティクスを重視していました。このコミットが行われた2008年11月は、Go言語がまだ活発に開発され、その仕様が固まりつつあった時期です。

このコミットの背景には、以下の必要性があったと考えられます。

  1. 型等価性の明確化: プログラミング言語において、2つの型が「等しい」とはどういうことかという定義は非常に重要です。これは、コンパイラが型チェックを行う際や、プログラマがコードの挙動を理解する上で不可欠な基盤となります。初期の仕様では、各型(配列、構造体、ポインタなど)のセクションで個別に型等価性について言及されていましたが、これらを統一的かつ厳密なルールとして「型等価性」の独立したセクションで定義することで、仕様の一貫性と明確性を高める必要がありました。特に、Go言語の構造的型付けの側面を考慮すると、この厳密な定義は不可欠でした。
  2. 型ガードの導入: Go言語のインターフェースは、ポリモーフィズムを実現するための強力な機能です。しかし、インターフェース型の変数が実際にどのような具象型を保持しているかを実行時に確認し、その具象型として扱うためのメカニズムが必要でした。これが「型ガード」(または型アサーション)の導入の背景です。これにより、インターフェースの柔軟性を保ちつつ、特定の具象型に安全にダウンキャストする手段が提供されます。
  3. break ステートメントの正確性: break ステートメントは、ループや switchselect ステートメントから抜け出すために使用されます。初期の仕様記述に曖昧さがあった場合、その動作をより正確に、特に select ステートメントを含む形で記述し直すことで、言語の挙動に関する誤解を防ぎ、コンパイラの実装を容易にする必要がありました。
  4. 仕様書のプロフェッショナル化: 技術仕様書は、客観的で権威あるトーンで書かれるべきです。「we」のような一人称表現は、非公式な文書では許容されますが、公式な言語仕様書では避けるべきと判断された可能性があります。これにより、文書の信頼性と専門性が向上します。

これらの変更は、Go言語の初期段階における言語設計の成熟度を高め、より堅牢で予測可能な言語仕様を確立するための重要なステップでした。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語の基本的な概念とプログラミング言語一般の知識が必要です。

  1. Go言語の型システム:

    • 基本型 (Basic Types): int, float64, bool, string など、Go言語に組み込まれているプリミティブな型。
    • 複合型 (Composite Types): 既存の型を組み合わせて作られる型。配列 (Array)、構造体 (Struct)、ポインタ (Pointer)、関数 (Function)、チャネル (Channel)、マップ (Map)、インターフェース (Interface) などがあります。
    • 型宣言 (Type Declaration): type MyType OriginalType のように、既存の型に新しい名前を付けて新しい型を定義すること。Goでは、型名が異なれば、たとえ基底の構造が同じでも異なる型とみなされます(名目型付けの側面)。
    • 型リテラル (Type Literal): []int (スライス型)、struct { Name string; Age int } (構造体型) のように、型を直接記述する構文。型リテラルは匿名型を定義する際に使われることが多いです。
    • 静的型 (Static Type): 変数宣言時にコンパイラによって決定される型。
    • 動的型 (Dynamic Type): インターフェース型の変数が実行時に実際に保持している値の具象型。
  2. インターフェース (Interfaces):

    • Go言語のインターフェースは、メソッドのシグネチャの集合を定義します。
    • ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型はそのインターフェースを「実装している」とみなされます(暗黙的な実装)。
    • インターフェース型の変数は、そのインターフェースを実装する任意の具象型の値を保持できます。
  3. 制御フロー (Control Flow):

    • for ステートメント: ループを記述するための構文。
    • switch ステートメント: 条件分岐のための構文。
    • select ステートメント: 複数のチャネル操作を待機し、準備ができた最初の操作を実行するための構文。
    • break ステートメント: forswitchselect ステートメントの最も内側のブロックから抜け出すために使用されます。
    • ラベル (Labels): breakcontinue ステートメントで、特定の外側のループや switch/select ステートメントを指定するために使用される識別子。
  4. プログラミング言語の型システムにおける概念:

    • 型安全性 (Type Safety): プログラムが型の規則に違反しないことを保証する性質。
    • 構造的型付け (Structural Typing): 型の互換性が、その型の構造(メンバーやメソッドのシグネチャ)に基づいて決定されるシステム。Goのインターフェースは構造的型付けの例です。
    • 名目型付け (Nominal Typing): 型の互換性が、その型の宣言された名前(名目)に基づいて決定されるシステム。Goのtypeキーワードによる新しい型定義は、名目型付けの側面を持ちます。

これらの概念を理解することで、コミットがGo言語の仕様にどのような影響を与え、なぜこれらの変更が必要だったのかを深く把握できます。

技術的詳細

このコミットは、Go言語の仕様書 doc/go_spec.txt に以下の重要な技術的変更を加えました。

1. 型等価性の厳密な定義 (Type equality セクションの追加)

以前は各型(配列、構造体など)のセクションで個別に型等価性について記述されていましたが、このコミットにより「Type equality」という独立したセクションが追加され、構造的型等価性 (Structural type equality)型同一性 (Type identity) の2つの概念が明確に区別され、それぞれについて厳密なルールが定義されました。

  • 構造的型等価性 (Structural type equality):

    • Go言語における「等しい型」の基本的な定義。
    • 2つの型が同じメモリレイアウトを持つ場合に等しいとみなされます。
    • ルール:
      • 配列型: 要素型が等しく、かつ同じ配列長を持つ固定長配列であるか、または両方ともオープン配列(スライス)である場合に等しい。
      • 構造体型: 同じ数のフィールドが同じ順序で並び、対応するフィールドが両方とも名前付きであるか匿名であるか、そして対応するフィールドの型が等しい場合に等しい。フィールド名は一致する必要がない点に注意。
      • ポインタ型: 基底型が等しい場合に等しい。
      • 関数型: 同じ数のパラメータと結果値を持ち、対応するパラメータと結果の型が等しい場合に等しい(可変長引数 ... も同様)。パラメータ名と結果名は一致する必要がない点に注意。
      • チャネル型: 値の型が等しく、かつ同じ方向(送受信、送信のみ、受信のみ)である場合に等しい。
      • マップ型: キーと値の型が等しい場合に等しい。
      • インターフェース型: 同じ名前と等しい関数型を持つメソッドの集合が同じである場合に等しい。メソッドの宣言順序は関係ない。
  • 型同一性 (Type identity):

    • より厳密な等価性で、主に型ガードの文脈で重要になります。
    • 2つの型が同じ型宣言から派生しているか、または型リテラルが同じ構造を持ち、対応するコンポーネントが同一である場合に同一とみなされます。
    • ルール: 構造的型等価性のルールに加えて、フィールド名、パラメータ名、結果名なども一致する必要があるなど、より厳密な条件が課されます。
    • 例: type T0 []stringtype T1 []string は構造的に等しいが、異なる型宣言から派生しているため同一ではない。[]int[]int は同一。

この区別は、Go言語が名目型付けと構造的型付けの両方の側面を持つため、非常に重要です。特に、インターフェースの型アサーション(型ガード)の挙動を正確に定義するために不可欠です。

2. 型ガードの導入 (Type guards セクションの追加)

x.(T) という構文が「型ガード」として導入されました。これは、インターフェース型の変数 x が、特定の型 T の値を保持しているかどうかを実行時にアサート(表明)するためのメカニズムです。

  • 構文: x.(T)
  • 動作:
    • x の型はインターフェース型でなければなりません。
    • T がインターフェース型でない場合: x の動的型が T同一であるとアサートします。
    • T がインターフェース型の場合: x の動的型がインターフェース T実装しているとアサートします。
    • アサートが成功した場合、x.(T) の値は x に格納されている値であり、その型は T になります。
    • アサートが失敗した場合、ランタイム例外が発生します。
  • 安全な型ガード:
    • v, ok = x.(T) または v, ok := x.(T) の形式を使用すると、型ガードの成否を ok 変数で確認できます。
    • 成功した場合、v には x の値(型 T)が、ok には true が設定されます。
    • 失敗した場合、v には T のゼロ値が、ok には false が設定され、ランタイム例外は発生しません。これは、Goのイディオムである「カンマ ok」パターンの一部です。

3. break ステートメントの修正

break ステートメントの適用範囲が拡張され、select ステートメントも対象に含まれることが明記されました。

  • 変更前: for または switch ステートメントの最も内側の実行を終了する。
  • 変更後: forswitch、または select ステートメントの最も内側の実行を終了する。
  • ラベル付き break の説明も、forswitchselect ステートメントを対象とすることが明確化されました。

4. その他の変更

  • 非人称表現への統一: 仕様書全体で「we can write」が「one can write」に、「we use」が「the term ... denotes」に、「we define it as」が「it is defined as」に変更されるなど、一人称の「we」が削除され、より客観的で非人称的な表現に統一されました。
  • 日付の更新: 仕様書の日付が「November 4, 2008」から「November 7, 2008」に更新されました。
  • 目次の更新: 「Type equality」が目次に追加されました。
  • Type 構文の更新: Type の定義が TypeName | TypeLit となり、TypeLit が導入されました。これにより、型名と型リテラルがより明確に区別されます。
  • Label declaration から Label declarations への変更: セクション名が複数形になり、例が追加されました。

これらの変更は、Go言語の仕様をより正確で、網羅的で、理解しやすいものにするための重要なステップでした。特に型等価性と型ガードの導入は、Goの型システムとインターフェースの強力な機能を最大限に活用するために不可欠な要素です。

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

このコミットは、Go言語の仕様書である doc/go_spec.txt のみを変更しています。以下に主要な変更箇所の抜粋と説明を示します。

1. 型等価性に関する新しいセクションの追加

--- a/doc/go_spec.txt
+++ b/doc/go_spec.txt
@@ -1381,14 +1370,120 @@ This allows the construction of mutually recursive types such as:
 	    bar(T1) int;
 	}
 
-Type equivalence: Two interface types are equal only if both declare the same
-number of methods with the same names, and corresponding (by name) methods
-have the same function types.
-
 Assignment compatibility: A value can be assigned to an interface variable
 if the static type of the value implements the interface or if the value is "nil".
 
 
+Type equality
+----
+
+Types may be ``different'', ``structurally equal'', or ``identical''.
+Go is a type-safe language; generally different types cannot be mixed
+in binary operations, and values cannot be assigned to variables of different
+types. However, values may be assigned to variables of structually
+equal types. Finally, type guards succeed only if the dynamic type
+is identical to or implements the type tested against (§Type guards).
+
+Structural type equality (equality for short) is defined by these rules:
+
+Two type names denote equal types if the types in the corresponding declarations
+are equal. Two type literals specify equal types if they have the same
+literal structure and corresponding components have equal types. Loosely
+speaking, two types are equal if their values have the same layout in memory.
+More precisely:
+
+	- Two array types are equal if they have equal element types and if they
+	  are either fixed arrays with the same array length, or they are open
+	  arrays.
+
+	- Two struct types are equal if they have the same number of fields in the
+	  same order, corresponding fields are either both named or both anonymous,
+	  and corresponding field types are equal. Note that field names
+	  do not have to match.
+
+	- Two pointer types are equal if they have equal base types.
+
+	- Two function types are equal if they have the same number of parameters
+	  and result values and if corresponding parameter and result types are
+	  equal (a "..." parameter is equal to another "..." parameter).
+	  Note that parameter and result names do not have to match.
+
+	- Two channel types are equal if they have equal value types and
+	  the same direction.
+
+	- Two map types are equal if they have equal key and value types.
+
+	- Two interface types are equal if they have the same set of methods
+	  with the same names and equal function types. Note that the order
+	  of the methods in the respective type declarations is irrelevant.
+
+
+Type identity is defined by these rules:
+
+Two type names denote identical types if they originate in the same
+type declaration. Two type literals specify identical types if they have the
+same literal structure and corresponding components have identical types.
+More precisely:
+
+	- Two array types are identical if they have identical element types and if
+	  they are either fixed arrays with the same array length, or they are open
+	  arrays.
+
+	- Two struct types are identical if they have the same number of fields in
+	  the same order, corresponding fields either have both the same name or
+	  are both anonymous, and corresponding field types are identical.
+
+	- Two pointer types are identical if they have identical base types.
+
+	- Two function types are identical if they have the same number of
+	  parameters and result values both with the same (or absent) names, and
+	  if corresponding parameter and result types are identical (a "..."
+	  parameter is identical to another "..." parameter with the same name).
+
+	- Two channel types are identical if they have identical value types and
+	  the same direction.
+
+	- Two map types are identical if they have identical key and value types.
+
+	- Two interface types are identical if they have the same set of methods
+	  with the same names and identical function types. Note that the order
+	  of the methods in the respective type declarations is irrelevant.
+
+Note that the type denoted by a type name is identical only to the type literal
+in the type name's declaration.
+
+Finally, two types are different if they are not structurally equal.
+(By definition, they cannot be identical, either).
+
+For instance, given the declarations
+
+	type (
+		T0 []string;
+		T1 []string
+		T2 struct { a, b int };
+		T3 struct { a, c int };
+		T4 *(int, float) *T0
+		T5 *(x int, y float) *[]string
+	)
+
+these are some types that are equal
+
+	T0 and T0
+	T0 and []string
+	T2 and T3
+	T4 and T5
+	T3 and struct { a int; int }
+
+and these are some types that are identical
+
+	T0 and T0
+	[]int and []int
+	struct { a, b *T5 } and struct { a, b *T5 }
+
+As an example, "T0" and "T1" are equal but not identical because they have
+different declarations.
+

2. 型ガードに関する新しいセクションの追加

--- a/doc/go_spec.txt
+++ b/doc/go_spec.txt
@@ -1734,7 +1829,41 @@ would have no effect on ``a''.
 Type guards
 ----
 
-TODO: write this section
+For an expression "x" and a type "T", the primary expression
+
+	x.(T)
+
+asserts that the value stored in "x" is an element of type "T" (§Types).
+The notation ".(T)" is called a ``type guard'', and "x.(T)" is called
+a ``guarded expression''. The type of "x" must be an interface type.
+
+More precisely, if "T" is not an interface type, the expression asserts
+that the dynamic type of "x" is identical to the type "T" (§Types).
+If "T" is an interface type, the expression asserts that the dynamic type
+of T implements the interface "T" (§Interface types). Because it can be
+verified statically, a type guard in which the static type of "x" implements
+the interface "T" is illegal. The type guard is said to succeed if the
+assertion holds.
+
+If the type guard succeeds, the value of the guarded expression is the value
+stored in "x" and its type is "T". If the type guard fails, a run-time
+exception occurs. In other words, even though the dynamic type of "x"
+is only known at run-time, the type of the guarded expression "x.(T)" is
+known to be "T" in a correct program.
+
+As a special form, if a guarded expression is used in an assignment
+
+	v, ok = x.(T)
+	v, ok := x.(T)
+
+the result of the guarded expression is a pair of values with types "(T, bool)".
+If the type guard succeeds, the expression returns the pair "(x.(T), true)";
+that is, the value stored in "x" (of type "T") is assigned to "v", and "ok"
+is set to true. If the type guard fails, the value in "v" is set to the initial
+value for the type of "v" (§Program initialization and execution), and "ok" is
+set to false. No run-time exception occurs in this case.
+
+TODO add examples
 
 
 Calls

3. break ステートメントの修正

--- a/doc/go_spec.txt
+++ b/doc/go_spec.txt
@@ -2584,14 +2707,14 @@ values:
 Break statements
 ----
 
-Within a for or switch statement, a break statement terminates execution of
-the innermost for or switch statement.
+Within a for, switch, or select statement, a break statement terminates
+execution of the innermost such statement.
 
 	BreakStat = "break" [ identifier ].
 
-If there is an identifier, it must be the label name of an enclosing
-for or switch
-statement, and that is the one whose execution terminates.
+If there is an identifier, it must be a label marking an enclosing
+for, switch, or select statement, and that is the one whose execution
+terminates.
 
 	L: for i < n {
 		switch i {

4. 非人称表現への変更例

--- a/doc/go_spec.txt
+++ b/doc/go_spec.txt
@@ -289,15 +290,15 @@ implementation, Go treats these as distinct characters.
 Characters
 ----
 
-In the grammar we use the notation
+In the grammar the term
 
 	utf8_char
 
-to refer to an arbitrary Unicode code point encoded in UTF-8. We use
+denotes an arbitrary Unicode code point encoded in UTF-8. Similarly,
 
 	non_ascii
 
-to refer to the subset of "utf8_char" code points with values >= 128.
+denotes the subset of "utf8_char" code points with values >= 128.

コアとなるコードの解説

このコミットの「コアとなるコード」は、Go言語の仕様書 doc/go_spec.txt のテキストそのものです。このファイルはGo言語の文法、セマンティクス、および標準ライブラリの動作を定義する唯一の公式文書です。したがって、このファイルへの変更は、Go言語自体の定義に対する変更を意味します。

1. 型等価性 (Type equality) の導入と詳細化

最も重要な変更は、Go言語の型システムにおける「型等価性」の概念を厳密に定義する新しいセクションが追加されたことです。

  • 背景: 以前は、配列、構造体、ポインタなどの各型に関するセクションで、それぞれの型における等価性のルールが個別に記述されていました。しかし、これは全体的な一貫性に欠け、特にGoのインターフェースや型アサーションの挙動を正確に理解する上で不十分でした。
  • 変更内容:
    • Type equality という独立した章が追加されました。
    • 構造的型等価性 (Structural type equality)型同一性 (Type identity) という2つの主要な概念が導入され、明確に区別されました。
    • それぞれの概念について、配列、構造体、ポインタ、関数、チャネル、マップ、インターフェースといったGoの主要な複合型ごとに、具体的な等価性/同一性のルールが詳細に記述されました。
    • 特に、構造体や関数型において、フィールド名やパラメータ名が型等価性には影響しないが、型同一性には影響する、といった微妙な違いが明記されました。
    • 具体的な型宣言の例と、それらの型が「等しい」か「同一である」かを示す例が提供され、理解を深める助けとなります。
  • 意義: この変更により、Go言語の型システムがより厳密に定義され、コンパイラの実装者や言語のユーザーが型の挙動を正確に理解するための強固な基盤が提供されました。特に、Goが名目型付けと構造的型付けの両方の側面を持つため、この明確な区別は言語のセマンティクスを理解する上で不可欠です。

2. 型ガード (Type guards) の導入

Go言語のインターフェースの強力な機能である型アサーション(x.(T))が、「型ガード」として正式に仕様に組み込まれました。

  • 背景: インターフェース型の変数が実行時にどのような具象型を保持しているかを確認し、その具象型として安全に扱うためのメカニズムは、Goのポリモーフィズムを実用的にするために不可欠です。
  • 変更内容:
    • Type guards という新しい章が追加され、x.(T) 構文のセマンティクスが詳細に記述されました。
    • T がインターフェース型である場合とそうでない場合で、型ガードの挙動が異なることが明記されました。
    • 型ガードが成功した場合の戻り値の型と値、および失敗した場合のランタイム例外の発生が定義されました。
    • Goのイディオムである「カンマ ok」パターン (v, ok = x.(T)) が導入され、型ガードの失敗時にパニックを発生させずにエラーを処理する安全な方法が提供されました。
  • 意義: 型ガードの導入により、Goプログラマはインターフェースの柔軟性を維持しつつ、必要に応じて具象型に安全にアクセスできるようになりました。特に、エラーハンドリングにおける「カンマ ok」パターンは、Goの堅牢なエラー処理スタイルを確立する上で重要な要素となりました。

3. break ステートメントの修正

break ステートメントの適用範囲が select ステートメントを含むように拡張され、より正確な記述になりました。

  • 背景: break ステートメントは、ループ (for) や条件分岐 (switch) から抜け出すために一般的に使用されますが、Goの並行処理における重要な構文である select ステートメントからも抜け出す必要がある場合があります。初期の仕様ではこの点が不明確だった可能性があります。
  • 変更内容: break ステートメントが適用される対象として、forswitch に加えて select が明示的に追加されました。また、ラベル付き break の説明も同様に更新されました。
  • 意義: この修正により、break ステートメントの動作がより明確になり、Goの並行処理コードにおける制御フローの予測可能性が向上しました。

4. 非人称表現への統一

仕様書全体で「we」のような一人称表現が削除され、より客観的で非人称的な表現に置き換えられました。

  • 背景: 公式な技術仕様書は、客観性と権威性を保つために、個人的な視点や意見を排除し、普遍的な事実として記述されるべきです。
  • 変更内容: 「we use」が「the term ... denotes」に、「we can write」が「one can write」に、「we define it as」が「it is defined as」に変更されるなど、多数の箇所で表現が修正されました。
  • 意義: この変更は、仕様書のプロフェッショナルなトーンを確立し、その権威性を高める上で重要です。

これらの変更は、Go言語の初期の仕様策定段階において、言語のセマンティクスをより厳密に定義し、プログラマが言語の挙動を正確に理解できるようにするための重要な改善点でした。

関連リンク

参考にした情報源リンク