これはMoritz Lenz氏のWebサイトPerlgeek.deで公開されているブログ記事"Perl 5 to 6" Lesson 12 - Lazinessの日本語訳です。
原文はCreative Commons Attribution 3.0 Germanyに基づいて公開されています。
本エントリにはCreative Commons Attribution 3.0 Unportedを適用します。
Original text: Copyright© 2008-2010 Moritz Lenz
Japanese translation: Copyright© 2011 SATOH Koichi
NAME
"Perl 5 to 6" Lesson 12 - 遅延性
SYNOPSIS
my @integers = 0..*;
for @integers -> $i {
say $i;
last if $i % 17 == 0;
}
my @even := map { 2 * $_ }, 0..*;
my @stuff := gather {
for 0 .. Inf {
take 2 ** $_;
}
}
DESCRIPTION
Perlプログラマは怠けがちです。彼らが使うリストも。
ここで怠惰という言葉が意味するのは、評価が可能な限り遅延されるということです。
@a := map BLOCK, @b
のようなコードを書いたとき、ブロックは一切実行されません。
@a
の要素にアクセスしようとしたときだけmap
は実際にブロックを実行し、必要とされる分だけ@a
を埋めます。
代入ではなくバインディングを使っていることに注意して下さい: 配列への代入は先行評価を強制することがあります(コンパイラがリストの無限性に気づかない限り; 無限リスト検出の詳細はまだ固まっていません)。 バインディングはそのようなことがありません。
遅延性は無限リストの取り扱いを可能にします: 引数すべてに操作を行うようなことさえしなければ、評価された要素に必要なだけのメモリしか必要としません。
しかし落とし穴があります: 長さの取得やソートは遅延性を殺します——無限リストであっても。その場合無限ループになるでしょう。
一般にスカラへの変換(例えばList.join
)は先行評価です。つまり遅延評価されません。
遅延性は不必要な計算を防ぎ、それによってコードの簡潔性を保ったままパフォーマンスを上げることができます。
Perl5で一行ずつファイルを読む場合、for (<HANDLE>)
はファイル全体をメモリに読み込んだ後に走査を始めるのであまり使われませんでした。
遅延性があればこれは問題になりません:
my $file = open '/etc/passwd';
for $file.lines -> $line {
say $line;
}
$file.line
はイテレータか遅延リスト(どっちでも同じことです)なので、行は必要な分だけがディスクから物理的に読み込まれます(当然バッファリングはかませた上で)。
gather/take
遅延リストを作るのに非常に便利な構造がgather { take }
です。
以下のようにして使います:
my @list := gather {
while True {
# 何か計算;
take $result;
}
}
gather BLOCK
は遅延リストを返します。
@list
の要素が必要になるとtake
が実行される位置までBLOCK
が走ります。
take
は丁度return
のようなもので、take
された要素はすべて@list
の生成に使われます。
@list
の要素がもっと必要になると、ブロックの実行がtake
の直後から再開されます。
gather/take
は動的スコープを持つので、gather
の字句的スコープの外でtake
を呼ぶことができます:
my @list = gather {
for 1..10 {
do_some_computation($_);
}
}
sub do_some_computation($x) {
take $x * ($x + 1);
}
遅延性の制御
遅延性には固有の問題があり(Haskellを学んだことがあるなら、そのIOシステムが遅延性と無副作用性のためにどれだけ妙なことになっているか気づいたでしょう)、時には遅延して欲しくないものもあります。その場合eager
を先頭に付けるだけです。
my @list = eager map { $block_with_side_effects }, @list;
逆に言うと、リストだけがデフォルトで遅延されます。ただしスカラも遅延させることができます:
my $ls = lazy { $expansive_computation };
MOTIVATION
計算機科学ではほとんどの問題は可能な組み合わせの木として記述され、その中で解が探索されます。 効率的なアルゴリズムの鍵は効率的な探索法だけではなく、木の重要な部分だけを生成することでもあります。
遅延リストを使えば、この木と探索法を再帰的に定義し、実際に使っている部分木のみを自動的に生成できます。
一般に遅延性はプログラミングを簡単にします。計算結果がすべて使われるか気にせず済むからです——遅延評価にするだけで、もし結果が使われなければ計算は実行されず、使われたなら何も損していません。
コメント
コメントを投稿