Skip to main content

References to Containers

Most of the time, containers in a script are used by storing a value directly into a container—or reading a value from one—by simply specifying a variable name (or other container identifier) directly in the script. Sometimes it can be extremely useful, however, to store a reference to a container, which can later be used to access the value of that container. The reference holds the identity of a container rather than its value, and accessing the reference will read or write the contents of the container that it refers to.

Characteristics of References

References have a few characteristics that you will want to be aware of as you work with them. To begin to understand how references work, consider the following example using a reference to a simple variable:

put 32 into age -- Age is now a variable containing the value 32
put a reference to age into yearsOnThePlanet -- Store a reference
put yearsOnThePlanet -- 32 (yearsOnThePlanet refers to age's value)
add 1 to age -- Age is now 33
put yearsOnThePlanet -- Puts 33 (references are dynamic)

In the first line above, a simple value (32) is assigned to an ordinary variable (age). The next line stores a reference to the variable age into the variable yearsOnThePlanet. Now, any access to the value of the yearsOnThePlanet variable will actually be accessing the value of the age variable, as illustrated by the next line of the script, The last two lines of the script increment that value of the age variable and show that the yearsOnThePlanet variable accesses the updated value.

References Bind Tightly

The connection established by a reference is “sticky” (yearsOnThePlanet is glued tightly to age in the above example) so assignments work in the other direction as well — changing the value of either will change the value of both:

put 29 into yearsOnThePlanet
put age -- Puts 29

Reference Syntax

References can be made using the words container, reference, reference to, or refer to, or using the shorthand symbol ‘@’ before the container. All of the following are equivalent:

put container thing into watcher
set watcher to be a reference to thing
put @thing into watcher
set watcher to reference thing
set watcher to refer to thing

container aContainer
{a} reference {to} aContainer
refer to aContainer
@ aContainer

References Can Be Stored in a List or a Property List

References can not only be stored in variables, as shown in the examples so far, but may also be stored as items in a list, or as properties of an object or property list. In this case, accessing the specific item or property that is a reference will access the container it refers to, as shown in this example:

put 13 into luckyNumber
put ["horseshoe", container luckyNumber, "penny"] into charms
put charms -- [horseshoe, 13, penny]
put 7 into item 2 of charms
put charms -- [horseshoe, 7, penny]
put luckyNumber -- 7

References May Refer to Properties, Files, and Chunks

References may refer to any type of container, not just variables. This includes all the different types of container discussed earlier in this section, including properties, files, and even chunks of any container:

set highScore to reference item 2 of line 3 of file "/tmp/stats"
put @ item 2 delimited by "." of the numberFormat into decimalFormat

References Are Dynamic

When a reference is made to a property of an object or to a chunk of a container (such as reference to word 3 of sentence) that reference is evaluated dynamically each time it is used. This means that if the contents of sentence are changed, the reference will always access the third word of its new contents.

References Are Not "Contagious"

Assignments in SenseTalk are always done by value unless a reference is explicitly indicated. So assigning a variable containing a reference to another variable will not create another reference, but only a copy of the current value of the source container. To create another reference, you must specifically indicate a reference during the asignment (specifying a reference to a reference will result in a second reference to the same source).

put 123 into source
put a reference to source into ref -- 123 (ref is a reference)
put ref into number -- 123 (number is NOT a reference)
put @ref into ref2 -- 123 (ref2 is now another reference to source)
put 456 into source
put ref -- 456
put number -- 123
put ref2 -- 456

References Can Be Reassigned

Storing a reference into a variable that already contains a reference will simply reassign that variable to the new reference. It will not change the value that the variable previously referred to:

put [123, 456] into [source1, source2]
put a reference to source1 into ref
put ref -- 123
put a reference to source2 into ref -- Ref is now reassigned
put 789 into ref -- Source1 is unaffected
put [source1, source2] -- [123, 789]

To change the container that was referred to into a reference instead of reassigning the original reference variable, you can request that explicitly using container referred to:

put @source1 into the container referred to by ref -- Ref still refers to source2, which now becomes a reference to source1
put "We're Twins" into ref -- Assign to ultimate container
put [source1, source2] -- [We're Twins,We're Twins]

Deleting By Reference

Deleting a reference to a chunk or property with the delete command will delete the chunk or property it refers to (note that this is not the same as deleting the variable containing the reference — see below).

put [1,3,5,7,9,11,13] into odds
set num to refer to item 5 of odds
put num -- 9 (num refers to item 5)
delete num
put odds -- [1,3,5,7,11,13]
put num -- 11 (num now refers to the new item 5)

References Are Persistent

Once a variable becomes a reference, it stays tied to the container it refers to until it is reassigned as a different reference, or the reference itself is deleted. You might think that you could free up a reference variable for other uses by simply putting empty into it, but this will merely put empty into the container it refers to! To use the variable for another purpose, you must first delete it, using the delete variable command (not the delete command — see above). If the reference is stored as an item in a list or a property of an object, you can delete that item or property.

put "green" into source
set localColor to refer to source
set localColor to "yellow"
put source -- "yellow"
delete variable localColor -- LocalColor is now undefined (no longer a reference)
set localColor to "blue" -- assigns its own value
put source -- "yellow"

There are two exceptional cases where a reference can become a plain variable without explicitly deleting it. Commands that implicitly set the value of the special variable it (see the list in All About "it") will first break any reference stored in it before assigning it a new value. Also, if a repeat construct specifies a loop variable that is currently a reference, that reference will be cleared before the variable is used in the repeat.

Using References

References can be used in many ways in your scripts. Here are a few of the most common uses.

Passing Parameters by Reference

One common use of references is to pass a reference as a parameter to another handler. By passing a reference, the called handler is able to change the value in the local handler. Here is an example:

put "candy" into tastyFood
pluralize @tastyFood -- Pass by reference
put tastyFood

to pluralize aWord
if aWord ends with "y" then put "ies" into last char of aWord
else put "s" after aWord
end pluralize

When the script above is run, it displays "candies". The variable tastyFood is passed by reference from the initial handler to the pluralize handler. Because it is a reference, when aWord is changed by the pluralize handler, it actually changes the value of the tastyFood variable in the initial handler.

Returning a Reference / Using a Function Result as a Container

Just as passing a reference to another handler gives that handler the ability to modify the contents of the container it refers to, it is sometimes useful for a function to be able to return a reference to a container that the caller can then modify. For instance, a function might return a reference to a global variable or a file selected by the function, or a reference to a chunk of such a container. To do this, there are two things that have to happen. First, the function being called must return a reference. Secondly, the calling handler must indicate that it wants to use the returned value as a container (by default, function return values are always treated by value otherwise).

As an example, suppose you want to insert a value into one of two lists, depending on which list has fewer items (a more realistic example might involve several lists). Here is a script that implements this idea using a function to select the appropriate list:

put "a,b,c,d,e,f" into firstBox
put "9,8,7,6" into secondBox
put ",Here" after container chooseContainer(@firstBox, @secondBox)
put firstBox -- "a,b,c,d,e,f"
put secondBox -- "9,8,7,6,Here"

to chooseContainer @c1, @c2
if the number of items in c1 < number of items in c2
then return container c1
else return container c2
end chooseContainer

There are a lot of references in this example. The word "container" (or "@" or "reference") is required before the call to chooseContainer() to tell SenseTalk to treat the value returned by the function call as a container. For this example to work, the parameters must also be passed by reference (@firstBox, @secondBox) since they originate in the calling script, and of course the selected container must be returned by reference. The result is a function that accepts references to any two containers, and returns a reference to the one containing fewer items. Marking the function's incoming parameters as references (@c1, @c2) is optional, as it has no functional meaning in the current version of SenseTalk, but it helps to remind users of the chooseContainer function that it should be called with parameters passed by reference.

Determining if a Value is a Reference

In the preceding example, the chooseContainer() function indicates to users that it should be called using references by the use of "@" symbols before its parameters (@c1, @c2). However, SenseTalk doesn't enforce this in any way. To make the function more robust, it could check whether the parameters were actually passed by reference, and alert the user if it was called incorrectly. The is a (or is not a) operator can be used for this purpose. Here is an improved version of the function that performs this test:

to chooseContainer @c1, @c2

if c1 is not a reference or c2 is not a reference then
throw "Not A Reference", "Must pass containers by reference"
end if

if the number of items in c1 < the number of items in c2
then return container c1
else return container c2

end chooseContainer

Repeat With Each ... By Reference

A special "by reference" option of the repeat with each command will set the iterator variable to be a reference to the current element rather than merely its value. This can greatly simplify writing a script that not only iterates over the chunks of a container but makes changes to some of those elements, as in this example:

put "come all you good people and make merry" into phrase

repeat with each word of phrase by reference
if the last char of it is not in "aeiou" then put "e" after it
if it is "you" then set it to "ye"
end repeat

put phrase -- "come alle ye goode people ande make merrye"