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

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

このコミットは、Go言語の標準ライブラリに含まれるregexp(正規表現)パッケージに対する重要なリファクタリングとコメントの追加を行っています。主な変更点は、パッケージ内部のメソッドやフィールドの可視性(エクスポート/非エクスポート)の調整、型名の変更、そして正規表現の構文に関する詳細なコメントの追加です。これにより、パッケージの内部実装がより適切にカプセル化され、外部に公開されるAPIが明確化されています。

コミット

commit a0feb3256760c71c87a977bfd9d81dc52bd690b8
Author: Rob Pike <r@golang.org>
Date:   Wed Mar 4 21:06:38 2009 -0800

    remove some incorrect capitalizations.
    add commentary.
    
    R=rsc
    DELTA=231  (41 added, 24 deleted, 166 changed)
    OCL=25724
    CL=25726

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

https://github.com/golang/go/commit/a0feb3256760c71c87a977bfd9d81dc52bd690b8

元コミット内容

このコミットの元の内容は以下の通りです。

  • いくつかの不適切な大文字化(エクスポートされた識別子)を修正。
  • コメントを追加。

これは、Go言語の初期開発段階において、コードの慣用的なスタイルとAPI設計を洗練させるための典型的な変更です。

変更の背景

このコミットが行われた2009年3月は、Go言語がまだ一般に公開される前の初期開発段階にありました。この時期のコミットは、言語の設計原則(特に「シンプルさ」と「明示性」)を標準ライブラリに適用し、APIの安定化と内部実装の整理を進めることが主な目的でした。

regexpパッケージは、Go言語の重要な標準ライブラリの一つであり、その設計は言語全体の哲学を反映する必要があります。初期のコードベースでは、Goの識別子の可視性ルール(大文字で始まる識別子はエクスポートされ、小文字で始まる識別子はパッケージ内部に限定される)が完全に適用されていない箇所や、APIの意図が不明瞭な箇所が存在しました。

このコミットの背景には、以下の意図が考えられます。

  1. APIの明確化とカプセル化の強化: パッケージの外部から利用されるべきでない内部的なメソッドやフィールドを非エクスポート(小文字始まり)にすることで、APIの表面積を減らし、利用者が混乱する可能性を低減します。これにより、パッケージの内部実装の変更が外部に影響を与えにくくなり、将来的なメンテナンス性が向上します。
  2. コードの可読性と理解の向上: 正規表現の構文に関する詳細なコメントを追加することで、パッケージの利用者がどのような正規表現がサポートされているかを容易に理解できるようになります。また、内部コードにもコメントを追加することで、開発者間の協力や将来のコード変更が容易になります。
  3. Go言語の慣用的なスタイルの確立: Go言語では、エクスポートされるべきでないものは小文字で始めるという明確な慣習があります。このコミットは、その慣習をregexpパッケージに徹底的に適用し、Goらしいコードベースを構築する一環です。
  4. 不要な抽象化の削除: Regexpインターフェースを削除し、Compile関数が直接具象型*regexp.Regexpを返すように変更することで、シンプルさを追求しています。初期の設計ではインターフェースが導入されていたかもしれませんが、その後の検討で不要と判断された可能性があります。

前提知識の解説

このコミットを理解するためには、以下のGo言語および正規表現に関する基本的な知識が必要です。

Go言語の識別子の可視性(エクスポート/非エクスポート)

Go言語では、識別子(変数名、関数名、型名、メソッド名、フィールド名など)の最初の文字が大文字か小文字かによって、その識別子がパッケージの外部からアクセス可能かどうかが決まります。

  • 大文字で始まる識別子: パッケージの外部にエクスポートされ、他のパッケージからアクセス可能です(publicに相当)。
  • 小文字で始まる識別子: パッケージ内部に限定され、他のパッケージからはアクセスできません(privateに相当)。

このコミットでは、パッケージ内部でのみ使用されるべきメソッドやフィールドが誤って大文字で始まっていたものを、小文字に修正することで、適切なカプセル化を実現しています。

正規表現(Regular Expression)

正規表現は、文字列のパターンを記述するための強力なツールです。特定の文字列の検索、置換、検証などに広く使用されます。regexpパッケージは、Go言語で正規表現を扱うための機能を提供します。

正規表現の基本的な要素には以下のようなものがあります。

  • リテラル文字: a, b, c など、そのままの文字にマッチします。
  • メタ文字: 特殊な意味を持つ文字。
    • . (ドット): 任意の1文字にマッチします(改行を除く)。
    • * (アスタリスク): 直前の要素が0回以上繰り返されることにマッチします。
    • + (プラス): 直前の要素が1回以上繰り返されることにマッチします。
    • ? (クエスチョン): 直前の要素が0回または1回出現することにマッチします。
    • | (パイプ): 論理OR。a|baまたはbにマッチします。
    • ^ (キャレット): 行の先頭にマッチします。
    • $ (ドル): 行の末尾にマッチします。
    • [] (角括弧): 文字クラス。[abc]abcのいずれか1文字にマッチします。[^abc]abc以外の1文字にマッチします。
    • () (丸括弧): グループ化。部分式を定義し、マッチした部分文字列をキャプチャするために使用されます。

NFA (Nondeterministic Finite Automaton)

regexpパッケージの内部実装は、NFA(非決定性有限オートマトン)に基づいていると考えられます。NFAは、正規表現を処理するための抽象的な計算モデルです。正規表現エンジンは、入力文字列をNFAの状態遷移として解釈し、パターンにマッチするかどうかを判断します。

このコミットで変更されているinstr(instruction)インターフェースや、_Start, _End, _Char, _Altなどの構造体は、NFAの状態や遷移を表す内部的な命令に対応しています。

技術的詳細

このコミットの技術的な変更は、主にsrc/lib/regexp/regexp.goファイルに集中しており、Go言語の設計原則である「シンプルさ」と「明示性」、そして「適切なカプセル化」を追求しています。

  1. パッケージコメントの拡充:

    • 以前は「Regular expression library.」という簡潔な説明しかなかったものが、正規表現の構文規則を詳細に記述したコメントに置き換えられました。これにより、パッケージの利用者は、どのような正規表現パターンがサポートされているかをコードから直接確認できるようになりました。これは、ドキュメントの充実とAPIの自己説明性を高めるための重要な変更です。
  2. instrインターフェースのメソッド名の変更:

    • instrインターフェースは、正規表現の内部表現である命令(instruction)の共通の振る舞いを定義しています。このインターフェースのメソッド名が、すべて小文字始まりに変更されました。
      • Type() -> kind()
      • Next() -> next()
      • SetNext() -> setNext()
      • Index() -> index()
      • SetIndex() -> setIndex()
      • Print() -> print()
    • これは、これらのメソッドがパッケージの外部から直接呼び出されることを意図しておらず、正規表現エンジンの内部実装の詳細であることを明確にするための変更です。Goの慣習に従い、内部的なメソッドは非エクスポートとすべきです。
  3. common構造体のフィールド名の変更:

    • common構造体は、すべてのinstr実装が埋め込む共通のフィールド(nextindex)を持っていました。これらも小文字始まりの_next_indexに変更されました。
    • アンダースコア(_)をプレフィックスとして使用することは、Goでは慣習的に「内部的な、直接アクセスすべきでないフィールド」を示すことがあります。これにより、これらのフィールドがcommon構造体を埋め込んだ型から直接アクセスされることを抑制し、アクセッサメソッド(next(), index()など)を介してのみ操作されるべきであることを示唆しています。
  4. _RE型のRegexp型への変更とインターフェースの削除:

    • 最も重要な変更の一つは、正規表現のコンパイル済み表現を表す内部的な型_REが、エクスポートされたRegexp型に名称変更されたことです。
    • 同時に、以前はRegexpという名前のインターフェースが存在し、Compile関数はそのインターフェースを返していました。このコミットでは、そのRegexpインターフェースが削除され、Compile関数は直接具象型である*regexp.Regexpを返すようになりました。
    • この変更は、Go言語の「インターフェースは利用側で定義する」という哲学("The bigger the interface, the weaker the abstraction.")に沿ったものです。正規表現の基本的な操作(Execute, Match, MatchStrings)は、具象型*Regexpのメソッドとして直接提供されることで十分であり、余分なインターフェース層は不要と判断されたと考えられます。これにより、APIがよりシンプルで直接的になります。
  5. Regexp(旧_RE)型のメソッド名の変更:

    • _RE型(新Regexp型)の内部的な処理を行うメソッドも、すべて小文字始まりに変更されました。
      • Error() -> setError()
      • Add() -> add()
      • EliminateNops() -> eliminateNops()
      • Dump() -> dump()
      • DoParse() -> doParse()
      • DoExecute() -> doExecute()
    • これらのメソッドは、正規表現のコンパイルや実行の内部ロジックを担っており、パッケージの外部から直接呼び出されるべきではありません。これにより、パッケージの内部実装がより堅牢になり、誤用を防ぎます。
  6. parser構造体と関連メソッドの変更:

    • 正規表現のパースを担当するparser構造体のreフィールドの型が*_REから*Regexpに変更されました。
    • parserのメソッドも、内部的な性質を反映して小文字始まりに変更されました。
      • Regexp() -> regexp()
      • CharClass() -> charClass()
      • Term() -> term()
      • Closure() -> closure()
      • Concatenation() -> concatenation()
    • これらの変更は、パースロジックがregexpパッケージの内部実装の一部であることを明確にしています。
  7. _CharClassメソッドの変更:

    • 文字クラスの処理を行う_CharClass型のメソッドも小文字始まりに変更されました。
      • AddRange() -> addRange()
      • Matches() -> matches()
    • これらも内部的なヘルパーメソッドであり、外部に公開する必要はありません。
  8. MatchStringsの改善:

    • MatchStringsメソッドにおいて、キャプチャグループがマッチしなかった場合に-1が返されるケースを明示的に処理するロジックが追加されました。
    • 以前はs[r[i] : r[i+1]]というスライス操作が直接行われていましたが、if r[i] != -1のチェックが追加され、マッチしなかった場合は空文字列が割り当てられるようになりました。これにより、より堅牢な挙動が保証されます。

これらの変更は、Go言語の初期段階における標準ライブラリの成熟度を高め、よりGoらしい設計原則に沿ったものにするための重要なステップでした。

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

このコミットで変更された主要なファイルは以下の2つです。

  1. src/lib/regexp/all_test.go
  2. src/lib/regexp/regexp.go

特にsrc/lib/regexp/regexp.goが変更の中心です。

src/lib/regexp/all_test.goの変更

--- a/src/lib/regexp/all_test.go
+++ b/src/lib/regexp/all_test.go
@@ -85,7 +85,7 @@ var matches = []tester {
 	tester{ `a*(|(b))c*`,	"aacc",	vec{0,4, 2,2, -1,-1} },
 }
 
-func compileTest(t *testing.T, expr string, error *os.Error) regexp.Regexp {
+func compileTest(t *testing.T, expr string, error *os.Error) *regexp.Regexp {
 	re, err := regexp.Compile(expr);
 	if err != error {
 		t.Error("compiling `", expr, "`; unexpected error: ", err.String());
  • compileTest関数の戻り値の型がregexp.Regexpインターフェースから*regexp.Regexp具象型へのポインタに変更されました。これは、regexp.Regexpインターフェースが削除されたことと整合性を保つための変更です。

src/lib/regexp/regexp.goの変更

このファイルには多数の変更が含まれますが、主要な変更点を抜粋します。

  1. パッケージコメントの追加:

    --- a/src/lib/regexp/regexp.go
    +++ b/src/lib/regexp/regexp.go
    @@ -2,8 +2,25 @@
     // Use of this source code is governed by a BSD-style
     // license that can be found in the LICENSE file.
     
    -// Regular expression library.
    -
    +// Package regexp implements a simple regular expression library.
    +//
    +// The syntax of the regular expressions accepted is:
    +//
    +//
    +//	regexp:
    +//		concatenation { '|' concatenation }
    +//	concatenation:
    +//		{ closure }
    +//
    +//	closure:
    +//		term [ '*' | '+' | '?' ]
    +//	term:
    +//		'^'
    +//		'$'
    +//		'.'
    +//		character
    +//		'[' [ '^' ] character-ranges ']'
    +//		'(' regexp ')'
     package regexp
    
    • パッケージの冒頭に正規表現の構文に関する詳細な説明が追加されました。
  2. instrインターフェースのメソッド名変更:

    --- a/src/lib/regexp/regexp.go
    +++ b/src/lib/regexp/regexp.go
    @@ -28,28 +45,30 @@ var ErrBadBackslash = os.NewError("illegal backslash escape");
     
     // An instruction executed by the NFA
     type instr interface {
    -	Type()	int;	// the type of this instruction: _CHAR, _ANY, etc.
    -	Next()	instr;	// the instruction to execute after this one
    -	SetNext(i instr);
    -	Index()	int;
    -	SetIndex(i int);
    -	Print();
    +	kind()	int;	// the type of this instruction: _CHAR, _ANY, etc.
    +	next()	instr;	// the instruction to execute after this one
    +	setNext(i instr);
    +	index()	int;
    +	setIndex(i int);
    +	print();
     }
    
    • Type, Next, SetNext, Index, SetIndex, Printがそれぞれ小文字始まりのkind, next, setNext, index, setIndex, printに変更されました。
  3. common構造体のフィールド名変更:

    --- a/src/lib/regexp/regexp.go
    +++ b/src/lib/regexp/regexp.go
    @@ -37,9 +56,9 @@ type common struct {
     	_index	int;
     }
     
    -func (c *common) Next() instr { return c.next }
    -func (c *common) SetNext(i instr) { c.next = i }
    -func (c *common) Index() int { return c.index }
    -func (c *common) SetIndex(i int) { c.index = i }
    +func (c *common) next() instr { return c._next }
    +func (c *common) setNext(i instr) { c._next = i }
    +func (c *common) index() int { return c._index }
    +func (c *common) setIndex(i int) { c._index = i }
    
    • nextindexフィールドが_next_indexに変更され、それに対応するアクセッサメソッドも小文字始まりに変更されました。
  4. _RE型のRegexp型への変更とコメント追加:

    --- a/src/lib/regexp/regexp.go
    +++ b/src/lib/regexp/regexp.go
    @@ -47,7 +66,9 @@ func (c *common) SetIndex(i int) { c.index = i }
     
     -type _RE struct {
    +// The representation of a compiled regular expression.
    +// The public interface is entirely through methods.
    +type Regexp struct {
      expr	string;	// the original expression
    -	ch	chan<- *_RE;	// reply channel when we're done
    +	ch	chan<- *Regexp;	// reply channel when we're done
      error	*os.Error;	// compile- or run-time error; nil if OK
      inst	*array.Array;
      start	instr;
    
    • _RERegexpにリネームされ、その目的を説明するコメントが追加されました。chフィールドの型も*Regexpに変更されています。
  5. Regexp(旧_RE)のメソッド名変更:

    --- a/src/lib/regexp/regexp.go
    +++ b/src/lib/regexp/regexp.go
    @@ -220,16 +239,16 @@ func (nop *_Nop) Print() { print("nop") }
     
     // report error and exit compiling/executing goroutine
    -func (re *_RE) Error(err *os.Error) {
    +func (re *Regexp) setError(err *os.Error) {
      re.error = err;
      re.ch <- re;
      sys.Goexit();
     }
     
    -func (re *_RE) Add(i instr) instr {
    -	i.SetIndex(re.inst.Len());
    +func (re *Regexp) add(i instr) instr {
    +	i.setIndex(re.inst.Len());
      re.inst.Push(i);
      return i;
     }
    
    • ErrorsetErrorに、Addaddにリネームされました。同様に、EliminateNops, Dump, DoParse, DoExecuteも小文字始まりのメソッド名に変更されています。
  6. Compile関数の戻り値とコメントの変更、Regexpインターフェースの削除:

    --- a/src/lib/regexp/regexp.go
    +++ b/src/lib/regexp/regexp.go
    @@ -530,19 +549,21 @@ func compiler(str string, ch chan *_RE) {
      ch <- re;
     }
     
    -// Public interface has only execute functionality
    -type Regexp interface {
    -	Execute(s string) []int;
    -	Match(s string) bool;
    -	MatchStrings(s string) []string;
    -}
    -
    -// Compile in separate goroutine; wait for result
    -func Compile(str string) (regexp Regexp, error *os.Error) {
    -	ch := make(chan *_RE);
    +// Compile parses a regular expression and returns, if successful, a Regexp
    +// object that can be used to match against text.
    +func Compile(str string) (regexp *Regexp, error *os.Error) {
    +	// Compile in a separate goroutine and wait for the result.
    +	ch := make(chan *Regexp);
      go compiler(str, ch);
      re := <-ch;
      return re, re.error
     }
    
    • Regexpインターフェースが削除され、Compile関数が*Regexpを直接返すようになりました。また、Compile関数のコメントがより詳細になりました。
  7. MatchStringsでの-1処理の追加:

    --- a/src/lib/regexp/regexp.go
    +++ b/src/lib/regexp/regexp.go
    @@ -715,9 +735,11 @@ func (re *_RE) DoExecute(str string, pos int) []int {
     func (re *_RE) MatchStrings(s string) []string {
     	r := re.DoExecute(s, 0);
      if r == nil {
      	return nil
      }
    -	a := make([]string, len(r)/2);
    +	a = make([]string, len(r)/2);
      for i := 0; i < len(r); i += 2 {
    -		a[i/2] = s[r[i] : r[i+1]]
    +		if r[i] != -1 {	// -1 means no match for this subexpression
    +			a[i/2] = s[r[i] : r[i+1]]
    +		}
      }
    -	return a
    +	return
     }
    
    • MatchStrings内で、キャプチャグループがマッチしなかった場合に返される-1をチェックし、適切な処理(空文字列の割り当て)を行うようになりました。

コアとなるコードの解説

このコミットの核心は、Go言語の正規表現パッケージの内部構造を整理し、外部に公開されるAPIをよりGoらしい慣用的なものにすることです。

  1. 内部メソッドの非エクスポート化:

    • instrインターフェースのメソッド(Type, Nextなど)や、Regexp(旧_RE)型の内部メソッド(Error, Add, DoParseなど)、parserのメソッド(Regexp, CharClassなど)が、すべて小文字始まりの識別子にリネームされました。
    • これは、Go言語の識別子の可視性ルールに厳密に従うための変更です。これらのメソッドは正規表現エンジンの内部動作を構成するものであり、パッケージの外部から直接呼び出されることを意図していません。非エクスポート化することで、パッケージの内部実装が外部から隠蔽され、APIの安定性が向上します。開発者は内部実装の変更を自由に行えるようになり、利用者は公開されたAPIのみに依存するため、コードの保守性が高まります。
  2. _REからRegexpへの型名変更とインターフェースの削除:

    • 正規表現のコンパイル済み表現を表す型が、内部的な_REからエクスポートされたRegexpに変更されました。
    • 同時に、以前存在したRegexpインターフェースが削除され、regexp.Compile関数は直接*regexp.Regexpを返すようになりました。
    • この変更は、Go言語の設計思想である「シンプルさ」と「不要な抽象化の排除」を反映しています。正規表現の基本的な操作(マッチング、部分文字列の取得など)は、具象型*Regexpのメソッドとして直接提供されることで十分であり、追加のインターフェース層は冗長と判断されたと考えられます。これにより、APIの利用がより直接的で分かりやすくなります。
    • Goでは「インターフェースは利用側で定義する」という原則があり、ライブラリが提供する具象型が特定のインターフェースを満たすことで、利用者がそのインターフェースを定義して多態的に扱うことができます。このコミットは、その原則に沿ったAPI設計への移行を示しています。
  3. コメントの追加と改善:

    • regexpパッケージの冒頭に、サポートされる正規表現の構文に関する詳細な説明が追加されました。これは、パッケージのドキュメントを充実させ、利用者がコードを読まずとも正規表現のルールを理解できるようにするための重要な改善です。
    • また、Regexp型やCompile関数など、主要な公開APIにも詳細なコメントが追加され、それぞれの役割と使い方を明確にしています。
  4. MatchStringsの堅牢性向上:

    • MatchStringsメソッドにおいて、キャプチャグループがマッチしなかった場合に返される-1というインデックスを適切に処理するロジックが追加されました。これにより、正規表現のマッチ結果がより正確かつ安全に扱えるようになりました。以前の実装では、-1がそのままスライス操作に渡されるとパニックを引き起こす可能性がありました。

これらの変更は、Go言語の初期段階において、標準ライブラリがより成熟し、Goの設計哲学に沿った堅牢で使いやすいものへと進化していく過程を示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式リポジトリ: https://github.com/golang/go
  • Go言語の初期開発に関する情報(Go Wikiなど)
  • 正規表現の基本的な概念に関する一般的な情報源