John Rees
John Rees

Reputation: 1813

Explain scoping for functions called from PowerShell closures

The following PowerShell code displays unexpected scoping behavior for functions called from closures. Can you explain whether this is "by design", or is a defect?

function runblock($block) {
    $x = 4
    & $block
}

function printx() {
    "  in printx: x=" + $x
}

"PSVersion $($PSVersionTable.PSVersion)"

$x = 1
$b = {"In block x=" + $x ; $x = 3 ; printx}
$x = 2
runblock $b

$x = 1
$b = {"In closure x=" + $x ; $x = 3 ; printx}.GetNewClosure()
$x = 2
runblock $b

Output from the above is

PSVersion 3.0
In block x=4
  in printx: x=3
In closure x=1
  in printx: x=4

Most of the output makes sense to me:

The script block outputs In block x=4 since its parent scope is the runblock function. The printx function outputs x=3 since its parent scope is the script block scope.

The closure outputs In closure x=1 since the value of $x was captured by GetNewClosure call. All as expected.

BUT: The call to printx from the closure outputs in printx: x=4. So the scope that printx executes within is unaffected by the scope of the closure where $x = 3.

It seems strange to me that a function called from a normal script block does see the variables in the script block scope, but a function called from a closure does not see the variables in the closure.

Upvotes: 3

Views: 1841

Answers (1)

user4003407
user4003407

Reputation: 22132

Consider following code:

function Run {
    param($ScriptBlock)
    $a = 3
    # Picture 4
    & $ScriptBlock
}
function Print {
    # Picture 6
    "Print `$a=$a"
    "Print `$b=$b"
}
$a = 1
$b = 1
# Picture 1
$SB = {
    # Picture 5
    "Closure `$a=$a"
    "Closure `$b=$b"
    Print
}.GetNewClosure()
# Picture 2
$a = 2
$b = 2
# Picture 3
Run $SB

It prints:

Closure $a=1
Closure $b=1
Print $a=3
Print $b=2

Picture 1: you start from global scope, where you define some variables.
Picture 1
Picture 2: GetNewClosure() create new module and copy variables to it. (red arrow show parent scope relationship)
Picture 2
Picture 3: you change value of variables. Module scope not affected.
Picture 3
Picture 4: function Run called. It create local variable $a. (blue arrow show call direction)
Picture 4
Picture 5: $SB script block called. Script block bound to module session state, so you transit to it.
Picture 5
Picture 6: function Print called. Function bound to global session state, so you return to it.
Picture 6

Upvotes: 13

Related Questions