Powershell script blocks are not closures
I've been experimenting some more with the script blocks, and I've found that my description of them as sort-of-closures in my other post is wrong. Here is an example that demonstrates it:
PS > function g { param($block) $a=234; &$block; }
PS > function f { $a=123; g { "The value of `$a is $a" } }
PS > f
The value of $a is 234
They simply use the variable values from the nearest enclosing block in the same module.
But there is a way to get a sort-of-closure too:
PS > function f { $a=123; g { "The value of `$a is $a" }.GetNewClosure() }
PS > f
The value of $a is 123
The sort-of part is because the value of $a in f() cannot be changed from inside the script block.
My next idea was to use the references but that didn't quite work out either:
PS > function f { $a=123; g { $aa=[ref]$a; "The value of `$a is $($aa.value)"; $aa.value = 10; "The new value of `$a is $($aa.value)" }.GetNewClosure(); $a }
PS > f
The value of $a is 123
The new value of $a is 10
123
The problem is GetNewClosure() doesn't create a reference to the variables in the current scope but just copies their values to the new variables. Thus [ref] gets a reference to the copy, and this copy is not visible inside the function f().
Without GetNewClosure, the script block ends up changing the value of the variable in the nearest scope, which happens to be in g():
PS > function g { param($block) $a=234; &$block; "in g `$a is $a" }
PS > function f { $a=123; g { $aa=[ref]$a; "The value of `$a is $($aa.value)"; $aa.value = 10; "The new value of `$a is $($aa.value)" }; $a }
PS > f
The value of $a is 234
The new value of $a is 10
in g $a is 10
123
The way to make it behave like a proper closure, being able to change the variables in the original scope, is to get the reference directly in f() and then fixate it with GetNewClosure():
PS C:\Users\sbabkin> function f { $a=123; $aa=[ref]$a; g {"The value of `$a is $($aa.value)"; $aa.value = 10; "The new value of `$a is $($aa.value)" }.GetNewClosure(); $a }
PS C:\Users\sbabkin> f
The value of $a is 123
The new value of $a is 10
in g $a is 234
10
It finally works but oh so painfully.
Comments
Anonymous
January 05, 2015
Hi Sergey - yes - closures are pretty weird in PowerShell, This is because the language is (kind of) dynamically scoped. All variables from the call (dynamic) scope are visible when a function is executed. Proper closures really require lexical scoping. However, the GetNewClosure() method allows you to have some of the benefits of proper closures in a dynamically scoped language. Basically what it does is create a new anonymous module, copies the variables from the nearest enclosing dynamic scope into that module and then binds the scriptblock to that module. When the scriptblock is executed it sees what was copied into that module. So you can, in fact, change these value by using the $script:var scope qualifier. From your example, this changes the value of abc: function f { $a=123; { "The value of$a is $a"; $script:a= "Hi there" ; "The value of
$a is $a"; }.GetNewClosure() } I go into a lot more detail on this stuff in my book. Cheers, -bruce- Anonymous
June 08, 2017
I'm really interested in accessing class methods from within a ScriptBlock being remotely executed, and having no luck at all finding anyone with experience in this.I've had the though that creating closures which reference the class methods available in the enclosing scope, but this seems REALLY kludgey.Any suggestions are appreciated immensely.Regards,Adam- Anonymous
June 23, 2017
The comment has been removed
- Anonymous
- Anonymous
Anonymous
January 05, 2015
Is there some reason why the variables are copied by value and not simply referenced? I think the trick with [ref] shows that if the original variables were referenced, they could be modified from the script blocks.