DekGenius.com
[ Team LiB ] Previous Section Next Section

9.5 Script Objects as Values

A script object is a datatype in AppleScript. This means that a variable's value can be a script object. In fact, a script object definition basically is such a variable, one whose name is the name of the script object. You can refer to this variable, and get and set its value, just as you would any other variable. Here, we fetch a script object as a value and assign it to another variable:

script myScript
        display dialog "Howdy"
end script
local x
set x to myScript
run x -- Howdy

You can also assign a new value to a script object. No law says that this new value must be another script object; you're just replacing the value of a variable, as with any other variable. So, you could do this if you wanted:

script myScript
        display dialog "Howdy"
end script
set myScript to 9
display dialog myScript -- 9

You can assign a script object the value of another script object, in effect replacing its functionality with new functionality. Of course, that new functionality must be defined somewhere to begin with. For example:

script sayHowdy
        display dialog "Howdy"
end script
script sayHello
        display dialog "Hello"
end script
set sayHowdy to sayHello
run sayHowdy -- Hello

9.5.1 Set By Reference

When you use set (as opposed to copy) to set a variable to a value which is a script object, you set the variable by reference. This means that the script object is not copied; the variable's name becomes a new name for the script object, in addition to any existing names for the script object. This has two important implications:

  • Setting a variable to a script object with set is extremely efficient, no matter how big the script object may be.

  • If a script object has more than one name, then whatever is done to it by way of one name is accessible by way of its other names as well.

The second point is the vital one. Here's an example:

script sayHello
        property greeting : "Hello"
        display dialog greeting
end script
local x
set x to sayHello
set sayHello's greeting to "Howdy"
display dialog x's greeting -- Howdy
run x -- Howdy

In that example, we changed a property of the script object sayHello; the same property of the script object x was changed to the same thing. That's because sayHello and x are merely two names for the same thing. And that's because we used set to set x's value by reference to the script object that sayHello was already the name of.

When a script property is initialized to a value that is a script object, it too is set by reference. Let's prove it:

script addend
        property whatToAdd : 0
end script
script adder
        property z : addend
        on add(x)
                return x + (z's whatToAdd)
        end add
end script
set addend's whatToAdd to 2
display dialog adder's add(3) -- 5

In that example, we changed a property of the script object addend, and this affected the result of the handler add which refers to the same script object by way of the property z.

9.5.2 Pass By Reference

A script object passed as a parameter to a handler is passed by reference (Chapter 8). Let's prove it:

script myScript
        property x : 10
end script
on myHandler(s)
        set s's x to (s's x) + 1
end myHandler
display dialog myScript's x -- 10
myHandler(myScript)
display dialog myScript's x -- 11

In that example, myHandler never speaks explicitly of myScript; yet after running myHandler, we find that myScript's property x has been changed. This is because in passing myScript as a parameter to myHandler, we pass it by reference; myHandler has access, and can do whatever it wishes, to myScript.

9.5.3 Script Object as Handler Result

The result of a handler can be a script object. Normally, this script object is a copy, passed by value; it could not be passed by reference, since after the handler finishes executing there is no script object back in the handler for a reference to refer to. (Actually, if the returned script object is the same script object that was passed in as a parameter by reference, then it is returned by reference as well; still, that fact isn't terribly interesting, since at the time the script object was passed in, you must have had a reference to it to begin with.)

For example:

on scriptMaker(  )
        script myScript
                property x : "Howdy"
                display dialog x
        end script
        return myScript
end scriptMaker
set myScript to scriptMaker(  )
run myScript -- Howdy

In the last two lines, we acquire the script object returned by the handler scriptMaker, and run it. Of course, if we didn't want to retain the script object, these two lines could be combined into one:

run scriptMaker(  ) -- Howdy

A handler can customize a script object before returning it. So, for example:

on scriptMaker(  )
        script myScript
                property x : "Howdy"
                display dialog x
        end script
        set myScript's x to "Hello"
        return myScript
end scriptMaker
set myScript to scriptMaker(  )
run myScript -- Hello

In that example, the handler scriptMaker not only created a script object, it also modified it, altering the value of a property, before returning it.

Obviously, instead of hardcoding the modification into the handler, we can pass the modification to the handler as a parameter:

on scriptMaker(s)
        script myScript
                property x : "Howdy"
                display dialog x
        end script
        set myScript's x to s
        return myScript
end scriptMaker
set myScript to scriptMaker("Hello")
run myScript -- Hello

Recall from Section 7.4.2 that, contrary to the general rules of scoping, a script object defined inside a handler can see the handler's local variables. This means that in the previous example we can save a step and initialize the property x directly to the incoming parameter s:

on scriptMaker(s)
        script myScript
                property x : s
                display dialog x
        end script
        return myScript
end scriptMaker
set myScript to scriptMaker("Hello")
run myScript -- Hello

The real power of this technique emerges when we retain and reuse the resulting script object. For example, here's a new version of the general list-filtering routine we wrote earlier (Section 8.6.1). In that earlier version, we passed a handler both a criterion handler and a list, and got back a filtered list. In this version, we pass just a criterion handler, and get back a script object:

on makeFilterer(crit)
        script filterer
                property criterion : crit
                on filter(L)
                        if L = {} then return L
                        if criterion(item 1 of L) then
                                return {item 1 of L} & filter(rest of L)
                        else
                                return filter(rest of L)
                        end if
                end filter
        end script
        return filterer
end makeFilterer

The script object that we get back from makeFilterer contains a filter handler that has been customized to filter any list according to the criterion we passed in at the start. This architecture is both elegant and efficient. Suppose you know you'll be filtering many lists on the same criterion. You can use makeFilterer to produce a single script object whose filter handler filters on this criterion, store the script object, and call its filter handler repeatedly with different lists. For example:

on makeFilterer(crit)
        // ... as before ...
end makeFilterer
on isNumber(x)
        return ({class of x} is in {real, integer, number})
end isNumber
set numbersOnly to makeFilterer(isNumber)
tell numbersOnly
        filter ({"hey", 1, "ho", 2, "ha", 3}) -- {1, 2, 3}
        filter ({"Mannie", 7, "Moe", 8, "Jack", 9}) -- {7, 8, 9}
end tell
9.5.3.1 Closures

A closure is one of those delightfully LISPy things that have found their way into AppleScript. It turns out that a script object carries with it a memory of certain aspects of its context at the time it was defined, and maintains this memory even though the script object may run at a different time and in a different place. In particular, a script object returned from a handler maintains a memory of the values of its own free variables.

For example, a script object inside a handler can see the handler's local variables. So a handler's result can be a script object that incorporates the value of the handler's local variables as its own free variables. This means we can modify an earlier example one more time to save yet another step:

on scriptMaker(s)
        script myScript
                display dialog s
        end script
        return myScript
end scriptMaker
set myScript to scriptMaker("Hello")
run myScript -- Hello

This is somewhat miraculous; in theory it shouldn't even be possible. The parameter s is local to the handler scriptMaker, and goes out of scope—ceases to exist—when scriptMaker finishes executing. Nothing in myScript explicitly copies or stores the value of this s; we do not, as previously, initialize a property to it. Rather, there is simply the name of a free variable s:

                display dialog s

This s is never assigned a value; it simply appears, in a context where it can be identified with a more global s (the parameter s), and so it gets its value that way. Yet in the last line, myScript is successfully executed in a completely different context, a context where there is no name s in scope. In essence, myScript "remembers" the value of its free variable s even after it is returned from scriptMaker. myScript is not just a script object; it's a closure—a script object along with a surrounding global context that defines the values of that script object's free variables.

Here's an example where the value of the free variable comes from a property of a surrounding script:

on makeGreeting(s)
        script outerScript
                property greeting : s
                script greet
                        display dialog greeting
                end script
        end script
        return outerScript's greet
end makeGreeting
set greet to makeGreeting("Howdy")
run greet -- Howdy

In that example, makeGreeting doesn't return outerScript; it returns just the inner script object greet. That script object uses a free variable greeting whose value is remembered from its original context as the value of outerScript's property greeting. In the last line, the script object greet runs even though there is no name greeting in scope at that point.

In Section 9.6.3, later in this chapter, we explore further this ability of script objects to remember their global context.

9.5.3.2 Constructors

Another use for a script object as a result of a handler is as a constructor. Here we take advantage of the fact that when a handler is called, it initializes any script objects defined within it. So a handler is a way to produce a copy of a script object whose properties are at their initial value.

As an example, consider a script object whose job is to count something. It contains a property, which maintains the count, and a handler that increments the count. (This is using a sledgehammer to kill a fly, but it's a great example, so bear with me.) A handler is used as a constructor to produce an instance of this script object with its property set to zero. Each time we need to count something new, we call the handler to get a new script object. So:

on newCounter(  )
        script aCounter
                property c : 0
                on increment(  )
                        set c to c + 1
                end increment
        end script
        return aCounter
end newCounter
-- and here's how to use it
set counter1 to newCounter(  )
counter1's increment(  )
counter1's increment(  )
counter1's increment(  )
set counter2 to newCounter(  )
counter2's increment(  )
counter1's increment(  )
display dialog counter1's c -- 4
display dialog counter2's c -- 1
    [ Team LiB ] Previous Section Next Section