[インデックス 1527] ファイルの概要
このコミットは、Go言語の初期開発段階における重要な設計変更、特に識別子の可視性(エクスポート)に関するメカニズムの変更を反映しています。pretty
という名前のパッケージ(Go言語の初期のパーサー、スキャナー、プリンター、型チェッカーを含むツールセットと推測されます)から、明示的なexport
キーワードとその関連ロジックが完全に削除されています。これにより、Go言語が最終的に採用した「識別子の先頭文字の大小で可視性を決定する」という設計原則への移行が明確に示されています。
コミット
commit 96c20204a7f4c2bc618b83368f98ff63b2a52038
Author: Robert Griesemer <gri@golang.org>
Date: Tue Jan 20 15:22:33 2009 -0800
- updated pretty (removed "export")
R=r
OCL=23134
CL=23134
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/96c20204a7f4c2bc618b83368f98ff63b2a52038
元コミット内容
このコミットの元々のメッセージは非常に簡潔です。
- updated pretty (removed "export")
これは、「pretty
パッケージを更新し、export
(エクスポート)の概念を削除した」ことを意味します。
変更の背景
このコミットは2009年1月20日に行われており、Go言語が一般に公開される(2009年11月)よりも前の、非常に初期の段階に当たります。Go言語の設計は当時まだ流動的であり、言語の構文やセマンティクスに関する多くの決定がなされていました。
多くのプログラミング言語では、モジュールやパッケージの外部に公開する要素(関数、変数、型など)を明示的に指定するために、public
、export
、extern
などのキーワードを使用します。Go言語の初期の設計においても、同様にexport
キーワードを導入する案があったと考えられます。しかし、このコミットは、その設計が変更され、より簡潔で慣習的なアプローチが採用されたことを示しています。
最終的にGo言語では、識別子の可視性をその名前の先頭文字の大小で決定するという、ユニークかつ非常にGoらしい設計が採用されました。すなわち、識別子の先頭が大文字であればそのパッケージの外部にエクスポートされ(公開)、小文字であればパッケージ内部に限定される(非公開)というルールです。このコミットは、その設計原則への移行をコードレベルで実現したものです。
前提知識の解説
このコミットを理解するためには、以下の概念が重要です。
- Go言語のパッケージと可視性(Visibility): Go言語では、コードは「パッケージ」という単位で組織されます。パッケージ内の識別子(変数、関数、型など)は、そのパッケージの外部からアクセスできるかどうかが「可視性」によって決まります。Go言語の最終的な設計では、識別子の先頭文字が大文字であればエクスポートされ、小文字であればエクスポートされないというルールが適用されます。
- 抽象構文木(AST: Abstract Syntax Tree): プログラムのソースコードを解析して得られる、その構造を木構造で表現したものです。コンパイラやリンター、フォーマッターなどのツールは、ASTを操作してコードの分析や変換を行います。
- スキャナー(Lexer/Tokenizer): ソースコードを読み込み、意味のある最小単位(トークン)に分割する役割を担います。例えば、
func
、var
、export
といったキーワードや、識別子、演算子などがトークンとして認識されます。 - パーサー(Parser): スキャナーが生成したトークンの列を文法規則に従って解析し、ASTを構築します。
- プリンター(Printer): ASTを元に、整形されたソースコードを生成します。
- 型チェッカー(Type Checker): ASTを走査し、プログラムが型システムの一貫性ルールに従っているか検証します。
pretty
パッケージ: コミットメッセージや変更されたファイルパス(usr/gri/pretty/...
)から判断すると、これはGo言語の初期のコンパイラツールチェーンの一部、特にコードの解析、整形、および基本的なセマンティックチェックを行うためのプロトタイプまたは初期実装であったと考えられます。gri
はコミットの著者であるRobert Griesemer氏のイニシャルであり、彼がこのツールの主要な開発者であったことを示唆しています。
技術的詳細
このコミットの技術的な核心は、Go言語の可視性メカニズムが、明示的なキーワードベースから、識別子の命名規則ベースへと移行した点にあります。
具体的には、以下の要素が削除または変更されました。
export
キーワードの廃止:- スキャナーから
EXPORT
トークンが削除されました。これにより、ソースコード中のexport
という文字列はもはや特別なキーワードとして認識されなくなりました。 - パーサーは
export
キーワードを処理するロジックを完全に削除しました。ParseDeclaration
関数からScanner.EXPORT
を処理するcase
が削除され、宣言をパースする際にexported
フラグを渡す必要がなくなりました。
- スキャナーから
- ASTからの
Exported
フラグの削除:ast.go
内のDecl
構造体からExported bool
フィールドが削除されました。これは、宣言がエクスポートされるかどうかという情報が、ASTノード自体に直接保持される必要がなくなったことを意味します。代わりに、この情報は識別子の名前から推論されるようになります。NewDecl
関数もexported bool
引数を削除し、ASTノードの生成時にエクスポート情報を渡す必要がなくなりました。
- 命名規則検証ロジックの削除:
parser.go
からVerifyExport1
およびVerifyExport
関数が完全に削除されました。これらの関数は、識別子がエクスポートされるべき場合に大文字で始まるか、そうでない場合に小文字で始まるかを検証する役割を担っていました。このロジックがパーサーから削除されたのは、おそらくこの検証がより後の段階(例えば、リンターや別のツール)で行われるようになったか、あるいはパーサーの役割から切り離されたためと考えられます。compilation.go
とpretty.go
から、この命名規則検証を制御するNaming bool
フラグが削除されました。
- プリンターからの
export
出力ロジックの削除:printer.go
内のDeclaration
関数から、d.Exported
がtrue
の場合に"export"
文字列を出力するロジックが削除されました。これは、もはやexport
キーワードがソースコードに現れないため、プリンターがそれを出力する必要がなくなったことを意味します。
- 型チェッカーからの
export
処理の削除:typechecker.go
内のCheckDeclaration
関数から、Scanner.EXPORT
ケースが削除されました。型チェッカーも、export
キーワードの存在を考慮する必要がなくなりました。
これらの変更は、Go言語の設計哲学である「シンプルさ」と「慣習による設計」を強く反映しています。明示的なキーワードを排除し、命名規則というシンプルな慣習に可視性の制御を委ねることで、言語の構文がより簡潔になり、コードの読みやすさも向上します。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下のファイルに集中しています。
usr/gri/pretty/ast.go
:Decl
構造体からExported bool
フィールドが削除。NewDecl
関数のシグネチャからexported bool
引数が削除。
usr/gri/pretty/parser.go
:Parser
構造体からnaming bool
フィールドが削除。Open
関数のシグネチャからnaming bool
引数が削除。VerifyExport1
およびVerifyExport
関数が完全に削除。ParseImportSpec
,ParseConstSpec
,ParseTypeSpec
,ParseVarSpec
,ParseSpec
,ParseDecl
,ParseFunctionDecl
といった宣言をパースする関数からexported bool
引数が削除され、関連するAST.NewDecl
の呼び出しも変更。ParseDeclaration
関数内のexport
キーワードを処理するロジックが削除。
usr/gri/pretty/scanner.go
:EXPORT
トークン定数が削除。TokenString
関数からcase EXPORT
が削除。
usr/gri/pretty/printer.go
:Declaration
関数内のd.Exported
に基づく"export"
文字列出力ロジックが削除。
コアとなるコードの解説
usr/gri/pretty/ast.go
--- a/usr/gri/pretty/ast.go
+++ b/usr/gri/pretty/ast.go
@@ -387,7 +387,6 @@ var BadStat = NewStat(0, Scanner.ILLEGAL);
type Decl struct {
Node;
- Exported bool;
Ident *Expr; // nil for ()-style declarations
Typ *Type;
Val *Expr;
@@ -397,14 +396,14 @@ type Decl struct {
}
-func NewDecl(pos, tok int, exported bool) *Decl {
+func NewDecl(pos, tok int) *Decl {
d := new(Decl);
- d.Pos, d.Tok, d.Exported = pos, tok, exported;
+ d.Pos, d.Tok = pos, tok;
return d;
}
-var BadDecl = NewDecl(0, Scanner.ILLEGAL, false);
+var BadDecl = NewDecl(0, Scanner.ILLEGAL);
Decl
構造体: 宣言を表すASTノードの構造体です。ここからExported bool;
が削除されました。これは、宣言がエクスポートされるかどうかという情報が、ASTの段階ではもはや明示的に保持されないことを意味します。この情報は、後続の処理(例えば、識別子の名前をチェックするリンターなど)によって推論されるようになります。NewDecl
関数:Decl
構造体の新しいインスタンスを作成するヘルパー関数です。exported bool
引数が削除され、d.Exported
への代入もなくなりました。これにより、宣言の作成時にエクスポート情報を渡す必要がなくなります。
usr/gri/pretty/parser.go
--- a/usr/gri/pretty/parser.go
+++ b/usr/gri/pretty/parser.go
@@ -13,7 +13,7 @@ import (
type Parser struct {
// Tracing/debugging
- verbose, sixg, deps, naming bool;
+ verbose, sixg, deps bool;
indent uint;
// Scanner
@@ -109,11 +109,10 @@ func (P *Parser) Next() {
}
-func (P *Parser) Open(verbose, sixg, deps, naming bool, scanner *Scanner.Scanner, tokchan <-chan *Scanner.Token) {
+func (P *Parser) Open(verbose, sixg, deps bool, scanner *Scanner.Scanner, tokchan <-chan *Scanner.Token) {
P.verbose = verbose;
P.sixg = sixg;
P.deps = deps;
-\tP.naming = naming;
P.indent = 0;
P.scanner = scanner;
@@ -192,33 +191,6 @@ func (P *Parser) Declare(p *AST.Expr, kind int) {
}
-func (P *Parser) VerifyExport1(p *AST.Expr, exported bool) {
- obj := p.Obj;
- if exported {
- if !obj.IsExported() {
- P.Error(obj.Pos, `"` + obj.Ident + `" should be uppercase`);
- }
- } else if P.scope_lev == 0 {
- if obj.IsExported() {
- P.Error(obj.Pos, `"` + obj.Ident + `" should be lowercase`);
- }
- }
-}
-
-
-func (P *Parser) VerifyExport(p *AST.Expr, exported bool) {
- if !P.naming {
- return;
- }
- for p.Tok == Scanner.COMMA {
- P.VerifyExport1(p.X, exported);
- p = p.Y;
- }
- P.VerifyExport1(p, exported);
-}
-
-
-
// ----------------------------------------------------------------------------
// AST support
@@ -1500,7 +1472,7 @@ func (P *Parser) ParseStatement() *AST.Stat {
func (P *Parser) ParseImportSpec(pos int) *AST.Decl {\n \tP.Trace(\"ImportSpec\");\n \n-\td := AST.NewDecl(pos, Scanner.IMPORT, false);\n+\td := AST.NewDecl(pos, Scanner.IMPORT);\n \tif P.tok == Scanner.PERIOD {\n \t\tP.Error(P.pos, `\"import .\" not yet handled properly`);\n \t\tP.Next();\
... (中略:ParseConstSpec, ParseTypeSpec, ParseVarSpec, ParseSpec, ParseDecl, ParseFunctionDecl のシグネチャ変更と `exported` 引数の削除) ...
@@ -1687,35 +1640,15 @@ func (P *Parser) ParseDeclaration() *AST.Decl {
indent := P.indent;
d := AST.BadDecl;
-\texported := false;
-\t// TODO don\'t use bool flag for export
-\tif P.tok == Scanner.EXPORT || P.tok == Scanner.PACKAGE {\n-\t\tif P.scope_lev == 0 {\n-\t\t\texported = true;\n-\t\t} else {\n-\t\t\tP.Error(P.pos, \"local declarations cannot be exported\");\n-\t\t}\n-\t\tP.Next();\n-\t}\
+\tswitch P.tok {\n \tswitch P.tok {\n \tcase Scanner.CONST, Scanner.TYPE, Scanner.VAR:\n-\t\td = P.ParseDecl(exported, P.tok);\n+\t\td = P.ParseDecl(P.tok);\n \tcase Scanner.FUNC:\n-\t\td = P.ParseFunctionDecl(exported);\n-\tcase Scanner.EXPORT:\n-\t\tif exported {\n-\t\t\tP.Error(P.pos, \"cannot mark export declaration for export\");\n-\t\t}\n-\t\tP.Next();\n-\t\td = P.ParseExportDecl();\n+\t\td = P.ParseFunctionDecl();\n \tdefault:\n-\t\tif exported && (P.tok == Scanner.IDENT || P.tok == Scanner.LPAREN) {\n-\t\t\td = P.ParseExportDecl();\n-\t\t} else {\n-\t\t\tP.Error(P.pos, \"declaration expected\");\n-\t\t\tP.Next(); // make progress\n-\t\t}\n+\t\tP.Error(P.pos, \"declaration expected\");\n+\t\tP.Next(); // make progress\n \t}\
Parser
構造体とOpen
関数:naming bool
フィールドと引数が削除されました。これは、パーサーが命名規則の検証を直接行わなくなったことを示します。VerifyExport1
とVerifyExport
関数: これらの関数は、識別子の先頭文字がエクスポートルールに従っているか(大文字か小文字か)を検証していました。これらが完全に削除されたことで、パーサーの段階ではこの検証が行われなくなりました。ParseDeclaration
関数: この関数は、const
,type
,var
,func
,export
といったキーワードで始まる宣言をパースする主要なロジックを含んでいました。変更前は、P.tok == Scanner.EXPORT
またはP.tok == Scanner.PACKAGE
の場合にexported
フラグを設定し、そのフラグをParseDecl
やParseFunctionDecl
に渡していました。変更後は、このexported
フラグを決定するロジックとScanner.EXPORT
のケースが完全に削除され、宣言のパースはキーワードの種類に直接基づくようになりました。
usr/gri/pretty/scanner.go
--- a/usr/gri/pretty/scanner.go
+++ b/usr/gri/pretty/scanner.go
@@ -76,7 +76,7 @@ const (
PERIOD;
// keywords
-\tKeywords_beg; // do not export eventually
+\tkeywords_beg;
BREAK;
CASE;
CHAN;
@@ -85,7 +85,6 @@ const (
DEFAULT;
ELSE;
-\tEXPORT;
FALLTHROUGH;
FOR;
@@ -106,7 +105,7 @@ const (
SWITCH;
TYPE;
VAR;
-\tKeywords_end; // do not export eventually
+\tkeywords_end;
// AST use only
EXPRSTAT;
@@ -187,7 +186,6 @@ func TokenString(tok int) string {
case DEFAULT: return "default";
case ELSE: return "else";
-\tcase EXPORT: return "export";
case FALLTHROUGH: return "fallthrough";
case FOR: return "for";
@@ -249,7 +247,7 @@ var keywords map [string] int;
func init() {
keywords = make(map [string] int);\
-\tfor i := Keywords_beg + 1; i < Keywords_end; i++ {\
+\tfor i := keywords_beg + 1; i < keywords_end; i++ {\
\t\tkeywords[TokenString(i)] = i;\
\t}\
}\
- トークン定数:
EXPORT
というトークン定数が削除されました。これは、スキャナーがexport
という文字列を特別なキーワードとして認識しなくなったことを意味します。 TokenString
関数: トークンに対応する文字列を返す関数です。case EXPORT
が削除されました。init
関数:keywords
マップを初期化するループで、Keywords_beg
がkeywords_beg
に、Keywords_end
がkeywords_end
にそれぞれ変更されています。これは、これらの定数がパッケージ外部に公開されないようにするための慣習的な変更(Goでは小文字で始まる識別子は非公開)と考えられます。
usr/gri/pretty/printer.go
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -791,10 +791,6 @@ func (P *Printer) Stat(s *AST.Stat) {
func (P *Printer) Declaration(d *AST.Decl, parenthesized bool) {
if !parenthesized {
-\t\tif d.Exported {\n-\t\t\tP.String(d.Pos, "export");\n-\t\t\tP.separator = blank;\n-\t\t}\
\t\tP.Token(d.Pos, d.Tok);\
\t\tP.separator = blank;\
\t}\
@@ -827,10 +823,6 @@ func (P *Printer) Declaration(d *AST.Decl, parenthesized bool) {
\t\t\tP.Expr(d.Val);\
\t\t\tP.separator = semicolon;\
\
-\t\tcase Scanner.EXPORT:\n-\t\t\tP.Expr(d.Ident);\n-\t\t\tP.separator = semicolon;\n-\
\t\tcase Scanner.TYPE:\
\t\t\tP.Expr(d.Ident);\
\t\t\tP.separator = blank; // TODO switch to tab? (but indentation problem with structs)\
Declaration
関数: 宣言を整形して出力する関数です。変更前は、d.Exported
がtrue
の場合に"export"
という文字列を出力していました。このロジックが削除されたことで、プリンターはもはやexport
キーワードを出力しなくなりました。また、case Scanner.EXPORT
の処理も削除されています。
これらの変更は、Go言語の可視性ルールが、明示的なキーワードから命名規則へと移行したことを、ツールチェーンの各コンポーネント(AST、スキャナー、パーサー、プリンター、型チェッカー)で一貫して反映していることを示しています。
関連リンク
- Go言語の公式ウェブサイト: https://golang.org/
- Go言語の仕様(現在の可視性ルールについて記載されています): https://golang.org/ref/spec#Exported_identifiers
参考にした情報源リンク
- Go言語の初期の設計に関する議論やメーリングリストのアーカイブ(一般公開されている場合)
- Go言語のソースコードリポジトリの歴史(特に初期のコミットログ)
- Go言語の公式ドキュメントやブログ記事(言語設計の背景について言及されているもの)
- Robert Griesemer氏のGo言語に関する講演や記事