Classes

  Concrete classes are classes which specify object implementations. A concrete class may define object attributes (Smalltalks's instance variables) which make up the internal state of objects, shared and constant attributes which are common to all instances of the class (Smalltalk's class variables or Java's static variables), routines which perform operations on the object, and iters which encapsulate iteration.

Features may be declared private to allow only the class in which they appear to access them. Accessor routines are automatically defined for reading object, shared, and constant attributes and for writing object and shared attributes. Attributes may also be declared to be readonly or private. Readonly attributes have a public reader and a private writer routine, while with private attributes both reader and writer are private. The private and readonly qualifications may also be used with shared attributes.

The set of non-private routines and iters in a class define its interface. In order to illustrate the simplest kinds of classes in Sather, consider an EMPLOYEE class, which holds information concerning an employee, such as his identification number and name.

EMPLOYEE definition

    The class is composed of several attributes which hold the employee information. The only routine provided is the ``create'' routine, which is used to create new employee objects, instances of the class EMPLOYEE.  

class EMPLOYEE is
   -- A concrete class i.e. a class whose features are all implemented. 

   readonly attr name: STR;
      -- Generates the interface routines:
      -- name: STR                      [the public reader routine]
      -- private name(new_value: STR)   [the private assignment routine]

   attr id: INT;

   private attr wage: INT;

   create(a_name: STR,a_id: INT, a_wage:INT): SAME is
     res ::= new;
     res.id := a_id;
     res.name := a_name;         -- Sugar for the writer routine "name(a_name)"
     res.wage := a_wage;  
     return(res);
   end;

   highly_paid: BOOL is
      return wage > 40000;
   end;
end;

TESTEMP definition

  The Employee class may be exercised using hte following main class.
class TESTEMP is
   
   main is
        john:EMPLOYEE := #EMPLOYEE("John",100,10000);   
        peter         ::= #EMPLOYEE("Peter",3,10000); -- Infer the Peter's type
        #OUT+john.name+"\n";    -- Prints "John"
        #OUT+peter.id+"\n";     -- Prints "3" 

        -- ILLEGAL! #OUT+john.wage+"\n";  "wage" is private

        john.id := 100;       -- Set the value of the attribute "id" in john
        -- ILLEGAL! john.name := "martha";   "name" is readonly.  
   end;

end;
The ``main'' routine in sather has a special meaning, similar to its meaning in C. A working sather program must specify its main class, which must contain a main routine that is the root of all routines in the implementation.

Running the example

To run the above example - type the code into a file emp.sa and then run the executable "emp"

cs emp.sa -main TESTEMP -o emp
This generates the executable "emp", using the "main" routine in TESTEMP as its starting point. You can browse the resulting code by calling
bs emp.sa -main TESTFOO

Attribute and Assignment Syntactic Sugar

    In the class above, we saw that attributes only accessible via the signatures they contribute to the class interface. In that case, what does it mean to assign to an attribute? Recall that each attribute declaration is visible as two routines in the class interface.

  attr a: INT;
is sugar for
  a: INT;                   -- Reader routine, used to get the value of a
  a(value: INT);            -- Writer routine, used to set the value of "a"
If the attribute access is restricted the interface routines are typed accordingly. If the attribute is readonly, the writer is private; if the attribute is private, both reader and writer routines are private.

In order to make attribute assignment work, the := sign is syntactic sugar for calling any function with one argument. In particular, it can be used to call the attribute assignment routine.

	foo is
	   a := 3; -- Equivalent to a(3);
	end;
Note that any function call to a function with one argument can make use of the syntactic sugar.

The advantage of this approach is that it is impossible to tell whether a certain feature is implemented as an attribute or as a routine. Hence, changing an implementation to use a routine rather than an attribute and vice-versa is trivial.

There is no performance loss associated with these interface routines - the compiler treats attribute reader and writer routines specially and generates inline assignments, as you might expect (this is true even with all optimizations off).

Note that not all assignments are syntactic sugar - assignments to locals and to out arguments are honest-to-goodness assigments and behave as they would in any other language.

  In addition to permitting the hash sign for create, Sather permits syntactic sugar to be used for many other routine names. For instance, "+" is syntactic sugar for the plus routine, "-" for the minus routine "*" for the times routine, "=" for the is_eq routine etc. Refer to the Sather manual for the sugared routines.   

       class POINT is
          attr x, y: INT;
          create(x,y: INT): SAME is 
                res ::= new; 
                res.x := x; res.y := y; 
                return res;
          end;
          plus(p: SAME): SAME is return #(x+p.x,y+p.y) end;
          minus(p: SAME): SAME is return #(x-p.x,y-p.y) end;
        end;
        main is
          p ::=#POINT(4,5);
          q ::= #POINT(11,15);
          l ::= p+q; -- Invoke the "plus" routine in "POINT"
          r ::= p-q; -- Invoke the "minus" routine in "POINT"
	end;
     end;

Constants

Constants are similar to attributes except that their values must be set at compile time to other constants or expressions consisting of other constants. Constants are usually set to literals of one of the built in types.

	const a: STR := "This is a test";
	const b: INT := 0;
	const c: CHAR := 'a';
	const d: FLT := 1.0;
	const f: ARRAY{FLT} := |1.0,2.0,3.0|;
Constants are used for values that should never change in the course of computation and can be computed at compile time. If a constant cannot be computed at compile time, a shared may be used in place of a constant as shown below. See initializing shareds

Shareds

  A shared may be viewed as a class variable (for those of you familiar with Smalltalk). There is a single instance of a shared attribute for a particular class, which is shared by all the objects of that class. Shared attributes can have all the qualifications that a regular attribute may have (private or readonly) and may be declared as follows:
	class POINT is
	    ... Definition as show above
	readonly shared origin: POINT := #POINT(0,0);
        ...
Shared attributes may be used either for efficiency reasons (to save on storage) or as globals, for communication between different objects. A shared may have a create expression that is built out of constant expressions. In the above example, the origin point is computed only once and may then be freely used later. If the shared requires a more complex creation expression, the following trick may be used
	class POINT is
	   private shared actual_point:POINT;
	   origin: POINT is
	     if void(actual_point) then actual_point := #POINT(0,0); end;
     	      return actual_point;
          end;
Through the use of this trick, the shared is initialized the first time it is accessed.

 

Good creation protocol

A common bug occurs in create routines when "res" is accidentally dropped.

   create(a_name: STR, a_id: INT): SAME is
      res ::= new;  
      res.id := a_id;
      name := a_name; -- ERROR! self is void, should be res.name := a_name
      return(res);
   end;
This will generate a run-time error, since the "name" refers to an attribute in "self"; self is still void - we are in the processs of creating "res". One simple way to avoid this bug is to use the following idiom:
   create(a_name: STR, a_id: INT): SAME is
      return new.init(a_name,a_id);
   end;
   
   private init(a_name: STR, a_id: INT): SAME is
      name := a_name; -- ERROR! self is void, should be res.name := a_name
      id := a_id;
      return self;
   end;
Another minor bug to avoid is naming the routine parameter the same as the attribute you are setting - the routine parameter will shadow the attribute.

   


Benedict A. Gomes
Mon Apr 29 10:12:43 PDT 1996