[インデックス 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言語に関する講演や記事