Using property lists
March 8, 2000
by Zac Belado
You've seen them in use. You've heard people talk about them. But you're not quite sure how to use them or how to approach them. I am talking about property lists. Possibly one of the most powerful and flexible variable types in Director, they are also misunderstood and seen by many users as being too complicated. This is simply not true and with a few examples and some explanations you'll be able to use these lists in your projects.
What is a property list?
The Director 7 documentation from Macromedia doesn't appear to actually explicitly define what a property list is so we're on our own when it comes to discussing them. Concisely put, a property list is a subtype of a regular, linear, list where each element in that list is a property/value pair. By that I mean that every entry in a property list is made up of two pieces of data: a property name and a value associated with that property. These two pieces of data are separated by a colon.
So if you saw a list that was defined as...
thisList = [#width:10]...#width would be the property name and 10 would be the value associated with the width property.
In a plain old linear list, you set and retrieve data from the list by referring to a specific position in that list. For example, you can get the third item in a list or set the fifth item in that list. You have to keep track of which position you need to access. But property lists are "smart" and don't rely on position. Instead of referring to a position in a list to set or access a value, you refer to a property of that list. Unlike a linear list, the data stored with the width property is retrieved by accessing the property. So if you wanted to retrieve that data associated with the width property you would use...
put thisList.width -- 10
... or in the classic syntax...
put getProp (thisList, #width) -- 10
By comparing the two alternative examples of syntax above, you can quickly see why it's preferable to use dot syntax to access data in a property list.
Even though there are two data elements in the list (the property name and the value) the list only has 1 actual entry.
put count(thisList) -- 1
Property names are typically defined as symbols, as in the above example, but they can be any type of data. So you could define as property list with strings as property names instead of symbols.
thisOtherList = ["width":10] put getProp(thisOtherList,"width") -- 10
You could even define a property list with integers.
thisOtherList = [1:10] put getProp(thisOtherList,1) -- 10
String values used as property names that are not in quotes will be converted to symbols.
thisList = [foo:99] put thisList -- [#foo: 99]
Using datatypes other than symbols can be rather problematic as they do not always allow you to access the data using dot syntax in Director 7. Let's say you define a list with one property name a string and another an integer.
thisList = ["foo":3.1400, 2:7] put thisList.foo -- 3.1400 put thisList.2 -- ["foo": 3.1400, 2: 7] 0.2000 put thisList."foo"
The first example gives us the "foo" property value. The second example gives us a very odd result. It appears that Director is interpreting the line as...
put thisList .2 (with a space after thisList)
...instead of...
put thisList.2
And the third example gives us
Which is what you never want to see.
You can safely access non-symbol property names using getProp...
put getProp(thisList, "foo") -- 3.1400 put getProp (thisList, 2) -- 7
...but this type of syntax is less than optimal if you are using Director 7 because D7 (and D8) allows you to use dot syntax to access these values instead of the getProp function. But only if the property names are valid symbols.
So while it's possible that there are some instances where you might want to use something other than a symbol as a property name (the case of sprite numbers comes to mind) you'll find that there are almost no instances where a non-symbol property name can't be replaced with a symbol. Also, as you'll see later, with the exception of integers, symbol access is faster than accessing any other type of data. This means that property lists that use strings as property names will be slower than a similar list built with symbols.
It should be noted that symbol based property lists are not well suited to situations where you need to sort data alphabetically or numerically and then access it based on that index. For example, if you needed to store score data and you had to frequently access it based on the position of the score relative to other scores, then symbols as property names will not be as functionally efficient as integers.
How do I access or modify data in a property list?
Data can be accessed in property lists in several ways. The simplest way is to use dot syntax to access the values.
thisList =[#width:217, #height:134, #weight:304] put thisList.weight -- 304
And you can modify it in the same fashion.
thisList.height = 2 put thisList -- [#width: 217, #height: 2, #weight: 304]
Attempting to access a non-existent property in a property list will cause a script error. The exception to this is that you can place the property in a square bracket (as a symbol or string but not an integer) and then try to access it. This will cause Director to return VOID if the property does not exist.
put thisList.fractalDimension [script error] put thisList[#fractalDimension] -- <Void>
Director has two functions to get values in a property:
getProp list, property
getAProp list, property
The first two (getProp and getAProp) are similar in that they return the value associated with a property in a property list. But getAProp will not generate a script error if the property you are trying to access is not in the list.
put getProp (thisList, #width) -- 217 put getAProp(thisList, #width) -- 217 put getProp (thisList, #fractalDimension) [script error] put getAProp (thisList, #fractalDimension) -- <Void>
Director also offers two commands to modify values in a property list.
setProp list, property, value
setAProp list, property, value
Like getProp and getAProp, setProp and setAProp have the same function as each other, namely set a property to a new value, except that setAProp will not generate a script error if the property is not already in the list. In fact, if the property is not in the list, setAProp will first create the property and then set the value of that property to the one you supplied. This is a handy way to add new properties to the list without having to worry about duplicate property values.
And how would you get duplicate properties in a property list? By using the addProp command.
addProp list, property, value
addProp is used to add new properties to a property list.
addProp thisList, #bar, 4 put thisList -- [#width: 217, #height: 2, #weight: 304, #foo: 3, #bar: 4]
The only problem is that if you use addProp and try to add a property that already exists Director will not give you an error, as you would think, but actually adds a duplicate of the property with the new value.
addProp thisList, #bar, 99 put thisList -- [#width: 217, #height: 2, #weight: 304, #foo: 3,¬ #bar: 4, #bar: 99]
If you then try to access that duplicated property, Director will return the value for the first instance of the property it finds. Because of this you should probably use setAProp instead of addProp to insert new properties into a property list.
You can also add and modify values in a property list using the square bracket access we looked at eariler. Using square brackets and providing the new symbol alsong with the new value will add that name/value pair to the proerty list.
x = [#a:1, #b:2] x[#c]="foo" put x -- [#a: 1, #b: 2, #c: "foo"]
Trying to do this with dot syntax will just generate a scipt error
x.d = "bar" [script error]
Director also has two commands that you can use to remove properties from a property list.
deleteProp list, property
deleteOne list, value
As you would expect, deleteProp removes the supplied property from the list. It will not generate an error if the property is not in the list.
thisList = [#width: 217, #height: 2, #weight: 304, #foo: 3, ¬ #bar: 4, #bar: 99] deleteProp thisList, #bar put thisList -- [#width: 217, #height: 2, #weight: 304, #foo: 3, #bar: 99]
You can also delete properties from a list by using deleteOne; it doesn't require a property, instead it delete the entry based on a value. It then deletes the first property (and thereby the value associated with that property) in a list that has that value.
put thisList -- [#width: 217, #height: 2, #weight: 304, #foo: 3, ¬ #bar: 4, #bar: 99] deleteOne thisList, 217 put thisList -- [#height: 2, #weight: 304, #foo: 3, #bar: 4, #bar: 99]
If you've read my introductory article on dot syntax you already know that you can also apply these commands and functions to a property list using dot syntax. So you could add a new property to a property list using setAProp in the "standard" fashion...
setAProp thisList, #viscosity, 0.17
... or by appending the function as if it were a method of the list itself:
thisList.setAProp(#viscosity, 0.17) put thisList -- [#height: 2, #weight: 304, #foo: 3, #bar: 99, #viscosity: 0.17]
Notice that the setAProp command does not need the name of the list as a parameter because it is being used as if it were a method of that list. And just in the same way that you can access "old style" commands and functions using dot syntax, you can also have some of the flexibility and readability of dot syntax using Director's older syntax.
Director has a way of referencing properties in objects or variables such as property lists using the word "of".
thisList = [#width: 217, #height: 2, #weight: 304] put the width of thisList -- 217
The format for this is:
the <property> of <object>
This syntax gives you the same direct access to a property that dot syntax does but doesn't require Director 7 or a radical rethinking in the way you write your code.
What is a symbol?
Since property lists might be your first introduction to using symbols extensively, perhaps a digression to more background on symbols would be helpful. A symbol is a string or other value that begins with the pound sign. At least that's what the Using Director book says. The problem is that the description is very unhelpful and actually wrong in some respects.
A symbol is a variable (either declared by the user or returned by some Lingo functions) that is differentiated from other variables by the pound sign that precedes it. Despite the fact that looks like a string, a symbol is a very unique type of variable.
The primary use of symbols is to create constants that can be used to represent things like states, flags or any other type of variable that would benefit from a descriptive or logical name.
For instance, consider a behavior that connects to a server and retrieves text. You could have a state property in the behavior that represents the current activity of the behavior. You could assign a series of symbols to that property to represent what the behavior was doing such as #idle, #connecting, #error or #done. These variables could be used and tested against...
if pState = #connecting then
...in your code almost as fast as integers, but provide you with far more illustrative feedback than integers would. States of 0, 1 or 2 don't provide you with any information about what they represent but states of #idle, #connecting and #done do.
Symbols can't...
- begin with a number
- contain a space
- contain any punctuation marks or Director reserved characters like #
- contain periods
So the following would be valid symbols...
#car1 #a_space #thisSymbol
...but these are not valid symbols:
#1 #2_car #this var ##thisVar #this&var
Aside from that you are free to make a symbol from anything you want. Although you will, for obvious reasons, want to make them some sort of comprehensible word or text.
Symbols are useful in Director because they are faster than strings but give you many of the same benefits as strings. Symbols are faster than strings because Director actually stores the symbol internally as an integer value. You can even see this at work.
thisVar = #foo put thisVar -1 -- 1427 put thisVar + 5 -- 1433
Director is actually storing #foo as an integer value of 1428. (The actual value is irrelevant and the user really has no access to setting it, so forget about that idea.)
So you get a variable that is easy to understand when you read it (for instance #height) but doesn't slow your code down in the same manner that a similar string would (using "height" for instance).
Comparing symbol values...
if #height = #width
...is over 250 times faster than comparing equivalent string values...
if "height" = "width"
... and is almost as fast as comparing integers.
Why use property lists?
Property lists are valuable because they allow you to define data not by some arbitrary position in a list but with some meaningful (hopefully), descriptive name that makes it easier to understand the context and function of the values in the list.
Look at these two lists.
scoreList = [23, 15, 76] scoreList = [#currentScore:23, #lastScore:15, #highScore:76]
The data in the first list is not immediately comprehensible. What is the difference between the first and second entries in the list? Or is there any difference? However, the data in the second list makes immediate sense.
This continues as you access the data in the lists in your project. To get the "highscore" from the first list you would use...
getAt(scoreList, 3)
...or...
scoreList[3]
And while you might understand the code while you're writing it, will you still understand it in a month, or in six months? In contrast, the Lingo code required to access the property list is far more easy to understand.
scoreList.highScore
You'll also find that using property lists makes reading calls to functions and methods easier as well.
storeScore (scoreList[3])
versus
storeScore (scoreList.highScore)
Where property lists truly shine (especially in Director 7 with its new dot syntax) is in nested access to complex data. Let's imagine a scoring system for a game that tracks data for each player. Each player has their name, email address and score data tracked. The score data consists of three different entries; a high score, a low score and the last score the player attained. Each score contains three pieces of data; the score itself, the level the player was at when they finished playing and the date.
This might seem like a tangled mass of data, but if you use property lists, it not only becomes easy to manage but it also makes the data easier to access. Let's see how.
The player data will be stored in a single property list with three properties: #name, #email and #score.
[#name:"zac", #email:"zac / at / director-online.com", #score:scoreList]
The #score property will have a second property list that we will use to contain the score data. It will have three properties: #lastScore, #highScore, #lowScore.
[#lastScore: lastScoreData, #highScore: highScoreData, ¬ #lowScore: lowScoreData]
And, as you might guess, the score data for each score is stored in a third property list which has three properties: #level, #score and #date. So the #lowScore might be stored like so...
[#level:12, #score:31415926, #date: date( 2000, 3, 5 )]
We can try to represent that graphically
So why is this easier than a series of individual lists? Well let's build the list and look at how we can access the data in order to see.
lastScoreData = [#level:12, #score:31415926, #date: date( 2000, 3, 5 )] highScoreData = [#level:21, #score:216776789, #date: date( 2000, 3, 5 )] lowScoreData = [#level:2, #score:3, #date: date( 2000, 3, 1 )] scoreList = [#lastScore: lastScoreData, #highScore: ¬ highScoreData, #lowScore: lowScoreData] thisList = [#name:"zac", #email:"zac / at / director-online.com", #score:scoreList] put thisList -- [#name: "zac", #email: "zac / at / director-online.com", #score: [#lastScore: [#level: 12, #score: 31415926, #date: date( 2000, 3, 5 )], #highScore: [#level: 21, #score: 216776789, #date: date( 2000, 3, 5 )], #lowScore: [#level: 2, #score: 3, #date: date( 2000, 3, 1 )]]]
Looks rather messy doesn't it?
But looks can be deceiving. Since we know the structure of the data, getting individual values in this list is actually quite easy. Say, for example, we want to know the date that the player got their highscore. We know that the score property list is a property of the main list. We know that the highscore is a property of the score list. We know that the date is a property of the highscore.
So we can string all these properties together to drill down through the list to the date.
put thisList.score.highscore.date -- date( 2000, 3, 5 )
Or using the "of" syntax:
put the date of the highscore of the score of thisList -- date( 2000, 3, 5 )
And since the month is a built-in property of the date we could even get the month of that highscore with a single line.
put thisList.score.highscore.date.month -- 3 put the month of the date of the highscore of the score of thisList -- 3
This example also shows you how efficient dot syntax is. Once you get used to reading the code, it is not only much easier to write but also much easier to read.
Using a property list (or a series of nested property lists) has several advantages. It provides a single access point for a large amount of data. All the values in the list we created are available from a single variable. Nesting property lists in this fashion also enforces a structure on the data that makes it easier to access and also easier to remember. And the final benefit is that the code you write to access this data is compact and easy to read.
Copyright 1997-2024, Director Online. Article content copyright by respective authors.