AppleScript Power Handlers

Add muscle to your AppleScript subroutines

BY MATT NEUBURG

Originally published in: MAC DEVELOPER JOURNAL Summer 2004

book

The AppleScript language should not be underestimated; for all its faults, it has some powerful abilities that many users may not be aware of. This article, reproducing two passages from my book AppleScript: The Definitive Guide, describes some surprising things one can do with handlers (subroutines).

Handlers as Values

A handler is a data type in AppleScript. This means that a variable's value can be a handler. In fact, a handler definition is in effect the declaration (and definition) of such a variable. (That variable's status is essentially the same as that of a property.) The variable's name is the name of the handler, and its value is the handler's byte code, its functionality.

A handler may thus be referred to like any other variable, and you can get and set its value, like this:

on sayHowdy()
    display dialog "Howdy"
end sayHowdy 
set sayHello to sayHowdy 
sayHello() -- Howdy

In that example, we stored a handler as the value of a variable, and then called the variable as if it were a handler! This works because the variable is a handler.

The value of a handler can also be set. No law says you have to set it to another handler. For example, you could do this:

on sayHowdy()
    display dialog "Howdy"
end sayHowdy
set sayHowdy to 9
display dialog sayHowdy -- 9

You can set one handler to the value of another, in effect substituting one entire functionality for another. Of course, the functionality has to be defined somewhere to begin with -- for example:

on sayHowdy()
    display dialog "Howdy"
end sayHowdy
on sayHello() 
    display dialog "Hello" 
end sayHello
set sayHello to sayHowdy 
sayHello() -- Howdy

Handlers as Parameters

At this point, you're probably thinking: "Wow! If I can store a handler as a variable, I can pass it as a parameter to another handler!" However, this code fails with a run-time error:

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

We did succeed in passing the handler sayHowdy as a parameter to doThis(), but now we can't seem to call it; 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 that an unqualified handler call is a message directed to the current script object; AppleScript looks at the current script object, which is the script as a whole, for a global named what (see page 151 of my book for further explanation of this rule).

One obvious workaround is to use such a global:

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

But globals are messy; we want a real solution. Here's one: we can pass a script object instead of a handler. This is actually very efficient because script objects are passed by reference, and it works, because now we can use the run command instead of a handler call:

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

Alternatively, we can define a handler in a script object dynamically and then pass it. This is more involved, but it 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

Observe that we can actually redefine a handler within a script object, from the outside!

My favorite solution is to pass a handler as parameter to doThis, just as in our first attempt, and to have a script object inside doThis waiting to receive it, like so:

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

This depends upon the remarkable rule that a script object within a handler can see that handler's local variables. Thanks to this rule, our property initialization for theHandler can see the incoming what parameter and store its value -- the handler. Now we are able to call the handler using the name theHandler, because this is the name of a global (a property) within this same script object.

For a useful application of this technique, let's return to the earlier example (page 153) where we filtered a list to get only those members of the list that were numbers. The trouble with that routine is that it is not general; we'd like a routine to filter a list on any Boolean criterion we care to provide. There are various ways to structure such a routine, but the approach that I consider the most elegant is to have filter() be a handler containing a script object, as in the example above. This handler accepts a list and a criterion handler and filters the list according to the criterion.

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.

Script Object as Handler Result

The result of a handler can be a script object. Normally, this script object is a copy, passed by value; it could not be passed by reference, since after the handler finishes executing there is no script object back in the handler for a reference to refer to. (Actually, if the returned script object is the same script object that was passed in as a parameter by reference, then it is returned by reference as well; still, that fact isn't terribly interesting, since at the time the script object was passed in, you must have had a reference to it to begin with.) For example:

on scriptMaker()
    script myScript 
        property x : "Howdy" 
        display dialog x 
    end script 
    return myScript 
end scriptMaker 
set myScript to scriptMaker() 
run myScript -- 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

A handler can customize a script object before returning it:

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

In that example, the handler scriptMaker not only created a script object, it also modified it, altering the value of a property, before returning it.

Obviously, instead of hard-coding the modification into the handler, we can pass the modification to the handler as a parameter:

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

As already mentioned, contrary to the general rules of scoping, a script object defined inside a handler can see the handler's local variables. This means that in the previous example we can save a step and initialize the property x directly to the incoming parameter s:

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

The real power of this technique emerges when we retain and reuse the resulting script object. For example, here's a new version of the general list-filtering routine we wrote earlier. In that earlier version, we passed a handler both a criterion handler and a list, and got back a filtered list. In this version, we pass just a criterion handler, and get back a script object:

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

The script object that we get back from makeFilterer contains a filter handler that has been customized to filter any list according to the criterion we passed in at the start. This architecture is both elegant and efficient. Suppose you know you'll be filtering many lists on the same criterion. You can use makeFilterer to produce a single script object whose filter handler filters on this criterion, store the script object, and call its filter handler repeatedly with different lists:

on makeFilterer(crit) 
    -- ... as before ... 
end makeFilterer 
on isNumber(x) 
    return ({class of x} is in {real, integer, number})
end isNumber
set numbersOnly to makeFilterer(isNumber) 
tell numbersOnly
    filter ({"hey", 1, "ho", 2, "ha", 3}) -- {1, 2, 3}
    filter ({"Mannie", 7, "Moe", 8, "Jack", 9}) -- {7, 8, 9}
end tell

Closures

A closure is one of those delightfully LISPy things that have found their way into AppleScript. It turns out that a script object carries with it a memory of certain aspects of its context at the time it was defined, and maintains this memory even though the script object may run at a different time and in a different place. In particular, a script object returned from a handler maintains a memory of the values of its own free variables.

For example, a script object inside a handler can see the handler's local variables. So a handler's result can be a script object that incorporates the value of the handler's local variables as its own free variables. This means we can modify an earlier example one more time to save yet another step:

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

This is somewhat miraculous; in theory it shouldn't even be possible. The parameter s is local to the handler scriptMaker, and goes out of scope -- ceases to exist -- when scriptMaker finishes executing. Nothing in myScript explicitly copies or stores the value of this s; we do not, as previously, initialize a property to it. Rather, there is simply the name of a free variable s:

display dialog s

This s is never assigned a value; it simply appears, in a context where it can be identified with a more global s (the parameter s), and so it gets its value that way. Yet in the last line, myScript is successfully executed in a completely different context, a context where there is no name s in scope. In essence, myScript "remembers" the value of its free variable s even after it is returned from scriptMaker. myScript is not just a script object; it's a closure -- a script object along with a surrounding global context that defines the values of that script object's free variables.

Here's an example where the value of the free variable comes from a property of a surrounding script:

on makeGreeting(s) 
    script outerScript 
        property greeting : s 
        script greet 
            display dialog greeting 
        end script
    end script
    return outerScript's greet 
end makeGreeting 
set greet to makeGreeting("Howdy") 
run greet -- Howdy

In that example, makeGreeting doesn't return outerScript; it returns just the inner script object greet. That script object uses a free variable greeting whose value is remembered from its original context as the value of outerScript's property greeting. In the last line, the script object greet runs even though there is no name greeting in scope at that point.

Constructors

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, which maintains the count, and a handler, which increments the count. (This 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 set to zero. Each time we need to count something new, we call the handler to get a new script object, like so:

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

Conclusion

AppleScript is a small language, and is usually thought of as directed at novice programmers, but it is capable of some remarkably sophisticated constructs. This article has presented examples of powerful and elegant things one can do with handlers in AppleScript. I hope you've found them as surprising as I did when I was writing my book!