Better AppleScript Through Expressions
When writing a short AppleScript to complete a specific task, do not write a procedure that checks if A exists, then operates on it if it does. Instead, write an expression that operates on A, and ignore any exceptions caused by A not existing. Surrounding the statement in a
try-block will ignore any exceptions it generates. The
whose commands can help. This almost always leads to cleaner, and faster, AppleScripts.
Here is an example, a re-write of code in EyeTV version 2.5 (if you have it, you can find this script at EyeTV.app/Contents/Resources/English.lproj/EyeTV\ Help/openreadme.scpt).
When run, this opens a readme inside the EyeTV application bundle, iff the EyeTV application is running. I did not write this, and I do not know exactly how it is used, or what constraints it was written under — just what it does.
set readmePath to "Contents:Resources:English.lproj:EyeTV Help:eyetvreadme.rtfd"
tell application "Finder"
set allApps to every application process
repeat with i from 1 to number of items in allApps
set thisApp to item i of allApps
if creator type of thisApp is "EyTV" then
open file (((application file of thisApp) as string) & readmePath)
Re-writing the code by making it an expression that assumes the correct
application process exists has a dramatic difference.
tell application "Finder"
set theEyeTVProcessBundle to file of first item of (every application process where creator type is "EyTV") as string
open (theEyeTVProcessBundle & "Contents:Resources:English.lproj:EyeTV Help:eyetvreadme.rtfd") as alias
We no longer need
repeat blocks — they are procedural constructs that were used to find the correct application process, and guard against trying to open a file that does not exist. (I also removed the variable
readmePath because I think the a path to “eyetvreadme.rtfd” is clearly a readme-path).
But it gets better. We could try removing the
try blocks as well, and just ignore any errors the interpreter complains about. That brings the the script down to four lines. We can make it one-line:
tell application "Finder" to open ((file of first item of (every application process where creator type is "EyTV") as string) & "Contents:Resources:English.lproj:EyeTV Help:eyetvreadme.rtfd") as alias, but I think that makes the script too hard to understand, because the name “theEyeTVProcessBundle” documents the long expression needed to reliably find the application bundle of the running process.
One line or four lines, that’s still a big reduction from 13 lines. Now it’s short enough to embed directly in Cocoa code with NSAppleScript.
Here’s another example. This is from IMLocation‘s uninstaller code, it It removes the IMLocationHelper from the list of items that will be opened at login. Here’s the original code:
tell application "System Events"
if (login item "IMLocationHelper") exists then
delete login item "IMLocationHelper"
Applying the approach of re-writing the procedure as an expression
tell application "System Events" to delete (login item "IMLocationHelper")
Taking things a step further, by removing the
try-block, and just ignoring errors when running the one-line script:
tell application "System Events" to delete (get login item "IMLocationHelper"). This makes a big difference in the clarity of the surrounding Cocoa code.
AppleScript is a pretty poor language on it’s own. Usually I see it being used to get some specific thing done. In other words, generally AppleScripts are essentially expressions, used to get or set a single value; rarely are they sub-programs. This is whyExpressions are such a good fit.