The second type of variable in Cicada is a composite variable, equivalent to a structure variable in C. Composite variables are collections of members defined inside of curly braces, just like a struct variable in C. Member definitions can go on their own lines, but make sure the opening brace is on the first line.
StreetAddress :: {
number :: int
street :: string
}
If we don’t care about the member names we can leave them out.
StreetAddress :: { int, string }
StreetAddress is a proper variable with its own storage, but for convenience Cicada allows us to also treat it as a new data type.
PetersPlace :: StreetAddress
In general any Cicada variable A can be used to define other variables B and C, which just copy A’s type definition (although not A’s data).
We access members of a composite variable the same way in Cicada as we do in C: using a period. For example (using the original definition with member names):
PetersPlace.number = 357
PetersPlace.street = "Bumbleberry Drive"
Composite variables may be nested inside one another, either using previously-defined types or by nesting curly braces. Here is an example showing both methods.
FullAddress :: {
first_line :: StreetAddress
second_line :: {
city :: state :: string
zip :: int
}
}
In this case, we need to use a ‘.’ twice to access any of the primitive fields.
FullAddress.first_line.number = 357
Notice that members of composite variables can also serve as data types.
GeneralWhereabouts :: FullAddress.second_line
GeneralWhereabouts.city = "Detroit"
One peculiarity of composite variables is that their internal structures can be rearranged after they have been defined. For example:
PaulineAddress :: FullAddress
remove PaulineAddress.first_line.street | it's a PO box
PaulineAddress.country :: string | in another country
By the end PaulineAddress will have a three members including one named country, and the street member of first_line will be missing. One thing to be aware of is that if we ever use a modified variable like PaulineAddress to define another variable:
PaulineOldAddress :: PaulineAddress
the new variable is always defined using the original definition. So PaulineOldAddress will not have a member named country and it will have street inside of first_line. This can lead to problems with the define-equate operator:
> PaulinesDogAddress := PaulineAddress
Error: type mismatch
The problem was that the define half of := constructed PaulinesDogAddress based on the original type definition of PaulineAddress, while the equate half of := copies data from the fields that are actually present between the two variables. Oops.
Assignment and equality-testing work with whole composite variables just like they do with primitive variables:
Tom :: Bob :: StreetAddress
...
Tom = Bob
if Tom == Bob then ( | yes, they will be equal
print("They're sharing a room.\n")
)
We were allowed to assign and compare Tom and Bob because they have the same { number, string } structure.
Numeric operations and comparisons do not work with composite variables, even if all their members are numeric. Expressions like Tom+Bob and Tom > Bob will always cause an error.
Last update: May 8, 2024