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

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

このコミットは、Go言語の初期開発段階における重要な変更点、特にインターフェースの埋め込み(embedded interfaces)に関する言語仕様の更新を記録しています。Go言語の設計思想である「コンポジション(合成)による再利用」をインターフェースにも適用するための基盤となる変更であり、Go言語の柔軟性と表現力を高める上で不可欠なステップでした。

コミット

commit 38c232fe37bbc58ba7b0a52348fa83993ab99a82
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Feb 11 15:09:15 2009 -0800

    - language for embedded interfaces (as discussed this morning)
    - fixed a syntax error
    
    R=r
    DELTA=17  (15 added, 0 deleted, 2 changed)
    OCL=24876
    CL=24889

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

https://github.com/golang/go/commit/38c232fe37bbc58ba7b0a52348fa83993ab99a82

元コミット内容

このコミットの元々のメッセージは以下の通りです。

  • language for embedded interfaces (as discussed this morning): 今朝議論された埋め込みインターフェースのための言語仕様の変更。
  • fixed a syntax error: 構文エラーの修正。

このメッセージは、Go言語の設計チーム内でインターフェースの埋め込み機能について議論が行われ、その結果として言語仕様に反映されたことを示唆しています。

変更の背景

Go言語は、その設計当初から「シンプルさ」「効率性」「並行処理の堅牢なサポート」を重視していました。特に、オブジェクト指向プログラミングにおける継承の複雑さを避け、代わりにコンポジション(合成)を推奨する設計思想が強く反映されています。インターフェースの埋め込みは、このコンポジションの原則をインターフェースにも適用するための重要なメカニズムとして導入されました。

従来のオブジェクト指向言語では、クラスの継承によってコードの再利用や機能の拡張が行われることが一般的でした。しかし、Go言語では多重継承による「ダイヤモンド問題」のような複雑さを避けるため、クラス継承の概念を採用していません。その代わりに、構造体やインターフェースの「埋め込み」という形で、他の型が持つフィールドやメソッドを自身の型に含めることを可能にしています。

このコミットが行われた2009年2月は、Go言語が一般に公開される前の初期開発段階にあたります。この時期にインターフェースの埋め込みに関する仕様が固められたことは、Go言語の設計思想が早期から明確に形成されていたことを示しています。インターフェースの埋め込みは、複数のインターフェースのメソッドセットを一つの新しいインターフェースにまとめることを可能にし、コードの再利用性とモジュール性を向上させます。例えば、io.Readerio.Writerという二つのインターフェースを組み合わせたio.ReadWriterのようなインターフェースを、冗長なメソッドの再宣言なしに定義できるようになります。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的な概念を理解しておく必要があります。

  1. Go言語のインターフェース: Go言語のインターフェースは、メソッドのシグネチャの集まりを定義する型です。Goのインターフェースの最大の特徴は「暗黙的な実装(Implicit Implementation)」です。つまり、ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型はそのインターフェースを「実装している」とみなされます。明示的なimplementsキーワードは不要です。これは「構造的型付け(Structural Typing)」の一種であり、型の互換性が名前ではなく構造(メソッドセット)によって決定されます。

  2. 構造体(Structs): Go言語の構造体は、異なる型のフィールドをまとめた複合データ型です。構造体は、関連するデータを一つの単位として扱うために使用されます。

  3. 型の埋め込み(Embedding): Go言語では、構造体やインターフェースの中に他の構造体やインターフェースを「埋め込む」ことができます。これにより、埋め込まれた型のフィールドやメソッドが、埋め込み先の型に「昇格(promoted)」され、あたかも自身のフィールドやメソッドであるかのように直接アクセスできるようになります。これは、継承の代わりにコンポジションを用いてコードを再利用するGo言語の主要なメカニズムです。

    • 構造体の埋め込み: ある構造体が別の構造体を匿名フィールドとして持つ場合、埋め込まれた構造体のフィールドやメソッドは、外側の構造体のインスタンスから直接アクセスできます。
    • インターフェースの埋め込み: あるインターフェースが別のインターフェースを匿名フィールドとして持つ場合、埋め込まれたインターフェースのメソッドセットは、外側のインターフェースのメソッドセットに含まれます。これにより、複数のインターフェースの機能を一つのインターフェースに集約できます。
  4. Go言語の仕様書(Go Spec): doc/go_spec.txtは、Go言語の公式な言語仕様書です。このファイルへの変更は、Go言語の構文やセマンティクスに直接的な影響を与えることを意味します。

技術的詳細

このコミットは、doc/go_spec.txtというGo言語の仕様書を更新することで、インターフェースの埋め込みに関する構文とセマンティクスを導入しています。具体的には、以下の2つのセクションに変更が加えられています。

  1. StructType (構造体型) の FieldDecl (フィールド宣言) の変更: 変更前: FieldDecl = (IdentifierList CompleteType | TypeName) [ Tag ] . 変更後: FieldDecl = (IdentifierList CompleteType | [ "*" ] TypeName) [ Tag ] .

    この変更は、構造体のフィールド宣言において、ポインタ型(*TypeName)を匿名フィールドとして埋め込むことを可能にしています。これにより、構造体の中に他の構造体へのポインタを埋め込み、そのポインタが指す構造体のフィールドやメソッドを「昇格」させることができるようになります。これは、Go言語におけるコンポジションの重要な側面です。

  2. InterfaceType (インターフェース型) の MethodSpec (メソッド仕様) の変更: 変更前: MethodSpec = IdentifierList Signature . 変更後: MethodSpec = IdentifierList Signature | TypeName .

    この変更が、本コミットの主要な目的である「インターフェースの埋め込み」を可能にするものです。変更後では、MethodSpecTypeName(型名)を取ることができるようになりました。これは、インターフェースの定義内で、メソッドのシグネチャの代わりに別のインターフェースの型名を記述できることを意味します。

    仕様書には、この変更に関する説明が追加されています。 An interface may contain a type name T in place of a method specification. T must denote another, complete (and not forward-declared) interface type. Using this notation is equivalent to enumerating the methods of T explicitly in the interface containing T. (インターフェースは、メソッド仕様の代わりに型名Tを含むことができます。Tは、別の完全な(そして前方宣言されていない)インターフェース型を示す必要があります。この表記法を使用することは、Tのメソッドを、Tを含むインターフェース内で明示的に列挙することと同等です。)

    この説明は、インターフェースの埋め込みが、埋め込まれるインターフェースのすべてのメソッドを、埋め込み先のインターフェースにコピーする(あるいはそのように振る舞う)シンタックスシュガーであることを明確にしています。これにより、開発者は複数のインターフェースのメソッドセットを簡単に組み合わせることができ、コードの記述量を減らし、可読性を向上させることができます。

    例として、コミットメッセージに示されているReadWriteインターフェースとFileインターフェースの例が追加されています。

    type ReadWrite interface {
    	Read, Write	(b Buffer) bool;
    }
    
    type File interface {
    	ReadWrite;  // same as enumerating the methods in ReadWrite
    	Lock;       // same as enumerating the methods in Lock
    	Close();
    }
    

    この例では、FileインターフェースがReadWriteインターフェースとLockインターフェースを埋め込んでいます。これにより、FileインターフェースはReadWriteLockのすべてのメソッド、そして自身のClose()メソッドを持つことになります。

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

このコミットにおけるコアとなるコードの変更は、doc/go_spec.txtファイル内の以下の差分に集約されます。

--- a/doc/go_spec.txt
+++ b/doc/go_spec.txt
@@ -1218,7 +1218,7 @@ types (§Types).
 
 	StructType = "struct" [ "{" [ FieldDeclList ] "}" ] .
 	FieldDeclList = FieldDecl { ";" FieldDecl } [ ";" ] .
-	FieldDecl = (IdentifierList CompleteType | TypeName) [ Tag ] .
+	FieldDecl = (IdentifierList CompleteType | [ "*" ] TypeName) [ Tag ] .
 	Tag = StringLit .
 
 	// An empty struct.
@@ -1387,7 +1387,7 @@ the set of methods specified by the interface type, and the value "nil".
 
 	InterfaceType = "interface" [ "{" [ MethodSpecList ] "}" ] .
 	MethodSpecList = MethodSpec { ";" MethodSpec } [ ";" ] .
-	MethodSpec = IdentifierList Signature .
+	MethodSpec = IdentifierList Signature | TypeName .
 
 	// An interface specifying a basic File type.
 	interface {
@@ -1425,6 +1425,21 @@ If S1 and S2 also implement
 
 they implement the Lock interface as well as the File interface.
 
+An interface may contain a type name T in place of a method specification.
+T must denote another, complete (and not forward-declared) interface type.
+Using this notation is equivalent to enumerating the methods of T explicitly
+in the interface containing T.
+\n+\ttype ReadWrite interface {\n+\t\tRead, Write\t(b Buffer) bool;\n+\t}\n+\n+\ttype File interface {\n+\t\tReadWrite;  // same as enumerating the methods in ReadWrite
+\t\tLock;       // same as enumerating the methods in Lock
+\t\tClose();
+\t}\n+\n Forward declaration:
 A interface type consisting of only the reserved word "interface" may be used in
 a type declaration; it declares an incomplete interface type (§Type declarations).

コアとなるコードの解説

上記の差分は、Go言語の構文定義(BNFライクな形式)を直接変更しています。

  1. FieldDecl の変更 (doc/go_spec.txt の 1221行目): FieldDecl = (IdentifierList CompleteType | [ "*" ] TypeName) [ Tag ] . この変更により、構造体のフィールド宣言において、TypeNameの前にオプションで*(ポインタ)を付けることが可能になりました。これは、構造体の中に他の型(特に構造体)へのポインタを匿名フィールドとして埋め込むことを許可し、埋め込まれたポインタが指す型のメソッドやフィールドを外側の構造体から直接アクセスできるようにするGoの「埋め込み」メカニズムをサポートします。

  2. MethodSpec の変更 (doc/go_spec.txt の 1390行目): MethodSpec = IdentifierList Signature | TypeName . この変更が、インターフェースの埋め込みを可能にする最も重要な部分です。以前は、インターフェースのメソッド仕様はIdentifierList Signature(メソッド名とシグネチャ)のみでした。この変更により、TypeName(型名)もMethodSpecとして許可されるようになりました。これにより、インターフェースの定義内で、別のインターフェースの型名を記述するだけで、そのインターフェースが持つすべてのメソッドを現在のインターフェースに含めることができるようになりました。

  3. インターフェース埋め込みに関する説明の追加 (doc/go_spec.txt の 1425行目以降): このコミットでは、インターフェースの埋め込みに関する詳細な説明とコード例が仕様書に追加されています。

    • 「インターフェースは、メソッド仕様の代わりに型名Tを含むことができる」というルールが明記されています。
    • 埋め込まれる型Tは「完全な(complete)」インターフェース型でなければならないという制約が示されています。
    • 埋め込みが「Tのメソッドを明示的に列挙することと同等である」というセマンティクスが説明されています。これは、埋め込みが単なるシンタックスシュガーであり、コンパイル時にはメソッドが展開されることを示唆しています。
    • ReadWriteFileインターフェースの具体的な例が提供され、埋め込みがどのように機能するかが視覚的に示されています。

これらの変更は、Go言語のインターフェースが、単なるメソッドの契約だけでなく、他のインターフェースの機能を合成するための強力なツールとなることを確立しました。これにより、Goの型システムはより柔軟になり、再利用可能なコンポーネントの設計が容易になりました。

関連リンク

  • Go Programming Language Specification: https://go.dev/ref/spec (現在のGo言語仕様)
  • Effective Go: https://go.dev/doc/effective_go (Go言語のイディオムとベストプラクティスに関する公式ドキュメント。インターフェースの設計原則についても触れられています。)

参考にした情報源リンク