[インデックス 1592] ファイルの概要
このコミットは、Go言語の仕様書 doc/go_spec.txt
に対する重要な変更を記録しています。主な目的は、関数型の構文を変更し、スライス、マップ、チャネルといった他の参照型と同様に、関数型を「参照型」として扱うようにすることです。これは、Go言語の設計初期段階におけるRuss Cox氏の提案の一環として行われた、関数とメソッドの扱いに関する根本的な見直しを示しています。
コミット
commit 2b9fe0ea24a3879cbe90f5bd160b7334756ff0b5
Author: Robert Griesemer <gri@golang.org>
Date: Fri Jan 30 14:48:29 2009 -0800
Test balloon: Changed the spec to see the implications of changing the
syntax of function types and making them "reference types" like slice,
map, and chan. First step in Russ' proposal.
DELTA=111 (32 added, 15 deleted, 64 changed)
OCL=23669
CL=23964
---
doc/go_spec.txt | 153 +++++++++++++++++++++++++++++++-------------------------
1 file changed, 85 insertions(+), 68 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2b9fe0ea24a3879cbe90f5bd160b7334756ff0b5
元コミット内容
Robert Griesemer氏によるこのコミットは、Go言語の仕様書 doc/go_spec.txt
を更新し、関数型の構文とセマンティクスを大幅に変更しています。具体的には、関数型を明示的なポインタ型としてではなく、スライス、マップ、チャネルと同様の「参照型」として扱うように定義し直しています。これにより、関数値の比較(nil
との比較や等価性比較)が可能になり、メソッドの扱いもより直感的になります。この変更は、Go言語の初期設計におけるRuss Cox氏の提案に基づいています。
変更の背景
Go言語の初期設計において、関数はC言語の関数ポインタのように扱われていました。しかし、これはGoの他の組み込み参照型(スライス、マップ、チャネル)との一貫性を欠いていました。これらの型は、内部的にポインタを保持しているものの、言語レベルでは値として扱われ、nil
との比較や等価性比較が可能です。
このコミットの背景には、関数をより「第一級オブジェクト」として扱い、他の参照型とのセマンティクスを統一するという設計思想があります。具体的には、Russ Cox氏が提唱した「関数型をポインタではなく値として扱う」という提案がこの変更の原動力となっています。これにより、Go言語の型システム全体の一貫性が向上し、プログラマが関数をより自然に扱えるようになることを目指しました。
変更前は、関数型は *()
のようにポインタとして表現され、関数リテラルも関数型へのポインタを生成していました。また、関数値の比較は許可されていませんでした。この設計は、Go言語のシンプルさと直感性という目標に反する部分がありました。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の基本的な概念と、一般的なプログラミング言語における型システムの知識が必要です。
- 型システム: プログラミング言語において、値の種類(型)を定義し、それらの値がどのように操作できるかを規定する規則の集合。Go言語は静的型付け言語であり、コンパイル時に型の整合性をチェックします。
- 関数型 (Function Types): 関数そのものを値として扱うための型。例えば、
func(int, string) bool
は、int
とstring
を引数にとり、bool
を返す関数の型を表します。 - 参照型 (Reference Types): 変数が直接値を保持するのではなく、値が格納されているメモリ上の場所(参照)を保持する型。Go言語では、スライス、マップ、チャネルが典型的な参照型です。これらの型は、
nil
(ゼロ値)を持つことができ、nil
との比較や、他の参照型との等価性比較が可能です。 - ポインタ (Pointers): メモリ上の特定のアドレスを指し示す変数。C言語などでは関数ポインタが広く使われます。Go言語にもポインタは存在しますが、C言語ほど直接的なメモリ操作は推奨されません。
- 関数リテラル (Function Literals): コード内で直接定義される匿名関数。Go言語ではクロージャとしても機能します。
- メソッド (Methods): 特定の型に関連付けられた関数。Go言語では、レシーバ引数を持つ関数として定義されます。
- Go言語の仕様書 (Go Language Specification): Go言語の構文、セマンティクス、標準ライブラリの動作などを詳細に記述した公式文書。言語の挙動に関する最終的な権威となります。
- Russ Cox: Go言語の主要な開発者の一人であり、Goプロジェクトのテクニカルリード。Go言語の設計と進化に大きな影響を与えています。
技術的詳細
このコミットにおける技術的な変更は、主にGo言語の仕様書 doc/go_spec.txt
の以下のセクションに集中しています。
-
関数型の構文変更:
- 変更前:
FunctionType = "(" [ ParameterList ] ")" [ Result ] .
- 変更後:
FunctionType = "func" Signature .
およびSignature = "(" [ ParameterList ] ")" [ Result ] .
- これにより、関数型を宣言する際に
func
キーワードが必須となり、より明示的で他の型宣言との一貫性が向上しました。例えば、以前は(int, float)
のような記述が関数型を意味しましたが、変更後はfunc (int, float)
となります。
- 変更前:
-
関数型のセマンティクス変更:
nil
値の導入: 関数型がnil
値を持つことができるようになりました。これは、スライス、マップ、チャネルと同様に、関数変数が何も参照していない状態を表します。- 比較可能性: 関数型変数を
nil
と比較したり、他の関数型変数と等価性比較したりすることが可能になりました。nil
との比較は、関数が設定されているかどうかをチェックするのに役立ちます。等価性比較は、同じ関数を参照している場合にtrue
を返します。 - ポインタからの脱却: 以前は「変数は関数へのポインタのみを保持でき、関数値そのものは保持できない」とされていましたが、この記述が削除されました。これにより、関数型はポインタではなく、直接関数値を表すようになりました。関数リテラルの型も、以前は「関数型へのポインタ」でしたが、「指定された関数型」に変更されました。
-
メソッドの扱い:
- メソッド値 (Method Values) の導入: 以前はメソッドのアドレス (
&t.M
) を取得して関数ポインタとして扱う必要がありましたが、このコミットによりt.M
という構文で直接「メソッド値」を取得できるようになりました。このメソッド値は、レシーバがバインドされた関数として振る舞います。 - メソッド値は、そのメソッドのレシーバ型を最初の引数として持つ関数型として定義されます。例えば、
func (tp *T) M(a int) int
というメソッドM
がある場合、そのメソッド値t.M
の型はfunc (t *T, a int) int
となります。 - これにより、メソッドをより柔軟に、第一級の関数として扱うことが可能になり、コールバックやインターフェースの実装において、より自然なコード記述が可能になりました。
- メソッド値 (Method Values) の導入: 以前はメソッドのアドレス (
-
その他の構文変更:
Tag = string_lit .
からTag = StringLit .
へ、そしてStringLit = string_lit { string_lit } .
の追加。これは、文字列リテラルの定義に関する微細な変更で、複数の文字列リテラルを連結して一つのStringLit
を構成できるようになったことを示唆しています。PackageFileName = StringLit .
の追加。これは、import
宣言におけるパッケージファイル名の構文をStringLit
に統一したものです。
これらの変更は、Go言語の型システムにおける関数とメソッドのセマンティクスを根本的に再定義し、言語全体の一貫性と表現力を向上させることを目的としています。
コアとなるコードの変更箇所
このコミットは、Go言語の仕様書 doc/go_spec.txt
のみを変更しています。以下に、特に重要な変更箇所を抜粋し、その意味を解説します。
--- a/doc/go_spec.txt
+++ b/doc/go_spec.txt
@@ -1225,7 +1225,7 @@ types (§Types).\n \t\tx, y int;\n \t\tu float;\n \t\tA *[]int;\n-\t\tF *();\n+\t\tF func();\n \t}\
- 意味: 構造体フィールドの宣言例で、関数型が
*()
からfunc()
に変更されています。これは、関数型がポインタではなくfunc
キーワードで直接表現されるようになったことを示しています。
--- a/doc/go_spec.txt
+++ b/doc/go_spec.txt
@@ -1326,9 +1326,10 @@ Function types\n ----\n \n A function type denotes the set of all functions with the same parameter\n-and result types.\n+and result types, and the value "nil".\n \n-\tFunctionType = "(\" [ ParameterList ] \")\" [ Result ] .\n+\tFunctionType = "func" Signature .\n+\tSignature = "(\" [ ParameterList ] \")\" [ Result ] .\
- 意味: 関数型の定義が根本的に変更されています。
- 「
nil
値」が関数型のセットに含まれることが明記されました。 FunctionType
の構文がfunc
キーワードを必須とするように変更され、実際のパラメータと結果のリストはSignature
という新しいプロダクションに分離されました。
- 「
--- a/doc/go_spec.txt
+++ b/doc/go_spec.txt
@@ -1345,22 +1346,32 @@ no additional arguments). If the parameters are named, the identifier\n list immediately preceding "..." must contain only one identifier (the\n name of the last parameter).\n \n-\t()\n-\t(x int)\n-\t() int\n-\t(string, float, ...)\n-\t(a, b int, z float) bool\n-\t(a, b int, z float) (bool)\n-\t(a, b int, z float, opt ...) (success bool)\n-\t(int, int, float) (float, *[]int)\n+\tfunc ()\n+\tfunc (x int)\n+\tfunc () int\n+\tfunc (string, float, ...)\n+\tfunc (a, b int, z float) bool\n+\tfunc (a, b int, z float) (bool)\n+\tfunc (a, b int, z float, opt ...) (success bool)\n+\tfunc (int, int, float) (float, *[]int)\
- 意味: 関数型の例が、新しい
func
キーワードを含む構文に更新されています。
--- a/doc/go_spec.txt
+++ b/doc/go_spec.txt
@@ -1356,22 +1346,32 @@ no additional arguments). If the parameters are named, the identifier\n list immediately preceding "..." must contain only one identifier (the\n name of the last parameter).\n \n-A variable can hold only a pointer to a function, not a function value.\n-In particular, v := func() {} creates a variable of type *(). To call the\n-function referenced by v, one writes v(). It is illegal to dereference a\n-function pointer.\n+If the result type of a function is itself a function type, the result type\n+must be parenthesized to resolve a parsing ambiguity:\n \n-Assignment compatibility: A function pointer can be assigned to a function\n-(pointer) variable only if both function types are equal.\n+\tfunc (n int) (func (p* T))\n+\n+Assignment compatibility: A function can be assigned to a function\n+variable only if both function types are equal.\n+\n+Comparisons: A variable of function type can be compared against "nil" with the\n+operators "==" and "!=" (§Comparison operators). The variable is\n+"nil" only if "nil" is assigned explicitly to the variable (§Assignments), or\n+if the variable has not been modified since creation (§Program initialization\n+and execution).\n+\n+Two variables of equal function type can be tested for equality with the\n+operators "==" and "!=" (§Comparison operators). The variables are equal\n+if they refer to the same function.\
- 意味:
- 関数がポインタとしてのみ扱われるという記述が削除されました。
- 関数型が結果型として関数型を持つ場合の曖昧さ解消のための括弧の必要性が説明されています。
- 関数型変数の代入互換性が、ポインタではなく関数値として記述されています。
- 最も重要な変更点の一つ: 関数型変数が
nil
と比較できること、および同じ関数を参照している場合に等価性比較ができることが明記されました。
--- a/doc/go_spec.txt
+++ b/doc/go_spec.txt
@@ -2384,45 +2383,62 @@ pointer to function. Consider the type T with method M:\n \tfunc (tp *T) M(a int) int;\n \tvar t *T;\n \n-To construct the address of method M, one writes\n+To construct the value of method M, one writes\n+\n+\tt.M\n \n-\t&t.M\n+using the variable t (not the type T).\
- 意味: メソッドのアドレス (
&t.M
) を取得するという概念が削除され、代わりにt.M
という構文で「メソッド値」を取得できるようになったことが示されています。
コアとなるコードの解説
このコミットは、Go言語の仕様書 doc/go_spec.txt
のみを変更しており、実際のGoコンパイラやランタイムのコード変更は含まれていません。しかし、この仕様書の変更は、Go言語の将来のバージョンにおけるコンパイラの挙動や言語機能に直接影響を与える、非常に重要なものです。
変更の核心は、Go言語における「関数」と「メソッド」の扱いを、C言語のような関数ポインタの概念から、より抽象化された「第一級の関数値」へと進化させた点にあります。
-
関数型のセマンティクス統一:
- 以前は、関数は「ポインタ」として扱われ、その型も
*()
のようにポインタ構文で表現されていました。これは、スライス、マップ、チャネルといった他のGoの組み込み参照型が、内部的にはポインタを持つものの、言語レベルでは値として扱われ、nil
との比較や等価性比較が可能であることと対照的でした。 - この変更により、関数型も
func
キーワードで明示的に宣言され、nil
値を持つことができ、他の関数値との等価性比較も可能になりました。これにより、Go言語の型システム全体の一貫性が向上し、プログラマは関数を他のデータ型と同様に、より自然に扱うことができるようになりました。例えば、関数がnil
であるかどうかをチェックしたり、特定の関数が別の関数と同じであるかを比較したりすることが、言語レベルでサポートされるようになりました。
- 以前は、関数は「ポインタ」として扱われ、その型も
-
メソッド値の導入:
- Go言語のメソッドは、特定の型に関連付けられた関数です。以前は、メソッドを関数として扱うには、そのアドレス (
&t.M
) を取得する必要があり、これはやや冗長でした。 - 新しい仕様では、
t.M
という構文で直接「メソッド値」を取得できるようになりました。このメソッド値は、レシーバ(t
)が既にバインドされた関数として振る舞います。これにより、メソッドをコールバックとして渡したり、変数に格納したりする際に、より簡潔で直感的なコード記述が可能になります。これは、Go言語が関数型プログラミングの要素をより深く取り入れていることを示しています。
- Go言語のメソッドは、特定の型に関連付けられた関数です。以前は、メソッドを関数として扱うには、そのアドレス (
これらの変更は、Go言語の表現力を高め、より柔軟なプログラミングスタイルを可能にするための基盤を築きました。特に、関数やメソッドを第一級の市民として扱うことで、高階関数やクロージャの利用がより自然になり、コードのモジュール性や再利用性が向上します。
関連リンク
- Go言語の公式ウェブサイト: https://go.dev/
- Go言語の仕様書 (現在のバージョン): https://go.dev/ref/spec
- Go言語の初期設計に関する議論 (Go Mailing Listなど): 2009年頃のGo言語のメーリングリストのアーカイブには、この変更に関する詳細な議論が含まれている可能性があります。
参考にした情報源リンク
- Go言語の公式ドキュメントおよび仕様書。
- Go言語の初期のコミット履歴と関連する議論。
- Go言語の設計原則に関する一般的な知識。
- Go言語の関数型とメソッドに関するブログ記事やチュートリアル(現在のGo言語の挙動を理解するためのもの)。
- Russ Cox氏のGo言語に関する発表や記事。