Quantcast
Channel: Hey, Scripting Guy! Blog
Viewing all articles
Browse latest Browse all 3333

Closures in PowerShell

$
0
0

Summary: Bruce Payette talks about Windows PowerShell closures and how to call the GetNewClosure method.

Microsoft Scripting Guy, Ed Wilson, is here. This week we will not have our usual PowerTip. Instead we have excerpts from seven books from Manning Press. In addition, each blog will have a special code for 50% off the book being excerpted that day. Remember that the code is valid only for the day the excerpt is posted. The coupon code is also valid for a second book from the Manning collection.

This excerpt is from Windows PowerShell in Action
  By Bruce Payette

Photo of book cover

Windows PowerShell uses dynamic modules to create dynamic closures. A closure in computer science terms (at least as defined in Wikipedia) is “a function that is evaluated in an environment containing one or more bound variables.” A bound variable is, for our purposes, a variable that exists and has a value. The environment in our case is the dynamic module. Finally, the function is simply a script block. In effect, a closure is the inverse of an object. An object is data with methods (functions) attached to that data. A closure is a function with data attached to that method.

The best way to understand what all this means is to look at an example. You’ll use closures to create a set of counter functions. The advantage closures give you over plain functions is that you can change what increment to use after the counter function has been defined. Here’s the basic function: 

function New-Counter ($increment=1)

{

    $count=0;

    {

        $script:count += $increment

        $count

    }.GetNewClosure()

}

There’s nothing you haven’t seen so far—you create a variable and then a script block that increments that variable—except for returning the result of the call to the Get-NewClosure() method. Let’s try this function to see what it does. First, create a counter: 

PS (1) > $c1 = New-Counter

PS (2) > $c1.GetType().FullName

System.Management.Automation.ScriptBlock 

Looking at the type of the object returned, you see that it’s a script block, so you use the & operator to invoke it: 

PS (3) > & $c1

1

PS (4) > & $c1

2

The script block works as you’d expect a counter to work. Each invocation returns the next number in the sequence. Now, create a second counter, but this time set the increment to 2

PS (5) > $c2 = New-Counter 2

Invoke the second counter scriptblock: 

PS (6) > & $c2

2

PS (7) > & $c2

4

PS (8) > & $c2

6

It counts up by 2. But what about the first counter? 

PS (9) > & $c1

3

PS (10) > & $c1

4

The first counter continues to increment by 1, unaffected by the second counter. So the key thing to notice is that each counter instance has its own copies of the $count and $increment variables. When a new closure is created, a new dynamic module is created, and then all the variables in the caller’s scope are copied into this new module.

Here are more examples of working with closures to give you an idea of how flexible the mechanism is. First, you’ll create a new closure by using a param block to set the bound variable $x. This is essentially the same as the previous example, except that you’re using a script block to establish the environment for the closure instead of a named function: 

PS (11) > $c = & {param ($x) {$x+$x}.GetNewClosure()} 3.1415

Now evaluate the newly created closed script block: 

PS (12) > & $c

6.283

This evaluation returns the value of the parameter added to itself. Because closures are implemented by using dynamic modules, you can use mechanisms to manipulate the state of a closure. You can do this by accessing the module object attached to the script block. You’ll use this object to reset the module variable $x by evaluating Set-Variable (sv) in the closure’s module context:

PS (13) > & $c.Module Set-Variable x "Abc"

Now evaluate the script block to verify that it’s been changed: 

PS (14) > & $c

AbcAbc

Next, create another script block closed over the same module as the first one. You can do this by using the NewBoundScriptBlock() method on the module to create a new script block that is attached to the module associated with the original script block:

PS (15) > $c2 = $c.Module.NewBoundScriptBlock({"x ia $x"})

Execute the new script block to verify that it’s using the same $x

PS (16) > & $c2

x ia Abc

Now use $c2.module to update the shared variable: 

PS (17) > & $c2.module sv x 123

PS (18) > & $c2

x ia 123

And verify that it’s also changed for the original closed script block: 

PS (19) > & $c

246

Finally, create a named function from the script block by using the function provider: 

PS (20) > $function:myfunc = $c

And verify that calling the function by name works: 

PS (21) > myfunc

246 

Set the closed variable yet again, but use $c2 to access the module this time: 

PS (22) > & $c2.Module sv x 3

Verify that it’s changed when you call the named function: 

PS (23) > myfunc

6

These examples should give you an idea about how all of these pieces—script blocks, modules, closures, and functions—are related. This is how modules work. When a module is loaded, the exported functions are closures bound to the module object that was created. These closures are assigned to the names for the functions to import. A fairly small set of types and concepts allow you to achieve advanced programming scenarios.

~Bruce

Here is the code for the discount offer today at www.manning.com: scriptw5
Valid for 50% off PowerShell in Action and SharePoint Web Parts in Action
Offer valid from April 5, 2013 12:01 AM until April 6, midnight (EST)

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy


Viewing all articles
Browse latest Browse all 3333

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>