[インデックス 19726] ファイルの概要
このコミットは、Go言語の仕様において、for range
ループでインデックス変数や値変数を明示的に指定しない構文 (for range x
) を許可するように変更するものです。これにより、特にチャネルのドレインや、イテレーション回数のみが必要な場合に、より簡潔なコード記述が可能になります。
コミット
commit 20ae6d9bc58f98355fcab6501e0fcb2c5b34f44c
Author: Robert Griesemer <gri@golang.org>
Date: Mon Jul 14 15:08:09 2014 -0700
spec: permit "for range x" (no index variables)
This is a fully backward-compatible language change.
There are not a lot of cases in the std library, but
there are some. Arguably this makes the syntax a bit
more regular - any trailing index variable that is _
can be left away, and there's some analogy to type
switches where the temporary can be left away.
Implementation-wise the change should be trivial as
it can be done completely syntactically. For instance,
the respective change in go/parser is a dozen lines
(see https://golang.org/cl/112970044 ).
Fixes #6102.
LGTM=iant, r, rsc
R=r, rsc, iant, ken
CC=golang-codereviews
https://golang.org/cl/104680043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/20ae6d9bc58f98355fcab6501e0fcb2c5b34f44c
元コミット内容
このコミットは、Go言語の仕様において、for range
ループでインデックス変数や値変数を指定しない構文、すなわち for range x
を許可するように変更するものです。これは後方互換性のある言語変更であり、標準ライブラリ内での使用例は多くないものの、構文をより規則的にし、アンダースコア (_
) で破棄される変数を省略できるという点で、型スイッチにおける一時変数の省略と類似しています。実装面では、構文解析器の変更のみで対応可能であり、非常に小規模な変更で実現できるとされています。この変更は、Go言語のIssue #6102を解決します。
変更の背景
この変更の背景には、Go言語のfor range
ループの柔軟性を高め、特定のユースケースにおいてコードをより簡潔に記述できるようにするという目的があります。
Go言語のIssue #6102「spec: range should not require a variable」は、2013年8月11日に提起されました。このIssueでは、for range
ループを使用する際に、イテレーション変数(インデックスや値)を常に宣言する必要があることに対する議論が行われました。
具体的には、以下のようなシナリオで不便さが指摘されていました。
- チャネルのドレイン: チャネルからすべての値を取り出してチャネルを閉じる、あるいはチャネルが閉じられるまで待つ場合、通常は値を使用しないため
for _ = range ch {}
のようにブランク識別子 (_
) を使用する必要がありました。これをfor range ch {}
と記述できれば、より直感的で簡潔になります。 - イテレーション回数のみが必要な場合: 配列やスライスを特定の回数だけループさせたいが、インデックスや値自体は不要な場合、
for i := range slice {}
のようにインデックス変数i
を宣言し、それを使用しないという状況がありました。この場合もfor range slice {}
と記述できると、コードの意図がより明確になります。 - 構文の規則性: コミットメッセージにもあるように、Go言語にはブランク識別子 (
_
) を使用して変数を破棄するメカニズムがあります。for range
においても、_
で破棄される変数を省略できることは、言語全体の構文規則性を高めるという考えがありました。これは、型スイッチで一時変数を省略できるのと同様の考え方です。
これらの背景から、for range
ループの構文を拡張し、イテレーション変数を省略できる形式を導入することで、コードの可読性と記述性を向上させることが目指されました。
前提知識の解説
Go言語の for range
ループ
Go言語の for range
ループは、配列、スライス、文字列、マップ、チャネルといったコレクション型の要素をイテレート(反復処理)するための構文です。基本的な形式は以下の通りです。
for index, value := range collection {
// index と value を使った処理
}
collection
: イテレート対象の配列、スライス、文字列、マップ、チャネル。index
: イテレーションごとに現在の要素のインデックス(配列、スライス、文字列の場合)またはキー(マップの場合)が代入される変数。チャネルの場合は使用できません。value
: イテレーションごとに現在の要素の値が代入される変数。:=
または=
: 変数の宣言と代入、または既存変数への代入。
for range
ループは、イテレート対象の型によって、返される値の数が異なります。
- 配列、スライス:
index
(int) とvalue
(要素の型) の2つの値が返されます。 - 文字列:
index
(int, バイトオフセット) とruneValue
(rune, Unicodeコードポイント) の2つの値が返されます。 - マップ:
key
(キーの型) とvalue
(値の型) の2つの値が返されます。 - チャネル:
value
(チャネルの要素の型) の1つの値のみが返されます。チャネルが閉じられるまで値を受信し続けます。
ブランク識別子 (_
)
Go言語のブランク識別子 (_
) は、変数を宣言したがその値を使用しない場合に、コンパイラのエラー(「宣言されたが使用されていない変数」)を回避するために使用されます。例えば、for range
ループでインデックスのみが必要で値が不要な場合、またはその逆の場合に利用されます。
// インデックスのみが必要な場合
for i, _ := range slice {
fmt.Println("Index:", i)
}
// 値のみが必要な場合
for _, v := range slice {
fmt.Println("Value:", v)
}
// チャネルから値を受け取るが使用しない場合
for _ = range ch {
// チャネルが閉じられるまで待つ、またはチャネルをドレインする
}
このコミット以前は、for range
ループでインデックスや値が不要な場合でも、ブランク識別子を使って明示的に変数を宣言する必要がありました。
技術的詳細
このコミットの技術的な核心は、Go言語の構文定義、特にfor
ステートメントのRangeClause
(範囲句)のEBNF(拡張バッカス・ナウア記法)を変更することにあります。
変更前と変更後のRangeClause
の定義を比較します。
変更前:
RangeClause = ( ExpressionList "=" | IdentifierList ":=" ) "range" Expression .
この定義では、range
キーワードの前に必ずExpressionList "="
(式リストと代入演算子)またはIdentifierList ":="
(識別子リストと短い変数宣言演算子)が存在しなければならないことを示しています。これは、for range
ループが常に少なくとも1つの変数(インデックスまたは値)を要求することを意味していました。
変更後:
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
変更後の定義では、ExpressionList "=" | IdentifierList ":="
の部分が角括弧 []
で囲まれています。EBNFにおいて角括弧は「オプション(省略可能)」であることを示します。これにより、range
キーワードの前の変数宣言部分が省略可能となり、for range Expression
という形式が文法的に有効になります。
この文法変更がもたらす具体的な影響は以下の通りです。
-
変数の省略:
for range x
の形式が許可されます。これは、イテレーションのインデックスも値も不要な場合に特に有用です。- 例えば、チャネルからすべての値を読み飛ばしてチャネルをドレインする場合、以前は
for _ = range ch {}
と書く必要がありましたが、この変更によりfor range ch {}
と書けるようになります。 - 配列やスライスの場合、
for range a {}
と書くと、for i := range a {}
と同様に、インデックスi
が0
からlen(a)-1
まで生成されますが、そのインデックス値は使用されません。これは、ループ回数だけが必要で、要素自体にはアクセスしない場合に役立ちます。
-
イテレーション変数の数の柔軟性:
- チャネルの場合、以前は「1つのイテレーション変数のみが許可される」とされていましたが、この変更により「最大1つのイテレーション変数が許可される」という表現に変わりました。これは、0個の変数(つまり
for range ch
)も許容されることを意味します。 - その他のコレクション型(配列、スライス、文字列、マップ)の場合も、「1つまたは2つのイテレーション変数」から「最大2つのイテレーション変数」という表現に変わり、0個の変数も許容されることを明確にしています。
- チャネルの場合、以前は「1つのイテレーション変数のみが許可される」とされていましたが、この変更により「最大1つのイテレーション変数が許可される」という表現に変わりました。これは、0個の変数(つまり
-
ブランク識別子との関係:
- 「最後のイテレーション変数がブランク識別子である場合、その範囲句はその識別子なしの同じ句と同等である」という説明が追加されました。これは、例えば
for i, _ := range slice
がfor i := range slice
と同等であり、さらにfor _, _ := range slice
がfor range slice
と同等であることを意味します。これにより、ブランク識別子を明示的に書く手間が省け、コードがより簡潔になります。
- 「最後のイテレーション変数がブランク識別子である場合、その範囲句はその識別子なしの同じ句と同等である」という説明が追加されました。これは、例えば
この変更は、Go言語のコンパイラやツールチェインにおいて、主に構文解析器(パーサー)のロジックに影響を与えます。コミットメッセージにもあるように、go/parser
における変更は非常に小規模であり、言語のセマンティクス(意味論)に大きな変更を加えることなく、構文の柔軟性を高めることに成功しています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、Go言語の公式仕様書である doc/go_spec.html
ファイル内の記述です。
具体的には、以下の箇所が変更されています。
-
RangeClause
の EBNF 定義の変更:--- a/doc/go_spec.html +++ b/doc/go_spec.html @@ -4714,41 +4714,42 @@ for { S() } is the same as for true { S() } A "for" statement with a "range" clause iterates through all entries of an array, slice, string or map, or values received on a channel. For each entry it assigns <i>iteration values</i> -to corresponding <i>iteration variables</i> and then executes the block. +to corresponding <i>iteration variables</i> if present and then executes the block. </p> <pre class="ebnf"> -RangeClause = ( ExpressionList "=" | IdentifierList ":=" ) "range" Expression . +RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression . </pre>
RangeClause
の定義が、(
と)
で囲まれていた部分が[
と]
で囲まれるように変更されました。 -
説明文の変更:
- イテレーション変数の代入に関する説明に「if present」(存在する場合)が追加されました。
- 左辺のオペランドに関する説明に「if present」(存在する場合)が追加されました。
- チャネルおよびその他のコレクション型におけるイテレーション変数の数に関する記述が、「only one」(唯一の1つ)から「at most one」(最大1つ)、「one or two」(1つまたは2つ)から「up to two」(最大2つ)に変更されました。
- ブランク識別子に関する説明が、「if the second iteration variable is the blank identifier」(2番目のイテレーション変数がブランク識別子の場合)から「If the last iteration variable is the blank identifier」(最後のイテレーション変数がブランク識別子の場合)に変更され、より一般的なケースをカバーするようになりました。
- 配列やポインタへの配列、スライスの場合のイテレーション値の生成に関する説明が、「If only the first iteration variable is present」(最初のイテレーション変数のみが存在する場合)から「If at most one iteration variable is present」(最大1つのイテレーション変数が存在する場合)に変更されました。
-
新しい使用例の追加:
--- a/doc/go_spec.html +++ b/doc/go_spec.html @@ -4841,6 +4842,9 @@ var ch chan Work = producer()\n for w := range ch {\n doWork(w)\n }\n+\n+// empty a channel +for range ch {}\n </pre>
チャネルを空にするための新しい例として
for range ch {}
が追加されました。
コアとなるコードの解説
このコミットの核となる変更は、Go言語のfor range
ループの構文規則を定義するEBNF(拡張バッカス・ナウア記法)の更新と、それに伴う仕様書の説明文の修正です。
EBNFの変更 (RangeClause
)
最も重要な変更は、RangeClause
のEBNF定義です。
-
変更前:
RangeClause = ( ExpressionList "=" | IdentifierList ":=" ) "range" Expression .
- この定義は、
range
キーワードの前に、必ずExpressionList "="
(既存の変数への代入)またはIdentifierList ":="
(新しい変数の宣言と初期化)のいずれかの形式で、イテレーション変数を指定する必要があることを示していました。括弧()
はグループ化を意味し、そのグループ全体が必須であることを示します。
- この定義は、
-
変更後:
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
- 変更後では、イテレーション変数を指定する部分が角括弧
[]
で囲まれています。EBNFにおいて角括弧は「オプション(省略可能)」であることを意味します。 - これにより、
range
キーワードの前のイテレーション変数宣言部分が完全に省略可能となり、for range Expression
という新しい有効な構文が導入されました。
- 変更後では、イテレーション変数を指定する部分が角括弧
このEBNFの変更は、Goコンパイラがfor range
ループの構文をどのように解釈するかという、言語の根幹部分に影響を与えます。これにより、コンパイラは変数宣言がないfor range
ループを正当な構文として認識し、処理できるようになります。
説明文の変更と新しい使用例
EBNFの変更に合わせて、仕様書内の説明文も更新され、新しい構文の振る舞いが明確にされています。
- 「if present」の追加: イテレーション変数への値の代入や、左辺のオペランドに関する説明に「if present」(存在する場合)という条件が追加されました。これは、変数が省略された場合にこれらの操作が行われないことを明確にします。
- イテレーション変数の数の柔軟性:
- チャネルの場合、「唯一の1つ」から「最大1つ」に変わったことで、
for range ch
のように変数を全く指定しない形式がチャネルに対しても有効であることが示されました。 - その他のコレクション型(配列、スライス、文字列、マップ)の場合も、「1つまたは2つ」から「最大2つ」に変わり、同様に0個の変数を許容することが明確化されました。
- チャネルの場合、「唯一の1つ」から「最大1つ」に変わったことで、
- ブランク識別子 (
_
) の扱い: 「最後のイテレーション変数がブランク識別子である場合、その範囲句はその識別子なしの同じ句と同等である」という記述は、for _, _ := range slice
のような形式がfor range slice
と同等であることを意味します。これにより、ブランク識別子を明示的に書く必要がなくなり、コードがより簡潔になります。 for range ch {}
の例:- この新しい例は、チャネルからすべての値を受け取り、チャネルが閉じられるまでループを続けるが、受け取った値自体は使用しないという一般的なユースケースを示しています。
- 以前は
for _ = range ch {}
と書く必要がありましたが、この変更によりfor range ch {}
と書けるようになり、コードの意図がより明確で簡潔になります。これは、チャネルのドレイン処理において非常に役立ちます。
これらの変更は、Go言語のfor range
ループの表現力を高め、開発者がより簡潔で意図が明確なコードを書けるようにすることを目的としています。特に、イテレーション値が不要な場合の冗長なブランク識別子の使用を避けることができるようになりました。
関連リンク
- Go言語 Issue #6102: https://github.com/golang/go/issues/6102
- Go言語 ChangeList 104680043: https://golang.org/cl/104680043
- Go言語 ChangeList 112970044 (go/parserの変更例): https://golang.org/cl/112970044
参考にした情報源リンク
- Go言語 Issue #6102: spec: range should not require a variable - GitHub: https://github.com/golang/go/issues/6102
- Go Programming Language Specification (Go言語の公式仕様書): https://go.dev/ref/spec (このコミットで変更された
doc/go_spec.html
の内容は、この公式仕様書に反映されています。) - A Tour of Go - For loops: https://go.dev/tour/flowcontrol/9
- Effective Go - For: https://go.dev/doc/effective_go#for