12

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 free variables which are bound at run time in a lambda expression.

function Get-Block {
  $b = "PowerShell"
  $value = {"Hello $b"}
  return $value
}
$block = Get-Block
& $block
# Hello
# PowerShell is not written as it is not defined in the scope
# in which the block was executed.


function foo {
  $value = 5
  function bar {
    return $value
  }
  return bar
}
foo
# 5
# 5 is written $value existed during the evaluation of the bar function
# it is my understanding that a function is a named scriptblock
#  which is also registered to function:

Calling GetNewClosure() on a ScriptBlock returns a new ScriptBlock which closes over the variables referenced. But this is very limited in scope and ability.

What is a ScriptBlock's classification?

Ian Davis
  • 3,848
  • 1
  • 24
  • 30
  • 3
    "Closure" and "Lambda" are both good approximations. I'm not sure it makes a lot of sense to dog after "exact terminology" - maybe it's just "ScriptBlock" ;) ANYWAY: [here's a good definition](http://www.bjd145.org/2008/02/one-of-interesting-features-that-is-i.html): "The way that I define it is a function or block of code that is assigned to a variable". IMHO... – paulsm4 Sep 24 '12 at 23:11
  • The point of the question is that I really want to know from a language theory perspective what a ScriptBlock technically is. – Ian Davis Sep 24 '12 at 23:16
  • 1
    Keep in mind that PowerShell doesn't use lexical scoping. It uses dynamic scoping (scoped by the current call stack up to the module scope (optionally) and then up to the global scope) and provides copy-on-write semantics for variables by default. – Keith Hill Sep 24 '12 at 23:39
  • 2
    @Ian Davis: My point was, that maybe there *isn't* an "exact term" in "language theory". Maybe "ScriptBlock" is the most precise term that exists. It's certainly better than "kind-of-like-but-not-quite-a-lambda" ;) – paulsm4 Sep 24 '12 at 23:46
  • 1
    I would argue that ["*anonymous functions*"](http://en.wikipedia.org/wiki/Anonymous_function) do not strictly imply closures, or even the ability to provide a lexical binding. However, they are *often* (but not exclusively) found with such behavior .. –  Sep 25 '12 at 01:01
  • @KeithHill I mention the dynamic scope in the first paragraph. You are correct, but it still doesn't help in understanding the classification of a ScriptBlock. – Ian Davis Sep 25 '12 at 12:51
  • @paulsm4 I would be very shocked if PowerShell came up with a new language construct. – Ian Davis Sep 25 '12 at 12:52

2 Answers2

8

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
latkin
  • 16,402
  • 1
  • 47
  • 62
  • you say "it is not a real closure". Out of curiosity, what would constitute a "real closure" in a dynamically scoped environment? – Sam Porch Nov 04 '15 at 00:51
7

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.

Sam Porch
  • 751
  • 5
  • 12