8.7. InheritanceScript objects may be linked into a chain of inheritance . One script object inherits from another if the second script is the parent of the first. Then, suppose an attempt is made to access a top-level entity of the first script object (using the special syntax described in "Accessing Top-Level Entities," earlier in this chapter). If the script object has no such top-level entity, the attempt is passed along to its parent to see whether it has such a top-level entity. It turns out that every script object has a parent property. This property is set for you if you don't set it (and so there is always an inheritance chain, even though you might not be aware of this). To link two script objects explicitly into a chain of inheritance, initialize the parent property of one to point to the other.
In this example, we explicitly arrange two script objects, mommy and baby, into an inheritance chain (by initializing baby's parent property). We can then tell baby to execute a handler that it doesn't have, but which mommy does have. Here we go:
script mommy
on talk( )
display dialog "How do you do?"
end talk
end script
script baby
property parent : mommy
end script
baby's talk( ) -- How do you do?
In that example, we told the child from outside to execute a handler that it doesn't have but the parent does. The child can also tell itself to execute such a handler:
script mommy
on talk( )
display dialog "How do you do?"
end talk
end script
script baby
property parent : mommy
talk( )
end script
run baby -- How do you do?
Getting and setting properties works the same way. In this example, we get and set the value of a property of baby that baby doesn't have: script mommy property address : "123 Main Street" end script script baby property parent : mommy end script display dialog baby's address -- 123 Main Street set baby's address to "234 Chestnut Street" display dialog mommy's address -- 234 Chestnut Street Again, the same thing can be done from code within the child; but now the name of the property must be prefixed with the keyword my. Like its when targeting a script object, my specifies that we're talking about a top-level entity of this script. (In fact, instead of my, you can use its; but that seems less intuitive, somehow.)
script mommy
property address : "123 Main Street"
end script
script baby
property parent : mommy
on tellAddress( )
display dialog my address
end tellAddress
end script
baby's tellAddress( ) -- 123 Main Street
Similarly, we can refer to a script object that the child doesn't have but the parent does:
script mommy
script talk
display dialog "How do you do?"
end script
end script
script baby
property parent : mommy
end script
run baby's talk -- How do you do?
Again, if the child wants to do this, it must use my (or its):
script mommy
script talk
display dialog "How do you do?"
end script
end script
script baby
property parent : mommy
run my talk
end script
run baby -- How do you do?
With a handler call, there is no need for my:
script mommy
on talk( )
display dialog "How do you do?"
end talk
end script
script baby
property parent : mommy
talk( )
end script
run baby -- How do you do?
8.7.1. PolymorphismWhen code within a script object refers to a top-level entity, the search for this top-level entity starts in the script object to which the message that caused this code to run was originally sent. This is called polymorphism . Again, when accessing a property or script object, you must use the keyword my (or its) to get polymorphism to operate. An example will clarify:
script mommy
on tellWeight( )
display dialog my weight
end tellWeight
end script
script baby
property parent : mommy
property weight : "9 pounds"
end script
baby's tellWeight( ) -- 9 pounds
We ask baby to tell us its weight, but baby doesn't know how to do this (a baby can't talk), so the message is passed along to the parent, mommy. There is now an attempt to access my weight. But mommy has no weight property. However, the search for weight starts with baby, because our original message was to baby (mommy is involved only because of inheritance). The property is found and the code works. If we take away my, polymorphism fails to operate and the entire mechanism breaks down. With the previous example, we would get a runtime error message: "The variable weight is not defined." Without my, the name weight is not an attempt to access a top-level entity. Instead, it's just a name. AppleScript looks for a defined variable weight in scope at the point where the name is used, and fails to find it. The reason for the "poly" in the name "polymorphism" is that the response to the parent's use of a term can mean different things, depending on who the original target was. A parent whose code is running because of inheritance has no idea of this fact, so it has no idea exactly what its own code will do. This is officially cool, and is one of the key principles of object-based programming. For example: script mommy property weight : "120 pounds" on tellWeight( ) display dialog my weight end tellWeight end script script baby property parent : mommy property weight : "9 pounds" end script script baby2 property parent : mommy property weight : "8 pounds" end script mommy's tellWeight( ) -- 120 pounds baby's tellWeight( ) -- 9 pounds baby2's tellWeight( ) -- 8 pounds In that example, the parental phrase my weight gets three different interpretations, depending solely on what script object was targeted originally. Again, there is no need to use my (or its) with a handler call; polymorphism operates automatically, as this example shows:
script mommy
on cry( )
display dialog "Boo-hoo, sniff sniff..."
end cry
on beSad( )
cry( )
end beSad
end script
script baby
property parent : mommy
on cry( )
display dialog "Waaaaaaa!"
end cry
end script
baby's beSad( ) -- Waaaaaaa!
It's mommy that implements beSad, but the original target was baby, so it's baby's cry that is called. 8.7.2. ContinueA child can call an inherited handler by using the continue command. The syntax is the keyword continue followed by a complete handler call (parameters and all). You might wonder why this is needed, as after all the child can just send a message directly to the parent. It could do this, for instance, by referring to the parent as parent. But there's a crucial difference. If a message is sent to the parent by referring to it as parent, that's a new message with a new target. On the other hand, the continue command takes place in the context of the current message and the current target; it passes the current flow of control up the inheritance chain. Thus, the one breaks polymorphism, the other does not. This example demonstrates the difference:
script mommy
property weight : "120 pounds"
on tellWeight( )
display dialog my weight
end tellWeight
end script
script baby
property parent : mommy
property weight : "9 pounds"
parent's tellWeight( ) -- no polymorphism
continue tellWeight( ) -- polymorphism
end script
run baby -- 120 pounds, 9 pounds
8.7.3. The Implicit Parent ChainA script object without an explicitly specified parent has as its parent the script as a whole. That fact is surprising and is worth stressing. Mere physical nesting is not implicit parenthood: script myScript script myInnerScript my parent -- the top-level script, not myScript end script run myInnerScript end script run myScript Nor, indeed, can a nested script object be made to have a containing script object as its parent. AppleScript will balk if you try this: script myScript script myInnerScript property parent: myScript -- compile-time error end script end script (I think the reason for this restriction must be that the demands of parenthood would conflict irresolvably with the rules of scoping.) The implicit parent chain explains why certain things work that look like they shouldn't work. Here's an example:
property x : 10
script outer
property x : 20
script inner
end script
end script
get outer's inner's x -- 10
If you go by your intuitions alone, you might think the search for x starts in inner and works its way up the nest. We come immediately to x in outer. So the result of the script should be 20. Your intuitions are wrong. They are right for how scope works (see Chapter 10), but this isn't about scope; it's about accessing top-level entities. Top-level entities are sought up the parent chain, and the top-level script, not outer, is inner's parent. Similarly:
on sayHowdy( )
display dialog "Howdy"
end sayHowdy
script s
end script
tell s to sayHowdy( ) -- Howdy
That works not because s can "see" sayHowdy but because sayHowdy is a top-level entity of s's parent. If you don't believe me, I can prove it by breaking the inheritance chain. This isn't easy, because if I set s's parent to a different script object, the script itself will be the parent of that script object, and we'll get the same result. So I'll set s's parent to something that isn't a script object: on sayHowdy( ) display dialog "Howdy" end sayHowdy script s property parent : 3 end script tell s to sayHowdy( ) -- Error: Can't get sayHowdy of 3 We can take advantage of the inheritance chain to refer to a top-level entity of the script itself, even though the script itself has no name . Consider this code:
property x : 5
script myScript
property x : 10
display dialog x
end script
run myScript -- 10
It looks like we can never access the top-level property x from within the script object myScript, because the name x is overshadowed by myScript's own property x. A script object's top-level entities can be accessed by using the name of the script object, as we know. But the script as a whole has no name. But now we know another way to refer to the script as wholeas myScript's parent:
property x : 5
script myScript
property x : 10
display dialog my parent's x
end script
run myScript -- 5
Unfortunately, that trick won't work if myScript has a different parent. In that case the solution is to give the script as a whole a name. You can do this by initializing a top-level property to the value me (see "Me" in Chapter 11).
property topLevel : me
property x : 5
script scriptOne
property x : 10
end script
script scriptTwo
property x : 20
property parent : scriptOne
display dialog topLevel's x
end script
run scriptTwo -- 5
Surprisingly, there's a parent beyond the script as a whole. The script as a whole has as its parent the AppleScript scripting component. This appears to your code as a script object called AppleScript. The AppleScript script object has some properties that you can access (listed in Chapter 16). Normally you do this without having to refer to AppleScript explicitly, because these properties are in scope globally; it's as if every script were surrounded by another invisible script with property declarations for these properties. But in a context where a name overshadows the name of one of these properties, it would be necessary to be explicit, in order to jump past the current scope and up to the level of AppleScript: set pi to 3 display dialog pi -- 3 display dialog AppleScript's pi -- 3.141592... display dialog parent's pi -- 3.141592... The AppleScript scripting component has a parent toothe current application . This is the host application that summoned the AppleScript scripting component to begin with. The current application is the absolute top of the inheritance chain, and can be referred to in code as current application. For example:
display dialog (get name of current application) -- Script Editor
To sum up: script myScript my parent -- «script», the anonymous top level my parent's parent -- «script AppleScript» my parent's parent's parent -- current application end script run myScript 8.7.4. Non-Script ParentA script object's parent doesn't have to be a script object. It can be a value of a different type altogether. In that case, the script object suddenly appears to be that other object, in cases where it cannot respond to a message itself. I have never seen this feature employed usefully in code, but it's definitely cool: script s property parent : {"Mannie", "Moe", "Jack"} on greeting( ) return "Howdy" end greeting end script tell s greeting( ) -- "Howdy" get item 2 -- "Moe" end tell In effect, the script object s is now a kind of list wrapper; it can do things that a list can do, such as report on its items, but it can also respond to the greeting( ) message. However, this trick breaks down against other features of AppleScript; for example, operators treat the script object as a script object, not as a list:
script s
property parent : {"Mannie", "Moe", "Jack"}
end script
get s & "Pep" -- {«script s», "Pep"}, not {"Mannie", "Moe", "Jack", "Pep"}
8.7.5. Handler Calls, Commands, and Script ObjectsSeveral times in this chapter I've pointed out that you don't need to use its (or my) with a handler call when accessing a handler that is a top-level entity of a script object. You do have to use its in order to access the handler as a value. It's the call that gives you access without its. So, as you know, you can't say this (without its): script s property p : 10 end script tell s get p end tell At runtime, you get an error saying that p is undefined. That's because without its, AppleScript doesn't know you're talking about s's property p. It thinks you must be talking about some other p, and there isn't one. This is true for a handler too, if we simply treat the handler as the value of a variable: script s on h( ) return 10 end h end script tell s get h end tell Again, we get an error that h is undefined. But a handler call is different. This works without its:
script s
on h( )
return 10
end h
end script
tell s
h( ) -- 10
end tell
So a handler call is automatically treated as an attempt to access a top-level entity. It's important to stay conscious of this, because if you go by your intuitions alone you may be confused. Consider this script:
script x
property greeting : "Howdy"
on myHandler( )
display dialog greeting
end myHandler
script y
display dialog greeting
myHandler( )
end script
end script
run x's y -- Howdy, then error: «script y» doesn't understand the myHandler message
It looks like the property greeting and the handler myHandler are in the same place, so they should have the same status. And that's true, as far as it goes. But y apparently can see greeting, yet it fails in its attempt to call myHandler. That is also true. The reason is that a handler call is not the same thing as just saying the name of some variable. A handler call is routed as a search for a top-level entity. The search starts in y. myHandler isn't there, so we pass to the parent. The parent isn't x; it's the top-level script. myHandler isn't there either. So the search fails. This is not to say that you can never call myHandler at all. You simply have to target x explicitly when you do:
script x
property greeting : "Howdy"
on myHandler( )
display dialog greeting
end myHandler
script y
x's myHandler( ) -- explicit target
end script
end script
run x's y -- Howdy
I think the reason for this special status of a handler call is that AppleScript wants to treat a handler call as a command. Its status is like that of the built-in commands, such as run and count. When you give a command to a script object, you don't want to have to talk in some special way; you are sending a message and you want that message to go to the right place all by itself. So a handler call works that way too. To see that this probably the right sort of explanation, consider how the message-passing mechanism works when you send a built-in command to a script object:
script s
end script
tell s to count -- 0
The count message is routed just the way a handler call is routed. If a script object implements count, it is this count that is called:
script theCount
on count
return "1, 2, 3, ha ha ha"
end count
end script
tell theCount to count -- "1, 2, 3, ha ha ha"
Alternatively, if we give s a parent that implements count in a different way, then the count command is routed to that parent:
script s
property parent : {"Mannie", "Moe", "Jack"}
end script
tell s to count -- 3
(We'll return to weird handlers like count in Chapter 9, and to objects and the message-sending mechanism in Chapter 11.) So it appears that the inheritance chain is involved in every command. Whether it's a handler call or a built-in command, it has some target and is passed up the inheritance chain from that target until we come to someone who can obey. This probably also explains the curious language of the error message you get when you misdirect a handler call to a scriptable application: tell application "Finder" sayHowdy( ) -- Finder got an error: Can't continue sayHowdy. end tell It's as if the Finder were a sort of script object, saying, "I don't know what this command means, and I haven't got a parent to pass it along to." Because handler calls and commands are passed up the inheritance chain, the current application is the implicit target of every command not otherwise targeted. This fact is rarely useful, though. For example, in theory, if a script that drives BBEdit is to run in BBEdit's Script menu, it doesn't need to target BBEdit in a tell block; BBEdit is the current application, so BBEdit is implicitly the target of all commands. But in reality such a script usually will target BBEdit in a tell block, not as a way of directing its commands but as a way of getting its terminology to compile. The script could, however, get its terminology to compile with a terms block (see Chapter 19), and then it would indeed have no need to target BBEdit explicitly. |