Difference between revisions of "Dynamic type testing"
m (Typo) |
|||
(29 intermediate revisions by 11 users not shown) | |||
Line 1: | Line 1: | ||
− | [[Category:Book]] |
||
Sometimes, you need to decide at run-time if a variable |
Sometimes, you need to decide at run-time if a variable |
||
− | (or [[Glossary#Expression|expression]]) refers an instance of a specific class or type. |
+ | (or [[Glossary#Expression|expression]]) refers to an instance of a specific class or type. |
− | Well, it is always better to rely on [[dynamic dispatch]], but, in some situations, it can be necessary |
+ | Well, it is almost always better to rely on [[dynamic dispatch]], but, in some situations, it can be necessary |
− | to |
+ | to explicitly test the [[Glossary#DynamicType|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 |
+ | the |
− | [[#AssignmentTest|<tt>?:=</tt>]] |
+ | [[#AssignmentTest|<tt>?:=</tt>]] assignment test operator, |
the [[#ForcedAssignment|<tt>::=</tt>]] forced assignment statement as well as the |
the [[#ForcedAssignment|<tt>::=</tt>]] forced assignment statement as well as the |
||
traditional [[#AssignmentAttempt|<tt>?=</tt>]] assignment attempt statement. |
traditional [[#AssignmentAttempt|<tt>?=</tt>]] assignment attempt statement. |
||
For some examples about dynamic type testing, check also the |
For some examples about dynamic type testing, check also the |
||
− | <tt> |
+ | <tt>tutorial/downcasting.e</tt> file. |
+ | |||
+ | Please note that assignment testing cannot be used with [[Expanded_or_reference|expanded types]], for which [[dynamic dispatch]] is not applicable. |
||
<div id="AssignmentTest"> |
<div id="AssignmentTest"> |
||
Line 17: | Line 18: | ||
</div> |
</div> |
||
The assignment test <tt>?:=</tt> is a [[library_class:BOOLEAN|<tt>BOOLEAN</tt>]] expression |
The assignment test <tt>?:=</tt> is a [[library_class:BOOLEAN|<tt>BOOLEAN</tt>]] expression |
||
− | allowing you to check |
+ | allowing you to check if the given right-hand side expression refers to an object which conforms to |
some explicitly written type. |
some explicitly written type. |
||
− | As an example, you can use the <tt>?:=</tt> |
+ | As an example, you can use the <tt>?:=</tt> operator as follows to check that |
− | some <tt>expression</tt> actually holds an instance which could be assigned |
+ | some <tt>expression</tt> actually holds an instance which could be assigned to some |
variable declared with <tt>SOME_TYPE</tt>: |
variable declared with <tt>SOME_TYPE</tt>: |
||
if {SOME_TYPE} ?:= expression then |
if {SOME_TYPE} ?:= expression then |
||
− | print ("The 'expression' |
+ | print ("The 'expression' denotes something which can be held by the variable declared of type SOME_TYPE.") |
else |
else |
||
print ("Something else which is non Void.") |
print ("Something else which is non Void.") |
||
end |
end |
||
− | It evaluates <tt>expression</tt> and returns <tt>True</tt> |
+ | It evaluates <tt>expression</tt> and returns <tt>True</tt> if the resulting object |
− | can be legally assigned |
+ | can be legally assigned to some variable declared of type <tt>SOME_TYPE</tt>. |
To take now a more concrete example using existing types of our library: |
To take now a more concrete example using existing types of our library: |
||
if {FAST_ARRAY[INTEGER]} ?:= my_collection then |
if {FAST_ARRAY[INTEGER]} ?:= my_collection then |
||
Line 35: | Line 36: | ||
print ("Something else which is non Void.") |
print ("Something else which is non Void.") |
||
end |
end |
||
− | In the previous example, <tt>my_collection</tt> is |
+ | In the previous example, <tt>my_collection</tt> is declared of type |
[[library_class:COLLECTION|<tt>COLLECTION</tt>]]<tt>[</tt>[[library_class:INTEGER|<tt>INTEGER</tt>]]<tt>]</tt>. |
[[library_class:COLLECTION|<tt>COLLECTION</tt>]]<tt>[</tt>[[library_class:INTEGER|<tt>INTEGER</tt>]]<tt>]</tt>. |
||
As explained below, [[library_class:COLLECTION|<tt>COLLECTION</tt>]]<tt>[</tt>[[library_class:STRING|<tt>STRING</tt>]]<tt>]</tt> would make the previous code |
As explained below, [[library_class:COLLECTION|<tt>COLLECTION</tt>]]<tt>[</tt>[[library_class:STRING|<tt>STRING</tt>]]<tt>]</tt> would make the previous code |
||
Line 51: | Line 52: | ||
end |
end |
||
The check performed by the compiler is that an expression of type <tt>SOME_TYPE</tt> could be |
The check performed by the compiler is that an expression of type <tt>SOME_TYPE</tt> could be |
||
− | legally assigned |
+ | legally assigned to some variable declared of the |
[[Glossary#StaticType|static type]] of <tt>some_expression</tt>. |
[[Glossary#StaticType|static type]] of <tt>some_expression</tt>. |
||
Actually, this simply means that you trust the type system and that you really want to reject |
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. |
+ | invalid or, to say the least, weird code. |
Because of the redefinition mechanism, the validation check for the <tt>?:=</tt> operator is only |
Because of the redefinition mechanism, the validation check for the <tt>?:=</tt> operator is only |
||
performed at the class where it appears, and is '''not''' revalidated in subtypes even if they |
performed at the class where it appears, and is '''not''' revalidated in subtypes even if they |
||
Line 60: | Line 61: | ||
The latest decision was driven by our experimentations. |
The latest decision was driven by our experimentations. |
||
We made this decision because the strict rule would reject mostly valid situations. |
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 [[#ForcedAssignment|<tt>::=</tt>]] statement |
(the [[#ForcedAssignment|<tt>::=</tt>]] statement |
||
and the [[#AssignmentAttempt|<tt>?=</tt>]] statement). |
and the [[#AssignmentAttempt|<tt>?=</tt>]] statement). |
||
Line 66: | Line 67: | ||
As we have seen previously, the left-hand side operand of the <tt>?:=</tt> type test can be some |
As we have seen previously, the left-hand side operand of the <tt>?:=</tt> type test can be some |
||
explicit type name. |
explicit type name. |
||
− | The second form of the <tt>?:=</tt> test operator is to |
+ | The second form of the <tt>?:=</tt> test operator is to be used in place of the |
explicit type, some [[Syntax diagrams#Writable|writable]] entity as in: |
explicit type, some [[Syntax diagrams#Writable|writable]] entity as in: |
||
if my_variable ?:= expression then |
if my_variable ?:= expression then |
||
Line 74: | Line 75: | ||
end |
end |
||
which evaluates to <tt>True</tt> iff the actual content of <tt>expression</tt> |
which evaluates to <tt>True</tt> iff the actual content of <tt>expression</tt> |
||
− | ''could be assigned'' |
+ | ''could be assigned'' to <tt>my_variable</tt>. |
Note that the assignment is '''not''' performed. |
Note that the assignment is '''not''' performed. |
||
− | This variant avoids the need to repeat |
+ | This variant avoids the need to repeat the static type of <tt>my_variable</tt> here. |
It is also because the <tt>?:=</tt> type test operator is often used in conjunction with |
It is also because the <tt>?:=</tt> type test operator is often used in conjunction with |
||
the [[#ForcedAssignment|<tt>::=</tt>]] forced assignment. |
the [[#ForcedAssignment|<tt>::=</tt>]] forced assignment. |
||
Line 84: | Line 85: | ||
== The <tt>::=</tt> forced assignment == |
== The <tt>::=</tt> forced assignment == |
||
</div> |
</div> |
||
− | The <tt>::=</tt> forced assignment statement |
+ | The <tt>::=</tt> forced assignment statement allows you to actually perform an assignment |
proved valid at runtime with a corresponding [[#AssignmentTest|<tt>?:=</tt>]] assignment |
proved valid at runtime with a corresponding [[#AssignmentTest|<tt>?:=</tt>]] assignment |
||
test. |
test. |
||
Line 98: | Line 99: | ||
satisfy the <tt>?:=</tt> condition, you get an error at runtime. |
satisfy the <tt>?:=</tt> condition, you get an error at runtime. |
||
In <tt>-boost</tt> mode no runtime check is performed and the runtime cost of a |
In <tt>-boost</tt> mode no runtime check is performed and the runtime cost of a |
||
− | <tt>::=</tt> statement is |
+ | <tt>::=</tt> statement is exactly the runtime cost of a common |
<tt>:=</tt> statement. |
<tt>:=</tt> statement. |
||
A very low cost indeed. |
A very low cost indeed. |
||
− | Usage of <tt>::=</tt> can |
+ | Usage of <tt>::=</tt> can be slightly more efficient (and result in simpler code) than |
similar code using the traditional [[#AssignmentAttempt|<tt>?=</tt>]] assignment attempt. |
similar code using the traditional [[#AssignmentAttempt|<tt>?=</tt>]] assignment attempt. |
||
Besides, it helps debugging because it will raise an exception when your assumption is not |
Besides, it helps debugging because it will raise an exception when your assumption is not |
||
+ | correct. |
||
− | satisfied. |
||
As explained for the [[#AssignmentTest|<tt>?:=</tt>]] assignment test, one can not use |
As explained for the [[#AssignmentTest|<tt>?:=</tt>]] assignment test, one can not use |
||
any combination of static types. |
any combination of static types. |
||
− | The |
+ | The same checks are performed for the <tt>::=</tt> statement. |
− | To summarize checks that are performed, |
+ | To summarize checks that are performed, please consider: |
my_variable ::= expression |
my_variable ::= expression |
||
The entity <tt>my_variable</tt> must be a valid [[Syntax diagrams#Writable|writable]]. |
The entity <tt>my_variable</tt> must be a valid [[Syntax diagrams#Writable|writable]]. |
||
The declaration type of <tt>my_variable</tt> must be a possible valid source type of some |
The declaration type of <tt>my_variable</tt> must be a possible valid source type of some |
||
normal assignment into some destination variable declared with the |
normal assignment into some destination variable declared with the |
||
− | [[Glossary# |
+ | [[Glossary#StaticType|static type]] of <tt>expression</tt>. |
Finally, the check in performed only where the <tt>::=</tt> is written. |
Finally, the check in performed only where the <tt>::=</tt> is written. |
||
Line 131: | Line 132: | ||
Again, there is no rush, and the traditional |
Again, there is no rush, and the traditional |
||
<tt>?=</tt> will be supported for some more years. |
<tt>?=</tt> will be supported for some more years. |
||
− | From compiler writer's point of view, the support for <tt>?=</tt> is really cheap because |
+ | From a compiler writer's point of view, the support for <tt>?=</tt> is really cheap because |
− | + | it is implemented in terms of <tt>?:=</tt> and <tt>::=</tt>. |
|
Furthermore, it is easy and elegant to explain <tt>?=</tt> as a simple |
Furthermore, it is easy and elegant to explain <tt>?=</tt> as a simple |
||
Line 138: | Line 139: | ||
Consider the following <tt>?=</tt> usage: |
Consider the following <tt>?=</tt> usage: |
||
destination_variable ?= source_variable |
destination_variable ?= source_variable |
||
− | If the value of <tt>source_variable</tt> |
+ | If the value of <tt>source_variable</tt> cannot be legally assigned |
− | + | to <tt>destination_variable</tt>, the <tt>destination_variable</tt> will be set to |
|
[[Void|<tt>Void</tt>]] instead of the value of the <tt>source_variable</tt>. |
[[Void|<tt>Void</tt>]] instead of the value of the <tt>source_variable</tt>. |
||
Note that if <tt>source_variable</tt> actually contains <tt>Void</tt>, |
Note that if <tt>source_variable</tt> actually contains <tt>Void</tt>, |
||
Line 154: | Line 155: | ||
because the transformation scheme incurs two evaluations of the right-hand side |
because the transformation scheme incurs two evaluations of the right-hand side |
||
part. |
part. |
||
− | To be valid, the previous transformation scheme |
+ | To be valid, the previous transformation scheme assumes that the right-hand side |
expression incurs no [[Glossary#SideEffect|side effect]]. |
expression incurs no [[Glossary#SideEffect|side effect]]. |
||
Anyway, this transformation scheme is just here to explain the semantic of |
Anyway, this transformation scheme is just here to explain the semantic of |
||
− | the <tt>?=</tt> statement and |
+ | the <tt>?=</tt> statement and not as a general translation scheme to patch |
your (not so obsolete) code. |
your (not so obsolete) code. |
||
Line 163: | Line 164: | ||
one can not use any combination of [[Glossary#StaticType|static type]] on |
one can not use any combination of [[Glossary#StaticType|static type]] on |
||
both sides of <tt>?=</tt> operator. |
both sides of <tt>?=</tt> operator. |
||
− | The very same |
+ | The very same kind of checks are performed for the <tt>?=</tt> statement. |
To summarize checks, now consider: |
To summarize checks, now consider: |
||
my_variable ?= expression |
my_variable ?= expression |
||
Line 172: | Line 173: | ||
Finally, as usual the check is performed only where the <tt>?=</tt> statement is written. |
Finally, as usual the check is performed only where the <tt>?=</tt> statement is written. |
||
− | Assuming that type <tt>TRUCK</tt> |
+ | Assuming that type <tt>TRUCK</tt> and type <tt>APPLE</tt> are completely distinct types (i.e. |
you are not allowed to perform an assignment of <tt>TRUCK</tt> into <tt>APPLE</tt> |
you are not allowed to perform an assignment of <tt>TRUCK</tt> into <tt>APPLE</tt> |
||
nor of <tt>APPLE</tt> into <tt>TRUCK</tt>), the following assignment attempt is statically |
nor of <tt>APPLE</tt> into <tt>TRUCK</tt>), the following assignment attempt is statically |
||
rejected, which is good news: |
rejected, which is good news: |
||
− | my_apple ?= my_truck -- hopefully this is statically rejected |
+ | my_apple ?= my_truck -- hopefully this is statically rejected! |
− | + | You can always workaround by using the following trick: |
|
a_any := a_truck |
a_any := a_truck |
||
an_apple ?= a_any -- This is valid, APPLE conforms to ANY |
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 |
+ | The example above is clearly a weird one, but you could need something like this if you have two |
− | unrelated |
+ | unrelated classes with a common heir. And at least the code shows that you know what you are doing. |
− | This situation |
+ | This situation should arise in extremely rare situations only, that are not worth to be explained |
here. |
here. |
Latest revision as of 19:45, 27 June 2016
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 exactly 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.