Stuart Cheshire, January 1994
First impressions of AppleScript.
AppleScript is a kind of Object-Oriented Basic with COBOL syntax.
It's "Object-Oriented" in that variables are called "properties" and scripts are called "objects" and procedure calls are called "messages". There's also a notion of single inheritance, but I would be a little afraid if someone wrote a program in AppleScript which was big enough to need inheritance.
It is like BASIC in that it has little or no compile-time type checking — you cannot specify the types of parameters to your routines — the program simply fails at run-time if the wrong parameters are given. You cannot even specify what type variables are.
It's like COBOL in that it is written in a verbose pseudo-English style in the belief that non-programmers will be able to understand programs, not because they understand the underlying concepts, but because they understand each statement in isolation, because it is written in "plain english".
There are (at least) five alternatives to writing "if (a>b)":
For example, where in C we might write:
FrontWindow()->name
where "FrontWindow()" is a function to get a pointer to the front window, and "name" is a field of a window structure, in AppleScript we write:
get the name of the front window
where "the front window" is a reference to the front window, and "get the xxx of yyy" is the way you access the field called "xxx" of an object called "yyy".
This does in fact look a lot like "plain english". In fact, "get the front window's name" is also valid syntax. My scepticism is as to whether understanding individual statements in isolation is the same as understanding a program. It's a bit like the Chinese room. Does the MBA student really understand the program, or is he just reading the sentences out, following the rules of English that he was taught in kindergarten? (Enclosed at the end is a COBOL program I think illustrates this. I'd like to set an exam to students asking them what the program does, and see how many of the answers talk about porridge tasting.)
The first thing to understand about AppleScript is that, like Unix shells, it actually does nothing on its own. It acts as a central controlling environment from which other programs are invoked. It is therefore useless without a large suite of scriptable applications to go with it.
Unlike Unix shells, AppleScript does not invoke other programs by launching them, passing them command line arguments, and waiting while they run to completion.
AppleScript coordinates a group of concurrently running applications, and during the execution lifetime of a script, its flow of control can weave in and out of running applications like a factory supervisor wandering around the factory's departments. eg:
tell application "Scriptable Text Editor" get the front window's name get the first paragraph of the first document end tell tell application "THINK Project Manager" get the third segment's name get the include files of the source "prog2.cpp" end tell
The first disconcerting aspect of this is that the syntax of a script is not consistent throughout — it depends on the application you are talking to. For example, you might tell Scriptable Text Editor to "get the first paragraph" but you have to tell other editors to "get the text of the first paragraph", because to them, "the first paragraph" is a record with many fields describing styles, fonts and formatting etc, and "the text of" the paragraph is just *one* of those fields.
AppleScript has variables and operators to let you manipulate objects, records (aka structures, objects etc.) and lists, but without external applications to use that data, there is little of use you can do.
I have a strong feeling that when I'm programming, I like to be able to write a routine, and specify with confidence what it will do for any given arguments. For example, I write a routine to take two objects and make a list of two elements using the & operator:
on makelist(x, y) return (x & y) end makelist
I test it (Comments are introduced by "--"):
makelist(5, 7) -- result {5, 7} makelist(5, "World") -- result {5, "World"}
It looks pretty good until I try:
makelist("Hello", "World") -- result "HelloWorld"
What? AppleScript decided that since both operands were strings, it would concatenate them instead of making a list of two elements.
Another inconsistency is the command search path, which can be different depending on which application you are talking to. Sometimes your own user-defined commands (subroutines) override AppleScript built-ins, and sometimes they don't.
Another inconsistency is that if you talk to an application on your local machine which is not running, AppleScript will launch it automatically. If you talk to an application on a remote machine, it must be running already, or the script will fail.
You can write "get word 1 through word 3" or you can abbreviate it to "get words 1 through 3". You can write "get text from word 1 to word 3". Can you abbreviate it to "get text from words 1 through 3"? No. There is in fact a reason for this, but it's hardly a "plain english" reason.
How about this:
tell application "Microsoft Excel" get formula of first Cell -- result "=B1+C1" end tell
This gets the formula from the cell in Row 1 Column 1 — it is "=B1+C1".
Now I try:
tell application "Microsoft Excel" set formula of first Cell to "=B1+C1*2" end tell
and it says: "AppleEvent handler failed." What it didn't tell me, is that while Microsoft Excel returns cell references in the form A1 (ie letter+number), it only accepts them in the rorm "RxCx" (ie row number + column number). I have to change my statement to:
tell application "Microsoft Excel" set formula of first Cell to "=R1C2+R1C3*2" end tell
With so many variant ways of saying the same thing, it becomes hard to tell when you are saying the same thing and when you are not. Do the following mean the same thing?:
run application "Scriptable Text Editor" tell application "Scriptable Text Editor" to run
What about:
run script "xyz" tell script "xyz" to run
Apparently the answer is "yes" for applications, and "no" for scripts.
Another wonderful ambiguity: x is a real. Will this work?:
get x as integer
We test it. Yes, it works. If we say "set x to 7.0" then "get x as integer" returns 7. However, if x is 7.5, then at runtime AppleScript says "Execution Error: Can't make 7.5 into a integer".
To make up for failing to catch most compile time errors, AppleScript is really strict with other things. For instance, this won't compile until the file "TestProg" has been created:
set testprogram to application "TestProg"
If you try to refer to an application by full path name, AppleScript will test for its existence at compile time. This can make it pretty tough to write or edit scripts which are intended to run on someone else's computer.
It does have labelled subroutine parameters, which makes it possible to have routines with large numbers of option parameters in a sensible way. For example, to prompt the user to select a file from the standard file dialog box, we can write "set chosenfile to choose file". To be a bit more helpful, we can say:
set chosenfile to choose file with prompt "Choose a document:"
And to be a little more selective, we can say:
set chosenfile to choose file of type "TEXT" with prompt "Choose a document:"
AppleScript also has try-block style exception handling to catch errors and deal with them, and types (classes) are first class values in the language. For example, you can write:
if class of x is not contained by {real, integer} then display dialog "Error: x is not a number" end if
However, if, in the spirit of "plain english", we try to make it more readable by writing:
if class of x is not contained by the list {real, integer} then display dialog "Error: x is not a number" end if
then we get System Error. Bong! Restart Macintosh.
AppleScript is not quite all there yet.
An illustration of why understanding the individual lines in a computer program is not the same as understanding a computer program.
(or, Understanding Programs One Line at a Time)
by Philip Stanley
IDENTIFICATION DIVISION. PROGRAM-ID. ACOBOLFABLE. DATE WRITTEN. ONCE UPON A TIME. REMARKS. THIS IS EXAMPLE OF COBOLX VERSATILITY ENVIRONMENT DIVISION. CONFIGURATION SECTION. OBJECT COMPUTER. ANY MUSIC BOX, MEMORY SIZE 8^64 BYTES, 19 TAPE DRIVES, 11 DISC DRlVES, 1 GOLDILOCKS, 3 BEARS. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT TAPE DRIVES, ASSIGN THEM TO CREDITOR. SELECT DISC DRIVES. SELECT GOLDILOCKS, SELECT BEARS— ASSIGN TO ONE COTTAGE. I-O-CONTROL. APPLY RED TAPE TO TAPE DRIVES, APPLY HOFFNUNG RECORD TO DISC DRIVE, APPLY GOLDI, BEARS TO COTTAGE. DATA DIVISlON. FD GOLDI. LABEL RECORDS ARE STANDARD, VALUE OF IDENTIFICATION IS "GOLDILOCKS" DATA RECORD IS GOLDILOCKS. 01 GOLDILOCKS. 02 HGT SIZE IS 62 INS. 02 WGT SIZE IS 110 LBS. 02 VITAL-STATS 03 B 38 03 W "4. 03 H 36. 02 RATING 100% FD 3-BEARS. LABEL RECORDS ARE STANDARD, VALUE OF IDENTIFICATION IS "BEARS" DATA RECORDS ARE DADY-BEAR, MUMMY-BEAR, BABY-BEAR 01 DADDY-BEAR. 02 HGT 70 INS. 02 WGT 750 LBS. 02 COLOR OF EYES BLOODSHOT. 02 DISPOSITlON UNBEARABLE. 01 MUMMY-BEAR. 02 HGT 65 INS. 02 WGT 700 LBS. 02 COLOR OF EYES BLUE. 02 DISPOSITION BEARABLE. 01 BABY-BEAR. 02 HGT 40 INS. 02 WCT 200 LBS. 02 COLOR OF EYES BLUE. 02 DISPOSITION INFANTILE. WORKING-STORAGE SECTION. 01 COTTAGE. PICTURE IS COZY. 02 KITCHEN. 03 TABLE SIZE IS LARGE, VALUE IS 1. 03 CHAIRS SIZE IS MEDIUM, VALUE IS 3. 02 PORRIDGE. 03 KlNG-SIZE OCCURS 1 TIME. 03 QUEEN-SIZE OCCURS 1 TIME. 03 PRINCE-SIZE OCCURS 1 TIME. 02 DOOR SIZE IS USUAL, VALUE IS OPEN. 02 BEDROOM 03 BED 04 LARGE OCCURS 1 TIME. 04 MEDIUM OCCURS 1 TIME. 04 SMALL OCCURS 1 TIME. 03 WINDOW SIZE IS SMALL VALUE IS OPEN. 01 RIGHT-COTTAGE REDEFINES COTTAGE VALUE IS SAME. 01 KING-SIZE-BED-SLEPT-IN SIZE IS BIG VALUE IS ROCK-BOTTOM. 01 QUEEN-SIZE-BED-SLEPT-IN SIZE IS MEDIUM VALUE IS DEPRESSED. 01 NO-PORRIDGE SIZE lS SMALL VALUE ZERO. 01 SIP SlZE lS LITTLE VALUE IS "SSSLUP'. 01 SLUMBERLAND SIZE IS UNLIMITED VALUE IS ZZZZZZZZ. CONSTANT SECTION. 01 COMMENT1. SIZE IS 36 A VALUE IS "SOMEBODY HAS BEEN EATING MY PORRIDGE." 01 COMMENT2. SLZE IS 36 A VALUE IS "SOMEBODY HAS BEEN SLEEPING IN MY BED." PROCEDURE DIVISION. FOREST SECTION. START-OF-TALE. OPEN STORY. READ FOLLOWING. FIRST-MOVE. MOVE GOLDILOCKS TO COTTAGE. IF DOOR IS CLOSED OR BEARS ARE GREATER THAN ZERO ALTER ENTER-GOLDILOCKS TO PROCEED TO HASTY-RETREAT. ENTER-GOLDILOCKS. GO TO KITCHEN-SCENE. KITCHEN-SCENE. IF PORRIDGE IS KING-SIZE PERFORM TASTE-ROUTINE VARYING PORRIDGE FROM KING-SIZE BY 1 UNTIL PORRIDGE EQUALS PRINCE-SIZE OTHERWISE COMPUTE IF COTTAGE = RIGHT-COTTAGE GO TO BEDROOM-SCENE. TASTE-ROUTINE. SUBTRACT SIP FROM PORRIDGE (KING-SIZE). SUBTRACT SIP FROM PORRIDGE (QUEEN-SIZE). SUBTRACT SIP FROM PORRIDGE (PRINCE-SIZE) GIVING NO PORRIDGE. BEDROOM-SCENE. MOVE GOLDILOCKS TO BEDROOM. ADD GOLDILOCKS TO BED (LARGE). DISPLAY "IT lS TOO HARD". SUBTRACT GOLDILOCKS FROM BED (LARGE) GIVlNG KING-SIZE-BED-SLEPT-IN. MOVE GOLDILOCKS TO BED (MEDIUM). DISPLAY "IT IS TOO SOFT". SUBTRACT GOLDILOCKS FROM BED (MEDIUM) GIVING QUEEN-SIZE-BED-SLEPT-IN. MOVE GOLDILOCKS TO BED (SMALL). DISPLAY "IT IS JUST RIGHT". ADD GOLDILOCKS TO SLUMBERLAND. BEARS-RETURN. MOVE DADDY-BEAR, MUMMY-BEAR, BABY-BEAR TO KITCHEN. MOVE CORRESPONDING BEARS TO PORRIDGE. DISPLAY "DADDY BEAR", COMMENT1. DISPLAY "MUMMY BEAR", COMMENT1. DISPLAY "BABY BEAR", COMMENT1, "AND EATEN IT ALL UP". MOVE BEARS TO BEDROOM. BEARS-IN-BEDROOM. EXAMINE BEDS REPLACING ALL GOLDILOCKS WITH BEARS. DISPLAY "DADDY-BEAR", COMMENT2. DISPLAY "MUMMY-BEAR", COMMENT2. DISPLAY "BABY BEAR", COMMENT2, "AND HERE SHE IS". HASTY-RETREAT. IF WINDOW IS OPEN EXIT GOLDILOCKS OTHERWISE MOVE GOLDILOCKS TO DOOR. EXIT. END. CLOSE STORY. DISPLAY "WOULD YOU BELIEVE CINDERELLA lN PL/I". END TALE.