A record
is an unordered collection of name-value pairs. Each value may be of any type. Records are passed to a few important commands, such as make
, and are returned by scriptable applications and scripting additions as a way of providing a "table" of information (see Chapter 21). They are useful for passing as parameters to handlers because they can contain any number of items. AppleScript provides some operators for testing the contents of a record and for concatenating records to form a new record (see Chapter 15).
A literal record looks like a literal list except that each item has a name. The name is separated from the corresponding value with a colon:
set R to {who:"Matt", town:"Ojai"}
There is no empty
record as distinct from the empty list; the empty list is treated as the empty record for purposes of containment and concatenation. A record has no item
elements, its items cannot be referred to by index number, and you can't talk about the beginning
or end
of a record.
You can assign a record of values to a literal record of variable names or other references as a shorthand for performing multiple assignment. The assignments are performed pairwise by name, independently. If the record of values includes names that aren't in the record of variables, the extra values are ignored; if it's missing any names that are in the record of variables, there's a runtime error. See "List," earlier in this chapter. For example:
local who, town
set {who:who, town:town} to {town:"Ojai", who:"Matt"}
{who, town} -- {"Matt", "Ojai"}
When you use set
(as opposed to copy
) to set a variable to a value that is a record, you set the variable by reference. This means that the record is not copied; the variable's name becomes a new name for the record, in addition to any names for the record that may already exist. The same is true when a record is passed as a parameter to a handler. This special treatment is in common between lists, records, dates, and script objects, the four datatypes that can be mutated in place. (See "Set by Reference" in Chapter 7 and "Pass by Reference" in Chapter 9.) For example:
set R2 to {who:"Matt", town:"Ojai"}
set R1 to R2
set who of R2 to "Jaime"
R1 -- {who:"Jaime", town:"Ojai"}
In a literal record, a variable representing a list, record, date, or script object is itself set by reference . For example:
set L to {"Mannie", "Moe"}
set R to {pep:L}
set end of R's pep to "Jack"
item 3 of L -- "Jack"
The only mutation you can perform in place on an existing record is to replace the value of an individual item by assignment. Other changes in the record require generation of a new record. So, for example, if you wish to add to a record an item with a name that doesn't already exist in that record, you must make a new record using concatenation:
set R to {who:"Matt", town:"Ojai"} set who of R to "Jaime" R -- {who:"Jaime", town:"Ojai"} set R to R & {friend:"Steve"} R --{who:"Jaime", town:"Ojai", friend:"Steve"}
There is no penalty for concatenating an item with a name that already exists in a record , but it has no effect. For example:
set R to {who:"Matt", town:"Ojai"} & {who:"Jaime"}
R -- {who:"Matt", town:"Ojai"}
Clearly, order is all-important here, and you can use this fact to your advantage. Suppose you want to assign a friend
value within a record. If the record already has such an item, you can do it by assignment. If it doesn't, you can do it by concatenation
. But what if you don't know whether it has such an item or not? You can do it regardless by concatenating in the opposite order:
set R to {who:"Jaime", town:"Ojai"} set R to {friend:"Steve"} & R R -- {friend:"Steve", who:"Jaime", town:"Ojai"} set R to {who:"Jaime", town:"Ojai", friend:"Matt"} set R to {friend:"Steve"} & R R --{friend:"Steve", who:"Jaime", town:"Ojai"}
A record can recurse, for the same reason that a list can:
set R to {who:"Matt", town:"Ojai", cycle:null}
set cycle of R to R
who of cycle of cycle of cycle of cycle of R -- "Matt"
You can make records mutually recurse; you can even make a list and record that mutually recurse. Does anyone have an aspirin?
The following are the properties of a record:
Please pretend now that I'm jumping up and down, waving a big red flag and screaming: the names of a record's items are properties. The names are not strings; the names are not any kind of variable or value. They are effectively tokens created by AppleScript at compile time, like the names of variables. Actually, these tokens are created in two different ways. If the name is an existing property or class name, the token is its four-letter code, and the item is stored as a property. Otherwise, it is a string and is called a user
property; internally, user property
-value pairs are stored as a list (called a 'usrf'
) of alternating names and values.
Thus, a record consisting of two predefined properties and two user properties actually contains three items internally: the two predefined properties, followed by a list of four items (the name and value of each user property). For example:
set R to {name:"Matt", character:"impeccable", town:"Ojai", age:51}
It happens that name
is a property and character
is a class implemented in AppleScript itself, so these are tokenized as their four-letter codes, 'pnam'
and 'cha '
respectively. But town
and age
are user properties, so the third item of storage in the record is a 'usrf'
list containing the strings "town"
and "age"
and their values:
{ 'pnam':"Matt", 'cha ':"impeccable", 'usrf':[ "town", "Ojai", "age", 51 ] }
You can avoid having AppleScript treat existing property names as properties by putting those names in pipes (vertical bars). If you do this, you must put that name in pipes to refer to that item of the record later on, as otherwise AppleScript thinks you're talking about a property, which isn't defined for this record.
set R to {|name|:"Matt", |character|:"impeccable", town:"Ojai", age:51} get |name| of R -- "Matt" get name of R --error: Can't get name of...
When you talk to a record, it is the target, and its item names are used to interpret the vocabulary you use. The first thing AppleScript does is look to see whether any of this vocabulary is the name of an item of the record. That's why you can't assign to a nonexistent item in a record—the name you're using for that item is meaningless. No terminological confusion normally arises, because the context is clear. So:
set town to "Ojai"
set R to {name:"Matt", town:null}
set town of R to town -- no problem
Of course, you can confuse AppleScript if you set your mind to it. This code just sets the existing value of the variable town
to itself; the record is untouched:
set town to "Ojai"
set R to {name:"Matt", town:null}
tell R
set town to town
end tell
R -- {name:"Matt", town:null}
But you know how to fix that—right? (Hint: see Chapter 11.)
set town to "Ojai"
set R to {name:"Matt", town:null}
tell R
set its town to town
end tell
R -- {name:"Matt", town:"Ojai"}
There is no built-in way to obtain a list of the names of the items of a record. A record has no such introspective abilities. You (a human being) can see the names of the items of a record in AppleScript's display of the record's value. But your code can't see this information; the names of the items are not values of any kind, and cannot easily be turned into values. I have seen many elaborate attempts to work around this problem, but I'm not going to show you any of them. This is a big shortcoming of AppleScript itself, and it needs to be fixed on the level of AppleScript itself. Until it is, you can get assistance from Late Night Software's free List & Record Tools scripting addition. For example:
set R to {name:"Matt", town:"Ojai"} get property IDs of R -- {"pnam"} get user property names of R --{"town"}
It is possible to fetch or assign to the value of an item of a record using a variable to represent the name, through a second level of evaluation (through the run script
command, as explained in Chapter 19). Here's a way to fetch an item's value:
global r set r to {name:"Matt", age:51} on getWhat(what) set s to "on run {r} get " & what & " of r end" run script s with parameters {r} end getWhat getWhat("name")
It's a pity that such trickery is needed, and I don't really recommend this approach. Again, the List & Record Tools scripting addition provides a better alternative. See also "List Coercions" in the next chapter.