Section 9.10. Power Handler Tricks


9.10. Power Handler Tricks

A handler takes values as parameters and returns a value. A handler is a value. A script object is a value and can contain a handler. If these facts suggest to your mind an intimation of amazing possibilities, read on.

9.10.1. Handler and Script Object as Parameter

You can pass a handler as a parameter to handler. The difficulty is in calling it. This code fails with a runtime error:

 on sayHowdy( )     display dialog "Howdy" end sayHowdy on doThis(what)     what( ) end doThis doThis(sayHowdy) -- error: «script» doesn't understand the what message

The trouble is that AppleScript refuses to identify the what( ) in the handler call with the what that arrived as a parameter. This is actually another case of the rule (see "Handler Calls, Commands, and Script Objects" in Chapter 8) that an unqualified handler call is a message directed to the current script object, which in this case is the script as a whole.

One possible workaround is to use a global. This approach works because by copying the handler to a global we're putting it where a message directed to the script as a whole can find it (see "Scope of Globals" in Chapter 10):

 on sayHowdy( )     display dialog "Howdy" end sayHowdy on doThis(what)     global what2     set what2 to what     what2( ) end doThis doThis(sayHowdy) -- Howdy

This solution is clever, but now we've broken encapsulation. Global variables pose risks (other code might access this same global, or we might be tromping accidentally on some other code's global), and besides, if we're going to use a global there's little point to passing a parameter in the first place.

Another possible workaround is to pass a script object instead of a handler:

 script sayHowdy     display dialog "Howdy" end script on doThis(what)     run what end doThis doThis(sayHowdy) -- Howdy

This is very efficient because script objects are passed by reference. But we ended up having to use the run command instead of a handler call. That's not going to be very pretty if our handler takes any parameters, because it's hard to pass parameters to a run handler (as shown earlier in this chapter). But waita script object can contain a handler! So we can use it as a kind of envelope. We can define a handler in a script object and pass the script object. In fact, this device permits both the script and the handler that receives it as a parameter to be completely general:

 script myScript     on doAnything( )     end doAnything     doAnything( ) end script on doThis(what)     run what end doThis on sayHowdy( )     display dialog "Howdy" end sayHowdy set myScript's doAnything to sayHowdy doThis(myScript) -- Howdy

However, there's another way entirely. This happens to be my favorite solution. We don't pass a script object; we pass a handler, just as in our first attempt. But inside the handler, we have a script object waiting to receive it:

 on sayHowdy( )     display dialog "Howdy" end sayHowdy on doThis(what)     script whatToDo         property theHandler : what         theHandler( )     end script     run whatToDo end doThis doThis(sayHowdy) -- Howdy

The fact that this code works is astonishing (at least, when I stumbled upon it I was astonished). It depends upon an obscure but powerful rule of scope (see "Scope of Locals" in Chapter 10) which says that a script object within a handler can see that handler's local variables. An incoming parameter is such a variable. Thanks to this rule, our property declaration for theHandler can see the incoming what parameter and store its value. So now the property theHandler is a handler! But it is also a top-level entity of the script object, which means that we can call it from within this same script object. Tricky, eh?

For a useful application of this technique, let's return to the example earlier in this chapter where a handler called filter filtered a list to get only those members of the list that were numbers. That handler is not general; we'd like a way to filter a list on any criterion we care to provide. So we want to pass it both a list and a handler (a handler that takes a single argument and returns a boolean saying whether it fits the criterion). We can do so by writing filter( ) as a handler containing a script object:

 on filter(L, crit)     script filterer         property criterion : crit         on filter(L)             if L = {} then return L             if criterion(item 1 of L) then                 return {item 1 of L} & filter(rest of L)             else                 return filter(rest of L)             end if         end filter     end script     return filterer's filter(L) end filter on isNumber(x)     return ({class of x} is in {real, integer, number}) end isNumber filter({"hey", 1, "ho", 2, 3}, isNumber)

I consider that example to be the height of the AppleScript programmer's art, so perhaps you'd like to pause a moment to admire it.

9.10.2. Handler and Script Object as Result

A handler may be returned as the result of a handler. Because you can't define a handler directly within a handler, you might have to define it as a handler within a script object within the handler; but this is no bother. So, for example:

 on makeHandler( )     script x         on greet( )             display dialog "Howdy"         end greet     end script     return x's greet end makeHandler set y to makeHandler( ) y( ) -- Howdy

Of itself, however, this device is not terribly useful; in real life, you're more likely to return a script object rather a handler:

 on scriptMaker( )     script myScript         property x : "Howdy"         display dialog x     end script     return myScript end scriptMaker set s to scriptMaker( ) run s -- Howdy

In the last two lines, we acquire the script object returned by the handler scriptMaker, and run it. Of course, if we didn't want to retain the script object, these two lines could be combined into one:

 run scriptMaker( ) -- Howdy

Why might it be useful to return a script object as the result of a handler? Mostly because the handler can customize the script object before returning it. For example, instead of hard-coding the property x that will be displayed, we can pass that value into the handler as a parameter:

 on scriptMaker(what)     script myScript         property x : what         display dialog x     end script     return myScript end scriptMaker set s to scriptMaker("Hello") run s -- Hello

The real virtue of this technique emerges when we retain and reuse the resulting script object. To illustrate, here's one more version of the general list-filtering routine we wrote earlier in this chapter. Previously, we passed a handler both a criterion handler and a list, and got back a filtered list. Now we're going to pass just a criterion handler, and get back a script object, which we will retain. Now we've got a script object containing a filter handler that is already customized to filter any list according the criterion we passed in at the outset. In effect, we've built a custom handler! Now we can reuse that handler repeatedly with different lists. We can even create more than one such handler, each customized to be a certain kind of filter. This architecture is elegant and efficient (and LISPy):

 on makeFilterer(crit)     script filterer         property criterion : crit         on filter (L)             if L = {} then return L             if criterion(item 1 of L) then                 return {item 1 of L} & (filter (rest of L))             else                 return filter (rest of L)             end if         end filter     end script     return filterer end makeFilterer on isNumber(x)     return ({class of x} is in {real, integer, number}) end isNumber on isText(x)     return ({class of x} is in {string, text, Unicode text}) end isText set numbersOnly to makeFilterer(isNumber) set textOnly to makeFilterer(isText) tell numbersOnly     filter ({"hey", 1, "ho", 2, "ha", 3}) -- {1, 2, 3}     filter ({"Mannie", 7, "Moe", 8, "Jack", 9}) -- {7, 8, 9} end tell tell textOnly     filter ({"hey", 1, "ho", 2, "ha", 3}) -- {"hey", "ho", "ha"}     filter ({"Mannie", 7, "Moe", 8, "Jack", 9}) -- {"Mannie", "Moe", "Jack"} end tell

Another use for a script object as a result of a handler is as a constructor . Here we take advantage of the fact that when a handler is called, it initializes any script objects defined within it. So a handler is a way to produce a copy of a script object whose properties are at their initial value.

As an example, consider a script object whose job is to count something. It contains a property c, which maintains the count, and a handler that increments the count. (This approach is using a sledgehammer to kill a fly, but it's a great example, so bear with me.) A handler is used as a constructor to produce an instance of this script object with its property c set to zero. Each time we need to count something new, we call the handler to get a new script object. Then we can repeatedly count that thing by calling the increment handler of the corresponding script object.

 on newCounter( )     script aCounter         property c : 0         on increment( )             set c to c + 1         end increment     end script     return aCounter end newCounter -- and here's how to use it set counter1 to newCounter( ) counter1's increment( ) counter1's increment( ) counter1's increment( ) set counter2 to newCounter( ) counter2's increment( ) counter1's increment( ) display dialog counter1's c -- 4 display dialog counter2's c -- 1




AppleScript. The Definitive Guide
AppleScript: The Definitive Guide, 2nd Edition
ISBN: 0596102119
EAN: 2147483647
Year: 2006
Pages: 267
Authors: Matt Neuburg

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net