Perfect Developer basic tutorial 3 This page last modified 2011-10-29 (JAC)

Type Conversions and Type Enquiries

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

Type Enquiries

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.

Type Conversions

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.

Automatic type conversions

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:

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
Tutorials Overview Main site   
Copyright © 1997-2012 Escher Technologies Limited. All rights reserved. Information is subject to change without notice.