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. They can explicitly be
displayed as “«handler foo»” and “handler” is a public class—you
can ask for the class of a handler and get back “handler”, you
can programmatically test whether an object is a handler, and
you can invoke it by sending it a message. As in AppleScript,
fetching the value of a property via OSA can return a handler,
the result of executing a script can be a handler, and OSA's
OSAExecuteEvent can be used to execute a handler.
Yeah, I'm aware of the OSA API's capabilities. However, I'm trying
to keep it sufficiently simple for everyone else to understand.
Plus, the OSA API still doesn't address the
handlers-are-not-closures problem, which means that moving a handler
using OSA calls will break static bindings just as surely as moving
it using AS's `set` statement.
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', it sends a message to a [script] object, which checks to
see if it has a handler with the same name, invoking that handler if
it does or delegating the message to its parent object if it
doesn't. AS message passing is like that of Smalltalk or Ruby, and
not to be confused with the way that Python or _javascript_ do it,
which is to get a bound function from an object and call it.
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,
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. (As I've said previously: undocumented behaviors do
tend to be undocumented for a reason.:)
Also, as previously stated, `«handler foo»` isn't a closure, so this
'trick' is a disaster waiting to happen the moment `foo` contains
any free variables or calls other handlers. Move the handler to
another context, and Boom!!!
In other words, the mere fact that you can do this sort of thing
does not mean you should. If the original AS team had intended for
AS users to move handlers around as objects, they'd have made them
take their original bindings with them. Besides, like I say, in a
Smalltalk-y object system it doesn't really make sense anyway. Even
Ruby, which is as cowboy as it comes, makes a clear distinction
between methods, which are permanently bound to an object, and
'procs' that are created by asking an object to parcel a particular
method into a portable format.
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». And bear in mind there are many things the OSA
API lets you do that aren't actually valid operations: it's
extremely loose and sloppy in its design, and even vaguer in its
documentation. (Just look at all the sloppiness, vagueness, and
confusion over which OSA APIs should or shouldn't be used when
working with Script OSAIDs versus Value OSAIDs versus Context
OSAIDs, which even the current AS team doesn't understand how to use
correctly; as demonstrated by `osascript`'s embarrassingly broken
interactive mode.)
Although this isn't spelled out, these are designed
to work. If something here doesn't work, file a bug.
On the List of AppleScript Shit that Could Seriously Do with Being
Sorted a Decade Ago, I would rank this about #10,001, which puts it
well below "Redesign and Rewrite AppleScript from Scratch", and way,
way, WAY below "Implement a Built-in Fricking `find/replace text`
Handler Already". Honestly, don't do it. Just don't. AS is built
around [script] objects which respond to messages, not callable
'function' objects. Stick to the clearly defined, documented,
approved idioms, and avoid bouts of excessive cleverness that have a
nasty tendency to come back on you.
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); it's just inconsistent in how it's applied.
And I'm fairly sure Self and Smalltalk don't let you modify handler
slots without jumping through serious hoops (and I would hope
they're a lot more rigorous in not breaking scope bindings doing
so). Self even knows how to do read-only data slots (i.e. a data
storage slot that has a getter but not a setter), providing a
robust, user-friendly way to define 'constants' that are actually
*constant*.
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.
More importantly: although it could be convenient to
be able to declare a read-only property to make your intent
clearer and to see errors if you inadvertently try to write one,
not being able to declare a property read-only doesn't prevent
you from being able to write scripts that do what you want.
There is no missing capability here, only a minor convenience
for catching errors in your code.
Honestly Chris, advocating deep technical enhancements to the core
language is pointless. AppleScript is a legacy language, like Tcl or
Perl, and well into the long tail of its lifetime. Furthermore, it
would just be an exercise in turd-polishing: AppleScript is
fundamentally flawed in its design (even Dr Cook acknowledges this),
and there is no way to fix those flaws short of throwing the whole
thing out and rethinking, redesigning, and reimplementing everything
from scratch, thereby breaking everyone's existing AppleScripts, of
course.
The best ways in which you can improve *AppleScript* are through
small, simple enhancements, such as a decent set of standard
libraries. Yeah, I know corporate devs love a grand exciting
complicated challenge over small simple BORING tweaks and
improvements - I've fallen into that trap myself - but what will
really thrill and improve the daily lives of jobbing AppleScripters
are things like a robust, well documented standard library
containing handlers for text, list, and date manipulation, and
cleaner, simpler, more robust file reading and writing APIs
(including full range of text file encoding options, and automatic
closing of file handles after use).
And no, I'm not going to write a Radar ticket for this because, 1. I
bogged off to languages that already give me all this stuff for free
years ago, so AS's myriad failings don't affect me directly (short
of Apple deciding to ditch AS - and thus AEs - altogether, in which
case I'm totally boned too), 2. I'm all out of Xmas cheer after my
most recent email exchange with Sal (so reckon it's someone else's
turn to offer the next olive branch for a change), and 3. this stuff
is so BASIC AND OBVIOUS that ANY conscientious developer who doesn't
live their entire life in a perfect glass bubble should already be
FULLY aware of where their product is failing its users both hardest
AND in ways that could be very quickly, easily, and simply solved by
himself in plenty time for the next OS release, even if he has to
write his own Radar ticket just to keep the beancounters square.
> Similarly, if you use load
script:
>
> set x to load script "/Users/shane/Desktop/Test.scpt"
> x's stripWhiteSpace
>
> And script libraries are really load script in new
clothing.
That's technically incorrect: whereas `load script` creates new
`script` objects within your current script (i.e. they become
part of your own script's state), AS's official library
mechanism loads scripts into the current Scripting Component
Instance (CI) as new Script instances (OSAIDs) that live in
parallel to your own scripts, and calls are passed from your
scripts to library scripts using OSA's existing delegation
system or [more likely] chicken voodoo.
Now who's relying on implementation
details? ;-) Your apparently two different descriptions are in
fact nearly identical, and there is no essential difference
between scripts loaded with “load script” and scripts loaded via
“script” element references, or, in fact, scripts defined in
your source code.
Actually I'm relying on a decade of having to totally black-box
reverse engineer the deepest, darkest recesses of AppleScript's
inner workings[1], as Apple don't share any of that information with
outsiders (and I suspect it'd be largely impenetrable even to
insiders, assuming it exists at all). :p
AFAIK, the `load script` handler does not store the de-serialized
«script» object under a new, permanent OSAID, but instead simply
returns it as its result. (It might use a temporary OSAID entry
while reading and processing the script, but tons of OSA operations
do that.) Whereas loading a library via a `script NAME` specifier
does create a new OSAID entry in the CI's scripts table (or a
similar internal table) in addition to returning it, thus acting as
a reusable cache of all previously loaded library scripts. (e.g.
Python and Ruby also do this, which is why they're an unsafe
crapshoot for embedded use too.)
BTW, I hope you realize I'm just using 'OSAID' as convenient
shorthand. I'm fully aware[2] it's just a numeric identifier that
refers to a script object instance within whatever
hash/array/tree/etc the AS CI uses to hold all script instances
created via OSALoad/OSACompile. (Again, I'm trying to keep the
hideous jargon vaguely within the realm of comprensibility, as the
previous sentence attests.)
Of course, I *could* be wrong about the exact operation of `load
script` versus `script NAME`, but I don't think I am. It's not mere
idle hypothesis but *working* one, so would've had huge holes
rapidly blown in it years ago, back when I was doing a lot more
complex AE/OSA work. Obscure issues like data sharing between
totally unrelated scripts that wouldn't touch most AS users tend to
blow up pretty quickly when you start pushing this stuff as hard as
I've done.
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.
STL;SDR: You write public APIs for the system you already have, not
the system you *wish* you had.
has
[1] <https://en.wikipedia.org/wiki/Grue_(monster)>
[2] Anyone who's curious about what an OSA language component
actually looks like inside (or thinks I don't know what I'm talking
about:p), take a look at the [very rapidly written and rather messy
but largely working] _javascript_ OSA component
<http://sourceforge.net/projects/appscript/files> I put
together last year while trying to explain/persuade/beg the AS team
to implement JXA right. The fact my component still works better
than their component, despite being hacked together by a total
amateur with no skill, clue, or documentation worth a damn...
|