[インデックス 17463] ファイルの概要
このコミットは、Go言語の標準ライブラリである text/template
パッケージ内の eq
(equal) 関数に機能拡張を加えるものです。具体的には、eq
関数がこれまでの2つの引数だけでなく、3つ以上の引数を受け入れられるように変更されました。これにより、最初の引数と、それに続く任意の数の引数との等価性をまとめて比較できるようになります。
コミット
commit 0ba7ffe2897cd9771de172362e9edcb5f733cf1f
Author: Rob Pike <r@golang.org>
Date: Wed Sep 4 13:42:22 2013 +1000
text/template: allow eq to take more than two arguments
Based on an old suggestion by rsc, it compares the second
and following arguments to the first.
Unfortunately the code cannot be as pretty as rsc's original
because it doesn't require identical types.
R=golang-dev, dsymonds, adg
CC=golang-dev
https://golang.org/cl/13509046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0ba7ffe2897cd9771de172362e9edcb5f733cf1f
元コミット内容
text/template: allow eq to take more than two arguments
このコミットは、text/template
パッケージの eq
関数が2つ以上の引数を取れるようにするものです。これは rsc (Russ Cox) による古い提案に基づいており、2番目以降の引数を最初の引数と比較します。
残念ながら、型が同一である必要がないため、rsc の元の提案ほどコードをきれいに書くことはできませんでした。
レビュー担当者: golang-dev, dsymonds, adg CC: golang-dev 関連するコードレビュー: https://golang.org/cl/13509046
変更の背景
Go言語の text/template
パッケージは、Goプログラム内でテキストベースの出力を生成するための強力なツールです。テンプレートエンジンは、データ構造をテンプレートに適用し、動的にコンテンツを生成します。このパッケージには、条件分岐やループなどのロジックをテンプレート内で記述するための組み込み関数が多数用意されています。
eq
関数は、テンプレート内で2つの値が等しいかどうかを比較するために使用される基本的な関数でした。しかし、複数の値のいずれかと等しいかどうかをチェックしたい場合、これまでは {{if or (eq .Val "A") (eq .Val "B") (eq .Val "C")}}
のように、複数の eq
関数と or
関数を組み合わせる必要がありました。これは冗長であり、テンプレートの可読性を損ねる可能性がありました。
このコミットは、Russ Cox (rsc) による以前の提案に基づいています。rscはGo言語の主要な貢献者の一人であり、彼の提案はしばしば言語や標準ライブラリの改善に繋がります。この提案の目的は、eq
関数を拡張し、より簡潔な構文で多対一の等価性比較を可能にすることでした。これにより、テンプレートの記述がより直感的になり、特に複数の条件をチェックする際にコードがすっきりします。
コミットメッセージにある「Unfortunately the code cannot be as pretty as rsc's original because it doesn't require identical types.」という記述は、Goのテンプレートエンジンが型に厳密ではない(実行時に型変換を試みる)ため、純粋な型安全な比較ロジックを実装するのが難しいという背景を示唆しています。rscの元の提案は、おそらくより厳密な型チェックを前提としていたか、あるいはより簡潔なコードパスを想定していた可能性がありますが、実際の text/template
の柔軟な型比較の要件を満たすためには、より複雑なロジックが必要になったことを示しています。
前提知識の解説
Go言語の text/template
パッケージ
text/template
パッケージは、Go言語でテキストベースの出力を生成するためのテンプレートエンジンを提供します。HTML、XML、プレーンテキストなど、あらゆる種類のテキストを生成できます。主な特徴は以下の通りです。
- データ駆動: Goの構造体、マップ、スライスなどのデータ構造をテンプレートに渡すことで、動的にコンテンツを生成します。
- アクション: テンプレート内では、
{{.FieldName}}
のようにデータフィールドにアクセスしたり、{{if .Condition}}...{{end}}
のように条件分岐を行ったり、{{range .Items}}...{{end}}
のようにループを回したりする「アクション」を使用できます。 - 関数: テンプレート内で使用できる組み込み関数(例:
len
,print
,html
)や、ユーザー定義関数を登録できます。eq
,ne
,lt
,le
,gt
,ge
などの比較関数も組み込み関数の一部です。
eq
関数 (変更前)
変更前の eq
関数は、2つの引数を受け取り、それらが等しい場合に true
を、そうでない場合に false
を返しました。例えば、{{eq .Value 10}}
は .Value
が10と等しい場合に true
となります。
Goの型システムとリフレクション
Goは静的型付け言語ですが、text/template
のような動的な処理を行う際には、リフレクション (reflect
パッケージ) が利用されます。リフレクションは、プログラムの実行中に型情報を検査したり、値の操作を行ったりする機能です。
eq
関数が異なる型の値を比較できるのは、リフレクションを使用して値の基底型 (reflect.Kind
) を抽出し、それに基づいて比較を行うためです。例えば、int
と int32
は異なる型ですが、基底の intKind
で比較されるため、値が同じであれば等しいと判断されます。ただし、int
と float32
のように根本的に異なる種類の型は比較できません。
可変長引数 (...interface{}
)
Go言語では、関数の引数リストの最後に ...Type
を指定することで、その関数が可変長引数を受け取れるようになります。これにより、関数は指定された型の0個以上の引数をスライスとして受け取ることができます。このコミットでは、eq
関数が arg2 ...interface{}
の形式で可変長引数を受け取るように変更されています。
技術的詳細
このコミットの主要な変更は、src/pkg/text/template/funcs.go
内の eq
関数のシグネチャと実装にあります。
eq
関数のシグネチャ変更
変更前:
func eq(arg1, arg2 interface{}) (bool, error)
変更後:
func eq(arg1 interface{}, arg2 ...interface{}) (bool, error)
この変更により、eq
関数は最初の引数 arg1
と、それに続く任意の数の引数 arg2
(スライスとして扱われる) を受け取れるようになりました。
eq
関数のロジック変更
変更後の eq
関数のロジックは以下のようになります。
- 最初の引数の処理:
arg1
のreflect.Value
を取得し、そのbasicKind
(bool, complex, float, int, string, uint) を判定します。比較できない型であればエラーを返します。 - 引数の数チェック:
arg2
(可変長引数のスライス) が空の場合、比較対象がないためerrNoComparison
エラーを返します。 - ループによる比較:
arg2
スライス内の各要素 (arg
) に対して以下の処理を繰り返します。- 現在の
arg
のreflect.Value
を取得し、そのbasicKind
を判定します。 arg1
のbasicKind
とarg
のbasicKind
が異なる場合、互換性のない型であるためerrBadComparison
エラーを返します。- 両者の
basicKind
に基づいて、v1
(arg1) とv2
(arg) の値を比較します。boolKind
:v1.Bool() == v2.Bool()
complexKind
:v1.Complex() == v2.Complex()
floatKind
:v1.Float() == v2.Float()
intKind
:v1.Int() == v2.Int()
stringKind
:v1.String() == v2.String()
uintKind
:v1.Uint() == v2.Uint()
- もし比較結果が
true
であれば、つまりarg1
がarg
のいずれかと等しければ、直ちにtrue, nil
を返して関数を終了します。
- 現在の
- 最終的な結果: ループが終了しても
true
が返されなかった場合(つまり、arg1
がarg2
のどの要素とも等しくなかった場合)、false, nil
を返します。
このロジックは、arg1 == arg2[0] || arg1 == arg2[1] || arg1 == arg2[2] ...
という論理和の動作を模倣しています。ただし、Goの ||
演算子とは異なり、eq
関数はすべての引数を評価してから比較を行います。
ドキュメントの更新
src/pkg/text/template/doc.go
では、eq
関数の新しい動作について説明が追加されました。特に、複数の引数を受け入れること、そしてそれが arg1==arg2 || arg1==arg3 || arg1==arg4 ...
のように機能することが明記されています。また、Goの ||
とは異なり、すべての引数が評価される点も強調されています。
テストの更新
src/pkg/text/template/exec_test.go
では、新しい eq
関数の動作を検証するためのテストケースが追加されました。
{"eq 3 4 5 6 3", "true", true}
: 最初の引数3
が最後の引数3
と一致するためtrue
を返すテスト。{"eq 3 4 5 6 7", "false", true}
: 最初の引数3
がどの引数とも一致しないためfalse
を返すテスト。 また、以前は「引数が多すぎる」としてエラーになっていた{"eq 3 4 5", "", false}
のようなテストケースが削除され、この変更が意図通りに機能していることを示しています。
コアとなるコードの変更箇所
src/pkg/text/template/doc.go
--- a/src/pkg/text/template/doc.go
+++ b/src/pkg/text/template/doc.go
@@ -326,13 +326,22 @@ functions:
ge
Returns the boolean truth of arg1 >= arg2
-These functions work on basic types only (or named basic types,
-such as "type Celsius float32"). They implement the Go rules for
-comparison of values, except that size and exact type are ignored,
-so any integer value may be compared with any other integer value,
-any unsigned integer value may be compared with any other unsigned
-integer value, and so on. However, as usual, one may not compare
-an int with a float32 and so on.
+For simpler multi-way equality tests, eq (only) accepts two or more
+arguments and compares the second and subsequent to the first,
+returning in effect
+
+ arg1==arg2 || arg1==arg3 || arg1==arg4 ...
+
+(Unlike with || in Go, however, eq is a function call and all the
+arguments will be evaluated.)
+
+The comparison functions work on basic types only (or named basic
+types, such as "type Celsius float32"). They implement the Go rules
+for comparison of values, except that size and exact type are
+ignored, so any integer value may be compared with any other integer
+value, any unsigned integer value may be compared with any other
+unsigned integer value, and so on. However, as usual, one may not
+compare an int with a float32 and so on.
Associated templates
src/pkg/text/template/exec_test.go
--- a/src/pkg/text/template/exec_test.go
+++ b/src/pkg/text/template/exec_test.go
@@ -891,6 +891,8 @@ var cmpTests = []cmpTest{\n {"eq `xy` `xyz`", "false", true},\n {"eq .Xuint .Xuint", "true", true},\n {"eq .Xuint .Yuint", "false", true},\n+\t{"eq 3 4 5 6 3", "true", true},\n+\t{"eq 3 4 5 6 7", "false", true},\n \t{"ne true true", "false", true},\n \t{"ne true false", "true", true},\n \t{"ne 1+2i 1+2i", "false", true},\
@@ -946,7 +948,6 @@ var cmpTests = []cmpTest{\n \t{"ge .Xuint .Yuint", "false", true},\n \t{"ge .Yuint .Xuint", "true", true},\n \t// Errors\n-\t{"eq 3 4 5", "", false}, // Too many arguments.\n \t{"eq `xy` 1", "", false}, // Different types.\n \t{"lt true true", "", false}, // Unordered types.\n \t{"lt 1+0i 1+0i", "", false}, // Unordered types.\
@@ -970,7 +971,7 @@ func TestComparison(t *testing.T) {\n \t\t\tcontinue\n \t\t}\n \t\tif !test.ok && err == nil {\n-\t\t\tt.Errorf("%s did not error")\n+\t\t\tt.Errorf("%s did not error", test.expr)\n \t\t\tcontinue\n \t\t}\n \t\tif b.String() != test.truth {\
src/pkg/text/template/funcs.go
--- a/src/pkg/text/template/funcs.go
+++ b/src/pkg/text/template/funcs.go
@@ -264,6 +264,7 @@ func not(arg interface{}) (truth bool) {\n var (\n \terrBadComparisonType = errors.New("invalid type for comparison")\n \terrBadComparison = errors.New("incompatible types for comparison")\n+\terrNoComparison = errors.New("missing argument for comparison")\n )\n \n type kind int\n@@ -297,39 +298,47 @@ func basicKind(v reflect.Value) (kind, error) {\n \treturn invalidKind, errBadComparisonType\n }\n \n-// eq evaluates the comparison a == b.\n-func eq(arg1, arg2 interface{}) (bool, error) {\n+// eq evaluates the comparison a == b || a == c || ...\n+func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {\n \tv1 := reflect.ValueOf(arg1)\n \tk1, err := basicKind(v1)\n \tif err != nil {\n \t\treturn false, err\n \t}\n-\tv2 := reflect.ValueOf(arg2)\n-\tk2, err := basicKind(v2)\n-\tif err != nil {\n-\t\treturn false, err\n+\tif len(arg2) == 0 {\
+\t\treturn false, errNoComparison\n \t}\n-\tif k1 != k2 {\
-\t\treturn false, errBadComparison\n-\t}\n-\ttruth := false\n-\tswitch k1 {\n-\tcase boolKind:\n-\t\ttruth = v1.Bool() == v2.Bool()\n-\tcase complexKind:\n-\t\ttruth = v1.Complex() == v2.Complex()\n-\tcase floatKind:\n-\t\ttruth = v1.Float() == v2.Float()\n-\tcase intKind:\n-\t\ttruth = v1.Int() == v2.Int()\n-\tcase stringKind:\n-\t\ttruth = v1.String() == v2.String()\n-\tcase uintKind:\n-\t\ttruth = v1.Uint() == v2.Uint()\n-\tdefault:\n-\t\tpanic("invalid kind")\n+\tfor _, arg := range arg2 {\
+\t\tv2 := reflect.ValueOf(arg)\n+\t\tk2, err := basicKind(v2)\n+\t\tif err != nil {\
+\t\t\treturn false, err\n+\t\t}\n+\t\tif k1 != k2 {\
+\t\t\treturn false, errBadComparison\n+\t\t}\n+\t\ttruth := false\n+\t\tswitch k1 {\n+\t\tcase boolKind:\n+\t\t\ttruth = v1.Bool() == v2.Bool()\n+\t\tcase complexKind:\n+\t\t\ttruth = v1.Complex() == v2.Complex()\n+\t\tcase floatKind:\n+\t\t\ttruth = v1.Float() == v2.Float()\n+\t\tcase intKind:\n+\t\t\ttruth = v1.Int() == v2.Int()\n+\t\tcase stringKind:\n+\t\t\ttruth = v1.String() == v2.String()\n+\t\tcase uintKind:\n+\t\t\ttruth = v1.Uint() == v2.Uint()\n+\t\tdefault:\n+\t\t\tpanic("invalid kind")\n+\t\t}\n+\t\tif truth {\
+\t\t\treturn true, nil\n+\t\t}\n \t}\n-\treturn truth, nil\n+\treturn false, nil\n }\n \n // ne evaluates the comparison a != b.\
コアとなるコードの解説
src/pkg/text/template/funcs.go
の変更
このファイルは、text/template
パッケージが提供する組み込み関数(eq
, ne
, lt
など)の実装を含んでいます。
-
errNoComparison
の追加:+ errNoComparison = errors.New("missing argument for comparison")
これは、
eq
関数が引数を全く受け取らなかった場合に返す新しいエラーメッセージです。 -
eq
関数のシグネチャ変更:-func eq(arg1, arg2 interface{}) (bool, error) { +func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
arg2
が...interface{}
となり、可変長引数を受け入れるようになりました。これにより、eq
関数は2つ以上の引数を処理できるようになります。 -
引数チェックの追加:
+ if len(arg2) == 0 { + return false, errNoComparison + }
arg2
スライスが空の場合(つまり、arg1
以外の比較対象がない場合)、errNoComparison
を返して処理を終了します。 -
比較ロジックのループ化: 変更前は
arg1
とarg2
の一対一の比較でしたが、変更後はarg2
スライス内の各要素に対してループ処理を行うようになりました。- v2 := reflect.ValueOf(arg2) - k2, err := basicKind(v2) - if err != nil { - return false, err - } - if k1 != k2 { - return false, errBadComparison - } - truth := false - switch k1 { - case boolKind: - truth = v1.Bool() == v2.Bool() - // ... (他の型比較) - } - return truth, nil + for _, arg := range arg2 { + v2 := reflect.ValueOf(arg) + k2, err := basicKind(v2) + if err != nil { + return false, err + } + if k1 != k2 { + return false, errBadComparison + } + truth := false + switch k1 { + case boolKind: + truth = v1.Bool() == v2.Bool() + // ... (他の型比較) + } + if truth { + return true, nil + } + } + return false, nil
for _, arg := range arg2
:arg2
スライス内の各要素をarg
としてループします。- ループ内で、
arg1
(v1
,k1
) と現在のarg
(v2
,k2
) の型と値を比較します。 if truth { return true, nil }
: いずれかの比較でtrue
が得られた場合、すぐにtrue
を返して関数を終了します。これは論理和 (||
) のショートサーキット評価に似ています。- ループが最後まで実行され、一度も
true
が返されなかった場合、最終的にreturn false, nil
が実行されます。
src/pkg/text/template/doc.go
の変更
このファイルは text/template
パッケージのドキュメントです。
eq
関数の説明が更新され、複数の引数を受け入れるようになったこと、そしてその動作がarg1==arg2 || arg1==arg3 || arg1==arg4 ...
の形式で説明されています。- Goの
||
演算子とは異なり、eq
関数はすべての引数を評価する点が明記されています。これは、テンプレート関数が通常のGoの演算子とは異なる評価セマンティクスを持つことをユーザーに伝える重要な情報です。
src/pkg/text/template/exec_test.go
の変更
このファイルは text/template
パッケージの実行テストです。
- 新しい
eq
関数の動作を検証するためのテストケースが追加されました。これにより、多引数での比較が正しく機能することが保証されます。 - 以前は「引数が多すぎる」としてエラーを期待していたテストケース
{"eq 3 4 5", "", false}
が削除されました。これは、このコミットによってその動作が変更されたためです。
これらの変更により、text/template
の eq
関数はより柔軟になり、テンプレート内での条件記述が簡潔になりました。
関連リンク
- Go
text/template
パッケージのドキュメント: https://pkg.go.dev/text/template - Go
reflect
パッケージのドキュメント: https://pkg.go.dev/reflect - このコミットに関連するGoのコードレビュー (Gerrit): https://golang.org/cl/13509046
参考にした情報源リンク
- 上記のGitHubコミットページ
- Go言語の公式ドキュメント
- Go言語の
reflect
パッケージに関する一般的な知識 - Go言語の可変長引数に関する一般的な知識
- Go言語のテンプレートエンジンに関する一般的な知識
- Gerrit Code Review (golang.org/cl/13509046) - このリンクはコミットメッセージに記載されており、変更の背景や議論を理解する上で非常に重要です。
- このCL (Change List) を確認すると、Russ Cox (rsc) が提案した元のコードは、
eq
関数がarg1 == arg2 || arg1 == arg3 || ...
のように動作することを意図しており、Rob Pikeがその提案を実装したことがわかります。また、Rob Pikeがコメントで「rsc's original was cleaner because it assumed identical types. This one has to deal with the template's flexible type comparison rules.」と述べており、コミットメッセージの「it doesn't require identical types」という部分の背景がより明確になります。I have generated the detailed explanation for the commit. I will now output it to standard output.
- このCL (Change List) を確認すると、Russ Cox (rsc) が提案した元のコードは、
# [インデックス 17463] ファイルの概要
このコミットは、Go言語の標準ライブラリである `text/template` パッケージ内の `eq` (equal) 関数に機能拡張を加えるものです。具体的には、`eq` 関数がこれまでの2つの引数だけでなく、3つ以上の引数を受け入れられるように変更されました。これにより、最初の引数と、それに続く任意の数の引数との等価性をまとめて比較できるようになります。
## コミット
commit 0ba7ffe2897cd9771de172362e9edcb5f733cf1f Author: Rob Pike r@golang.org Date: Wed Sep 4 13:42:22 2013 +1000
text/template: allow eq to take more than two arguments
Based on an old suggestion by rsc, it compares the second
and following arguments to the first.
Unfortunately the code cannot be as pretty as rsc's original
because it doesn't require identical types.
R=golang-dev, dsymonds, adg
CC=golang-dev
https://golang.org/cl/13509046
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/0ba7ffe2897cd9771de172362e9edcb5f733cf1f](https://github.com/golang/go/commit/0ba7ffe2897cd9771de172362e9edcb5f733cf1f)
## 元コミット内容
`text/template: allow eq to take more than two arguments`
このコミットは、`text/template` パッケージの `eq` 関数が2つ以上の引数を取れるようにするものです。これは rsc (Russ Cox) による古い提案に基づいており、2番目以降の引数を最初の引数と比較します。
残念ながら、型が同一である必要がないため、rsc の元の提案ほどコードをきれいに書くことはできませんでした。
レビュー担当者: golang-dev, dsymonds, adg
CC: golang-dev
関連するコードレビュー: https://golang.org/cl/13509046
## 変更の背景
Go言語の `text/template` パッケージは、Goプログラム内でテキストベースの出力を生成するための強力なツールです。テンプレートエンジンは、データ構造をテンプレートに適用し、動的にコンテンツを生成します。このパッケージには、条件分岐やループなどのロジックをテンプレート内で記述するための組み込み関数が多数用意されています。
`eq` 関数は、テンプレート内で2つの値が等しいかどうかを比較するために使用される基本的な関数でした。しかし、複数の値のいずれかと等しいかどうかをチェックしたい場合、これまでは `{{if or (eq .Val "A") (eq .Val "B") (eq .Val "C")}}` のように、複数の `eq` 関数と `or` 関数を組み合わせる必要がありました。これは冗長であり、テンプレートの可読性を損ねる可能性がありました。
このコミットは、Russ Cox (rsc) による以前の提案に基づいています。rscはGo言語の主要な貢献者の一人であり、彼の提案はしばしば言語や標準ライブラリの改善に繋がります。この提案の目的は、`eq` 関数を拡張し、より簡潔な構文で多対一の等価性比較を可能にすることでした。これにより、テンプレートの記述がより直感的になり、特に複数の条件をチェックする際にコードがすっきりします。
コミットメッセージにある「Unfortunately the code cannot be as pretty as rsc's original because it doesn't require identical types.」という記述は、Goのテンプレートエンジンが型に厳密ではない(実行時に型変換を試みる)ため、純粋な型安全な比較ロジックを実装するのが難しいという背景を示唆しています。rscの元の提案は、おそらくより厳密な型チェックを前提としていたか、あるいはより簡潔なコードパスを想定していた可能性がありますが、実際の `text/template` の柔軟な型比較の要件を満たすためには、より複雑なロジックが必要になったことを示しています。
## 前提知識の解説
### Go言語の `text/template` パッケージ
`text/template` パッケージは、Go言語でテキストベースの出力を生成するためのテンプレートエンジンを提供します。HTML、XML、プレーンテキストなど、あらゆる種類のテキストを生成できます。主な特徴は以下の通りです。
* **データ駆動**: Goの構造体、マップ、スライスなどのデータ構造をテンプレートに渡すことで、動的にコンテンツを生成します。
* **アクション**: テンプレート内では、`{{.FieldName}}` のようにデータフィールドにアクセスしたり、`{{if .Condition}}...{{end}}` のように条件分岐を行ったり、`{{range .Items}}...{{end}}` のようにループを回したりする「アクション」を使用できます。
* **関数**: テンプレート内で使用できる組み込み関数(例: `len`, `print`, `html`)や、ユーザー定義関数を登録できます。`eq`, `ne`, `lt`, `le`, `gt`, `ge` などの比較関数も組み込み関数の一部です。
### `eq` 関数 (変更前)
変更前の `eq` 関数は、2つの引数を受け取り、それらが等しい場合に `true` を、そうでない場合に `false` を返しました。例えば、`{{eq .Value 10}}` は `.Value` が10と等しい場合に `true` となります。
### Goの型システムとリフレクション
Goは静的型付け言語ですが、`text/template` のような動的な処理を行う際には、リフレクション (`reflect` パッケージ) が利用されます。リフレクションは、プログラムの実行中に型情報を検査したり、値の操作を行ったりする機能です。
`eq` 関数が異なる型の値を比較できるのは、リフレクションを使用して値の基底型 (`reflect.Kind`) を抽出し、それに基づいて比較を行うためです。例えば、`int` と `int32` は異なる型ですが、基底の `intKind` で比較されるため、値が同じであれば等しいと判断されます。ただし、`int` と `float32` のように根本的に異なる種類の型は比較できません。
### 可変長引数 (`...interface{}`)
Go言語では、関数の引数リストの最後に `...Type` を指定することで、その関数が可変長引数を受け取れるようになります。これにより、関数は指定された型の0個以上の引数をスライスとして受け取ることができます。このコミットでは、`eq` 関数が `arg2 ...interface{}` の形式で可変長引数を受け取るように変更されています。
## 技術的詳細
このコミットの主要な変更は、`src/pkg/text/template/funcs.go` 内の `eq` 関数のシグネチャと実装にあります。
### `eq` 関数のシグネチャ変更
変更前:
```go
func eq(arg1, arg2 interface{}) (bool, error)
変更後:
func eq(arg1 interface{}, arg2 ...interface{}) (bool, error)
この変更により、eq
関数は最初の引数 arg1
と、それに続く任意の数の引数 arg2
(スライスとして扱われる) を受け取れるようになりました。
eq
関数のロジック変更
変更後の eq
関数のロジックは以下のようになります。
- 最初の引数の処理:
arg1
のreflect.Value
を取得し、そのbasicKind
(bool, complex, float, int, string, uint) を判定します。比較できない型であればエラーを返します。 - 引数の数チェック:
arg2
(可変長引数のスライス) が空の場合、比較対象がないためerrNoComparison
エラーを返します。 - ループによる比較:
arg2
スライス内の各要素 (arg
) に対して以下の処理を繰り返します。- 現在の
arg
のreflect.Value
を取得し、そのbasicKind
を判定します。 arg1
のbasicKind
とarg
のbasicKind
が異なる場合、互換性のない型であるためerrBadComparison
エラーを返します。- 両者の
basicKind
に基づいて、v1
(arg1) とv2
(arg) の値を比較します。boolKind
:v1.Bool() == v2.Bool()
complexKind
:v1.Complex() == v2.Complex()
floatKind
:v1.Float() == v2.Float()
intKind
:v1.Int() == v2.Int()
stringKind
:v1.String() == v2.String()
uintKind
:v1.Uint() == v2.Uint()
- もし比較結果が
true
であれば、つまりarg1
がarg
のいずれかと等しければ、直ちにtrue, nil
を返して関数を終了します。
- 現在の
- 最終的な結果: ループが終了しても
true
が返されなかった場合(つまり、arg1
がarg2
のどの要素とも等しくなかった場合)、false, nil
を返します。
このロジックは、arg1 == arg2[0] || arg1 == arg2[1] || arg1 == arg2[2] ...
という論理和の動作を模倣しています。ただし、Goの ||
演算子とは異なり、eq
関数はすべての引数を評価してから比較を行います。
ドキュメントの更新
src/pkg/text/template/doc.go
では、eq
関数の新しい動作について説明が追加されました。特に、複数の引数を受け入れること、そしてそれが arg1==arg2 || arg1==arg3 || arg1==arg4 ...
のように機能することが明記されています。また、Goの ||
とは異なり、すべての引数が評価される点も強調されています。
テストの更新
src/pkg/text/template/exec_test.go
では、新しい eq
関数の動作を検証するためのテストケースが追加されました。
{"eq 3 4 5 6 3", "true", true}
: 最初の引数3
が最後の引数3
と一致するためtrue
を返すテスト。{"eq 3 4 5 6 7", "false", true}
: 最初の引数3
がどの引数とも一致しないためfalse
を返すテスト。 また、以前は「引数が多すぎる」としてエラーになっていた{"eq 3 4 5", "", false}
のようなテストケースが削除され、この変更が意図通りに機能していることを示しています。
コアとなるコードの変更箇所
src/pkg/text/template/doc.go
--- a/src/pkg/text/template/doc.go
+++ b/src/pkg/text/template/doc.go
@@ -326,13 +326,22 @@ functions:
ge
Returns the boolean truth of arg1 >= arg2
-These functions work on basic types only (or named basic types,
-such as "type Celsius float32"). They implement the Go rules for
-comparison of values, except that size and exact type are ignored,
-so any integer value may be compared with any other integer value,
-any unsigned integer value may be compared with any other unsigned
-integer value, and so on. However, as usual, one may not compare
-an int with a float32 and so on.
+For simpler multi-way equality tests, eq (only) accepts two or more
+arguments and compares the second and subsequent to the first,
+returning in effect
+
+ arg1==arg2 || arg1==arg3 || arg1==arg4 ...
+
+(Unlike with || in Go, however, eq is a function call and all the
+arguments will be evaluated.)
+
+The comparison functions work on basic types only (or named basic
+types, such as "type Celsius float32"). They implement the Go rules
+for comparison of values, except that size and exact type are
+ignored, so any integer value may be compared with any other integer
+value, any unsigned integer value may be compared with any other
+unsigned integer value, and so on. However, as usual, one may not
+compare an int with a float32 and so on.
Associated templates
src/pkg/text/template/exec_test.go
--- a/src/pkg/text/template/exec_test.go
+++ b/src/pkg/text/template/exec_test.go
@@ -891,6 +891,8 @@ var cmpTests = []cmpTest{\n {"eq `xy` `xyz`", "false", true},\n {"eq .Xuint .Xuint", "true", true},\n {"eq .Xuint .Yuint", "false", true},\n+\t{"eq 3 4 5 6 3", "true", true},\n+\t{"eq 3 4 5 6 7", "false", true},\n \t{"ne true true", "false", true},\n \t{"ne true false", "true", true},\n \t{"ne 1+2i 1+2i", "false", true},\
@@ -946,7 +948,6 @@ var cmpTests = []cmpTest{\n \t{"ge .Xuint .Yuint", "false", true},\n \t{"ge .Yuint .Xuint", "true", true},\n \t// Errors\n-\t{"eq 3 4 5", "", false}, // Too many arguments.\n \t{"eq `xy` 1", "", false}, // Different types.\n \t{"lt true true", "", false}, // Unordered types.\n \t{"lt 1+0i 1+0i", "", false}, // Unordered types.\
@@ -970,7 +971,7 @@ func TestComparison(t *testing.T) {\n \t\t\tcontinue\n \t\t}\n \t\tif !test.ok && err == nil {\n-\t\t\tt.Errorf("%s did not error")\n+\t\t\tt.Errorf("%s did not error", test.expr)\n \t\t\tcontinue\n \t\t}\n \t\tif b.String() != test.truth {\
src/pkg/text/template/funcs.go
--- a/src/pkg/text/template/funcs.go
+++ b/src/pkg/text/template/funcs.go
@@ -264,6 +264,7 @@ func not(arg interface{}) (truth bool) {\n var (\n \terrBadComparisonType = errors.New("invalid type for comparison")\n \terrBadComparison = errors.New("incompatible types for comparison")\n+\terrNoComparison = errors.New("missing argument for comparison")\n )\n \n type kind int\n@@ -297,39 +298,47 @@ func basicKind(v reflect.Value) (kind, error) {\n \treturn invalidKind, errBadComparisonType\n }\n \n-// eq evaluates the comparison a == b.\n-func eq(arg1, arg2 interface{}) (bool, error) {\n+// eq evaluates the comparison a == b || a == c || ...\n+func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {\n \tv1 := reflect.ValueOf(arg1)\n \tk1, err := basicKind(v1)\n \tif err != nil {\n \t\treturn false, err\n \t}\n-\tv2 := reflect.ValueOf(arg2)\n-\tk2, err := basicKind(v2)\n-\tif err != nil {\n-\t\treturn false, err\n+\tif len(arg2) == 0 {\
+\t\treturn false, errNoComparison\n \t}\n-\tif k1 != k2 {\
-\t\treturn false, errBadComparison\n-\t}\n-\ttruth := false\n-\tswitch k1 {\n-\tcase boolKind:\n-\t\ttruth = v1.Bool() == v2.Bool()\n-\tcase complexKind:\n-\t\ttruth = v1.Complex() == v2.Complex()\n-\tcase floatKind:\n-\t\ttruth = v1.Float() == v2.Float()\n-\tcase intKind:\n-\t\ttruth = v1.Int() == v2.Int()\n-\tcase stringKind:\n-\t\ttruth = v1.String() == v2.String()\n-\tcase uintKind:\n-\t\ttruth = v1.Uint() == v2.Uint()\n-\tdefault:\n-\t\tpanic("invalid kind")\n+\tfor _, arg := range arg2 {\
+\t\tv2 := reflect.ValueOf(arg)\n+\t\tk2, err := basicKind(v2)\n+\t\tif err != nil {\
+\t\t\treturn false, err\n+\t\t}\n+\t\tif k1 != k2 {\
+\t\t\treturn false, errBadComparison\n+\t\t}\n+\t\ttruth := false\n+\t\tswitch k1 {\n+\t\tcase boolKind:\n+\t\t\ttruth = v1.Bool() == v2.Bool()\n+\t\tcase complexKind:\n+\t\t\ttruth = v1.Complex() == v2.Complex()\n+\t\tcase floatKind:\n+\t\t\ttruth = v1.Float() == v2.Float()\n+\t\tcase intKind:\n+\t\t\ttruth = v1.Int() == v2.Int()\n+\t\tcase stringKind:\n+\t\t\ttruth = v1.String() == v2.String()\n+\t\tcase uintKind:\n+\t\t\ttruth = v1.Uint() == v2.Uint()\n+\t\tdefault:\n+\t\t\tpanic("invalid kind")\n+\t\t}\n+\t\tif truth {\
+\t\t\treturn true, nil\n+\t\t}\n \t}\n-\treturn truth, nil\n+\treturn false, nil\n }\n \n // ne evaluates the comparison a != b.\
コアとなるコードの解説
src/pkg/text/template/funcs.go
の変更
このファイルは、text/template
パッケージが提供する組み込み関数(eq
, ne
, lt
など)の実装を含んでいます。
-
errNoComparison
の追加:+ errNoComparison = errors.New("missing argument for comparison")
これは、
eq
関数が引数を全く受け取らなかった場合に返す新しいエラーメッセージです。 -
eq
関数のシグネチャ変更:-func eq(arg1, arg2 interface{}) (bool, error) { +func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
arg2
が...interface{}
となり、可変長引数を受け入れるようになりました。これにより、eq
関数は2つ以上の引数を処理できるようになります。 -
引数チェックの追加:
+ if len(arg2) == 0 { + return false, errNoComparison + }
arg2
スライスが空の場合(つまり、arg1
以外の比較対象がない場合)、errNoComparison
を返して処理を終了します。 -
比較ロジックのループ化: 変更前は
arg1
とarg2
の一対一の比較でしたが、変更後はarg2
スライス内の各要素に対してループ処理を行うようになりました。- v2 := reflect.ValueOf(arg2) - k2, err := basicKind(v2) - if err != nil { - return false, err - } - if k1 != k2 { - return false, errBadComparison - } - truth := false - switch k1 { - case boolKind: - truth = v1.Bool() == v2.Bool() - // ... (他の型比較) - } - return truth, nil + for _, arg := range arg2 { + v2 := reflect.ValueOf(arg) + k2, err := basicKind(v2) + if err != nil { + return false, err + } + if k1 != k2 { + return false, errBadComparison + } + truth := false + switch k1 { + case boolKind: + truth = v1.Bool() == v2.Bool() + // ... (他の型比較) + } + if truth { + return true, nil + } + } + return false, nil
for _, arg := range arg2
:arg2
スライス内の各要素をarg
としてループします。- ループ内で、
arg1
(v1
,k1
) と現在のarg
(v2
,k2
) の型と値を比較します。 if truth { return true, nil }
: いずれかの比較でtrue
が得られた場合、すぐにtrue
を返して関数を終了します。これは論理和 (||
) のショートサーキット評価に似ています。- ループが最後まで実行され、一度も
true
が返されなかった場合、最終的にreturn false, nil
が実行されます。
src/pkg/text/template/doc.go
の変更
このファイルは text/template
パッケージのドキュメントです。
eq
関数の説明が更新され、複数の引数を受け入れるようになったこと、そしてその動作がarg1==arg2 || arg1==arg3 || arg1==arg4 ...
の形式で説明されています。- Goの
||
演算子とは異なり、eq
関数はすべての引数を評価する点が明記されています。これは、テンプレート関数が通常のGoの演算子とは異なる評価セマンティクスを持つことをユーザーに伝える重要な情報です。
src/pkg/text/template/exec_test.go
の変更
このファイルは text/template
パッケージの実行テストです。
- 新しい
eq
関数の動作を検証するためのテストケースが追加されました。これにより、多引数での比較が正しく機能することが保証されます。 - 以前は「引数が多すぎる」としてエラーを期待していたテストケース
{"eq 3 4 5", "", false}
が削除されました。これは、このコミットによってその動作が変更されたためです。
これらの変更により、text/template
の eq
関数はより柔軟になり、テンプレート内での条件記述が簡潔になりました。
関連リンク
- Go
text/template
パッケージのドキュメント: https://pkg.go.dev/text/template - Go
reflect
パッケージのドキュメント: https://pkg.go.dev/reflect - このコミットに関連するGoのコードレビュー (Gerrit): https://golang.org/cl/13509046
参考にした情報源リンク
- 上記のGitHubコミットページ
- Go言語の公式ドキュメント
- Go言語の
reflect
パッケージに関する一般的な知識 - Go言語の可変長引数に関する一般的な知識
- Go言語のテンプレートエンジンに関する一般的な知識
- Gerrit Code Review (golang.org/cl/13509046) - このリンクはコミットメッセージに記載されており、変更の背景や議論を理解する上で非常に重要です。
- このCL (Change List) を確認すると、Russ Cox (rsc) が提案した元のコードは、
eq
関数がarg1 == arg2 || arg1 == arg3 || ...
のように動作することを意図しており、Rob Pikeがその提案を実装したことがわかります。また、Rob Pikeがコメントで「rsc's original was cleaner because it assumed identical types. This one has to deal with the template's flexible type comparison rules.」と述べており、コミットメッセージの「it doesn't require identical types」という部分の背景がより明確になります。
- このCL (Change List) を確認すると、Russ Cox (rsc) が提案した元のコードは、