Scoping Out The Problem
This month’s topic isn’t very sexy, but it just might help you track down those pesky error messages dealing with variables. If you have ever gotten the dreaded “The variable yourvariable is not defined,” message, pull up a chair and lend an ear.
The “not defined” message means that in some dark corner of your script you used a variable that you thought was kosher and it wasn’t. “What was wrong with it?” you might ask. More than likely you used a local variable when you meant to use a global variable or a script property.
What’s the difference? Quite a bit, as far as Applescript is concerned. To explain, let’s start at the lowest level, the local variable.
Local variables are what you probably use the most. A local variable is easy to define, and doesn’t require any special declaration. We usually do it like this:
set localVar to "I'm a local variable."
When you pass a value to a handler, that is also an example of a local variable. In the following script, theVar is a local variable.
set localVar to "I'm a local variable." showVar(localVar) on showVar(theVar) display dialog theVar end showVar
Local variables, like some insects, have very short life spans. In the preceding script, theVar only lives as long as the handler it is used in lives. Furthermore, a local variable can only extend its reach to the current handler, even if a new handler is defined that uses it. Try this:
set localVar to "L" display dialog returnMe() on returnMe() return localVar end returnMe
When you compile the script, it compiles just fine, doesn’t it? But if you try to run it, you will get “The variable localVar is not defined.”
Local variables exist only as long as they are used. For example, if you write a handler…
on doSomething() set x to 12 return x end doSomething
…the variable x only exists from the first time it is mentioned until the end of the handler. Once the handler is finished, x is destroyed. By default, you implicitly declare a variable to be local by using it in a set statement. To avoid confusion, you can also declare a variable to be local explicitly, like this:
local x --or local x, y, z
Scripters who have studied other, more structured languages will recognize this form of variable declaration. However, unlike other languages, Applescript is very flexible about what a variable can contain, be it a real number, a string of characters, a list or other class object. Therefore, we don’t need to state what sort of data the variable will contain, just that it is local.
“Why,” you may ask, “would I want to declare a variable as local if the language assumes it to begin with?” To answer that question, we will need to explore another type of variable, the global variable.
Global variables (as the name implies) have a much larger scope than local variables. Their scope extends across handlers and script objects. For example if you write:
global theVar set theVar to "foo" setGlobal() display dialog theVar on setGlobal() set theVar to "bar" end setGlobal
then you will see that theVar has been updated to “bar” in the handler, even though it is not declared as global. Because we did not explicitly set theVar to a local variable in setGlobal(), the Applescript interpreter, when it encountered the variable looked back up the hierarchy until it found a global variable of the same name, and used it. This behavior is different than if we had not declared theVar to be global. Notice the effect of removing the global declaration:
set theVar to "foo" setGlobal() display dialog theVar on setGlobal() set theVar to "bar" end setGlobal
Essentially there are two copies of theVar, the local copy in the handler setGlobal and the local copy in the top level run handler. One cannot see the other under these circumstances. But there may be times when you want a global variable to be visible to some handlers and not to others. Is this possible? Sure.
Let’s say that you like to name variables with “my” and whatever the object is. So in your script you might say:
set myString to "Kevin Bradley" --and you have lines of code that make use of myString display dialog myString
but later on, you have a handler that does some function with strings, say making them lower case. You write:
on lowerCase(inputString) set myString to "abcdefghijklmnopqrstuvwxyz" --some code to convert inputString to lower case return inputString end
So far so good, both copies of “myString” are local and don’t interfere with one another. But if you have another handler that outputs your name and address, you might want to declare “myString” as a global variable to make it available to that handler:
global myString set myString to "Kevin Bradley" local theText set theText to "" --and perhaps you call lowerCase BEFORE you get to the dialog display set theText to lowerCase(theText) globalValueHandler() on lowerCase(inputString) set myString to "abcdefghijklmnopqrstuvwxyz" --some code to convert inputString to lower case return inputString end on globalValueHandler() display dialog myString end
You see the problem? Now the local copy of myString in the lowerCase() handler will reset the value of the global copy! We have corrupted the data in our global variable and by the time we get around to trying to use it in globalValueHandler(), it’s toast.
The answer in this case is to declare the copy of “myString” in the lowerCase() handler as local:
global myString set myString to "Kevin Bradley" local theText set theText to "" --and perhaps you call lowerCase BEFORE you get to the dialog display set theText to lowerCase(theText) globalValueHandler() on lowerCase(inputString) local myString set myString to "abcdefghijklmnopqrstuvwxyz" --some code to convert inputString to lower case return inputString end on globalValueHandler() display dialog myString end
By always declaring the variables in a handler as local, you can ensure that you are not going to step on anything in the larger scope. And while it is not necessary to be explicit when you use a global variable in a handler, it is good form (and easier to understand) if you declare that you are referencing a global variable and not a local one:
global myString set myString to "Kevin Bradley" local theText set theText to "" --and perhaps you call lowerCase BEFORE you get to the dialog display set theText to lowerCase(theText) globalValueHandler() on lowerCase(inputString) local myString set myString to "abcdefghijklmnopqrstuvwxyz" --some code to convert inputString to lower case return inputString end on globalValueHandler() global myString display dialog myString end
Get Off My Property!
Lastly, we have properties, something that most of us have also used. Properties are much like global variables. Script properties are visible in handlers and throughout the script in which they were defined. However, it IS possible to have a handler use a property WITHOUT seeing the value of the property at the script level. Let’s say you defined a script object within your script, call it “myScript:”
script myScript property myProperty : 1 on handleIt() set myProperty to myProperty + 1 display dialog "handleIt:" & myProperty end handleIt handleIt() end script --run the handler without running the whole the script tell myScript to handleIt() --run the whole script run script myScript
When we just run the handler, we get “1.” When we run the whole script, we get “2.” By comparison, here’s what would happen if we used a global variable, instead:
script myScript global myProperty set myProperty to 1 on handleIt() global myProperty set myProperty to myProperty + 1 display dialog "handleIt:" & myProperty end handleIt handleIt() end script tell myScript to handleIt() run script myScript
This time we get an “undefined” error.` Properties exist within the script almost as objects unto themselves, whereas global variables have to have values set before they truly exist. Even saying “global myProperty” inside the handler wasn’t enough for it to pull the value of myProperty from the script above it.
Those of you who use properties in your scripts also know that properties have other advantages. Firstly, you can assign a value to a property at the same time that you declare it. Second, their values persist between executions of the script. Below is a script that, once compiled, you can run again and again and each time the value of theProp will get larger.
property theProp: 0 set theProp to theProp +1 display dialog theProp
Once a script is re-compiled, however, a property “resets” to its original value and starts over. One of my scripts in the SHAZAM! collection of beginner scripts, Conversation, uses this technique to store vocabulary between executions. Essentially, it gets “smarter” each time you run the script, learning new words.
Let’s Go Live to Our Studio
Now, all of the above applies to Applescript Studio except that properties do not persist between executions of Studio apps. However, one advantage of using properties in Studio apps is that you can access them from the other scripts of your application! For example if you have a property in your application.applescript and you need to see it from your document.applescript, here’s how you do it. In the document.applescript you say:
set mainScript to first script of me set someProperty to mainScript's someProperty
You can’t do this with global variables without loading another copy of the other script using load script. And you can’t do it with local variables at all. The end result being that most Studio scripters don’t use global variables - they use properties.
But by using a combination of global, property, and local objects you can make sure that something that should be protected (like an internal value) isn’t visible to scripts outside of the proper scope. Those of you who are familiar with object-oriented programming you will recognize this as the difference between public and private declarations of variables. For a bit more info, check out Apple’s page on the subject.
Just One More Thing
While your scripts and Studio apps will work just fine without formal declarations, you might think about this: How often have you started to write a script or a handler and gotten in a corner and just thrown in a “surprise” variable (usually called something unromantic like “temp”) in order to get out of said corner?
Those moments are the time when you should back up and rethink the section you’re writing. Usually it means you haven’t thought of something until it was too late. By declaring your variables and sketching out your intended code as pseudocode in comments before you code, you can avoid those kinds of “dead ends.”
Another reason to declare variables is that it makes your code easier to read. By putting a section at the top of the handler or script that declares all the variables you will be using, your program is much clearer for someone else to read (or for you to read if you come back to it months or years later).
As always, have fun, and crunch some code!