• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
Re: JavaScript for automations: bug or correct behavior
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: JavaScript for automations: bug or correct behavior


  • Subject: Re: JavaScript for automations: bug or correct behavior
  • From: has <email@hidden>
  • Date: Tue, 20 Jan 2015 23:42:25 +0000

On 20/01/2015 21:18, Alex Zavatone wrote:
Has, nice observation on the returning 0 in one case and returning 1 in the other.  Eeeegg.

Regarding your support and documentation of an AE bridge, which AE bridge are you talking about?  I'm rather clueless in that respect.


I wrote appscript, which eventually appeared in Python, Ruby, MacRuby, and ObjC flavors until I finally pulled the plug in 2012 due to completely buggering up on the logistics. [0] I also wrote a JavaScriptOSA component last year (no relation to Late Night Software's long-deceased OSA component of the same name), which included an appscript-style Apple event bridge.

Other Apple event bridges of particular notoriety are 10.5's Scripting Bridge framework, the AE bridge included in 10.10's JavaScript for Automation OSA component, and, of course, AppleScript's own built-in AE bridge, which is also the de facto standard against which every scriptable app of the last 20 years has been developed and tested, and most heavily used.

And then there's the various other third-party attempts from over the years: Userland's Frontier (which provided both AE and OSA support, and predated even AppleScript), Perl's Mac::Glue, Python's gensuitemodule+aetools, Bill Fancher's OSAPython, Bob Ippolito's aeve (Python again), LNS's JavaScriptOSA, Laurent Sansonetti's RubyOSA (AE bridge-only, despite the name), and there might even be one or two others that I've forgotten, or aren't even aware of.


Ah well, in for a penny, in for a pound. Learning moment ahead. Be warned...



Apple events use 1-indexing natively (for by-index specifiers and AEList descriptors).

All "scriptable" (i.e. Apple event-aware) apps respect AEs' 1-indexing semantics.

The AppleScript language uses 1-indexing natively (for AS list items). AppleScript's own Apple event bridge preserves AEs' 1-indexing semantics; that AEs and AS semantics just happen to be identical here is, shall we say, "convenient". [1]


So far, so good. The headaches begin when you try to bridge Apple events to a 0-indexed language such as C/ObjC, Python, Ruby, JavaScript, etc. Now your bridge is faced with an impedance mismatch between the native AE semantics (1-indexed) and the native language semantics (0-indexed), and there's only two possible ways it can deal with that:

1. Deal with the mismatch itself (by adding/subtracting 1 to/from index numbers in various places).

2. Drop the whole mess onto the user and leave them to deal with it.


On the face of it, #1 is the clear choice: hide all the complexity and work of dealing with such mismatches within the bridge so that the user never has to know about or deal with it. So the bridge developer scurries off and implements that, and - Yay! - users' lives are made simple and easy, because Everything Just Works exactly as they'd expect.

Of course, if you believe that then I have a bridge to sell you. Joel Spolsky coined a great term for the actual reality: the "leaky abstraction" [2].

Or, if you prefer my own more localized, profane version:

"The AppleScript universe *hates* smartasses and *always* kicks their butts."

Both of which are just variations on the old "your sin will find you out" admonishment, of course.

Basically, trying to resolve such mismatches internally works, but only up to a point. Beyond that point it starts to break down, and frequently breaks hard - because not only is the technology itself broken but also all of the users' previous understanding and confidence in it. It's one of the lessons I learned during my first year on appscript (versions 0.5.x and earlier). Initially I did the 0-indexing thing because it was what Pythonistas were used to but, as myself and others started using it for real work, the leaks began to spring.

For instance, did you know that FileMaker used index 0 to specify a special table? Since early appscript "corrected" index number 0 to index number 1, it could never construct specifiers for table 0 so its FileMaker compatibility was broken. Don't recall if it was Derp #1 ("Derp #0"?), but it was certainly one of the first gotchas I ran afoul of in trying to "Pythonize" Apple event semantics. And, needless to say, it would not be the only one.

Things got worse as I built out support for more complex reference forms. While 0-indexing mostly worked in single-element by-index specifiers, when it came to by-range specifiers I had a nasty realization: while AE and AS element ranges were fully inclusive, Python ranges were not:

    text 2 thru 5 of "ABCDEF" --> "BCDE"

    "ABCDEF"[2:5] --> "BCD", i.e. the last index is *not* included!

So I tried to compensate for that too by adjusting the end index number up and down as appropriate. Now you could write:

    app('TextEdit').documents[0].text.characters[2:5].get()

and it'd return characters 2, 3, and 4 as Python users would expect.

Then I tried to figure out how to make the same trick work when the by-range specifier contained more complex start/end values: element names, relative specifiers, etc. For example, should the following specifier include the Movies folder or exclude it?

    app('Finder').home.folders['Documents':'Movies']

And how should it deal with the following:

app('Address Book').people[0:con.people[its.last_name.starts_with('P')]]

Obviously, the bridge can't just add and subtract 1 any more. It could always append a `previous <element>` selector to the existing specifier so that, e.g., con.folders['Movies'] automatically converts to `con.folders['Movies'].previous(k.folder)` before being sent to the app. But what should it do when the app decides that this query is just too complicated for its poor little brain and throws back an error? And what about the user? She *knows* this query should work, because she's already tried it in AppleScript, and now the bridge is not only throwing back an error message but also the message itself is describing a specifier that looks nothing like the one she actually wrote.


The more I tried to make appscript more "intelligent" and "helpful" by layering on more and more of these "clever" abstractions, the more complicated, slow, and brittle it became; and still fresh leaks would spring. And, of course, being a dumbass I spent a whole year falling down that rabbit hole before *finally* going "WTF?!?!?", throwing out the whole lot, and redesigning and rewriting it from scratch (appscript 0.6+). And it's not like I hadn't studied all the successful and unsuccessful AE bridges that had already been developed by other much smarter, skilled programmers than myself. (Heck, even the mighty Mark Aldritt managed to botch his own JavaScriptOSA implementation in several excitingly creative ways.)

Honestly, excessively clever software will be the doom of us all.


In the end, the only approach *proven* to work is that already used by AppleScript's own AE bridge: expose Apple events' own semantics directly to the user, and carefully apply light touches of syntactic sugar to make it more pleasant to use *only* where it won't create any confusion [3] or compatibility problems. So I replicated that and - presto - a couple weeks of coding and a couple years of field-testing and debugging later, one proven industrial-strength Python-to-Apple-event bridge served. Everything else then becomes an education problem, and full documentation and diligent user support goes a heckuva long way to solving that.

But you try telling that to the young programmers of today... they won't believe you.


HTH

has


[0] Failed to get appscript into 10.5; failed to influence the design of 10.5's Scripting Bridge so that it wouldn't suck donkey nuts; could see the writing on the wall for the Carbon APIs on which it depended, and suspected AppleScript might not be around too many more years either. Never let it be said I don't suck too.

[1] To be fair, the Apple Event Manager was designed back when Pascal, which also used 1-indexing, was still commonly used for Mac development. So it's more likely the AEM was initially designed to be Pascal-friendly, and AppleScript just happened to luck out as a result of that.

[2] See <http://www.joelonsoftware.com/Articles/LeakyAbstractions.html> for Joel's original article, or the dread Wikipedia for the short version <http://en.wikipedia.org/wiki/Leaky_abstraction>.

[3] Yes, well, even AppleScript's not perfect on that score. (Implicit "get", anyone?) But that's why the final appscript design makes even AppleScript look like some whacked-out hippie liberal by comparison. Appscript's what turned me from the wildly reckless cowboy coder of yesteryear to the ragingly conservative cowboy coder of today - Thanks, AppleScript!

_______________________________________________
Do not post admin requests to the list. They will be ignored.
AppleScript-Users mailing list      (email@hidden)
Help/Unsubscribe/Update your Subscription:
Archives: http://lists.apple.com/archives/applescript-users

This email sent to email@hidden


  • Follow-Ups:
    • Re: JavaScript for automations: bug or correct behavior
      • From: has <email@hidden>
References: 
 >Re: JavaScript for automations: bug or correct behavior (From: has <email@hidden>)
 >Re: JavaScript for automations: bug or correct behavior (From: Alex Zavatone <email@hidden>)

  • Prev by Date: File rename strategy
  • Next by Date: Re: JavaScript for automations: bug or correct behavior
  • Previous by thread: Re: JavaScript for automations: bug or correct behavior
  • Next by thread: Re: JavaScript for automations: bug or correct behavior
  • Index(es):
    • Date
    • Thread