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


## 目標

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

```ps1
> 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

```ps1
> $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

### 小目標

#### パイプライン適用

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

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

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





#### 通常適用

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

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

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

#### 出力

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

```ps1
> @(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`を通るたびに平坦化される。

```ps1
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

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

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

  ,$x
}
```

```ps1
> 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



```

### 実装

`fid`と`cid`の結果から、下記の実装に落ち着く。

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

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






## const

### 小目標

#### 通常適用

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

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

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

### パイプライン適用

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

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

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

### 実装

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

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

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

  $x
}
```

### replicate 3

```ps1
> 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`についても同様。


### 実装

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

```ps1
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`も例外を投げずに終了し、
かつそれらの結果が同一となること。


### 実装

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

```ps1
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)を使って実装している。黒い。

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

```ps1
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

```ps1
> id const | curry | uncurry | apply "OK" "NG"
OK
```

```ps1
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

```ps1
> 2, 3, 5 | until {$_ -ge 10} {$_ * 2}
16
12
10
```

```ps1
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

```ps1
> @(1, 2, @(3, 4, @(5, 6))) | asTypeOf @()
False
False
True
```

```ps1
filter asTypeOf
{
  Param
  (
    [Parameter(Mandatory = $true)]
    $x,

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

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

  return $false
}
```