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:
tell application "Script Editor" aHandler(4) --> error number -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
tell aHandler to double(aValue)
aHandler's 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
tell aHandler double(aValue) double(aValue + 1) foo(aValue) end tell
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 «handler double»
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:
to incrementBy(n) set x to 0 script to next() set x to x + n end next end script end incrementBy
set closure1 to incrementBy(1) set closure2 to incrementBy(5)
{closure1's next(), closure2's next(), closure1's next(), closure2's next()} --> {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) set x to 0 function next() set x to x + n end next end incrementBy
set closure1 to incrementBy(1) set closure2 to incrementBy(5)
{closure1(), closure2(), closure1(), closure2()} --> {1, 5, 2, 10}
-- Chris Page The other, other AppleScript Chris
|