Perfect Developer basic tutorial 3 | This page last modified 2011-10-29 (JAC) |
Although Perfect is strongly typed, it nevertheless supports the
declaration of entities whose exact type is not known at compile time. This is
done by uniting two or more types using the
||
operator, or by
applying the keyword from to the name of a non-final class (this
will be covered in the tutorial on inheritance).
The types of such values can be tested at run-time using a type enquiry
expression. For example, if variable answer has type
string || int || void
then you can use the following type enquiry expressions:
answer within int
will yield true when the current value of answer is of type int, otherwise false;
answer ~within string
yields true when the current value of answer is not of type string;
answer within int || string
yields true when the current value of answer is an int or a string (since
the only other possibility is void which has the single value null, this yields the same value as the expression
answer ~= null
); and
answer like guess
yields true when the run-time values of answer and guess are of the same type.
Turning now to type conversions, there is the type narrowing conversion:
answer is int
which is only valid when the current value of answer is of type int, and yields the value of answer as a plain int.
The converse is a type widening conversion such as:
42 as int || void
which converts the value 42 to type int || void.
If a within, is or as expression is used as an operand in a larger expression or postcondition, you will need to enclose it in brackets.
The only automatic type conversion performed in Perfect is the widening of a value from its original type to a type that encompasses the original.
Widening is performed automatically on expressions in the following contexts:
^=
symbol, if a type has been declared for the entity being defined;For example, you could declare:
const theAnswer: string || int ^= 42;
Because you have declared theAnswer to be of type string || int, the value that follows the is-defined-as symbol should conform to this type; but instead we have supplied an expression of type int. The compiler will automatically widen the expression 42, equivalent to changing the declaration to:
const theAnswer:
string || int ^= 42 as string || int;
The compiler will never automatically insert the inverse type conversion (corresponding to an is conversion); for example, you cannot supply theAnswer as defined above in a context that requires an int.
The rule for conditional expressions is that there must be at least one
branch whose expression type T includes the types of all the other
branches. Then the other branches will be automatically widened to type T.
For example,
([finished]: 42,
[]: null)
is not allowed because the types int and void do not overlap; however,
([finished]: 42, []: null as
int || void)
is legal because the type of the expression 42 can be automatically widened to int || void.
Although widening is applied to the operands of normal operators, it is not applied to operands of comparison operators. This is because comparisons are in any case permitted between operands of different types, provided only that the types have some common ancestor besides the root class anything.
When determining type compatibility, constraints are ignored. Perfect compilers will generate validity conditions to check that constraints are satisfied whenever a value is required to conform to a constrained type, such as during assignment or parameter passing.
Next: Classes and Methods
Save My Place | Glossary | Language Reference Manual |
Copyright © 1997-2012 Escher Technologies Limited. All rights reserved. Information is subject to change without notice. |