It is sometimes necessary to bypass the abstraction and make use of information about the actual type of the object to perform a particular action. Given a variable of an abstract type, we might like to make use of the actual type of the object it refers to, in order to determine whether it either has a particular implementation or supports other abstractions.
The typecase statement provides us with the ability to make use of the actual type of an object held by a variable of an abstract type.
a:$OB := 5; ... some other code... res:STR; typecase a when INT then -- 'a' is of type INT in this branch #OUT + "Integer result: " + a; when FLT then -- 'a' is of type FLT in this branch #OUT + "Real result: " + a; when $STR then -- 'a' is $STR and supports '.str' #OUT + "Other printable result: " + a.str; else #OUT + "Non printable result"; end; |
The typecase must act on a local variable or an argument of a method.On execution, each successive type specifier is tested for being a supertype of the type of the object held by the variable. The statement list following the first matching type specifier is executed and control passes to the statement following the typecase.
It is not legal to assign to the typecase variable within the statement lists.
If the object's type is not a subtype of any of the type specifiers and an else clause is present, then the statement list following it is executed.
It is a fatal error for no branch to match in the absence of an else clause.
If the value of the variable is void when the typecase is executed, then its type is taken to be the declared type of the variable. In the above example, the declared type of a is $OB , which does not match any of the branches, so the else clause is taken
The variable of the typecase must be a local variable or a method argument.
If the typecase appears in an iterator, then the mode of the argument must be once; otherwise, the type of object that such an argument holds could change.
The typecase does not search for the branch with the tightest match - it goes down the first branch that matches.
After a branch has been selected, the typecase tries to cast the variable to as narrow a type as possible - if the declared type of the variable is actually stronger than (a subtype of) the chosen branch, then the variable will keep the stronger, declared type. For instance
a:$SET{INT}; typecase a when INT then ... -- a will never get here, INT is not < $SET{INT} when $OB then ... -- a has the type of $SET{INT} which is stronger than $OB |
For instance, suppose we want to know the total number of subordinates in an array of general employees.
peter ::= #EMPLOYEE("Peter",1); -- Name = "Peter", id = 1 paul ::= #MANAGER("Paul",12,10); -- id = 12,10 subordinates mary ::= #MANAGER("Mary",15,11); -- id = 15,11 subordinates employees: ARRAY{$EMPLOYEE} := |peter,paul,mary|; totalsubs: INT := 0; loop employee:$EMPLOYEE := employees.elt!; -- yields array elements typecase employee when MANAGER then totalsubs := totalsubs + employee.numsubordinates; else end; end; #OUT + "Number of subordinates: " + totalsubs + "\n"; |
Within each branch of the typecase, the variable has the type of that branch (or a more restrictive type, if the declared type of the variable is a subtype of the type of that branch).