TL;DR: Handlers are objects, just like any other AppleScript value—also, file radars.
On 17/12/2015 12:54, Chris Page wrote:
On Dec 6, 2015, at 9:12 AM, has
<email@hidden>
wrote:
While handlers may be implemented as objects of type `handler`,
that's purely an internal implementation detail.
That is incorrect (or, at least, overstating the
case): while it may not be well-documented, the existence of
handlers as objects is part of the public interface of
AppleScript, and of the OSA API.
…
Yeah, I'm aware of the OSA API's capabilities. However, I'm trying
to keep it sufficiently simple for everyone else to understand.
You brought these details up, and your comments deserved a response.
I think we should leave it to each reader to decide whether or not it's interesting to them. We're not standing in a crowded room; it's reasonable to post something to a list that may have a narrower audience.
Others sometimes start off-list discussions with me because they think it may be “too technical” for this list, but that just deprives subscribers of the opportunity to learn and participate.
Specifically, I think that knowing that all AppleScript values are objects, including handlers (and scripts!), is important for anyone interested in AppleScript to know. This is conceptually simpler than leaving someone to think that handlers are somehow different from every other type of value.
e.g., in AppleScript we can send a message to a
handler like so:
on foo(x)
x
end foo
set bar to foo
tell bar
to foo(42) --> 42
From a message-passing POV, AS doesn't 'send a message to a
handler'…
The above code does exactly that—that's why I posted it. It sends the “foo(42)” message to a handler. Incidentally, if you actually think about the above example, it
really makes no logical sense: if «handler foo» were actually being
bound to variable `bar`, which is what the code claims to be doing…
That is exactly what the code is doing. As it happens, I was looking at the message-passing implementation code just hours before I wrote that. I wrote it to illustrate the behavior, not to discover it. … then you'd actually invoke it using `bar(42)`, not `foo(42) of bar`
- which is akin to writing `foo(42) of «handler foo»` and thus makes
no sense at all.
That is incorrect.
You are thinking in terms of “calling a function”, but that is not what that code does. That line of code sends an “invoke function foo with argument 42” message to the handler, just as writing “make new document” sends a “make with the ‘new’ argument set to ‘document’” message.
When “foo” is either a user identifier or a property identifier, the syntax “foo of bar” asks for the value of that property of bar, whereas a function-call _expression_ like “foo(42) of bar” sends an “invoke this function” message to bar, just as “count of bar” (“count” is an event identifier) sends a “count” message to bar. If you send that message to a script object it looks up the handler, but if you send it to a handler, it executes the handler without any further dispatch (if it matches the handler's name).
That's why the next line in the code—which you omitted from your reply—returns an error: because the “foo” handler only responds to the “foo(x)” message, not the “bar(x)” message:
tell bar to bar(42) --> error "«handler foo» doesn’t understand the “bar” message." number -1708
As you can see, the error message says that the handler doesn't understand the message. This further illustrates that it sent the message to the handler.
Note that this syntax
also works in the script I posted. However, it still isn't looking up the value of “bar” as a separate step; rather, it is sending a “bar(42)” message to the current script, and since this code is in the top-level implicit run handler, “bar” is a global, and globals are properties, and handler definitions merely define a property whose value is a handler, so it's just sending the message to the current script and finding the script's handler. If you move that code into some other handler, where “bar” is just a local variable, that line fails:
on foo(x) x end foo
on test() set bar to foo tell bar to foo(42) --> 42 bar(42) --> error "«script» doesn’t understand the “bar” message." number -1708 from «script» tell bar to bar(42) --> error "«handler foo» doesn’t understand the “bar” message." number -1708 end test
test()
To put it another way, the syntax “bar(42)” does not mean “look up the value of the property ‘bar’, whose value is a handler, then call the handler with the ‘42’ argument”; rather, it means “send a ‘bar(42)’ message”. You can see this in the Script Editor event log if you run the following script:
the event log is:
tell application "Script Editor" foo(42) --> error number -1708
As you can see, it sent an “invoke function ‘foo’ with argument ‘42’” message to the application, rather than sending a “get property foo” message to get a handler to invoke. It behaves the same way when sending the message to a script or any other object. For example, you can use OSA to compile a handler
without a surrounding script (without a “context”), then invoke
it. You can even send it to another application process and
execute it there.
Can't think why you'd want to send a bare «handler» instead of a
complete «script».
The compiler can compile any bit of source text, including individual values like “42”, into a standalone object. You can compile a number or a string, a record, a list…or a handler. A script is just one of many types of values you can compile, as are handlers.
Reasons to do this include setting the value of a property via OSA, or comparing with the property's current value or with the result of running a script. Or to ask for its display string. Or to create a value that you then ask OSA to transform into an AEDesc. You can also produce a standalone value by asking OSA to pull it out of an AEDesc. This is what happens when OSAExecuteEvent sends a message to a script, or when AppleScript sends an Apple Event or receives a reply: values are encoded into AEDescs and decoded from them. (And any of these values can be returned from OSA as an OSAID.)
And bear in mind there are many things the OSA
API lets you do that aren't actually valid operations…
Not the ones I described, so I don't understand why you mentioned that.
The real problem is that
AppleScript's `script` objects lack a mechanism for making slots
read-only, as that would allow slots containing handlers to be
locked down so they cannot be moved or replaced. But AppleScript
is full of these sorts of design defects and oversights, as most
first attempts are…
That is incorrect: it is not a flaw, it is a simple
matter of…simplicity. AppleScript is a lightweight dynamic,
object-oriented language like Self and Smalltalk—neither of
which has read-only properties. AppleScript is hardly alone in
this. Many dynamic languages don't have read-only properties.
Nonsense. AppleScript already knows how to do read-only slots (e.g.
the `parent` property)…
“Nonsense” is incorrect (or, at least, overstating the case). There is no facility for defining arbitrary read-only properties, is the point.
That's a good point about the parent property being read-only. But that's the only one. It's special. All programming languages have special cases. Any system for manipulating symbols, whether it's a programming language or a formal system, will either have special cases or be too trivial to be useful. Whereas AppleScript «constant»s are not only constant, they're not
even constants but are symbols instead; while supposedly 'constant'
properties such as `pi` are eminently assignable, although I suppose
`set pi to 3` will please at least Biblical literalists.
There are multiple meanings for “constant” in AppleScript documentation. One is for enumerations, which are constant values, like “current application” and “yes”. Another is for class identifiers that are used as values, like “missing value” and “January”. Yet another meaning is for global properties like “pi”, “return” and “quote”, which, although modifiable, are rarely modified by scripts. The documentation in the ASLG is attempting to describe all these in-practice-if-not-necessarily-in-theory read-only values with a common term so they can be discussed together as a set without a ton of jargon. If not “constant,” what term do you suggest for this concept?
Honestly Chris, advocating deep technical enhancements to the core
language is pointless.
Obviously, I disagree, and not just with words, but with my own actions:
I've been improving AppleScript every release for years, involving significant changes. I added libraries and use clauses, support for terminology in scripts, parameter type declarations and default arguments for handlers, “AppleScript/Objective-C everywhere”, and more. In particular, most of these are focused on making it possible to write libraries that can replace scripting additions, with a lot less code compared to writing event handlers in C.
To everyone else reading this, I'll reiterate: please file radars.
TL;DR: When a script uses `load script` to load libraries, those
libraries are *owned* by that script only. When a script uses
`script NAME` to load libraries, those libraries are *shared* by all
scripts within the same CI. The latter creates a risk of very nasty,
very non-obvious behavioral bugs occurring, because while it may be
good practice for an app to create a new CI for every new, unrelated
script it loads, this was never a documented requirement - or even a
concern - for the first 20 years of AppleScript's life.
A given component instance can contain more than one script, and all the scripts share state like “text item delimiters”. This is a feature: you can have more than one script interact with state and with each other, executing one, then another, then the first one again, or what-have-you. Scripts are persistent, stateful objects that you send messages to, not just linear code that runs once, independently of all other scripts in the component instance, and component instances contain state shared by all the scripts within them. This has always been the case, and it has always been documented.
Moreover, the state inside the component instance isn't the only state scripts share. They also share the state of the application they run in, and of the objects they send events to. For example, one script can tell TextEdit to make a new document, and another can modify that document—and these scripts don't even have to be in the same component instance. If you consider it a risk to share state, then nearly every script has this risk. But in practice, it's the nature of scripting and it works fine.
Clients who wish to keep scripts more isolated from one another should place them in separate component instances. This has always been the case, and has always been documented (if not spelled out exactly in a way you recognized, of course). It is not a requirement, it is a capability available to clients who chose to make use of it. For example, I changed Script Editor to keep each script document in its own instance—because in a development environment like SE, the scripts are usually meant to be (more) independent of one another, often mean to run in separate applications. But it doesn't have to do that (it didn't for a long time)—it just seems to fit the use-case better.
But there is no such thing as a script that references or changes application state without sharing that state with other scripts. Any shared state within a component instance should be the least of your worries, if you consider it a risk. And I don't think it's something to “worry” about: it's just one of the many things to be aware of to write correct code.
Furthermore, this behaves just like Mach-O frameworks and plugins, and libraries in many languages. All the Mach-O code in a process shares the same global state with all the frameworks. Yet, unlike frameworks, OSA offers clients the flexibility of controlling where to place the boundaries between scripts, by arranging them in component instances however they see fit. i.e., because the AppleScript runtime is a virtual machine, you aren't restricted to the granularity of process boundaries; instead, you can have more fine-grained boundaries within a single process. This is a powerful feature, not a bug, and it is an option, not a requirement.
-- Chris Page The other, other AppleScript Chris
|