AppleScript_ The Definitive Guide - Matt Neuburg [87]
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 wait—a 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,