Dynamic type testing

From Liberty Eiffel Wiki
Revision as of 18:40, 27 June 2016 by Dkearns (talk | contribs) (s/LibertyEiffel/Liberty Eiffel/)
Jump to navigation Jump to search

Sometimes, you need to decide at run-time if a variable (or expression) refers to an instance of a specific class or type. Well, it is almost always better to rely on dynamic dispatch, but, in some situations, it can be necessary to explicitly test the dynamic type of some expression. While other languages provide a cast operator to do (unsafe) type casting, Liberty Eiffel provides you three operators for doing that: the ?:= assignment test operator, the ::= forced assignment statement as well as the traditional ?= assignment attempt statement.

For some examples about dynamic type testing, check also the tutorial/downcasting.e file.

Please note that assignment testing cannot be used with expanded types, for which dynamic dispatch is not applicable.

The ?:= assignment test

The assignment test ?:= is a BOOLEAN expression allowing you to check if the given right-hand side expression refers to an object which conforms to some explicitly written type. As an example, you can use the ?:= operator as follows to check that some expression actually holds an instance which could be assigned to some variable declared with SOME_TYPE:

if {SOME_TYPE} ?:= expression then
    print ("The 'expression' denotes something which can be held by the variable declared of type SOME_TYPE.")
else
    print ("Something else which is non Void.")
end

It evaluates expression and returns True if the resulting object can be legally assigned to some variable declared of type SOME_TYPE. To take now a more concrete example using existing types of our library:

if {FAST_ARRAY[INTEGER]} ?:= my_collection then
    print ("Expression 'my_collection' is either Void or conforms to FAST_ARRAY[INTEGER].")
else
    print ("Something else which is non Void.")
end

In the previous example, my_collection is declared of type COLLECTION[INTEGER]. As explained below, COLLECTION[STRING] would make the previous code invalid thus rejected by the compiler.

To avoid testing impossible situations which are probably coding mistakes, one cannot use any type for the right-hand side of a ?:= test expression. The rule is simple and naturally related to the rule used for an ordinary assignment (the common well known := assignment). Consider the following general example:

if {SOME_TYPE} ?:= some_expression then
    ...
else
    ...
end

The check performed by the compiler is that an expression of type SOME_TYPE could be legally assigned to some variable declared of the static type of some_expression. Actually, this simply means that you trust the type system and that you really want to reject invalid or, to say the least, weird code. Because of the redefinition mechanism, the validation check for the ?:= operator is only performed at the class where it appears, and is not revalidated in subtypes even if they redefine the static type of some_expression. The latest decision was driven by our experimentations. We made this decision because the strict rule would reject mostly valid situations. Similar checks are performed for all other statements of the same family (the ::= statement and the ?= statement).

As we have seen previously, the left-hand side operand of the ?:= type test can be some explicit type name. The second form of the ?:= test operator is to be used in place of the explicit type, some writable entity as in:

if my_variable ?:= expression then
   ...
else
   ...
end

which evaluates to True iff the actual content of expression could be assigned to my_variable. Note that the assignment is not performed. This variant avoids the need to repeat the static type of my_variable here. It is also because the ?:= type test operator is often used in conjunction with the ::= forced assignment.

The ::= forced assignment

The ::= forced assignment statement allows you to actually perform an assignment proved valid at runtime with a corresponding ?:= assignment test. As an example, the following code would always work without any possible runtime error:

if destination_variable ?:= source_variable then
    destination_variable ::= source_variable
else
    -- Handle this case specially...
end

Actually, the ?:= test can be considered as the precondition of the ::= part. In non -boost mode, when some ::= statement is executed but does not satisfy the ?:= condition, you get an error at runtime. In -boost mode no runtime check is performed and the runtime cost of a ::= statement is exactely the runtime cost of a common := statement. A very low cost indeed. Usage of ::= can be slightly more efficient (and result in simpler code) than similar code using the traditional ?= assignment attempt. Besides, it helps debugging because it will raise an exception when your assumption is not correct.

As explained for the ?:= assignment test, one can not use any combination of static types. The same checks are performed for the ::= statement. To summarize checks that are performed, please consider:

my_variable ::= expression

The entity my_variable must be a valid writable. The declaration type of my_variable must be a possible valid source type of some normal assignment into some destination variable declared with the static type of expression. Finally, the check in performed only where the ::= is written.

The ?= assignment attempt

The traditional assignment attempt ?= statement is still supported for historical reasons. Indeed, now that we have both the ?:= assignment test and the ::= forced assignment statement, the ?= statement is no longer necessary. In the long-term future, let's say, one or two years, usage of ?:= and ::= may become the preferred coding style. Again, there is no rush, and the traditional ?= will be supported for some more years. From a compiler writer's point of view, the support for ?= is really cheap because it is implemented in terms of ?:= and ::=.

Furthermore, it is easy and elegant to explain ?= as a simple source code transformation using only ?:= and ::=. Consider the following ?= usage:

destination_variable ?= source_variable

If the value of source_variable cannot be legally assigned to destination_variable, the destination_variable will be set to Void instead of the value of the source_variable. Note that if source_variable actually contains Void, destination_variable will always be reset with Void. Finally, the previous ?= example can be translated into the equivalent form:

if destination_variable ?:= source_variable then
   destination_variable ::= source_variable
else
   destination_variable := Void
end

Note that the previous transformation scheme may not work for all kinds of right-hand side expressions because the transformation scheme incurs two evaluations of the right-hand side part. To be valid, the previous transformation scheme assumes that the right-hand side expression incurs no side effect. Anyway, this transformation scheme is just here to explain the semantic of the ?= statement and not as a general translation scheme to patch your (not so obsolete) code.

As explained for the ?:= assignment test, one can not use any combination of static type on both sides of ?= operator. The very same kind of checks are performed for the ?= statement. To summarize checks, now consider:

my_variable ?= expression

The entity my_variable must be a valid writable. The declaration type of my_variable must be a possible source type of some normal assignment into some destination variable declared with the static type of expression. Finally, as usual the check is performed only where the ?= statement is written.

Assuming that type TRUCK and type APPLE are completely distinct types (i.e. you are not allowed to perform an assignment of TRUCK into APPLE nor of APPLE into TRUCK), the following assignment attempt is statically rejected, which is good news:

my_apple ?= my_truck -- hopefully this is statically rejected!

You can always workaround by using the following trick:

    a_any := a_truck
    an_apple ?= a_any -- This is valid, APPLE conforms to ANY

The example above is clearly a weird one, but you could need something like this if you have two unrelated classes with a common heir. And at least the code shows that you know what you are doing. This situation should arise in extremely rare situations only, that are not worth to be explained here.