next up previous contents index
Next: Memory management Up: Algebraic Datatypes and Previous: Pattern syntax

Refining a datatype

A datatype definition can be refined in a few ways:

  1. inheritance A datatype can be declared to be inherited from one or more classes C1, C2, ..., Cn. The effect is that all variants of a datatype will be inherited from the classes C1, C2, ..., Cn.
  2. member functions Member functions and addition attributes can be attached to the datatype variants using the keyword with .

For example, the following datatype Exp is inherited from some user defined classes AST and Object. Furthermore, a virtual member function called print is defined.

datatype Exp : public AST, virtual public Object
   = INT int
         with { ostream& print(ostream&); }
   | PLUS  Exp, Exp
         with { ostream& print(ostream&); }
   | MINUS Exp, Exp
         with { ostream& print(ostream&); }
   | MULT  Exp, Exp
         with { ostream& print(ostream&); }
   | DIV   Exp, Exp
         with { ostream& print(ostream&); }
   | VAR { name : const char *, typ : Type }
         with { ostream& print(ostream&); }
   public:
   {  virtual ostream& print (ostream&) = 0;
      void * operator new (size_t);
   }
;

The allocator and printing methods can be defined as follows:

void * classof Exp::operator new(size_t) { ... }
ostream& classof INT::print(ostream& s)   { return s << INT; }
ostream& classof PLUS::print(ostream& s)
   { return s << '(' << this->#1 << '+' << this->#2 <<')'; }
ostream& classof MINUS::print(ostream& s)
   { return s << '(' << this->#1 << '-' << this->#2 <<')'; }
ostream& classof MULT::print(ostream& s)
   { return s << '(' << this->#1 << '*' << this->#2 <<')'; }
ostream& classof DIV::print(ostream& s)
   { return s << '(' << this->#1 << '/' << this->#2 <<')'; }
ostream& classof VAR::print(ostream& s)   { return s << name; }

The special type form classof  con is used to reference the type of a constructor. Arguments of a constructor uses the following naming convention: (i) if the constructor C takes only one argument, then the name of the argument is C; (ii) if C takes two or more unnamed arguments, then these are named #1, #2, etc; (iii) finally, labeled arguments are given the same name as the label.

For readability reasons, it is often useful to separate a datatype definition from the member functions and inheritance definitions. The refine  declaration can be used in this situation. For example, the above example can be restated more succinctly as follows:

//
// Datatype definition section
//
datatype Exp = INT int
             | PLUS  Exp, Exp
             | MINUS Exp, Exp
             | MULT  Exp, Exp
             | DIV   Exp, Exp
             | VAR { name : const char *, typ : Type }
;

//
// Refinement section
//
refine Exp : public AST, virtual public Object
       { virtual ostream& print (ostream&) = 0;
         void * operator new (size_t);
       }
and    INT, PLUS, MINUS, MULT, DIV, VAR
       { ostream& print(ostream&);
       }
;

The general syntax of the refine  declaration is as follows:

Refine_Decl ::=refine Refine_Spec and ¼ and Refine_Spec ;
Refine_Spec ::=Id, ¼ , Id
    [ : Inherit_List ]
    [ :: Datatype_Qualifiers ]
    [ { Datatype_Body } ]
Here, Id refers to either a constructor name or a datatype name.



Allen Leung
Mon Apr 7 14:33:55 EDT 1997