Perl/PPCs/ppcs/ppc0001 N-at-a-time with for の翻訳
この文書は、Perl/PPCs/ppcs/ppc0001-n-at-a-time-forを翻訳したものです。
原題は、「Multiple-alias syntax for foreach」です
Multiple-alias syntax for foreach
(foreachに対する複数エイリアス記法)
Preamble 序文
翻訳注:こちらは原文の情報です。
Author:
Sponsor: Nicholas Clark <nick@ccl4.org>
ID: 0001
Status: Implemented
Abstract 要約
現行では不正な記法である for my ($key, $value) (%hash) { ... }
を実装し、
ハッシュに対して一度に2個の要素の取得を行います。 - これによりどのようなリストに対しても、
一度にN個の要素が取得できるよう一般化します。
Motivation 動機
each関数は実装の詳細に問題があるため一般的には非推奨ですが、 次のようにもっとも"わかりやすく(pretty)"、簡潔な、ループ文の書き方です:
while (my ($key, $value) = each %hash) { ... }
代替案として、forループ文で1ループ毎に、複数の要素を取得する書き方が考えられます。
for my ($key, $value) (%hash) { ... }
これを"バンドラー(bundler)"として一般化し、指定された変数の個数と同じ数だけ要素を抜き出すようにしたらどうでしょう。例えば:
for my ($foo, $bar, $baz) (@array) {
# $foo, $bar, and $baz are the next three elements of @array,
# or undef if overflowed
Rationale 提案理由
ハッシュのキーと値に対する反復時の現行の記法:
while (my ($key, $value) = each %hash) { ... }
これにはいくつかの問題があります。
- 正確さのため、ハッシュの内部状態が”クリーン”であることを仮定している - 頑健性のため、無効コンテキストで 'keys %hash' をして、はじめに繰り返し演算の初期化をしなければならない
- 初心者に説明するのが難しい - ここで何が起こっているか説明するには
- リストへの要素割り当て規則
- 空のリストは偽であり、空でないリストは真であること
- ハッシュは内部的イテレーターを持っていること
- イテレーターを混乱させることなく、反復の内部でハッシュを修正することができない
次のように2行で書くことはできます:
for my $k (keys %hash) {
my $v = $hash{$h};
...
}
(もし %hash
が実際に複雑な表現であなたが二回評価したくないなら3行)
しかし、ワンライナーを書くに当たっては明らかに良い方法ではありません。
配列において、より一般的な N個の要素を同時に 反復する際には、単純で包括的な解決策はありません。 例えば、 破壊的 に 配列 を反復することはできます。
while (@list) {
my ($x, $y, $z) = splice @list, 0, 3;
...
}
(上述の3は明示的に記述される必要があり、レキシカルの数から導き出すことはできない)。
リストにたいして次のように非破壊的に反復することができます:
my @temp = qw(Ace Two Three Four Five Six Seven Eight Nine Ten Jack Queen King);
for (my $i = 0; $i < @temp; $i += 3) {
my ($foo, $bar, $baz) = @temp[$i .. $i + 2];
print "$foo $bar $baz\n";
}
しかしこれは、
- 冗長である
- 正しく書くのが難しい
- 3回性を3つの場所で繰り返している
- リストに対して一時的配列(すなわちコピー)が必要
- すべての値のレキシカルへのコピーもしている
提案した文法は、これらの問題のすべてを解決します。
Specification 仕様
diff --git a/pod/perlsyn.pod b/pod/perlsyn.pod
index fe511f052e..490119d00e 100644
--- a/pod/perlsyn.pod
+++ b/pod/perlsyn.pod
@@ -282,6 +282,14 @@ The following compound statements may be used to control flow:
PHASE BLOCK
+As of Perl 5.36, you can iterate over multiple values at a time by specifying
+a list of lexicals within parentheses
+
+ no warnings "experimental::for_list";
+ LABEL for my (VAR, VAR) (LIST) BLOCK
+ LABEL for my (VAR, VAR) (LIST) BLOCK continue BLOCK
+ LABEL foreach my (VAR, VAR) (LIST) BLOCK
+ LABEL foreach my (VAR, VAR) (LIST) BLOCK continue BLOCK
+
If enabled by the experimental C<try> feature, the following may also be used
try BLOCK catch (VAR) BLOCK
@@ -549,6 +557,14 @@ followed by C<my>. To use this form, you must enable the C<refaliasing>
feature via C<use feature>. (See L<feature>. See also L<perlref/Assigning
to References>.)
+As of Perl 5.36, you can iterate over a list of lexical scalars n-at-a-time.
+If the size of the LIST is not an exact multiple of number of iterator
+variables, then on the last iteration the "excess" iterator variables are
+undefined values, much like if you slice beyond the end of an array. You
+can only iterate over scalars - unlike list assignment, it's not possible to
+use C<undef> to signify a value that isn't wanted. This is a limitation of
+the current implementation, and might be changed in the future.
+
Examples:
for (@ary) { s/foo/bar/ }
@@ -574,6 +590,17 @@ Examples:
# do something which each %hash
}
+ foreach my ($foo, $bar, $baz) (@list) {
+ # do something three-at-a-time
+ }
+
+ foreach my ($key, $value) (%hash) {
+ # iterate over the hash
+ # The hash is eagerly flattened to a list before the loop starts,
+ # but as ever keys are copies, values are aliases.
+ # This is the same behaviour as for $var (%hash) {...}
+ }
+
Here's how a C programmer might code up a particular algorithm in Perl:
for (my $i = 0; $i < @ary1; $i++) {
Backwards Compatibility 後方互換性
新しい文法は現存するPerlでは文法エラーになります。次のようなエラーメッセージが生成されます:
Missing $ on loop variable at /home/nick/test/three-at-a-time.pl line 4.
静的ツールは文法エラーとして認識できるはずですが、変更を加えない限りよりよい診断は得られないでしょう。
提案した実装には、B::Deparse
, B::Concise
, Devel::Cover
, Devel::NYTProf
, Perl::Critic
で新しい文法をうまく扱うためのテストとパッチが含まれています。
よりバージョンの古いPerlではこの文法を再現することができないと思います。将来的にどんなAPIをパーサーに追加すればそれが可能になるのかわからず、なんらかのAPIを加えられたとして、それが脆弱であるかどうかと、まさに現在の実装ときつく連結しているかどうかはわかりません。
Security Implications
この変更で、脆弱性を呈したり類似する問題は起こりそうにないと思われます。
Examples 例
Motivationに記載の例で十分かと思います。
Prototype Implementation プロトタイプ実装
See https://github.com/nwc10/perl5/commits/smoke-me/nicholas/pp_iter
Future Scope 将来の見込み
スカラーのリストにundef
を許容する
for my ($a, undef, $c) (1 .. 9) { ... }
"この値は無視して"という意味にundefを割り当てることは、リスト割り当ての一般化として妥当です。また、foreach
に対して(今回の提案で あるいは 後日に)実装することは、安全な文法ですし、いくつかの特異なケースにおいては役に立ちそうです。しかし、それを 持たない としても、提案の基本的な有用性は損なわれません。
一度に N 個 の foreach
のもっとも簡単な実装は、もし正確に n 個のスカラーがあれば、 for
ループそれ自体のなかですべて宣言されることです。というのは、この方法で隣接するPadスロットを占領するからです。
これは、optreeの中に一つだけ特別な整数値があることを意味し、一度に N 個であること と ターゲット変数のアドレスの計算の両方に使われます。undef
をその混合物に加えると、明らかに単純で分かりやすい実装を締めだしてしまいます。
もし、複雑にすることなく undef
を加える良い方法をみつけたら、その時はそれをふくめることを検討すべきです。
Rejected Ideas 却下されたアイディア
レキシカルのリストの中に@array または %hashを許容する
for my ($key, $value, %rest) (%hash) { ... }
for my ($first, $second, @rest) (@array) {... }
記法的に、これらはすべて動作しますし、すべてのレキシカルは隣接したPadスロットに置かれるという仮定に違反はしません。
しかし、ランタイムに複雑さが増えます。
一度に 1 スカラー から 一度に n スカラー へと一般化することは、ほぼ、C言語の for
ループを(動作している)既存のコードの中に付け足すだけです。
これらを実装するということは、基本的におかしな書き方である下記のようなプログラムのためにコードを追加することを意味します。
{ my ($key, $value, %rest) = %hash; ... }
{ my ($first, $second, @rest) = @array; ... }
{ my ($key, $value, %rest) = %hash; ... }
{ my ($first, $second, @rest) = @array; ... }
my
同様に our
を許容する
my
の代わりに our
を使っても記法的には問題ありません。すなわち、下記をパースすることができます:
for our ($foo, $bar, $baz) (@array) {
しかし、実装はずっと複雑になります。それぞれのパッケージ変数は一対の GV と RV2GV オプションをレキシカルの中にエイリアス化のために設定する必要があり、私たちはENTERITERに与えるリストを生成し、Padsを設定するためにそのリストを処理する必要があります。その複雑さは労力に見合いません。
パース可能な別の記法(Father Chrysostomos氏提案)
この記法が提案された2017年、Father Chrysostomos氏は一つ目に加えて、そのあとの2つも現行ではエラーになりパース出来ないことを観察しています。
for my ($key, $value) (%hash) { ... }
for my $key, my $value (%hash) { ... }
for $foo, $bar (%hash) { ... }
氏は、パースできない不正な記法は次のものだけだと記しました。
for ($foo, $bar) (%hash) { ... } # syntax error
https://www.nntp.perl.org/group/perl.perl5.porters/2017/04/msg243856.html
厳密には、私たちは次のものもパース出来ません:
for (my $key, my $value) (%hash) { ... }
これは、 for
のなかで次に類似するような記法の選択を提供できないことを意味します:
(my $foo, my $bar, my $baz) = @ARGV
my ($foo, $bar, $baz) = @ARGV
これは同じoptreeにパースされます。
しかし、この2行は 同一ではありません。
my $key, my $value = @pair;
my ($key, $value) = @pair;
したがって、私たちは次のような別の記法を提供すべきではありません:
for my $key, my $value (%hash) { ... }
実装上の問題から、私たちはレキシカルのみを扱いたいです。このことは次のものを除外します:
for $foo, $bar (%hash) { ... }
このように、これらの可能な別の記法は提供されるべきではありません。
feature guardの後ろにおかれるべきですか?
新しい記法はかつてはエラーであったように、あるいは以前には似たような状況でfeature guardを使わなかったように、いまは必要ないと考えます。
Open Issues
Copyright
Copyright (C) 2021, Nicholas Clark
This document and code and documentation within it may be used, redistributed and/or modified under the same terms as Perl itself.