As part of learning its basics i am implementing a ternary operator cmdlet in pws. I have it taking scriptblocks, to emulate the conditional evaluation ternary operators usually have. And in most instances it works fine.
function valOf($var){
if($var -is [scriptblock]){
return & $var }
else {
return $var }
}
function ternary([bool]$condition, $t, $f){
if($condition){
return valOf($t) }
else{
return valOf($f) }
#return @($t,$f)[!$condition]
}
I got in trouble when i started nesting scriptblocks:
$i=56;
&{
$i=0
ternary($true) {$script:i+=2} {write-host "onFalse"}
$i #wanted:2 #reality: 58
<# without '$script: $i' is indeed 0, but cannot be edited #>
}
$i #wanted:56 #reality:58
How can i access the middle scope?
browsing the documentation as well as the forum this seems to be quite a common issue, but the theme is anything but clear x.x
Perhaps an invokeCommand that optsOut from the copyOnWrite behaviour..?
An alternative to Santiago's helpful answer that makes it unnecessary to use special syntax in the script-block arguments passed to your ternary function:
You can combine dynamic modules with dot-sourcing:
Note: For brevity:
The ternary
function below doesn't handle the case where the arguments aren't script blocks, but that's easy to add.
The [CmdletBinding()]
attribute is omitted; you don't strictly need an advanced function to make the solution work, though it's certainly advisable, and adding something like [Parameter(Mandatory)]
would implicitly make your function an advanced one.
# Create (and implicitly import) a dynamic module that
# hosts the ternary function.
$null = New-Module {
# Define the ternary function.
function ternary {
param(
[bool] $Condition,
[scriptblock] $trueBlock,
[scriptblock] $falseBlock
)
# Dot-source the appropriate script block,
# which runs directly in the *caller's* scope,
# given that's where it was created and given that the
# module's code runs in a separate scope domain ("session state")
if ($Condition) { . $trueBlock } else { . $falseBlock }
}
}
$i=56;
& {
$i=0
# Now you can use $i as-is
# in order to refer to the current scope's $i.
ternary $true { $i+=2 } {write-host "onFalse"}
$i # -> 2
}
$i # -> 56
Note:
While a dynamic module is used above, the technique equally works with persisted modules (*.psm1
)
This answer provides a comprehensive overview of scopes in PowerShell, including the separate scope domains (trees) for modules, somewhat unfortunately called session states in the official documentation