Casting Values

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

The 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 *int Model.

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).

Advertisements

One response

  1. It all depends on who knows about who. If a float class asks an integer class for a float version of an integer, it implies the integer class knows enough about floats to create one. If the float class just creates its own float version of an integer, that implies the float class knows enough about integers to create one. Usually, you would expect a more “complicated” class to know about less “complicated” classes and not vice-versa.

    As an example, in languages that support constructors and overloading, it’s common to build as many constructors for objects as possible.

%d bloggers like this: