[ Team LiB ] |
9.5 Script Objects as ValuesA 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 ReferenceWhen 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:
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 ReferenceA 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 ResultThe 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 ClosuresA 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 ConstructorsAnother 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 ] |