What exactly is a PowerShell ScriptBlock

后端 未结 2 900
南旧
南旧 2021-02-07 20:37

A PowerShell ScriptBlock is not a lexical closure as it does not close over the variables referenced in its declaring environment. Instead it seems to leverage dynamic scope and

相关标签:
2条回答
  • 2021-02-07 21:13

    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 = $HelloSB
    PS> & $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 = { ... }  # ✕ Error
    PS> function Greeter { ... }.GetNewClosure()  # ✕ Error
    
    PS> Set-Item -Path "Function:Greeter" -Value $Greeter  # (defined above)  ✓ OK
    PS> $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.

    0 讨论(0)
  • 2021-02-07 21:25

    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
    
    0 讨论(0)
提交回复
热议问题