1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

パイプラインでそれっぽく書く練習

Posted at

目標

下記の通りそれっぽく書きたい

> id "OK"
OK
> id const | apply "OK" "NG"
OK
> id const | flip | apply "NG" "OK"
OK
> id const | flip | curry | apply "NG" | apply "OK"
OK

$PSVersionTable

> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      3.0
WSManStackVersion              3.0
SerializationVersion           1.1.0.1
CLRVersion                     4.0.30319.18449
BuildVersion                   6.2.9200.17065
PSCompatibleVersions           {1.0, 2.0, 3.0}
PSRemotingProtocolVersion      2.2

id

小目標

パイプライン適用

@(1, 2, @(3, 4, @(5, 6))) | id | id | ... | id | Out-Hos

が、下記コマンドと同じ結果を出力する。

@(1, 2, @(3, 4, @(5, 6))) | Out-Host

通常適用

id (id (... (id (@(1, 2, @(3, 4, @(5, 6))))))

が、下記コマンドと同じ結果を出力する。

@(1, 2, @(3, 4, @(5, 6)))

出力

目標の出力結果は下記の通り。

> @(1, 2, @(3, 4, @(5, 6))) | Out-Host
1
2
3
4


Length         : 2
LongLength     : 2
Rank           : 1
SyncRoot       : {5, 6}
IsReadOnly     : False
IsFixedSize    : True
IsSynchronized : False
Count          : 2



> @(1, 2, @(3, 4, @(5, 6)))
1
2
3
4


Length         : 2
LongLength     : 2
Rank           : 1
SyncRoot       : {5, 6}
IsReadOnly     : False
IsFixedSize    : True
IsSynchronized : False
Count          : 2



fid

素朴に実装するとfidを通るたびに平坦化される。

filter fid
{
  Param (
    [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
    $x
  )

  $x
}
> @(1, 2, @(3, 4, @(5, 6))) | fid | Out-Host
1
2
3
4
5
6

cid

下記のように実装すると、平坦化の効果を打ち消せる。
ただし、通常適用された場合も平坦化の効果が消える。

filter cid
{
  Param (
    [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
    $x
  )

  ,$x
}
> cid @(1, 2, @(3, 4, @(5, 6)))
1
2


Length         : 3
LongLength     : 3
Rank           : 1
SyncRoot       : {3, 4, 5 6}
IsReadOnly     : False
IsFixedSize    : True
IsSynchronized : False
Count          : 3



実装

fidcidの結果から、下記の実装に落ち着く。

filter id
{
  Param (
    [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
    $x
  )

  if ($MyInvocation.ExpectingInput) { $x = ,$x }
  
  $x
}

const

小目標

通常適用

const @(1, 2, @(3, 4, @(5, 6))) "NG"

が、下記コマンドと同じ結果を出力する。

@(1, 2, @(3, 4, @(5, 6)))

パイプライン適用

"NG" | const @(1, 2, @(3, 4, @(5, 6))) | Out-Host

が、下記コマンドと同じ結果を出力する。

@(1, 2, @(3, 4, @(5, 6))) | Out-Host

実装

idと違って、パイプライン由来の値は使用しないので素朴な実装で目標を達成できる。

filter const
{
  Param
  (
    [Parameter(Mandatory = $true)]
    $x,

    [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
    $y
  )

  $x
}

replicate 3

> 1..3 | const "Hello"
Hello
Hello
Hello

flip

& f $x $yが例外を投げずに終了するとき、
& (flip f) $y $xや、
& (flip (flip f)) $x $yも例外を投げずに終了し、
かつそれらの結果が同一となること。

& (id f | flip) $y $xや、
& (id f | flip | flip) $x $yについても同様。

実装

スクリプトブロックを返せば良い。

filter flip
{
    Param
    (
      [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
      $f
    )

    {
      Param
      (
        $y,

        [Parameter(ValueFromPipeline = $true)]
        $x
      )

      Process
      {
        if ($MyInvocation.ExpectingInput) { $x = ,$x }

        & $f $x $y
      }
    }.GetNewClosure()
}

curry

小目標

& f $x $yが例外を投げずに終了するとき、
& (& (curry f) $x) $y
& (& (id f | curry) $x) $yも例外を投げずに終了し、
かつそれらの結果が同一となること。

実装

2重のスクリプトブロックを返せば良い。

filter curry
{
  Param
  (
    [Parameter(ValueFromPipeline = $true, Mandatory = $true)] 
    $f
  )

  {
    Param
    (
      [Parameter(ValueFromPipeline = $true)] 
      $x
    )

    Process
    {
      $f = $f
      if ($MyInvocation.ExpectingInput) { $x = ,$x }

      {
        Param
        (
          [Parameter(ValueFromPipeline = $true)] 
          $y
        )

        Process { 
          if ($MyInvocation.ExpectingInput) { $y = ,$y }

          & $f $x $y
        }
      }.GetNewClosure()
    }
  }.GetNewClosure()
}

apply

小目標

& f $x1 $x2 ... $xn
が例外を投げずに終了するとき、
apply f $x1 $x2 ... $xn

id f | apply $x1 $x2 ... $xn
も例外を投げずに終了し、
かつそれらが同じ結果になること。

実装

Invoke-Expression(eval)を使って実装している。黒い。

黒くない実装方法が知りたい。

filter apply
{
  if ($args.length -eq 0)
  { 
    if ($MyInvocation.ExpectingInput) { return & $_ }

    return Write-Error "適用対象の関数が指定されていません。"
  }

  $xs = $args

  $tail = "`$xs[" + ((0..($xs.length - 1)) -join "] `$xs[") + "]"

  if ($MyInvocation.ExpectingInput) { return Invoke-Expression ("& `$_ " + $tail) }

  Invoke-Expression ("& " + $tail)
}

おまけ

uncurry

> id const | curry | uncurry | apply "OK" "NG"
OK
filter uncurry{
  Param
  (
    [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
    $f
  )

  {
    Param
    (
      $x,

      [Parameter(ValueFromPipeline = $true)]
      $y
    )

    Process
    {
      if ($MyInvocation.ExpectingInput) { $y = ,$y }

      & (& $f $x) $y
    }
  }.GetNewClosure()
}

until

> 2, 3, 5 | until {$_ -ge 10} {$_ * 2}
16
12
10
filter until
{
  Param
  (
    [Parameter(Mandatory = $true)]
    $p,

    [Parameter(Mandatory = $true)]
    $f,

    [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
    $x
  )

  $_ = if ($MyInvocation.ExpectingInput) { ,$x } else { $x }

  while (-not (& $p $_)) { $_ = & $f $_ }

  $_
}

asTypeOf

> @(1, 2, @(3, 4, @(5, 6))) | asTypeOf @()
False
False
True
filter asTypeOf
{
  Param
  (
    [Parameter(Mandatory = $true)]
    $x,

    [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
    $y
  )

  if ($x -is $y.GetType()) { return $true }

  return $false
}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?