What exactly is a PowerShell ScriptBlock
Per the docs, a scriptblock is a "precompiled block of script text." So by default you just a pre-parsed block of script, no more, no less. Executing it creates a child scope, but beyond that it's as if you pasted the code inline. So the most appropriate term would simply be "readonly source code."
Calling GetNewClosure
bolts on a dynamically generated Module which basically carries a snapshot of all the variables in the caller's scope at the time of calling GetNewClosure
. It is not a real closure, simply a snapshot copy of variables. The scriptblock itself is still just source code, and variable binding does not occur until it is invoked. You can add/remove/edit variables in the attached Module as you wish.
function GetSB{ $funcVar = 'initial copy' {"FuncVar is $funcVar"}.GetNewClosure() $funcVar = 'updated value' # no effect, snapshot is taken when GetNewClosure is called}$sb = GetSB& $sb # FuncVar is initial copy$funcVar = 'outside'& $sb # FuncVar is initial copy$sb.Module.SessionState.PSVariable.Remove('funcVar')& $sb # FuncVar is outside
A PowerShell ScriptBlock is equivalent to a first-class, anonymous function. Most of the confusion I've seen is not with ScriptBlocks, but with the function keyword.
- PowerShell does support function closures, however the function keyword does not.
Examples
Function:
PS> function Hello {>> param ([string] $thing)>> >> return ("Hello " + $thing)>> }PS> Hello "World""Hello World"
ScriptBlock:
PS> $HelloSB = {>> param ([string] $thing)>> >> return ("Hello " + $thing)>> }PS> & $HelloSB "World""Hello World"PS> $HelloRef = $HelloSBPS> & $HelloRef "Universe""Hello Universe"
Closure:
PS> $Greeter = {>> param ([string] $Greeting)>> >> return ( {>> param ([string] $thing)>> >> return ($Greeting + " " + $thing)>> }.GetNewClosure() )>> }PS> $Ahoy = (& $Greeter "Ahoy")PS> & $Ahoy "World""Ahoy World"PS> $Hola = (& $Greeter "Hola")PS> & $Hola "Mundo""Hola Mundo"
Although you can get around the limitation of the function keyword with the "Set-Item" cmdlet:
PS> function Greeter = { ... } # ✕ ErrorPS> function Greeter { ... }.GetNewClosure() # ✕ ErrorPS> Set-Item -Path "Function:Greeter" -Value $Greeter # (defined above) ✓ OKPS> $Hola = Greeter "Hola"PS> & $Hola "Mundo""Hola Mundo"
The Value parameter of the "Set-Item" cmdlet can be any ScriptBlock, even one returned by another function. (The "Greeter" function, for example, returns a closure, as shown above.)
PS> Set-Item -Path "Function:Aloha" -Value (Greeter "Aloha")PS> Aloha "World""Aloha World"
Two other important points:
PowerShell uses dynamic scoping, not lexical scoping.
A lexical closure is closed on its source-code environment, whereas a dynamic closure is closed based on the active/dynamic environment that exists when GetNewClosure() is called. (Which is more appropriate for a scripting language.)
PowerShell may have "functions" and "return" statements, but actually its input/output is based on streams and piping. Anything written out of a ScriptBlock with the "Write-Output" or "write" cmdlet will be returned.