19.9 Guard Statement

Sometimes, the code one may wish to execute may depend on the (dynamic) type of the object reference. This situation is similar to ones that result in the use of a CASE statement, where various courses of action are selected based on the value of some variable at the time. However, the CASE statement will not serve in this instance, because it is selection on the dynamic type of the object, not on its value that one is interested in.

A statement that selects actions based on some dynamic state is said to be a guard statement, and the actions that are protected by the statement are said to be guarded.

Thus, OOM-2 introduces a new kind of selection, or guarded statement with a syntax similar to that of CASE, but specialized to the dynamic type of an object reference. The simplest syntax is shown in the skeleton that follows:

GUARD objectDenoter AS
  : className1 DO
    statementSequence1 |
  : className2 DO
    statementSequence2 |
    ....
  ELSE
    statementSequence(n+1)
  END;

Although the guarded statement sequence uses DO, there is no END, but rather, the next selection in the list is delimited from the last by the vertical bar. Thus, a prettyprinting style has been chosen that lines up the items in the list of selections and the ELSE and END. Here, the names of the classes to which the object's dynamic class is being matched are preceded by a colon.

The first (in order of appearance) class name to which the object denoter is assignment compatible is selected and its statement sequence is executed. As with a CASE statement, execution of the GUARD statement then terminated and control goes to the next statement after the END.

WARNING: An empty object reference selects the ELSE clause. If there is no match and no ELSE clause, an exception is generated.

The following trivial example only detects the class of the variable and prints a message. It is provided only to illustrate the syntax and semantics. ClassA is the superclass of the others, with classes B and D as subclasses of ClassA. ClassC is a subclass of ClassB.

UNSAFEGUARDED MODULE guardTest;
(* to illustrate simple guard statement
  by R. Sutcliffe 1998 09 28 
  This example done in a StonyBrook beta version
  in which TRACED objects had not yet been implemented. *)
FROM STextIO IMPORT
  WriteString, ReadChar;
FROM Storage IMPORT
  ALLOCATE;
CLASS ClassA;
  END ClassA;
CLASS ClassB;
INHERIT ClassA;
  END ClassB;
CLASS ClassC;
INHERIT ClassB;
  END ClassC;
CLASS ClassD;
INHERIT ClassA;
  END ClassD;

VAR
  ch : CHAR;
  a : ClassA;
  b : ClassB;
  c : ClassC;
  d : ClassD;
BEGIN
  CREATE (a);
  CREATE (b);
  CREATE (c);
  CREATE (d);
  a := d;
  GUARD a AS
   : ClassA DO
      WriteString ("Class a found ");|
   : ClassB DO
       WriteString ("Class b found ");|
   : ClassC DO
       WriteString ("Class c found ");|
   : ClassD DO
       WriteString ("Class d found ");
   ELSE
     WriteString ("none found ");
   END; (* guard *)
  ReadChar (ch);
END guardTest.

Look at the above code carefully in the light of the semantics given. It will always output the message for ClassA. Since the object a is of that class, ant it comes first in the list, it will always be selected. What if the order of the list is reversed to be D-C-B-A? ClassD would then be selected, because the object a is of the dynamic type ClassD and it comes first in the list. If ClassD were not present as a selection, ClassA would eventually be chosen. If we changed the reversed code and wrote:

  b := c;
  GUARD b AS
   : ClassD DO
      WriteString ("Class d found ");|
   : ClassC DO
       WriteString ("Class c found ");|
   : ClassB DO
       WriteString ("Class b found ");|
   : ClassA DO
       WriteString ("Class a found ");
   ELSE
     WriteString ("none found ");
   END; (* guard *)

we would select and get the message for ClassC, the first one to which b is assignment compatible (because ClassC is the dynamic type of b at that point.) Care must therefore be taken when writing a GUARD statement that the list is in an appropriate order for what the programmer wants the code to do.

There is more to the GUARD statement than this, however. The components of the selector can be accessed by GUARDing it AS a specific entity of the given class type. To do this, an object denoter is placed before the colon for that item in the list, and this effectively opens up a scope for that name, which reference is assigned the original selector. Via the new reference denoter, the components of the original object are now accessible. Here is a simple illustration, this time with only two classes. The object reference a is passed to a procedure parameter of the base class type (which can also accept any reference to a subclassed object). Depending on the dynamic type of the actual parameter, the procedure selects and prints information correctly. Observe that the denoters itema and itemb do not need to be declared; they are generated (and are only visible) locally to the statement sequence. These denoters become local aliases within the portion of the GUARD statement where they are in scope, and this prevents inappropriate use of attribute or method components of a class by ensuring that they are only referred to in a dynamic context where they make sense.

WARNING: The denoters indicated at the beginning of each list item (where used) are not only local to their own statement sequence, they are immutable there (they cannot be reassigned.)

MODULE GuardTest2;
(* to illustrate guard statement with new denoter access
  by R. Sutcliffe 1998 09 28 *)
FROM STextIO IMPORT
  WriteString, WriteLn;
FROM SWholeIO IMPORT
  WriteCard, WriteInt;
TRACED CLASS ClassA;
  REVEAL card;
  VAR
    card : CARDINAL;
  END ClassA;
TRACED CLASS ClassB;
INHERIT ClassA;
  REVEAL int;
  VAR
    int : INTEGER;
  END ClassB;

PROCEDURE Check (item : ClassA);
BEGIN
  GUARD item AS
    itemb : ClassB DO
       WriteInt (itemb.int, 10);
       WriteLn; |
    itema : ClassA DO
       WriteCard (itema.card, 10);
       WriteLn; |
    ELSE
      WriteString ("none found ");
    END; (* guard *)
END Check;

VAR
  a : ClassA;
  b : ClassB;
BEGIN
  CREATE (a);
  CREATE (b);
  a.card := 12;
  b.int := -15;
  Check (a); (* should print value of a.card *)
  Check (b); (* should print value of b.int *)
  a := b;    (* change dynamic type of a *)
  Check (a); (* should print value of b.int *)
END GuardTest2.

The output from this program was, as expected:

        12
       -15
       -15

Contents