目標
下記の通りそれっぽく書きたい
> 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
実装
fid
とcid
の結果から、下記の実装に落ち着く。
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
}