PowerShell で LINQ

id:NyaRuRu:20080112:p1 の続き.

初版からの変更点

  • take のバグフィックス
  • 実験的に PowerShell v2.0 対策 (内部実装に依存する対策なので微妙)
  • ストリーミング出力の自動フラット化を抑制 (LINQ のセマンティクスを採用)
  • xrepeat の引数を scriptblock で一本化し,switch で using を抑制できるようにした
  • C# 版に .ForEach(Console.WriteLine) を,PowerShell 版に暗黙の Out-Default を明示して対応を明確にした
  • (追記)PowerShell v2.0 (CTP) で end block が実行されない問題対策
#PowerShell 2.0

function xrepeat
{
  param([scriptblock] $initializer = $(throw "element-initializer should be set"),
        [switch] $suppressdispose)
  begin
  {
    $element=&$initializer
  }
  process
  {
    trap { continue }
    while($true){ ,$element }
  }
  end
  {
    if(! $suppressdispose)
    {
      $disposable = $element -as [IDisposable]
      if($disposable -ne $null) { $disposable.Dispose() }
    }
  }
}

function xtake
{
  param([int] $count)
  begin
  {
    if(0 -ge $count) { break }
  }
  process
  {
    ,$_
    if(0 -ge --$count) { break }
  }
}

filter xselect
{
  param([scriptblock] $selector)
  ,(&$selector $_)
}

filter xwhere
{
  param([scriptblock] $predicate)
  if(&$predicate $_) { ,$_ }
}

filter xtakewhile
{
  param([scriptblock] $predicate)
  if(&$predicate $_) { ,$_ } 
  else { break }
}

こんな感じで filter を用意して,『C# 3.0 と while(true) と Iterator』を再現.

win.ini のダンプ

// C# 3.0 with EnumerableEx
EnumerableEx.Repeat(() => new StreamReader(@"c:\windows\win.ini"))
            .Select(sr => sr.ReadLine())
            .TakeWhile(line => line != null)
            .ForEach(Console.WriteLine);
# PowerShell
xrepeat {new-object IO.StreamReader 'c:\windows\win.ini'} `
  | xselect { $_.ReadLine() } `
  | xtakewhile { $_ -ne $null } `
  | Out-Default

1 桁の乱数を 100 個得る

// C# 3.0 with EnumerableEx
EnumerableEx.Repeat( new Random() )
            .Select(rand => rand.Next(0, 10));
            .Take(100)
            .ForEach(Console.WriteLine);
# PowerShell
xrepeat {new-object Random} `
  | xselect { $_.Next(0,10) } `
  | xtake 100 `
  | Out-Default

10 秒カウントダウン

// C# 3.0 with EnumerableEx
EnumerableEx.Repeat(() => DateTime.Now.AddSeconds(10.0))
            .Select(time => time - DateTime.Now)
            .TakeWhile(span => span.TotalSeconds > 0);
            .Select(span => span.TotalSeconds)
            .ForEach(Console.WriteLine);
# PowerShell
xrepeat { (Get-Date).AddSeconds(10.0) } `
  | xselect { $_ - (Get-Date) } `
  | xtakewhile { $_.TotalSeconds -gt 0 } `
  | xselect { $_.TotalSeconds } `
  | Out-Default