PowerShellのパイプラインを遅延評価にしてみる
パイプラインからの入力をスクリプトブロックに包んで返せば、遅延評価にできるのでは? という思い付きのネタです。
例題
1から10までの整数列から偶数を抽出してその値を2乗するだけの簡単な処理を例に見てみます。
C#で書くとこんな感じ
var query = Enumerable.Range(1,10) .Where(n=> (n % 2) == 0) .Select(n=> n*n); foreach(var n in query) { Console.WriteLine(n); } /** output ** 4 16 36 64 100 */
PowerShell では以下のように書けます。
C#のSelect()
に相当する処理はSelect-Object
ではなくForEach-Object
を使いました。
また、このForEach-Object
は出力時のforeach ステートメントも兼ねています。
この一連の処理は遅延評価はされず、各コマンドレットへ値が渡った時点で即時実行されます。
PS C:\> 1..10 | Where-Object {($_ % 2) -eq 0 } | Foreach-Object { $_*$_ } 4 16 36 64 100
遅延評価版 Where、Select、Foreach
では、遅延評価のためのコマンドレットを作ってみます。
各コマンドレットの名前は Where-Block 、Select-Block、Foreach-Block とします。
Where-Block 、Select-Block はほぼ同じコードで、以下のような処理を行っています。
- Where-Block では $Predicate パラメータに bool値を返すスクリプトブロックを指定します。
- Select-Block では $Selector パラメータに 任意のオブジェクトを返すスクリプトブロックを指定します。
- Process{}では、スクリプトブロックを返却します
- スクリプトプロック内では以下の処理を行います
Where-Block、Select-Block とコマンドをつなげて実行すると、つなげたコマンドの数だけネストしたスクリプトブロック(の配列)が得られます。 これを Foreach-Block で 一気に実行する、という仕組みです。
実行してみる
$Predicate,$Selector に渡ってくる引数はParam($x)でアクセスします。
PS C:\> $query = 1..10 | Where-Block -Predicate {Param($x) ($x % 2) -eq 0} | Select-Block -Selector {Param($x) $x*$x} PS C:\> $query | Foreach-Block 4 16 36 64 100
期待通りの結果にはなりました。が、本当に遅延評価になっているのかよくわかりません。
各コマンドレットに Write-Verbose を仕込んで確認してみましょう。
(Write-Verbose版のスクリプトはこちら https://gist.github.com/pierre3/c62db52d977bc2841ff4)
PS C:\> $query = 1..10 | Where-Block -Predicate {Param($x) ($x % 2) -eq 0} -Verbose | Select-Block -Selector {Param($x) $x*$x} -Verbose PS C:\> $query | Foreach-Block -Verbose 詳細: [Where] Input Data = 1 詳細: [Where] Predicate(1) 詳細: [Where] Input Data = 2 詳細: [Where] Predicate(2) 詳細: [Where] return 2 詳細: [Select] Predicate(2) 詳細: [Select] return 4 詳細: [Foreach] return 4 4 詳細: [Where] Input Data = 3 詳細: [Where] Predicate(3) 詳細: [Where] Input Data = 4 詳細: [Where] Predicate(4) 詳細: [Where] return 4 詳細: [Select] Predicate(4) 詳細: [Select] return 16 詳細: [Foreach] return 16 16 詳細: [Where] Input Data = 5 詳細: [Where] Predicate(5) 詳細: [Where] Input Data = 6 詳細: [Where] Predicate(6) 詳細: [Where] return 6 詳細: [Select] Predicate(6) 詳細: [Select] return 36 詳細: [Foreach] return 36 36 詳細: [Where] Input Data = 7 詳細: [Where] Predicate(7) 詳細: [Where] Input Data = 8 詳細: [Where] Predicate(8) 詳細: [Where] return 8 詳細: [Select] Predicate(8) 詳細: [Select] return 64 詳細: [Foreach] return 64 64 詳細: [Where] Input Data = 9 詳細: [Where] Predicate(9) 詳細: [Where] Input Data = 10 詳細: [Where] Predicate(10) 詳細: [Where] return 10 詳細: [Select] Predicate(10) 詳細: [Select] return 100 詳細: [Foreach] return 100 100
1行目の実行時には何も表示されず、Foreach-Block を実行した時点で一度に表示されました。
スクリプトブロックが順番に実行される様子も良く分かります。
スクリプトブロックにまとめる段階で一度 配列(コレクション)が展開されてしまうのは、ショウガナイ?