This works.
----------------------
property
someHandler : ""
set
aValue to "3"
on double(aNumber)
return
aNumber * 2
end double
on call(someHandler,
aValue)
someHandler(aValue)
end
call
set someHandler
to double
someHandler(4) --=> 8
call(double, 4)
On Jan 15, 2016, at 7:19 PM, Mark J. Reed < email@hidden> wrote:
I
find it odd that this works:
to double(aNumber)
return aNumber * 2
end double
set someHandler to double
someHandler(4) --=> 8
But this doesn't:
to call(aHandler, aValue)
aHandler(aValue)
end
call(double, 4)
error "«script» doesn’t understand the “aHandler” message." number -1708 from «script»
What is different about the parameter of a handler compared to a local variable that explains this inconsistency?
There are several things to be aware of:
1. As I mentioned in the recent thread about handlers being objects, “foo(…)” does not mean “evaluate foo and then invoke it as a handler”; rather, it means “send an ‘invoke foo with arguments (…)’ event to the current target”.
If you're familiar with Objective-C, it's equivalent to the message-sending syntax “[it foo:…]” rather than the C-syntax “foo(…)”.
In AppleScript, both “foo(…)” and “open …” mean “send an event”. AppleScript's “foo(…)” syntax, using a user-identifier for the event name, behaves the same as AppleScript’s “open …” (or “run …” or “count …”, etc.) syntax, using terminology from a dictionary
for the event name—they both mean “send an event”, they just have different types of event identifiers.
2. Sending an event (aka “message”) to a script object first searches the script and its parents for a property whose name is the same as the event identifier. If no “foo” property is found, the event is sent on to the current application (or wherever
the parent chain leads).
Note that this is the same as when using an event identifier from terminology: “open …” will look for a property whose name is the terminology event identifier “open”. (Because of the language syntax, you can't write AppleScript code to get the property's
value in this case—to be accessible to a script, the name must be a user identifier. However, the OSA API allows reading and writing properties with any type of identifier, since you construct the identifier programmatically.)
In your example, if you look at the event log you'll see that it sent an “invoke aHandler with arguments (4)” event to the current application:
tellapplication
"Script Editor"
aHandler(4)
-->
errornumber
-1708
It did not send a “get the value of property aHandler” event to the application. Note that you can transmit handlers in Apple Events, so an application could in fact define a property “aHandler” whose value is a handler, which you could get and then invoke,
but that's not what this syntax means.
Since “aHandler” is a local variable (parameters are local variables), not a property, the “invoke aHandler” event is sent on to the current application.
The reason that “someHandler(4)” works is that that code is in the script's implicit “run” handler, and in a “run” handler variables are global by default (and global variables are properties). So “set someHandler to double” defines a “someHandler” property
and the “invoke someHandler” event finds it. If you precede that line with “local someHandler” to explicitly make it a local variable you'll see that it can no longer handle the event.
3. To directly invoke a handler object, rather than sending an event to a script object to invoke its handler, you must send the handler an event just like one you would have sent to a script.
To invoke aHandler, write
tellaHandlerto
double(aValue)
Note that the event name must be “double” or you'll get an error. Handlers record their event identifiers. If you get the value of “someHandler” you'll see that it's “«handler double»”,
not just “«handler»”.
This all becomes more apparent if you write
tellaHandler
double(aValue)
double(aValue
+ 1)
foo(aValue)
endtell
Here we're sending three different events to the handler. The first two invoke the handler with different arguments. The last one sends the handler an event with a name it doesn't recognize, which returns an error:
error "«handler double» doesn’t understand the “foo” message."
number -1708
from «handlerdouble»
If you're wondering why “someHandler(4)” works even though the handler's event identifier is “double”, that's because, after event dispatching finds a matching property and gets its value (and it's a handler) it directly invokes the handler, ignoring the
handler's name. This makes invoking the handler faster than sending the event to the handler like you must do in AppleScript code, and allows you to invoke a handler assigned to a property whether it was defined with “to …” syntax or “property …” syntax (or
implicitly defined like “someHandler”).
I know that AS doesn't really do higher-order functions, and the way to fake it is with script objects, kind of like pre-closure Java. But I noticed that variables could hold handlers and thought maybe that had been rectified...
AppleScript does do higher-order functions. A “higher order function” is a function that takes one or more functions as an argument. You can pass functions to AppleScript handlers. A separate issue is exactly what types of functions a given language
supports.
Closures are one type of function, and they are orthogonal to higher-order functions (although they're obviously complementary), and in languages that support closures, only some functions are closures—those that actually close over variables
in a surrounding scope.
Now, AppleScript actually does have closures—which I'll illustrate below—but they are
also handlers, and, as in most languages, handlers/methods/member-functions don't close over properties/instance-variables; rather, they are (in many languages, implicitly) given a reference to me/self/this when they are invoked, so they access
the properties of whatever object the event/message was sent to at runtime.
Coming at it from the other direction: unlike methods in many languages, AppleScript handlers can
also be closures. AppleScript has the ability to create script objects at runtime in a local scope and their handlers can close over local variables visible in that scope. But, because handlers are object methods, they're closely tied to the
surrounding script and cannot be arbitrarily used independently of it.
Here's an example closure in AppleScript:
toincrementBy(n)
setxto
0
script
tonext()
setxto
x +
n
endnext
endscript
endincrementBy
setclosure1to
incrementBy(1)
setclosure2to
incrementBy(5)
{closure1'snext(),
closure2'snext(),
closure1'snext(),
closure2'snext()}
--> {1, 5, 2, 10}
The “incrementBy” handler constructs a script with a “next” handler that closes over both the argument “n” and the local variable “x”. Although the current language requires the intermediate “script”, the handler doesn't close over any of the
script's properties.
The only thing that's missing is the ability to write the code with greater brevity, e.g., something like
to incrementBy(n)
setx
to 0
functionnext()
setx
to x +
n
end next
end incrementBy
set closure1to
incrementBy(1)
set closure2to
incrementBy(5)
{closure1(),
closure2(), closure1(),
closure2()} --> {1, 5, 2, 10}
--
Chris Page
The other, other AppleScript Chris
|