DekGenius.com
[ Team LiB ] Previous Section Next Section

7.4 Scoping of Variables

The notion of scoping has to do with where an entity is visible. Your code consists of regions; these regions are each continuous, and some of them may be inside others, but they do not partially intersect—given two regions, either one is entirely inside the other or they are completely distinct. The region in which a variable is visible is called its scope. A variable that is visible at a certain point is said to be in scope at that point.

Scoping of variables in AppleScript is extraordinarily complicated (in my opinion). It's also very important to understand, so don't skip this section.

7.4.1 How Scoping Is Meaningful

Before we can talk about the scoping of variables in particular, you must understand the basic principles of AppleScript scoping in general. These are:

  • The top level of all scope is the script as a whole.

  • The regions of scope are handlers and script objects (and the top level).

  • A script object may contain a handler. A handler may contain a script object. A script object may contain a script object. But a handler may not (directly) contain a handler.

Let's start with the first rule. Your script as a whole is itself the ultimate region of scope, containing everything else. So, let's say that your script consists of just the following code:

set x to 7

In terms of scope, where are we when this code executes? This is all the code there is, and we're not in a handler or a script object, so we are at the top level of all scope, the script as a whole.

Now I will illustrate the second and third rules, even though I have not yet explained rigorously what a script object or a handler is; all you need to know is that in code a script object is a block declared by the word script and a handler is a block declared by the word on. This code, then, is legal:

on handlerOne(  )
        script scriptOne
        end script
end handlerOne
script scriptTwo
        on handlerTwo(  )
        end handlerTwo
        script scriptThree
        end script
end script

Everything you see in that code is inside the top-level script. Within the top-level script are handlerOne and scriptTwo. Within handlerOne is scriptOne. Within scriptTwo are handlerTwo and scriptThree.

Along with the top level, these handlers and script objects are the regions of scope. Each region starts with the declaration of the handler or script object, and ends with the corresponding end line. Into this code may be inserted further code, and every line of this further code is in some definite region of scope.

The question we want to answer, then, is how variables in these various regions of scope are visible to code within other regions of scope.

7.4.2 Explicit Locals

An explicit local is a variable declared with the keyword local. It is legal to declare more than one variable local in the same command, by separating them with commas. So:

local x
local y, z

In general, an explicit local is visible only within the scope where it is declared. (There is one exception, which I'll mention in a moment.)

Different local variables in different scopes can thus have the same name without trampling on one another. Suppose a script object starts like this:

script myScript
        local x

The moment that local declaration for x is encountered, it means that from now on when code in this script object's scope says x it means this local x and no other. Other scopes may declare their own local x, they may declare a global x, they may bang the floor and have a temper tantrum, but they absolutely will not be able to have any effect upon myScript's x, nor will anything myScript does with its x have any effect upon them.

Here's an example of a local variable in action:

local x
set x to 5
script myScript
        display dialog x
end script
run myScript -- error

(The code inside a script object does not run when the script object is defined. To run the code inside a script object, you tell that script object to run. This is formally explained in Chapter 9. So in this example, first we define myScript, then we run it.)

That code compiles, but it won't run; it stops with a runtime error at the display dialog x command, objecting that x is not defined. There is a variable called x and it is defined, but it is declared local and therefore is visible only within its own scope. In this case, that scope is the top-level script. The display dialog x command is in a different scope, that of the script object myScript. Therefore AppleScript takes this to be a different x, and this different x has never been assigned a value.

Now let's do it the other way round:

on myHandler(  )
        local x
        set x to 5
end myHandler
myHandler(  )
display dialog x -- error

(The code inside a handler does not run when the handler is defined. To run the code inside a handler, you say its name followed by parentheses. This is formally explained in Chapter 8. So in this example, first we define myHandler, then we run it.)

This code stops with a runtime error at the display dialog x command, objecting that x is not defined. There is a variable x that has been defined, but that happened inside the scope of the handler myHandler, where this x was declared local. The display dialog x command is in a different scope, namely the top level. Therefore AppleScript takes this to be a different x, and this different x has never been assigned a value.

There is, however, this one great exception to the rule about the scope of local variables: a script object defined in a handler can see the handler's local variables . For example:

on myHandler(  )
        local x
        set x to 5
        script myScript
                display dialog x
        end script
        run myScript
end myHandler
myHandler(  ) -- 5

This remarkable exception to the local scoping rule will permit us to pass a handler as a parameter to another handler and call it (Section 8.6.1). We will also combine it elegantly with a handler's ability to return a script object (Section 9.5.3).

7.4.3 Global Declarations: The Downward Effect

Our next topic will be variables declared with the keyword global. It is legal to declare more than one variable global in the same command, by separating them with a comma. So:

global x
global y, z

For clarity, I'm going to discuss the effect of a global declaration in two stages. First I'm going to explain what I call the downward effect of a global declaration. By this I mean the effect a global declaration has on code in the same scope as the declaration, or a deeper scope within that scope.

Here's the rule. A variable declared global is visible subsequently in the same scope as the declaration, and within all handlers and scripts defined subsequently in the same scope, to an infinite depth.

For example, this code runs:

global x
set x to 5
on myHandler(  )
        display dialog x
end myHandler
myHandler(  ) -- 5

The variable x is declared global; it is then visible downward into the scope of myHandler, because myHandler is defined subsequently in the same scope as x. (Do you see why I call this the downward effect of the declaration?)

In that example, I proved that code inside myHandler could see x by having that code fetch its value. But such code can equally well set its value:

global x
on myHandler(  )
        set x to 5
end myHandler
myHandler(  )
display dialog x -- 5

That's important. Code exposed to a variable by a global declaration is very powerful. The code gains full access to the variable.

Let's prove that the downward visibility created by a global declaration operates to a greater depth (it operates, as I said earlier, "to an infinite depth," but I don't see how to prove that):

global x
on myHandler(  )
        script myScript
                on mySecondHandler(  )
                        set x to 5
                end mySecondHandler
        end script
        myScript's mySecondHandler(  )
end myHandler
myHandler(  )
display dialog x -- 5

The line where x is given a value appears in a handler within a script object within a handler. Nevertheless that line can see the variable x declared global in the first line, and the script runs successfully.

Now let's concentrate on the word "subsequently." A variable not declared global until after the definition of a script object or handler cannot be seen by code within that script object or handler. The following code does not work:

on myHandler(  )
        script myScript
                on mySecondHandler(  )
                        set x to 5
                end mySecondHandler
        end script
        myScript's mySecondHandler(  )
end myHandler
global x
myHandler(  )
display dialog x -- error

The variable x was not declared global until after the definition of the handler myHandler. Therefore code within myHandler cannot see x. The line set x to 5 was executed, but it failed to set the global variable x's value; therefore when we reach the last line, the global variable x has no value and a runtime error occurs.

(You might now be wondering: "Okay, the line set x to 5 didn't set the value of the global variable x, but it didn't cause a runtime error either; so what did it do?" I'll get to that, I promise.)

Naturally, any downward scope may shield itself from the downward effect of a global declaration at a higher level, simply by declaring the same variable name as local for its own scope. For example:

global x
set x to 5
script myScript
        local x
        on myHandler(  )
                set x to 10
        end myHandler
        myHandler(  )
        set x to 20
end script
run myScript
display dialog x -- 10 (not 20)

The dialog displays 10, not 20. The global x declaration in the first line has a downward effect on myScript and, within it, on myHandler. But myScript then shields itself from this effect with a local x declaration. The variable x declared global in the first line is thus visible at top level and at the third level in myHandler, but at the second level in myScript it is not visible, and the x referred to there is a different x. When myHandler sets x to 10, that is the same x as at the top level. When myScript then sets x to 20, that's a different x and this has no effect on the value displayed in the last line.

7.4.4 Global Declarations: The Upward Effect

We are now ready to talk about what a global variable really is. In the previous section, in talking about the downward effect of a global declaration, I was talking about just the declaration; a global declaration can appear anywhere. But now we're talking about the actual variable named by a global declaration—a global variable. The rule is: a global variable actually exists at the top level.

However, it turns out that a global variable, even though it exists at top level, cannot automatically be seen in every scope. In order for a global variable to be seen in a scope other than top level, it must be explicitly declared.

There are two ways for a global variable to be declared so as to be visible in a scope other than top level. One we have already seen: the top level may declare it, which makes it visible subsequently downwards. The other way is for the deeper scope to ask to see the global variable; it does this with a global declaration in its own scope.

The upward effect of declaring a variable global is to identify the declared variable with the top-level global variable of the same name. This top-level global variable may or may not exist already; if it doesn't exist, we may say that the declaration also creates it. (But the global variable doesn't really exist until it is defined by being assigned a value.)

To illustrate:

on setX(  )
        global x
        set x to 5
end setX
on getX(  )
        global x
        display dialog x
end getX
setX(  )
getX(  ) -- 5

When setX runs, its declaration of x as global creates the global variable x at top level (because no such global variable exists previously), and identifies this x with that x; thus, in setting the value of x, it sets the global's value. Then when getX runs, its declaration of x as global identifies this x with the now existing global variable x at top level. Thus, when it displays the value of x, it is the global's value that it displays. Both setX and getX have access to the very same global variable x; and they have that access because they each asked for it, with a global declaration.

It goes without saying (said he, saying it) that such a global declaration, by virtue of its downward effects, also gives to all downward scopes the same powers over a top-level global that it gives its own scope (unless, of course, they deliberately shield themselves from this power by means of a local declaration, as in the example at the end of the previous section). For example, this code works:

script myScript
        global x
        on myHandler(  )
                set x to 5
        end myHandler
        myHandler(  )
end script
on getX(  )
        global x
        display dialog x
end
run myScript
getX(  ) -- 5

By the time we come to the last line, the script object myScript has run, and its handler myHandler has executed the line set x to 5. Because there has been a higher-level global declaration of x, this x is that global x. (That's the downward effect of the global declaration.) But this x is also the top-level global x. (That's the upward effect of the global declaration.) Therefore, even though it contains no global declaration within itself, myHandler is able to set the value of the top-level global x. Then when getX runs, since it starts with a global declaration for x, it sees the same top-level global variable, and displays its value.

7.4.5 Undeclared Variables

We come now to the question of undeclared variables. Say that at compile time, a variable name is encountered, and this name has not been declared for this scope—that is, it has not been declared as local in this same scope, and it has not been declared as global in the same or a higher scope. Now, a variable can only be local or global. What will AppleScript do?

It turns out that there are two different answers, depending on where the code occurs—at the top level, or elsewhere.

7.4.5.1 Undeclared variables at top level

Code at the top level of the script is special. (Technically, what I mean here is "code at the top level of the run handler." See Section 8.5.3.) Code at the top level of the script doesn't need a global declaration in order to create, set, or see a global variable. An undeclared variable name at top level is treated as global. But lacking the explicit global declaration, it lacks the downward effect that an explicit global declaration would have. We may call such a variable an implicit global.

So, for example:

set x to 5
on getX(  )
        global x
        display dialog x
end getX
getX(  ) -- 5

In the first line, the previously undeclared x was created as a top-level global, implicitly, and given a value. When getX runs, it is able to access that value with a global declaration, and can display it.

Now let's do the converse:

on setX(  )
        global x
        set x to 5
end setX
setX(  )
display dialog x -- 5

By the time we get to the last line, setX has run; it has created a top-level global variable x, and has set its value. In the last line, the previously undeclared x spoken of at top level is an implicit global, so it is that same top-level global variable x. Thus the value 5 is displayed.

But this code fails with a runtime error, unless the first line is uncommented:

-- global x
set x to 5
on getX(  )
        display dialog x
end getX
getX(  ) -- error, unless you restore the global x declaration

The presence of the global x declaration has a downward effect, enabling code inside the getX handler to see x without a global x declaration of its own. Without such an explicit global x declaration anywhere, x is still global, but code in getX can't see it.

It is perfectly possible for code at the top level to shut off its own access to a global variable, just as any other scope may do, by declaring a local variable of the same name. For example:

local x
set x to 5
on setX(  )
        global x
        set x to 10
end setX
setX(  )
display dialog x -- 5

This displays 5, not 10. That's because there has been a declaration that the x spoken of at top level is a local. It's true that setX set a top-level global to 10, but that's a different variable! The top-level code isn't accessing that variable; it closed off its access to it, through the local declaration.

7.4.5.2 Undeclared variables not at top level

An undeclared variable name not at top level is treated exactly as if that variable had been declared local in its scope. We may call such a variable an implicit local .

For example:

set x to 5
script myScript
        set x to 10
end script
run myScript
display dialog x -- 5

The dialog displays 5. You should now understand why. Each set x line creates a different x—the first creates an implicit global at top level, the second creates an implicit local in its own scope. Thus the x that is set to 10 is a different x from the x that was set to 5; it is the x that was set to 5 that is displayed in the last line. There was no runtime error; no one tried to access the value of an undefined variable. But myScript's x was wasted; it was set to 10 and then was immediately destroyed as it went out of scope, with no effect on anything else in the code.

7.4.5.3 Declare your variables

Sounds like "eat your vegetables," doesn't it? Well, it should. Each motto is good advice, no matter how unpalatable it may seem at first. I strongly advise you (once again) to declare all your variables—even your locals.

Now that you understand what happens when variables are not declared, you can imagine the sorts of confusion that can arise. If you let yourself become lulled into a false sense of security by the fact that there's no need to declare your variables, then you can be surprised when some other scope tramples them.

For example, imagine that your script starts like this:

set x to 5

That's top-level code, so you've just implicitly declared x a global. This means that any other handler or script object anywhere in this script can access your x and change its value, just by saying this:

global x

This other code may not have intended to trample on your x; perhaps it was trying to establish a global of its own, possibly in order to communicate its value to code at a lower level. But the damage is done, because of the upward effects of a global declaration. And to think you could have prevented this, just by declaring your x local to start with.

In the case of script objects the problem is particularly insidious, because it is possible to run a script object whose code you can't see, by loading it from a compiled script file on disk. There will be more about that later (Chapter 9), but here's a quick example:

set x to 5
run (load script alias "myHardDrive:aScriptFile.scpt")
display dialog x

The frightening fact is that x could now be anything! If the script in the file aScriptFile.scpt happens for any reason to declare global x, it can freely change the value of your x.

The converse is also true. Pretend you have a large script and that this code occurs somewhere within it:

on myHandler(  )
        set x to 5
end

Is x a local or a global here? You don't know! It depends upon the context. If x has previously been declared global at a higher level, this x is global (by the downward effect of that declaration). If not, this x is local. But it is intolerable that you should have to look elsewhere to learn the scope of x within myHandler! All you have to do is explicitly declare it global or local, right here in myHandler, and then you'll know for sure.

7.4.6 Free Variables

An entity defined outside a handler or script object but globally visible within it, and not overshadowed by a declaration of the same name, is called a free variable with respect to that handler or script object. For example:

global x, y, z
script myScript
        property x : 1
        local y
        yy
        z
end script

Within myScript, x is explicitly defined as a property (as explained later in this chapter in Section 7.5) and y is explicitly defined as a local, so neither is a free variable. The variable yy isn't explicitly defined within myScript, but it isn't defined outside it either, so it is an implicit local and not a free variable. But the variable z is globally visible within myScript (from the global declaration at the start of the code), and the name is not redeclared within myScript, so z within myScript is a free variable, and is identified with the global z declared in the first line.

A free variable takes its value within the handler or script object at the time the code runs, not at the time the handler or script object is defined. For example:

set x to 5
on myHandler(  )
        global x
        display dialog x
end myHandler
set x to 10
myHandler(  ) -- 10 (not 5)

The dialog displays 10, not 5. It doesn't matter that x had been set to 5 when myHandler was defined; it only matters what its value is when the code inside myHandler actually runs. By that time, x has been set to 10.

It is important here (as we saw earlier) that x has been declared global in code that appears before the code where myHandler speaks of it. Free variables' values are determined at runtime, but the identification of a variable as a free variable, and its association with some particular globally visible variable, is performed during compilation (and AppleScript's compilation is single-pass, remember). This way of resolving the meaning of free variable names is called lexical scoping.

For example:

set x to 5
on myHandler(  )
        display dialog x
end myHandler
global x
myHandler(  ) -- error

It's true that x is declared global before (temporally) myHandler runs. But that's not good enough. We must declare x global before (physically) myHandler speaks of it; otherwise, myHandler's x isn't the global x, and the code fails with a runtime error because myHandler's x isn't defined.

7.4.7 Redeclaration of Locals and Globals

It is a compile-time error to redeclare an implicit global as local:

set x to 5
local x -- compile-time error

It is a compile-time error to redeclare an implicit local as global:

on getX(  )
        display dialog x
        global x -- compile-time error
end getX

It is a compile-time error to redeclare as local a variable declared global in the same scope (except at top level):

on getX(  )
        global x
        local x -- compile-time error
end getX

It is a compile-time error to redeclare as global a variable declared local in the same scope (except at top level):

on getX(  )
        local x
        global x -- compile-time error
end getX

At top level, it is not an error to declare a variable local and then declare it global in the same scope. But it doesn't have any effect within the top-level scope either. For example:

local x
global x
set x to 5
on setX(  )
        set x to 10
end setX
on getX(  )
        display dialog x
end getX
setX(  )
getX(  ) -- 10
display dialog x -- 5

Once x is declared global, both setX and getX have automatic access to a top-level global variable x. But the code in the top level does not have such access. There, x has already been declared local; nothing can change this. Once a local, always a local. The x that is set to 5, and that is displayed at the end, is this local x, which is different from the global x.

At top level, it is not an error to declare a variable global and then declare it local in the same scope. But access to the global variable is lost in the top-level scope. For example:

global x
set x to 5
local x
on getX(  )
        display dialog x
end getX
getX(  ) -- 5
display dialog x -- error

After the first two lines, there is a top-level global variable x and its value is 5, and code at a deeper level can access it; the subsequent local declaration has no effect on this fact, even though it precedes the definition of the deeper-level code. But the top-level code has lost its access to this global variable, and can never recover it.

    [ Team LiB ] Previous Section Next Section