TOP | UP: UserTalk Basics | NEXT: Control Structures |
Tables, lists, records, strings, and binaries are all arrays. Arrays are ordered collections of items. A table's items are its entries, which can be of any type; list and record items can be of any scalar type; a string's items are chars. The items of a binary are treated as shorts or chars, depending on the context. As arrays, these datatypes have certain features in common; we will discuss these, and then proceed to some features that tables do not share.
The chief thing that all arrays have in common is that an individual item can be specified by means of an array specifier. Array specifier syntax, or array notation, is theArray[ theIndex ], where theIndex can be a number, or, in the case of tables and records, a name. The number is 1-based: that is, the first item is item 1. The name index for a table is a string; the name index for a record is a string4.
Array specifiers may be used to get or set the value of an item of an array. For example, if the first entry in workspace is a string object called aLittleString, whose value is "hi", then:
workspace [1]
« "hi"
workspace ["aLittleString"]
« "hi"
Array specifier syntax using a name index on a table should not be confused with the syntax for constructing an element of an object reference at runtime. It is true that you can also access workspace.aLittleString by saying:
workspace.["aLittleString"]
This accesses the same entry, but in a different way. The name array specifier asks for that element of workspace whose name is aLittleString. The dot form constructs an entire object name, workspace.aLittleString, and uses it. The difference is clearer if the name of an entry in workspace is a number, say 2000: workspace.[2000] will access it, because we are forming the normally illegal name workspace.2000, but workspace[2000] will not, because there is (I presume) no 2000th entry in workspace.
Similarly for lists, records, and strings:
local
myList = {"hello", "there"}
myRecord = {'firs': "hello", 'seco': "there"}
myString = "hello there"
myList [1]
« "hello"
myRecord [1]
« "hello"
myRecord ['firs']
« "hello"
myString [1]
« 'h'
Of course, the index does not have to be a literal; it is sufficient that it should evaluate to the desired value, or be coercible to the desired value. Thus:
local (x = 7 - 5)
myList [x]
« "there"
myRecord ["firs"] « okay to use a string coercible to a string4
« "hello"
myRecord ["1"] « okay to use a string coercible to a number, too
« "hello"
Indexes can be "chained": the first specifies an item of an array, so if that's an array too, you can specify an item in it, and so on:
myList [1] [5]
« 'o'
When using array notation to set the value of an array item, the change is made "in place": you aren't making a copy with a substitution performed, you're reaching right in and changing an item. In the case of a list or record you can use numeric array notation to create an entirely new item; the number must be exactly one more than the size of the list or record. In the case of a record, you can use name array notation to create a new item by name:
myString [1] = 'm'; myString
« "mello there"
myRecord [2] = "and goodbye"; myRecord
« {'firs': "hello", 'seco': "and goodbye"}
myList [3] = "you"; myList
« {"hello", "there", "you"}
myRecord ['thir'] = "for now"; myRecord
« {'firs': "hello", 'seco': "and goodbye", 'thir': "for now"}
With lists only, it is permissible to create a new item by setting the (fictitious) zeroth item of the list; that is:
myList = {"hello", "there"}
myList [0] = "you"; myList
« {"hello", "there", "you"}, just like setting myList [3]
This saves one the trouble of determining beforehand how many items the list contains.
Items of tables and records specified by number can be handed to the verb nameOf() to find out their name, which is returned as a string:
nameOf ( myRecord [1] )
« "firs"
nameOf ( workspace [1] )
« "aLittleString"
The number of items of an array can be determined by handing the name of the array to sizeOf() :1
sizeOf (myList)
« 3
sizeOf (myString)
« 11
A specified item of an array is removed with delete() . This doesn't make a copy lacking the specified item; it acts directly on the array:
myString = "Hello there"
delete ( myString [7] ); myString
« "Hello here"
Some languages permit negative indices, letting you say such things as myArray[-1] to mean the last item of myArray, but UserTalk does not; you have to use sizeOf() and calculate the correct index. Here is a utility verb which returns the value of an array item specified by right-index. The parameters are the address of the array (to avoid copying a table or other large array) and a right-index value which can be positive or negative.
on rIndex (addrArray, index) |
return (addrArray^[sizeOf(addrArray^)+1-abs(index)]) |
This ends our discussion of tables qua arrays; we shall have much more to say about working with tables through UserTalk in later chapters, especially Chapter 18, Non-Scalars .
Several operators are defined on strings, binaries, lists, and records in common but not on tables. Note that there is normal implicit coercion of the operands (see Chapter 10, Datatypes ).
Strings can be tested for equality with strings; so can lists with lists, and records with records. The operator is == (or equals). In the case of strings, the match is case-sensitive. In the case of lists and records, every corresponding pair of items must pass its own equality test in order for the whole test to return true; in this equality test, there is no implicit coercion - the types of each pair of compared items must match (except that a char can match a one-character string, and an address can match a string):2
{0} == 0
« true, because the integer is coerced to a list first
{'h', 'i'} == {"h", "i"}
« true, because a char "is" still a one-character string
{0} == {false}
« false, even though 0 == false is true
Strings can be concatenated to strings, lists to lists, and records to records, using the plus-sign (+). The items of the second operand come after the items of the first operand in the result:
myString = "hello" + "there"
« "hellothere"
myList = {"hello"} + {"there"}
« {"hello", "there"}
myList = myList + "you"
« {"hello", "there", "you"}, after "you" is coerced to {"you"}
myRecord = {'firs': "hello"} + {'seco': "there"}; myRecord
« {'firs': "hello", 'seco': "there"}
In the case of a list, creating a new item by setting it directly is faster than "addition," and also doesn't involve implicit coercion to a list:
myList = {"hey", "ho"}
myList + {"hey", "nonny", "no"}
« {"hey", "ho", "hey", "nonny", "no"}
myList + "{\"hey\", \"nonny\", \"no\"}"
« {"hey", "ho", "hey", "nonny", "no"}
myList[0] = {"hey", "nonny", "no"}; myList
« {"hey", "ho", {"hey", "nonny", "no"}}
myList[0] = "{\"hey\", \"nonny\", \"no\"}"; myList
« {"hey", "ho", "{\"hey\", \"nonny\", \"no\"}"}
A substring can be removed from a string, or items from a list or record, using the minus sign (-). What is removed from the first operand is the first instance of all of the items of the second operand together and in the same order. This involves an implicit equality test between pairs of items, which works as described for == above. If the second operand isn't contained in the first, there is no error; the first operand is returned unchanged:
myString = "Madam, I'm Adam"
myString - "adam"
« "M, I'm Adam"
myString - "Adam"
« "Madam, I'm "
myList = {"h", "e", "l", "l", "o"}
myList - {"e", "o"}
« {"h", "e", "l", "l", "o"}, the sub-list wasn't found
myList - {"e", "l"}
« {"h", "l", "o"}
Three boolean tests, beginsWith , endsWith, and contains, tell whether the first operand contains all the items of the second together and in the same order - starting at the first operand's first item (beginsWith), or ending at its last item (endsWith), or anywhere (contains).3 Again, the item equality test works as for ==, discussed earlier:
"Madam, I'm Adam" beginsWith "Mad"
« true
{45, 63, 21, "hike"} beginsWith 45
« true, after 45 is coerced to {45}
{45, 63, 21, "hike"} contains {45, 21}
« false
{45, 63, 21, "hike"} contains "k"
« false, because none of its elements is "k"
Because of implicit coercion, it is not an error to apply these operators to things that are not lists, records, or strings, but some very strange results can be incurred:
2001 beginsWith 20
« true, because "2001" beginsWith "20" is true
workspace beginsWith "t"
« true, because workspace is coerced to "table: 26 items"!
Lists and records have a keyword unique to them, for...in, which loops over all the items. This is discussed in Chapter 12, Control Structures .
The empty string can be expressed as a literal as "", and the empty list can be expressed as a literal as {}. As for the empty record, though, while Frontier can portray an empty record as {}, you can't use this as a literal representation, because it will be seen as the empty list. The way to create an empty record is to use new() . Here, for instance, we assign the empty record to a local variable:
local (x)
new (recordType, @x)
The == test returns true only if the internal datatypes match, as well as every "character." "Addition" is supported, returning a binary with internal datatype '????', but "subtraction" is not supported. beginsWith, endsWith, and contains work, but they are a little tricky: for example, if x is binary("hello"), then x[1] is reported as a short, 104, but:
x beginsWith 104
« false
x beginsWith char(104)
« true
1. sizeOf() applied to a non-array reveals the amount of storage space it occupies, in bytes. This results in such wonders as sizeOf (infinity) == 4.
TOP | UP: UserTalk Basics | NEXT: Control Structures |