ルモーリン
ホーム更新サービス雑談ランドナーコースガイド鉄ゲタ自転車Linuxリンク連絡先

コンテキストによるreverseとファイルハンドルの挙動

こんなツイートを見かけて

再現に挑戦

コードはこちら。
#!/usr/bin/env perl -w

use utf8;
use strict;
use warnings;

use v5.10;
use boolean;
use Encode::Argv;
use Encode::Locale;

use open IO => ":utf8";
binmode STDIN, ":encoding(console_in)";
binmode STDOUT, ":encoding(console_out)";

$| = 1;

open FH, "<", "sample_50.txt" or die "オープン失敗";

while my $line (reverse <FH>) {
	chomp $line;
	say "'$line'";
}

close FH;

再現に失敗

オプション「-cw」を追加してチェックすると実行できない事が分かりました。
syntax error at sample_50.pl line 20, near "while my "
Global symbol "$line" requires explicit package name (did you forget to declare "my $line"?) at sample_50.pl line 20.
Global symbol "$line" requires explicit package name (did you forget to declare "my $line"?) at sample_50.pl line 21.
Global symbol "$line" requires explicit package name (did you forget to declare "my $line"?) at sample_50.pl line 22.
syntax error at sample_50.pl line 23, near "}"
sample_50.pl had compilation errors.
shell returned 255

再挑戦

whileをforにするとチェックが通ります。
#!/usr/bin/env perl -w

use utf8;
use strict;
use warnings;

use v5.10;
use boolean;
use Encode::Argv;
use Encode::Locale;

use open IO => ":utf8";
binmode STDIN, ":encoding(console_in)";
binmode STDOUT, ":encoding(console_out)";

$| = 1;

open FH, "<", "sample_50.txt" or die "オープン失敗";

for my $line (reverse <FH>) {
	chomp $line;
	say "'$line'";
}

close FH;

入力ファイルと実行結果

ファイルハンドルをリストコンテキストで評価、全部の行を読み込んで1行1要素のリストを返し、reverseが逆順にしてforループに渡しました。 簡単に言うとファイルの行を逆順にしています。
入力ファイルsample_51.txtの内容(utf8)
本日は晴天なり。
明日も良い天気になると良いなあ。
ここが3行目です。
これ↑がこう↓なります(結果を引用符で囲んで、改行位置をチェック)。
'ここが3行目です。'
'明日も良い天気になると良いなあ。'
'本日は晴天なり。'

途中経過の予想

ループの基準になるリストがどういう風に生成されるのか予想してみました。
これが元のコード
for my $line (reverse <FH>) {
	chomp $line;
	say "'$line'";
}
reverseのパラメタはリストコンテキスト。 リストコンテキストでのファイルハンドルは1行1要素のリストを返します。
for my $line (reverse "本日は晴天なり。\n", "明日も良い天気になると良いなあ。\n", "ここが3行目です。\n") {
	chomp $line;
	say "'$line'";
}
forのリストなのでリストコンテキスト(そりゃそうだ)ですから、reverseはリストの要素を逆順にしたリストを返します。
for my $line ("ここが3行目です。\n", "明日も良い天気になると良いなあ。\n", "本日は晴天なり。\n") {
	chomp $line;
	say "'$line'";
}
ループ内の処理をします。 説明用にchompにリテラルを書きましたけれど、リテラルを書き換えできない(左辺値でない)のでエラーになります。
chomp "ここが3行目です。\n";
say "'$line'";
chomp "明日も良い天気になると良いなあ。\n";
say "'$line'";
chomp "本日は晴天なり。\n";
say "'$line'";
chompが末尾の改行コードを削除します。
say "'ここが3行目です。'";
say "'明日も良い天気になると良いなあ。'";
say "'本日は晴天なり。'";
はい、できあがり(ホントかなあ)。
'ここが3行目です。'
'明日も良い天気になると良いなあ。'
'本日は晴天なり。'

reverseやファイルハンドルの挙動を確認

reverseとファイルハンドル(を山括弧で囲んだIO演算子)は、それぞれリストコンテキストとスカラコンテキストで挙動が異なります。

「リストコンテキストでは、LIST を構成するよ要素を逆順に並べた リスト値を返します。」
「スカラコンテキストでは、LIST の要素を連結して、 全ての文字を逆順にした文字列を返します。」
Perlの組み込み関数 reverse の翻訳 - perldoc.jp

「スカラーコンテキストで山括弧の中のファイルハンドルを評価すると、 そのファイルから、次の行を読み込むことになります (改行があればそれも含まれます)」
「<FILEHANDLE> がリストを必要とするコンテキストで用いられると、 1 要素に 1 行の入力行すべてからなるリストが返されます。」
perlop - Perl の演算子と優先順位 - perldoc.jp

確認してみましょう。 コードはこちら。
#!/usr/bin/env perl -w

use utf8;
use strict;
use warnings;

use v5.10;
use boolean;
use Encode::Argv;
use Encode::Locale;

use constant SAMPLE_FILE => "sample_49.txt";

use open IO => ":utf8";
binmode STDIN, ":encoding(console_in)";
binmode STDOUT, ":encoding(console_out)";

$| = 1;

say "ループで逆順に読む";
open my $fh, "<", SAMPLE_FILE or die "オープン失敗";
for my $line (reverse <$fh>) {
	chomp $line;
	say "'$line'";
}
close $fh;

open $fh, "<", SAMPLE_FILE or die "オープン失敗";
my @a = <$fh>;
chomp @a;
close $fh;
say "ファイルハンドルをリストコンテキストで呼ぶと「@a」が返る";

open $fh, "<", SAMPLE_FILE or die "オープン失敗";
my $b = <$fh>;
chomp $b;
close $fh;
say "ファイルハンドルをスカラコンテキストで呼ぶと「$b」が返る";

my @c = qw /あ い う え お/;
my $d = reverse @c;
say "リスト「@c」をスカラコンテキストでreverseに渡すと「$d」が返る";

my @e = reverse @c;
say "リスト「@c」をリストコンテキストでreverseに渡すと「@e」が返る";

say "スカラコンテキストでfunc_fを呼ぶ";
my $g = func_f();

say "リストコンテキストでfunc_fを呼ぶ";
my @h = func_f();

say "スカラコンテキストでreverseを呼ぶ際の引数にfunc_f";
my $i = reverse func_f();
say "リストコンテキストでreverseを呼ぶ際の引数にfunc_f";
my @j = reverse func_f();

say "forループのリストにreverseとfunc_f";
for my $line2 (reverse func_f()) {
	chomp $line2;
	say "'$line2'";
}

sub func_f {
	if (wantarray) {
		say "→func_fはリストコンテキストで呼ばれました";
	} else {
		say "→func_fはスカラコンテキストで呼ばれました";
	}

	return qw /たこルカ は 俺の嫁/;
}

実行結果

再現できず仕様通りの結果になってしまいました。
ループで逆順に読む
'ここが3行目です。'
'明日も良い天気になると良いなあ。'
'本日は晴天なり。'
ファイルハンドルをリストコンテキストで呼ぶと「本日は晴天なり。 明日も良い天気になると良いなあ。 ここが3行目です。」が返る
ファイルハンドルをスカラコンテキストで呼ぶと「本日は晴天なり。」が返る
リスト「あ い う え お」をスカラコンテキストでreverseに渡すと「おえういあ」が返る
リスト「あ い う え お」をリストコンテキストでreverseに渡すと「お え う い あ」が返る
スカラコンテキストでfunc_fを呼ぶ
→func_fはスカラコンテキストで呼ばれました
リストコンテキストでfunc_fを呼ぶ
→func_fはリストコンテキストで呼ばれました
スカラコンテキストでreverseを呼ぶ際の引数にfunc_f
→func_fはリストコンテキストで呼ばれました
リストコンテキストでreverseを呼ぶ際の引数にfunc_f
→func_fはリストコンテキストで呼ばれました
forループのリストにreverseとfunc_f
→func_fはリストコンテキストで呼ばれました
'俺の嫁'
'は'
'たこルカ'

まとめ

とりあえず仕様通りに動作していて、再現できませんでした(泣)。 古いバージョンで実行すると挙動が変わるのかも?(・_・ ) ( ・_・)