There is the situation when an operation takes multiple operands, and one (or more) of those operands are inappropriate for the operation. For example, suppose an integer add operation was passed an integer object and an open file object? There is generally no “add” semantic for an integer and a file.
Total incompatibility results in a run-time error, but sometimes values are compatible enough for the operation to proceed. How the system accomplishes that often involves casting one value into another.
Consider the following example:
. *int xi = 42 . *float xf . set:xf xi
set: Message to the
xf object has the
xi object as a parameter. When the
*float Model invokes the “set” Action on the
xf object, it discovers the parameter is an instance of a different Model — the
At this point an operation has two choices: If the parameter is of a familiar type and the operation knows how to get a value of the appropriate type from it, then it can just convert the parameter’s value on its own. But if the type is unfamiliar, it needs to ask the parameter object, “Can you provide a coherent float value?” (A reply of “Nope!” generates a run-time error.)
The first case can be trivial — converting an integer to a float, for example — but most cases are likely to be harder. Data encapsulation urges that the operation always has to ask for an appropriate value and that the answer ranges from: “Sure, no problem!” to “What? Are you crazy?!”
[There is a constraint that one of the objects has to know enough about the internals of the other to generate a translation.]
The first level of casting is that, when a Model Action is performing an operation, it asks any operand with an unexpected data type for an operand of the appropriate type. If no such operand can be created, the Action throws an Exception.
Some exceptions are allowed where a Model Action can use the unexpected data type’s value with a trivial conversion. The canonical example is converting integer values to float values (although there is a potential for a precision loss Exception, so a really smart system might support detection).
The second level of casting is explicitly doing what happens under the hood — sending the data type request in code:
. *int xi = 42 . *float xf . set: xf *float:xi;
By adding a colon, the name of any Model is a Message name with the semantic: “Return a copy of your value in this data type (or throw an Exception).” Such Messages have only a target (no parameter), so the semicolon is required.
Note that while the following compiles and runs, it has no lasting effect because the integer value created by the explicit cast is discarded as a temporary object:
. *int xi = 42 . *float xf . set: *int:xf; xi
Explicit casts always create a new (temporary) object.
An irony about casting as described above (implicit or explicit) is that the parameter’s Model, asked for a converted value, often ends up using the target’s Model to create it. The
*int Model might ask the
*float Model for a new float object.
It’s when that irony gets too heavy that Models are allowed to say, “Oh, integers! I know how to work with them!” Since they’re providing the converted value anyway, there’s not much point in the round trip.
The same logic applies to initializer lists. The first example does implicitly what the second does explicitly:
. *int pie1 = Pi . *int pie2 = *int:Pi;
One thing this all means is that Models need to respond to data type conversion Messages. Any Message name that is a Model name (i.e. starts with an asterisk) is such a request.
A data type query Message might be a nice feature. It provides the ability to ask a Model if it can handle a given conversion without triggering an Exception (or creating a new object).