The distinction between member type and variable type motivates two further define operators.
The variable-define operator ‘@::’ is identical to ordinary define (::), except that it only sets the target variable’s type, while leaving the member’s type unchanged. That means that if it is used to define a brand new member, that member’s type will be void. For example, here we use this operator to define and then redefine a member with different types of storage, and then alias it to another variable of a different type.
theVar @:: int
...
theVar = @nothing | need to unlink before creating a new variable
theVar @:: string
...
theBool :: bool
theVar = @theBool
Notice that the second @:: operator was forced to make a new variable because theVar had been unlinked (aliased to the void) on the previous line. Had we left out the unlinking command, Cicada would have tried to redefine the existing int variable as a string, causing a type-mismatch error.
The member-define operator ‘*::’ is the counterpart to variable-define: it only acts upon the member, without affecting any variable currently aliased by this member. Its symbol reflects the fact that, if one uses member-define to define a new member, it will indeed create a member of the desired type but it will not bother to create a variable for it: the member will point into the void. So the following code will cause an error:
myNum *:: int
myNum = 5 | will cause an error
which could have been avoided had we written myNum :: int or myNum @:: int between the two lines, in order to construct an integer variable for the member.
There are actually 256 possible define/equate/alias operators, most of which cannot be scripted directly unless we modify the language. See Section 5.
Finally, there is another assignment operator called ‘forced-equate’, and given the symbol ‘=!’ or ‘<-!’. While equate copies data between variables whose data structures match or are compatible (e.g. { int, string } to { double, string }), forced equate copies between variables having the same memory storage size. (Having a string or other list in the destination variable makes the storage requirement somewhat elastic.) A forced equate simply takes the data contained in its right-hand argument and stuffs those N bytes in the same order into the left-hand variable, with no restrictions on how the storage space is parceled out within the destination variable.
When forcing an equate from data having numeric constants, remember that Cicada interprets each written number as either as int or double. So for example, if long integers are 4 bytes and doubles are 8 bytes, a =! -4 and a =! 2e5 each copy 4 bytes while a =! 4. and a =! 2e10 each copy 8 bytes.
Here are some examples showing the difference between these two operators:
var1 :: { int, bool }
var2 :: { double, bool }
var3 :: { int, string }
var1 = var2 | OK
var1 = var3 | type-mismatch error
var1 =! var2 | unequal data size error
var1 =! var3 | unequal data size error unless string has 1 character
var3 =! var1 | OK, surprisingly!
The last line works because forced-equate resizes var3’s string as needed to soak up any extra bytes from var1.
Last update: November 12, 2025