[インデックス 17347] ファイルの概要
このコミットは、Go言語の標準ライブラリであるtext/template
パッケージに、基本的な型の比較機能(eq
, ne
, lt
, le
, gt
, ge
)を追加するものです。これにより、テンプレート内で数値、文字列、真偽値などの基本的なデータ型を直接比較できるようになり、より柔軟な条件分岐が可能になります。
コミット
commit 7bbe32067922643a30ac7adf8aa3da9785d89d13
Author: Rob Pike <r@golang.org>
Date: Wed Aug 21 11:27:27 2013 +1000
text/template: implement comparison of basic types
Add eq, lt, etc. to allow one to do simple comparisons.
It's basic types only (booleans, integers, unsigned integers,
floats, complex, string) because that's easy, easy to define,
and covers the great majority of useful cases, while leaving
open the possibility of a more sweeping definition later.
{{if eq .X .Y}}X and Y are equal{{else}}X and Y are unequal{{end}}
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/13091045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7bbe32067922643a30ac7adf8aa3da9785d89d13
元コミット内容
text/template: implement comparison of basic types
このコミットは、text/template
パッケージにeq
, lt
などの比較演算子を追加し、シンプルな比較を可能にします。
対象となるのは、真偽値、整数、符号なし整数、浮動小数点数、複素数、文字列といった基本的な型のみです。これは、実装が容易であり、定義が明確で、実用的なケースの大部分をカバーできるためです。将来的には、より広範な定義の可能性も残されています。
例: {{if eq .X .Y}}X and Y are equal{{else}}X and Y are unequal{{end}}
変更の背景
Go言語のtext/template
パッケージは、HTMLやテキストの生成に広く利用されています。しかし、このコミット以前は、テンプレート内で直接的な値の比較を行うための組み込み関数が提供されていませんでした。これにより、例えば「ある変数の値が特定の値と等しい場合にのみコンテンツを表示する」といった、ごく基本的な条件分岐を実現するためには、テンプレートの外部で比較ロジックを実装し、その結果をテンプレートに渡す必要がありました。これは、テンプレートの表現力を制限し、開発者がより複雑なロジックをGoコード側で記述することを強制していました。
このコミットの背景には、テンプレートの利便性と表現力を向上させ、一般的なユースケースにおけるコードの簡潔化を図る目的があります。特に、Webアプリケーションのビュー層などでは、データの比較に基づいて表示内容を動的に変更するニーズが頻繁に発生します。eq
, lt
などの比較関数をテンプレートに直接組み込むことで、これらの一般的なタスクをより直感的かつ効率的にテンプレート内で完結できるようになります。
Rob Pike氏がコミットメッセージで述べているように、「基本的な型のみ(真偽値、整数、符号なし整数、浮動小数点数、複素数、文字列)に限定したのは、実装が容易で、定義が明確であり、有用なケースの大部分をカバーできるため」という点が重要です。これは、テンプレートエンジンの設計において、過度な複雑さを避けつつ、最も頻繁に必要とされる機能を提供することに焦点を当てていることを示しています。また、「より広範な定義の可能性も残している」という記述からは、将来的にユーザー定義型や構造体の比較など、より高度な比較機能が追加される可能性も示唆されています。
前提知識の解説
Go言語のtext/template
パッケージ
text/template
パッケージは、Go言語に組み込まれているデータ駆動型テンプレートエンジンです。これは、Goのプログラムからテキスト出力を生成するために使用されます。主な用途としては、HTMLページの生成、設定ファイルの作成、コード生成などがあります。
テンプレートは、プレーンテキストと、Goのデータ構造から値にアクセスするための「アクション」と呼ばれる特殊な構文で構成されます。アクションは二重中括弧{{...}}
で囲まれます。
主な特徴:
- データ駆動: テンプレートは、Goの構造体、マップ、スライスなどのデータを受け取り、そのデータに基づいて出力を生成します。
- アクション:
- パイプライン:
|
を使用して、前のコマンドの出力を次のコマンドの入力として渡すことができます。 - 変数:
$
記号を使用して変数を定義し、値を保持できます。 - 条件分岐:
{{if ...}}{{else}}{{end}}
を使用して条件に基づいてコンテンツを表示できます。 - 繰り返し:
{{range ...}}{{end}}
を使用してスライスやマップの要素を反復処理できます。 - 関数: 組み込み関数やユーザー定義関数を呼び出すことができます。
- パイプライン:
- 組み込み関数:
printf
,html
,urlquery
など、多くの便利な組み込み関数が提供されています。このコミットで追加されたeq
,ne
,lt
,le
,gt
,ge
もこれに属します。 - セキュリティ:
html/template
パッケージは、HTMLエスケープを自動的に行い、クロスサイトスクリプティング(XSS)攻撃を防ぐための機能を提供します。text/template
は汎用的なテキスト生成用であり、HTMLエスケープは行いません。
Go言語のreflect
パッケージ
reflect
パッケージは、Goのプログラムが実行時に自身の構造を検査(リフレクション)することを可能にします。これにより、変数の型、値、メソッドなどを動的に調べたり、変更したりすることができます。
reflect
パッケージの主要な型は以下の通りです。
reflect.Type
: Goの型の静的な情報(名前、カテゴリ、メソッドなど)を表します。reflect.TypeOf(i interface{})
で取得できます。reflect.Value
: Goの値の動的な情報(実際の値、変更可能性など)を表します。reflect.ValueOf(i interface{})
で取得できます。
このコミットでは、テンプレート関数が受け取る引数の型を動的に判断し、適切な比較ロジックを適用するためにreflect
パッケージが利用されています。特に、reflect.Value.Kind()
メソッドは、値の基本的なカテゴリ(例: reflect.Int
, reflect.Float64
, reflect.String
など)を判別するために使用されます。これにより、異なる具体的な型(例: int
, int32
, int64
)であっても、それらが同じ「整数」というカテゴリに属していれば、共通の比較ロジックを適用できるようになります。
Go言語の型の比較ルール
Go言語では、異なる型の値の比較には厳格なルールがあります。
- 等価性(
==
,!=
):- ブール値、数値型(整数、浮動小数点数、複素数)、文字列、チャネル、ポインタ、関数、インターフェース、マップ、スライス、構造体、配列は、特定の条件下で等価性を比較できます。
- 数値型の場合、異なるサイズの整数型(例:
int
とint64
)や、異なる浮動小数点数型(例:float32
とfloat64
)は、型変換なしには直接比較できません。ただし、定数であればコンパイラが自動的に型を推論し、比較可能になる場合があります。 - 構造体は、すべてのフィールドが比較可能であれば比較できます。
- 配列は、要素の型が比較可能であれば比較できます。
- スライス、マップ、関数は、
nil
とのみ比較可能です。それ以外の値との比較はできません。
- 順序比較(
<
,<=
,>
,>=
):- 数値型と文字列型のみが順序比較可能です。
- ブール値、複素数、構造体、配列、スライス、マップ、チャネル、インターフェース、ポインタは順序比較できません。
このコミットで追加されるテンプレートの比較関数は、Go言語のこれらの比較ルールをベースにしつつ、テンプレートの利便性を考慮して一部の柔軟性を持たせています。例えば、異なるサイズの整数型(int
とint64
)であっても、テンプレート内では比較可能になるように実装されています。しかし、int
とfloat32
のような異なるカテゴリの型は、Goの基本的な比較ルールに従い、比較できないようになっています。
技術的詳細
このコミットは、text/template
パッケージに新しい比較関数群を導入するために、主に以下の3つのファイルに影響を与えています。
-
src/pkg/text/template/doc.go
:- このファイルは
text/template
パッケージのドキュメントを定義しています。 - 新しい比較関数
eq
,ne
,lt
,le
,gt
,ge
が追加され、それぞれの機能と、それらが基本的な型(真偽値、整数、符号なし整数、浮動小数点数、複素数、文字列)にのみ適用されることが明記されています。 - Goの比較ルールに従いつつも、「サイズと厳密な型は無視される」という重要な例外が説明されています。これは、例えば
int
とint64
のような異なるサイズの整数型でも比較できることを意味します。ただし、「int
とfloat32
のような比較はできない」というGoの基本的な型システムルールは維持されます。
- このファイルは
-
src/pkg/text/template/exec_test.go
:- このファイルは
text/template
パッケージの実行に関するテストを定義しています。 TestComparison
という新しいテスト関数が追加され、cmpTest
構造体とcmpTests
スライスが定義されています。cmpTests
スライスには、eq
,ne
,lt
,le
,gt
,ge
の各比較関数について、様々な型の組み合わせ(真偽値、整数、浮動小数点数、複素数、文字列、符号なし整数)での期待される結果が網羅的に記述されています。- また、引数の数が多すぎる場合や、異なるカテゴリの型を比較しようとした場合(例:
eq "xy" 1
)、順序比較できない型を比較しようとした場合(例:lt true true
、lt 1+0i 1+0i
)にエラーが発生することもテストされています。 - これらのテストは、新しい比較関数の正確性と、意図しない型での比較を防ぐための堅牢性を保証します。
- このファイルは
-
src/pkg/text/template/funcs.go
:- このファイルは
text/template
パッケージの組み込み関数を定義しています。 builtins
というFuncMap
に、新しく定義された比較関数eq
,ne
,lt
,le
,gt
,ge
が追加されています。これにより、これらの関数がテンプレート内で利用可能になります。basicKind
関数:reflect.Value
を受け取り、その値の基本的なカテゴリ(boolKind
,complexKind
,intKind
,floatKind
,stringKind
,uintKind
)を返すヘルパー関数です。これにより、Goの様々な数値型(例:int
,int8
,int64
など)を単一のintKind
として扱うことができ、比較ロジックを簡素化しています。サポートされていない型が渡された場合はerrBadComparisonType
エラーを返します。eq(arg1, arg2 interface{})
関数:- 2つの引数を受け取り、
==
比較を行います。 - まず、
basicKind
関数を使って両引数の基本的な型カテゴリを取得します。 - 型カテゴリが異なる場合(例: 整数と文字列)は
errBadComparison
エラーを返します。 - 同じ型カテゴリの場合、
reflect.Value
の適切なメソッド(Bool()
,Complex()
,Float()
,Int()
,String()
,Uint()
)を使って実際の値を取得し、Goの組み込みの==
演算子で比較します。
- 2つの引数を受け取り、
ne(arg1, arg2 interface{})
関数:eq
関数の結果を反転させることで!=
比較を実現します。lt(arg1, arg2 interface{})
関数:- 2つの引数を受け取り、
<
比較を行います。 eq
と同様に型カテゴリをチェックします。boolKind
やcomplexKind
のような順序比較できない型の場合、errBadComparisonType
エラーを返します。- 順序比較可能な型(浮動小数点数、整数、文字列、符号なし整数)の場合、適切な
reflect.Value
メソッドを使って値を取得し、Goの組み込みの<
演算子で比較します。
- 2つの引数を受け取り、
le(arg1, arg2 interface{})
関数:lt
またはeq
の結果に基づいて<=
比較を実現します。gt(arg1, arg2 interface{})
関数:le
関数の結果を反転させることで>
比較を実現します。ge(arg1, arg2 interface{})
関数:lt
関数の結果を反転させることで>=
比較を実現します。
- このファイルは
この実装の鍵は、reflect
パッケージを使用して実行時に引数の型を動的に検査し、Goの厳密な型システムとテンプレートの柔軟性のバランスを取っている点です。特に、異なるサイズの整数型を比較可能にする一方で、異なるカテゴリの型(例: 整数と浮動小数点数)の比較は許可しないというGoの哲学を維持しています。
コアとなるコードの変更箇所
このコミットの主要な変更は、src/pkg/text/template/funcs.go
ファイルに新しい比較関数群が追加された点です。
--- a/src/pkg/text/template/funcs.go
+++ b/src/pkg/text/template/funcs.go
@@ -6,6 +6,7 @@ package template
import (
"bytes"
+ "errors"
"fmt"
"io"
"net/url"
@@ -35,6 +36,14 @@ var builtins = FuncMap{
"printf": fmt.Sprintf,
"println": fmt.Sprintln,
"urlquery": URLQueryEscaper,
+
+ // Comparisons
+ "eq": eq, // ==
+ "ge": ge, // >=
+ "gt": gt, // >
+ "le": le, // <=
+ "lt": lt, // <
+ "ne": ne, // !=
}
var builtinFuncs = createValueFuncs(builtins)
@@ -248,6 +257,151 @@ func not(arg interface{}) (truth bool) {
return !truth
}
+// Comparison.
+
+// TODO: Perhaps allow comparison between signed and unsigned integers.
+
+var (
+ errBadComparisonType = errors.New("invalid type for comparison")
+ errBadComparison = errors.New("incompatible types for comparison")
+)
+
+type kind int
+
+const (
+ invalidKind kind = iota
+ boolKind
+ complexKind
+ intKind
+ floatKind
+ integerKind
+ stringKind
+ uintKind
+)
+
+func basicKind(v reflect.Value) (kind, error) {
+ switch v.Kind() {
+ case reflect.Bool:
+ return boolKind, nil
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return intKind, nil
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return uintKind, nil
+ case reflect.Float32, reflect.Float64:
+ return floatKind, nil
+ case reflect.Complex64, reflect.Complex128:
+ return complexKind, nil
+ case reflect.String:
+ return stringKind, nil
+ }
+ return invalidKind, errBadComparisonType
+}
+
+// eq evaluates the comparison a == b.
+func eq(arg1, arg2 interface{}) (bool, error) {
+ v1 := reflect.ValueOf(arg1)
+ k1, err := basicKind(v1)
+ if err != nil {
+ return false, err
+ }
+ 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()
+ case complexKind:
+ truth = v1.Complex() == v2.Complex()
+ case floatKind:
+ truth = v1.Float() == v2.Float()
+ case intKind:
+ truth = v1.Int() == v2.Int()
+ case stringKind:
+ truth = v1.String() == v2.String()
+ case uintKind:
+ truth = v1.Uint() == v2.Uint()
+ default:
+ panic("invalid kind")
+ }
+ return truth, nil
+}
+
+// ne evaluates the comparison a != b.
+func ne(arg1, arg2 interface{}) (bool, error) {
+ // != is the inverse of ==.
+ equal, err := eq(arg1, arg2)
+ return !equal, err
+}
+
+// lt evaluates the comparison a < b.
+func lt(arg1, arg2 interface{}) (bool, error) {
+ v1 := reflect.ValueOf(arg1)
+ k1, err := basicKind(v1)
+ if err != nil {
+ return false, err
+ }
+ 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, complexKind:
+ return false, errBadComparisonType
+ case floatKind:
+ truth = v1.Float() < v2.Float()
+ case intKind:
+ truth = v1.Int() < v2.Int()
+ case stringKind:
+ truth = v1.String() < v2.String()
+ case uintKind:
+ truth = v1.Uint() < v2.Uint()
+ default:
+ panic("invalid kind")
+ }
+ return truth, nil
+}
+
+// le evaluates the comparison <= b.
+func le(arg1, arg2 interface{}) (bool, error) {
+ // <= is < or ==.
+ lessThan, err := lt(arg1, arg2)
+ if lessThan || err != nil {
+ return lessThan, err
+ }
+ return eq(arg1, arg2)
+}
+
+// gt evaluates the comparison a > b.
+func gt(arg1, arg2 interface{}) (bool, error) {
+ // > is the inverse of <=.
+ lessOrEqual, err := le(arg1, arg2)
+ if err != nil {
+ return false, err
+ }
+ return !lessOrEqual, nil
+}
+
+// ge evaluates the comparison a >= b.
+func ge(arg1, arg2 interface{}) (bool, error) {
+ // >= is the inverse of <.
+ lessThan, err := lt(arg1, arg2)
+ if err != nil {
+ return false, err
+ }
+ return !lessThan, nil
+}
+
// HTML escaping.
var (
コアとなるコードの解説
src/pkg/text/template/funcs.go
における主要な変更点は、以下の通りです。
-
エラー定義:
errBadComparisonType
: 比較に適さない型が渡された場合に返されるエラー。例えば、ブール値や複素数を順序比較しようとした場合など。errBadComparison
: 互換性のない型同士を比較しようとした場合に返されるエラー。例えば、整数と文字列を比較しようとした場合など。
-
kind
型と定数:kind
は、Goの様々な具体的な型を、比較ロジックで扱いやすいように抽象化したカテゴリを表すカスタム型です。invalidKind
,boolKind
,complexKind
,intKind
,floatKind
,stringKind
,uintKind
といった定数が定義されており、それぞれ真偽値、複素数、整数、浮動小数点数、文字列、符号なし整数に対応します。
-
basicKind(v reflect.Value) (kind, error)
関数:- この関数は、
reflect.Value
を受け取り、その値がどのkind
に属するかを判断します。 v.Kind()
を使用して、Goの組み込み型(reflect.Bool
,reflect.Int
,reflect.Float32
など)を検査し、対応するkind
を返します。- これにより、例えば
int
,int8
,int64
といった異なるサイズの整数型がすべてintKind
として扱われ、共通の比較ロジックを適用できるようになります。 - サポートされていない型(例: 構造体、スライスなど)が渡された場合は
errBadComparisonType
を返します。
- この関数は、
-
比較関数群 (
eq
,ne
,lt
,le
,gt
,ge
):- これらの関数はすべて
interface{}
型の引数を2つ受け取ります。これは、Goのテンプレート関数が任意の型の値を受け取れるようにするためです。 - 関数内部では、まず
reflect.ValueOf()
を使って引数をreflect.Value
に変換し、次にbasicKind()
を使ってそれぞれの引数のkind
を取得します。 - 型の一致チェック:
k1 != k2
で両引数のkind
が異なる場合、errBadComparison
エラーを返します。これにより、例えば整数と文字列のような異なるカテゴリの型を比較しようとするとエラーになります。 - 実際の比較:
switch k1
文を使って、引数のkind
に応じた適切な比較ロジックが適用されます。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()
(順序比較も同様)
- 順序比較できない型のハンドリング:
lt
関数では、boolKind
やcomplexKind
が渡された場合、順序比較ができないためerrBadComparisonType
を返します。 - 派生比較関数:
ne
,le
,gt
,ge
は、eq
やlt
の結果を組み合わせて実装されています。例えば、ne
はeq
の結果を反転させ、le
はlt
またはeq
の結果を利用します。これにより、コードの重複を避け、ロジックの一貫性を保っています。
- これらの関数はすべて
-
builtins
への登録:- 最後に、定義された
eq
,ne
,lt
,le
,gt
,ge
関数がbuiltins
というFuncMap
に追加されます。これにより、これらの関数がtext/template
のテンプレート内で{{eq .X .Y}}
のように直接呼び出せるようになります。
- 最後に、定義された
この一連の変更により、text/template
は、Goの型システムとリフレクションの機能を活用しつつ、テンプレート内で安全かつ柔軟な基本的な型の比較をサポートするようになりました。
関連リンク
- Go言語の
text/template
パッケージ公式ドキュメント: https://pkg.go.dev/text/template - Go言語の
reflect
パッケージ公式ドキュメント: https://pkg.go.dev/reflect - このコミットのGo CL (Code Review) ページ: https://golang.org/cl/13091045
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語の
text/template
およびreflect
パッケージに関する各種技術記事 - GitHub上のGo言語リポジトリのコミット履歴とソースコードI have provided the detailed explanation of the commit as requested, following all the specified instructions and chapter structure. I have used the commit data and my knowledge of Go's
text/template
andreflect
packages to provide a comprehensive explanation. I have also included relevant links.
# [インデックス 17347] ファイルの概要
このコミットは、Go言語の標準ライブラリである`text/template`パッケージに、基本的な型の比較機能(`eq`, `ne`, `lt`, `le`, `gt`, `ge`)を追加するものです。これにより、テンプレート内で数値、文字列、真偽値などの基本的なデータ型を直接比較できるようになり、より柔軟な条件分岐が可能になります。
## コミット
commit 7bbe32067922643a30ac7adf8aa3da9785d89d13 Author: Rob Pike r@golang.org Date: Wed Aug 21 11:27:27 2013 +1000
text/template: implement comparison of basic types
Add eq, lt, etc. to allow one to do simple comparisons.
It's basic types only (booleans, integers, unsigned integers,
floats, complex, string) because that's easy, easy to define,
and covers the great majority of useful cases, while leaving
open the possibility of a more sweeping definition later.
{{if eq .X .Y}}X and Y are equal{{else}}X and Y are unequal{{end}}
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/13091045
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/7bbe32067922643a30ac7adf8aa3da9785d89d13](https://github.com/golang/go/commit/7bbe32067922643a30ac7adf8aa3da9785d89d13)
## 元コミット内容
`text/template: implement comparison of basic types`
このコミットは、`text/template`パッケージに`eq`, `lt`などの比較演算子を追加し、シンプルな比較を可能にします。
対象となるのは、真偽値、整数、符号なし整数、浮動小数点数、複素数、文字列といった基本的な型のみです。これは、実装が容易であり、定義が明確で、実用的なケースの大部分をカバーできるためです。将来的には、より広範な定義の可能性も残されています。
例: `{{if eq .X .Y}}X and Y are equal{{else}}X and Y are unequal{{end}}`
## 変更の背景
Go言語の`text/template`パッケージは、HTMLやテキストの生成に広く利用されています。しかし、このコミット以前は、テンプレート内で直接的な値の比較を行うための組み込み関数が提供されていませんでした。これにより、例えば「ある変数の値が特定の値と等しい場合にのみコンテンツを表示する」といった、ごく基本的な条件分岐を実現するためには、テンプレートの外部で比較ロジックを実装し、その結果をテンプレートに渡す必要がありました。これは、テンプレートの表現力を制限し、開発者がより複雑なロジックをGoコード側で記述することを強制していました。
このコミットの背景には、テンプレートの利便性と表現力を向上させ、一般的なユースケースにおけるコードの簡潔化を図る目的があります。特に、Webアプリケーションのビュー層などでは、データの比較に基づいて表示内容を動的に変更するニーズが頻繁に発生します。`eq`, `lt`などの比較関数をテンプレートに直接組み込むことで、これらの一般的なタスクをより直感的かつ効率的にテンプレート内で完結できるようになります。
Rob Pike氏がコミットメッセージで述べているように、「基本的な型のみ(真偽値、整数、符号なし整数、浮動小数点数、複素数、文字列)に限定したのは、実装が容易で、定義が明確であり、有用なケースの大部分をカバーできるため」という点が重要です。これは、テンプレートエンジンの設計において、過度な複雑さを避けつつ、最も頻繁に必要とされる機能を提供することに焦点を当てていることを示しています。また、「より広範な定義の可能性も残している」という記述からは、将来的にユーザー定義型や構造体の比較など、より高度な比較機能が追加される可能性も示唆されています。
## 前提知識の解説
### Go言語の`text/template`パッケージ
`text/template`パッケージは、Go言語に組み込まれているデータ駆動型テンプレートエンジンです。これは、Goのプログラムからテキスト出力を生成するために使用されます。主な用途としては、HTMLページの生成、設定ファイルの作成、コード生成などがあります。
テンプレートは、プレーンテキストと、Goのデータ構造から値にアクセスするための「アクション」と呼ばれる特殊な構文で構成されます。アクションは二重中括弧`{{...}}`で囲まれます。
**主な特徴:**
* **データ駆動**: テンプレートは、Goの構造体、マップ、スライスなどのデータを受け取り、そのデータに基づいて出力を生成します。
* **アクション**:
* **パイプライン**: `|`を使用して、前のコマンドの出力を次のコマンドの入力として渡すことができます。
* **変数**: `$`記号を使用して変数を定義し、値を保持できます。
* **条件分岐**: `{{if ...}}{{else}}{{end}}`を使用して条件に基づいてコンテンツを表示できます。
* **繰り返し**: `{{range ...}}{{end}}`を使用してスライスやマップの要素を反復処理できます。
* **関数**: 組み込み関数やユーザー定義関数を呼び出すことができます。
* **組み込み関数**: `printf`, `html`, `urlquery`など、多くの便利な組み込み関数が提供されています。このコミットで追加された`eq`, `ne`, `lt`, `le`, `gt`, `ge`もこれに属します。
* **セキュリティ**: `html/template`パッケージは、HTMLエスケープを自動的に行い、クロスサイトスクリプティング(XSS)攻撃を防ぐための機能を提供します。`text/template`は汎用的なテキスト生成用であり、HTMLエスケープは行いません。
### Go言語の`reflect`パッケージ
`reflect`パッケージは、Goのプログラムが実行時に自身の構造を検査(リフレクション)することを可能にします。これにより、変数の型、値、メソッドなどを動的に調べたり、変更したりすることができます。
`reflect`パッケージの主要な型は以下の通りです。
* `reflect.Type`: Goの型の静的な情報(名前、カテゴリ、メソッドなど)を表します。`reflect.TypeOf(i interface{})`で取得できます。
* `reflect.Value`: Goの値の動的な情報(実際の値、変更可能性など)を表します。`reflect.ValueOf(i interface{})`で取得できます。
このコミットでは、テンプレート関数が受け取る引数の型を動的に判断し、適切な比較ロジックを適用するために`reflect`パッケージが利用されています。特に、`reflect.Value.Kind()`メソッドは、値の基本的なカテゴリ(例: `reflect.Int`, `reflect.Float64`, `reflect.String`など)を判別するために使用されます。これにより、異なる具体的な型(例: `int`, `int32`, `int64`)であっても、それらが同じ「整数」というカテゴリに属していれば、共通の比較ロジックを適用できるようになります。
### Go言語の型の比較ルール
Go言語では、異なる型の値の比較には厳格なルールがあります。
* **等価性(`==`, `!=`)**:
* ブール値、数値型(整数、浮動小数点数、複素数)、文字列、チャネル、ポインタ、関数、インターフェース、マップ、スライス、構造体、配列は、特定の条件下で等価性を比較できます。
* 数値型の場合、異なるサイズの整数型(例: `int`と`int64`)や、異なる浮動小数点数型(例: `float32`と`float64`)は、型変換なしには直接比較できません。ただし、定数であればコンパイラが自動的に型を推論し、比較可能になる場合があります。
* 構造体は、すべてのフィールドが比較可能であれば比較できます。
* 配列は、要素の型が比較可能であれば比較できます。
* スライス、マップ、関数は、`nil`とのみ比較可能です。それ以外の値との比較はできません。
* **順序比較(`<`, `<=`, `>`, `>=`)**:
* 数値型と文字列型のみが順序比較可能です。
* ブール値、複素数、構造体、配列、スライス、マップ、チャネル、インターフェース、ポインタは順序比較できません。
このコミットで追加されるテンプレートの比較関数は、Go言語のこれらの比較ルールをベースにしつつ、テンプレートの利便性を考慮して一部の柔軟性を持たせています。例えば、異なるサイズの整数型(`int`と`int64`)であっても、テンプレート内では比較可能になるように実装されています。しかし、`int`と`float32`のような異なるカテゴリの型は、Goの基本的な比較ルールに従い、比較できないようになっています。
## 技術的詳細
このコミットは、`text/template`パッケージに新しい比較関数群を導入するために、主に以下の3つのファイルに影響を与えています。
1. **`src/pkg/text/template/doc.go`**:
* このファイルは`text/template`パッケージのドキュメントを定義しています。
* 新しい比較関数`eq`, `ne`, `lt`, `le`, `gt`, `ge`が追加され、それぞれの機能と、それらが基本的な型(真偽値、整数、符号なし整数、浮動小数点数、複素数、文字列)にのみ適用されることが明記されています。
* Goの比較ルールに従いつつも、「サイズと厳密な型は無視される」という重要な例外が説明されています。これは、例えば`int`と`int64`のような異なるサイズの整数型でも比較できることを意味します。ただし、「`int`と`float32`のような比較はできない」というGoの基本的な型システムルールは維持されます。
2. **`src/pkg/text/template/exec_test.go`**:
* このファイルは`text/template`パッケージの実行に関するテストを定義しています。
* `TestComparison`という新しいテスト関数が追加され、`cmpTest`構造体と`cmpTests`スライスが定義されています。
* `cmpTests`スライスには、`eq`, `ne`, `lt`, `le`, `gt`, `ge`の各比較関数について、様々な型の組み合わせ(真偽値、整数、浮動小数点数、複素数、文字列、符号なし整数)での期待される結果が網羅的に記述されています。
* また、引数の数が多すぎる場合や、異なるカテゴリの型を比較しようとした場合(例: `eq "xy" 1`)、順序比較できない型を比較しようとした場合(例: `lt true true`、`lt 1+0i 1+0i`)にエラーが発生することもテストされています。
* これらのテストは、新しい比較関数の正確性と、意図しない型での比較を防ぐための堅牢性を保証します。
3. **`src/pkg/text/template/funcs.go`**:
* このファイルは`text/template`パッケージの組み込み関数を定義しています。
* `builtins`という`FuncMap`に、新しく定義された比較関数`eq`, `ne`, `lt`, `le`, `gt`, `ge`が追加されています。これにより、これらの関数がテンプレート内で利用可能になります。
* **`basicKind`関数**: `reflect.Value`を受け取り、その値の基本的なカテゴリ(`boolKind`, `complexKind`, `intKind`, `floatKind`, `stringKind`, `uintKind`)を返すヘルパー関数です。これにより、Goの様々な数値型(例: `int`, `int8`, `int64`など)を単一の`intKind`として扱うことができ、比較ロジックを簡素化しています。サポートされていない型が渡された場合は`errBadComparisonType`エラーを返します。
* **`eq(arg1, arg2 interface{})`関数**:
* 2つの引数を受け取り、`==`比較を行います。
* まず、`basicKind`関数を使って両引数の基本的な型カテゴリを取得します。
* 型カテゴリが異なる場合(例: 整数と文字列)は`errBadComparison`エラーを返します。
* 同じ型カテゴリの場合、`reflect.Value`の適切なメソッド(`Bool()`, `Complex()`, `Float()`, `Int()`, `String()`, `Uint()`)を使って実際の値を取得し、Goの組み込みの`==`演算子で比較します。
* **`ne(arg1, arg2 interface{})`関数**: `eq`関数の結果を反転させることで`!=`比較を実現します。
* **`lt(arg1, arg2 interface{})`関数**:
* 2つの引数を受け取り、`<`比較を行います。
* `eq`と同様に型カテゴリをチェックします。
* `boolKind`や`complexKind`のような順序比較できない型の場合、`errBadComparisonType`エラーを返します。
* 順序比較可能な型(浮動小数点数、整数、文字列、符号なし整数)の場合、適切な`reflect.Value`メソッドを使って値を取得し、Goの組み込みの`<`演算子で比較します。
* **`le(arg1, arg2 interface{})`関数**: `lt`または`eq`の結果に基づいて`<=`比較を実現します。
* **`gt(arg1, arg2 interface{})`関数**: `le`関数の結果を反転させることで`>`比較を実現します。
* **`ge(arg1, arg2 interface{})`関数**: `lt`関数の結果を反転させることで`>=`比較を実現します。
この実装の鍵は、`reflect`パッケージを使用して実行時に引数の型を動的に検査し、Goの厳密な型システムとテンプレートの柔軟性のバランスを取っている点です。特に、異なるサイズの整数型を比較可能にする一方で、異なるカテゴリの型(例: 整数と浮動小数点数)の比較は許可しないというGoの哲学を維持しています。
## コアとなるコードの変更箇所
このコミットの主要な変更は、`src/pkg/text/template/funcs.go`ファイルに新しい比較関数群が追加された点です。
```diff
--- a/src/pkg/text/template/funcs.go
+++ b/src/pkg/text/template/funcs.go
@@ -6,6 +6,7 @@ package template
import (
"bytes"
+ "errors"
"fmt"
"io"
"net/url"
@@ -35,6 +36,14 @@ var builtins = FuncMap{
"printf": fmt.Sprintf,
"println": fmt.Sprintln,
"urlquery": URLQueryEscaper,
+
+ // Comparisons
+ "eq": eq, // ==
+ "ge": ge, // >=
+ "gt": gt, // >
+ "le": le, // <=
+ "lt": lt, // <
+ "ne": ne, // !=
}
var builtinFuncs = createValueFuncs(builtins)
@@ -248,6 +257,151 @@ func not(arg interface{}) (truth bool) {
return !truth
}
+// Comparison.
+
+// TODO: Perhaps allow comparison between signed and unsigned integers.
+
+var (
+ errBadComparisonType = errors.New("invalid type for comparison")
+ errBadComparison = errors.New("incompatible types for comparison")
+)
+
+type kind int
+
+const (
+ invalidKind kind = iota
+ boolKind
+ complexKind
+ intKind
+ floatKind
+ integerKind
+ stringKind
+ uintKind
+)
+
+func basicKind(v reflect.Value) (kind, error) {
+ switch v.Kind() {
+ case reflect.Bool:
+ return boolKind, nil
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return intKind, nil
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return uintKind, nil
+ case reflect.Float32, reflect.Float64:
+ return floatKind, nil
+ case reflect.Complex64, reflect.Complex128:
+ return complexKind, nil
+ case reflect.String:
+ return stringKind, nil
+ }
+ return invalidKind, errBadComparisonType
+}
+
+// eq evaluates the comparison a == b.
+func eq(arg1, arg2 interface{}) (bool, error) {
+ v1 := reflect.ValueOf(arg1)
+ k1, err := basicKind(v1)
+ if err != nil {
+ return false, err
+ }
+ 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()
+ case complexKind:
+ truth = v1.Complex() == v2.Complex()
+ case floatKind:
+ truth = v1.Float() == v2.Float()
+ case intKind:
+ truth = v1.Int() == v2.Int()
+ case stringKind:
+ truth = v1.String() == v2.String()
+ case uintKind:
+ truth = v1.Uint() == v2.Uint()
+ default:
+ panic("invalid kind")
+ }
+ return truth, nil
+}
+
+// ne evaluates the comparison a != b.
+func ne(arg1, arg2 interface{}) (bool, error) {
+ // != is the inverse of ==.
+ equal, err := eq(arg1, arg2)
+ return !equal, err
+}
+
+// lt evaluates the comparison a < b.
+func lt(arg1, arg2 interface{}) (bool, error) {
+ v1 := reflect.ValueOf(arg1)
+ k1, err := basicKind(v1)
+ if err != nil {
+ return false, err
+ }
+ 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, complexKind:
+ return false, errBadComparisonType
+ case floatKind:
+ truth = v1.Float() < v2.Float()
+ case intKind:
+ truth = v1.Int() < v2.Int()
+ case stringKind:
+ truth = v1.String() < v2.String()
+ case uintKind:
+ truth = v1.Uint() < v2.Uint()
+ default:
+ panic("invalid kind")
+ }
+ return truth, nil
+}
+
+// le evaluates the comparison <= b.
+func le(arg1, arg2 interface{}) (bool, error) {
+ // <= is < or ==.
+ lessThan, err := lt(arg1, arg2)
+ if lessThan || err != nil {
+ return lessThan, err
+ }
+ return eq(arg1, arg2)
+}
+
+// gt evaluates the comparison a > b.
+func gt(arg1, arg2 interface{}) (bool, error) {
+ // > is the inverse of <=.
+ lessOrEqual, err := le(arg1, arg2)
+ if err != nil {
+ return false, err
+ }
+ return !lessOrEqual, nil
+}
+
+// ge evaluates the comparison a >= b.
+func ge(arg1, arg2 interface{}) (bool, error) {
+ // >= is the inverse of <.
+ lessThan, err := lt(arg1, arg2)
+ if err != nil {
+ return false, err
+ }
+ return !lessThan, nil
+}
+
// HTML escaping.
var (
コアとなるコードの解説
src/pkg/text/template/funcs.go
における主要な変更点は、以下の通りです。
-
エラー定義:
errBadComparisonType
: 比較に適さない型が渡された場合に返されるエラー。例えば、ブール値や複素数を順序比較しようとした場合など。errBadComparison
: 互換性のない型同士を比較しようとした場合に返されるエラー。例えば、整数と文字列を比較しようとした場合など。
-
kind
型と定数:kind
は、Goの様々な具体的な型を、比較ロジックで扱いやすいように抽象化したカテゴリを表すカスタム型です。invalidKind
,boolKind
,complexKind
,intKind
,floatKind
,stringKind
,uintKind
といった定数が定義されており、それぞれ真偽値、複素数、整数、浮動小数点数、文字列、符号なし整数に対応します。
-
basicKind(v reflect.Value) (kind, error)
関数:- この関数は、
reflect.Value
を受け取り、その値がどのkind
に属するかを判断します。 v.Kind()
を使用して、Goの組み込み型(reflect.Bool
,reflect.Int
,reflect.Float32
など)を検査し、対応するkind
を返します。- これにより、例えば
int
,int8
,int64
といった異なるサイズの整数型がすべてintKind
として扱われ、共通の比較ロジックを適用できるようになります。 - サポートされていない型(例: 構造体、スライスなど)が渡された場合は
errBadComparisonType
を返します。
- この関数は、
-
比較関数群 (
eq
,ne
,lt
,le
,gt
,ge
):- これらの関数はすべて
interface{}
型の引数を2つ受け取ります。これは、Goのテンプレート関数が任意の型の値を受け取れるようにするためです。 - 関数内部では、まず
reflect.ValueOf()
を使って引数をreflect.Value
に変換し、次にbasicKind()
を使ってそれぞれの引数のkind
を取得します。 - 型の一致チェック:
k1 != k2
で両引数のkind
が異なる場合、errBadComparison
エラーを返します。これにより、例えば整数と文字列のような異なるカテゴリの型を比較しようとするとエラーになります。 - 実際の比較:
switch k1
文を使って、引数のkind
に応じた適切な比較ロジックが適用されます。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()
(順序比較も同様)
- 順序比較できない型のハンドリング:
lt
関数では、boolKind
やcomplexKind
が渡された場合、順序比較ができないためerrBadComparisonType
を返します。 - 派生比較関数:
ne
,le
,gt
,ge
は、eq
やlt
の結果を組み合わせて実装されています。例えば、ne
はeq
の結果を反転させ、le
はlt
またはeq
の結果を利用します。これにより、コードの重複を避け、ロジックの一貫性を保っています。
- これらの関数はすべて
-
builtins
への登録:- 最後に、定義された
eq
,ne
,lt
,le
,gt
,ge
関数がbuiltins
というFuncMap
に追加されます。これにより、これらの関数がtext/template
のテンプレート内で{{eq .X .Y}}
のように直接呼び出せるようになります。
- 最後に、定義された
この一連の変更により、text/template
は、Goの型システムとリフレクションの機能を活用しつつ、テンプレート内で安全かつ柔軟な基本的な型の比較をサポートするようになりました。
関連リンク
- Go言語の
text/template
パッケージ公式ドキュメント: https://pkg.go.dev/text/template - Go言語の
reflect
パッケージ公式ドキュメント: https://pkg.go.dev/reflect - このコミットのGo CL (Code Review) ページ: https://golang.org/cl/13091045
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語の
text/template
およびreflect
パッケージに関する各種技術記事 - GitHub上のGo言語リポジトリのコミット履歴とソースコード