Sather - A Language Manual - Classes and Objects

[back] [Abstract] [Copyright Notice] [Contents] [next]

# Sather - A Language Manual - Chapter 2
Classes and Objects

All entities in Sather are objects, and objects are defined by classes. Even the basic entities in Sather, such as integers and floating point values are objects in Sather. Sather has several different kinds of classes - reference classes, abstract classes, immutable classes, partial classes and external classes. The important kinds of classes are reference classes and abstract classes - the rest are used in restricted circumstances. There are also some special objects (closures) which are not directly defined by classes, but we will defer their discussion till later.

Each Sather object has an associated type which indicates the class that was used to create the object. A variable in Sather also has a type, which indicates the kinds of objects it can be assigned to.

This chapter will focus on the most common kind of classes, reference classes, and the standard constructs used to create classes. Though iterators are an essential component of Sather code, their discussion has been deferred to the next chapter, since they are a relatively novel language feature.

## 2.1 Preliminaries

To make it easier to present examples in the following sections, we will start by introducting a few basic classes - integers, floating point numbers and strings. We will also describe how to print out data and to use the compiler

### 2.1.1 Some basic classes - INT, FLT and STR

Though basic numbers and strings enjoy some special language support (such as a means to initialize them to values like 5 or "foo"), they are defined as regular classes, and are a part of the standard library.The FLT class represents floating point numbers, while the INT class represents integers and the STR class represents strings. Variables may be declared to be of any of these classes and assigned when they are declared

    a:FLT := 3.0;
    b:INT := 5;
    c:STR := "foo";

It is also possible to perform the usual operations on these classes, such as addition of numbers and concatenation of strings (represented by the "+" operator):

    a:STR := "foo";
    b:STR := "bar";       -- + concatentates strings
    c:STR := a + b;         -- c is "foobar"
    e:INT := 5;
    f:INT := 7;
    g:INT := e+f;           -- g is 12
    compare:BOOL := e > f;  -- compare is false
    #OUT + compare;         -- Prints out 'false'

Comments in Sather start with a `--` and extend to the end of the line. Note that all variables have a default initial `void` value. For the present, `void` may be thought of as either the NULL pointer for reference objects, `0` for integers, `0.0` for floats and `false` for booleans.

### 2.1.2 Printing output

You can print data of various types in Sather using the command `#OUT+`

    a:INT := 10;
    #OUT + "hello world " + a;  -- Prints out "hello world 10"

Treat `'#OUT+'` as an idiom for now; it is equivalent to the standard output routines in other languages.

### 2.1.3 Sather source files

Sather source files consist of lists of classes. In addition to the source files that a user specifies on the command line to the compiler, the standard library files are always implicitly examined. Definitions of the basic classes such as integers and strings as well as containers of all kinds are to be found in the standard library.

Execution of a Sather program begins with a routine named `'main'` in a specified class, (a class called `'MAIN'` is used by default). If main is declared to have a return value of type `INT`, this will specify the exit code of the program when it finishes execution.

### 2.1.4 Hello World

The hello world program is show below:

    class HELLO_WORLD is
       main is
          #OUT+"Hello World\n";
       end;
    end;

As we mentioned earlier, printing to standard output is obtained by calling `#OUT+`.

If the above code is stored in the file hw.sa, it can be compiled (using the ICSI Sather compiler) by:

    cs -main HELLO_WORLD -o hw hw.sa

The '-main' option simply indicates to the compiler that the main routine will be found in class `HELLO_WORLD.` The resulting executable, `'hw'` can be run as follows

    prompt> hw
    Hello World
    prompt>

## 2.2 Defining Classes and Creating Objects

Objects are usually models of conceptual or real-world entities; they consist of a combination of data, which models the state of the entity and operations which model the behavior of the entity. The body of a Sather class consists of a list of features which define the data and behavior of the class. A class defines a new type and may be used to create object instances of that type.[8]

We will start by describing the data elements and then move on to the operations. In subsequent sections, we will describe the definition of object behavior in the form of routines. We will then point out that Sather provides a level of abstraction, which permits the state and behavior of the object to be treated in a uniform manner. Finally, we will describe the somewhat unusual meaning of assignment in Sather that makes this uniformity possible.

### 2.2.1 Defining Simple Classes

The state of a class is defined by attributes, which are have the prefix `attr`:

    class POINT is
       attr x:INT;
       attr y:INT;
    end;

The `POINT` class above defines an `'x'` and a `'y'` attribute both of which are integers. This class is useless, as it stands, since it provides no way to create instances of itself.

Object Creation: `create` and `new`

To make objects of the `POINT` class, we have to introduce a `create` routine

    class POINT is
       attr x, y:INT;

       create(xvalue,yvalue:INT):POINT is
          res:POINT := new;
          res.x := xvalue;
          res.y := yvalue;
          return res;
       end;
    end;

The `create` routine first calls the special expression `'new'`. `'new'` creates a new uninitialized instance of the `POINT` class and returns it. All the attributes in the new instance have default 'void' values. It then assigns the `'x'` and `'y'` attributes of this new instance to `xvalue` and `yvalue` respectively. Instances of the `POINT` class can then be created as shown below

    p:POINT := POINT::create(3,5);

Since creation is such a common operation, Sather provides a special shorthand for calls to the routine `'create'`. The `'create'` routine shown could be invoked with the # sign as shown below

    point:POINT := #POINT(3,5);

Expressions using the `#` sign are referred to as creation expressions, and are a convenient shorthand used for creating new objects and initializing their attributes.

Attribute access

When an object of the class `POINT` is created, the `'x'` and `'y'` attributes may be accessed by 'dotting' into the object.

    a:POINT := #POINT(3,5);
    -- Create a new point
    #OUT + a.x ;
    -- Prints out the value of 'x', which is 3
    a.x := 5;
    -- Sets the value of the 'x' attribute to 5

Points to note

- The semantics of a class is independent of the textual order of its class elements. In particular, the actual attribute layout used by a Sather implementation is invisible to a programmer.

- The scope of feature names is the class body

- Feature names may be either lower or upper case.

- Class names must be all upper case letters (underscores and digits are permitted except as the first character).

- The feature namespace is separate from the class namespace.

- The scope of class names is the entire program; no two classes can have the same name (unless they have different number of parameters, which will be explained in the chapter on class parametrization).

- You have to explicitly call `'new'` in the create routine. The following code exhibits a common error:

      class POINT is
         attr x,y:INT;

         create(xval, yval:INT):POINT is
            x := xval;
            -- Run time error! We have no object as yet!
            y := yval;
         end;
      end;

### 2.2.2 Checking whether an object has been created

Before a variable is assigned to an object, the variable has the void value. The expression `'void'` may be used to determine whether a value is void or not

    a:POINT;

    if void(a) then
       #OUT + "a is void!";
    end;

The following example will print out the string "a is void!" since a `POINT` is a reference class and `'a'` has not been created.

    a:POINT := #POINT(3,5);

    if void(a) then
       #OUT + "a is void!"
    else
       #OUT + "a is not void!"
    end;

In the above version, the string "a is not void!" will be printed since an object has been assigned to the variable `'a'`.

Note that the above test will not work in the same way for some of the built-in classes such as integers and booleans[9].

### 2.2.3 Types Introduced

Each Sather variable and object has an associated type. The type of the object indicates the class that was used to create the object. In the following example, both '`a`' and '`b`' have the type `POINT`, indicating that they are associated with instances of the `POINT` class.

    a:POINT := #POINT(2,3);
    b:POINT := #POINT(4,5);

In this example, the type of the variable `'a'` is the same as the type of the object to which it is assigned. This is always the case with the reference classes we have seen so far.

When we introduce abstract classes (Abstract Classes and Subtyping, chapter 5), we will see that some Sather variables can hold objects of many different types. In this case, it is useful to distinguish between the type of the variable (called the declared type) and the type of the object that it holds (called the actual type or the concrete type).

### 2.2.4 Hiding features: private and readonly

A fundamental feature of object oriented programming languages is that they permit an object to hide certain features which are for internal use only. Attributes may be completely hidden by marking them `private`. Routines may likewise be marked private, meaning that they cannot be accessed outside the original class. Attributes can also be hidden so that they can be read but not modified from outside the class, by marking them `readonly`.

    class POINT2 is
       private attr x:INT;
       -- x cannot be seen from outside
       readonly attr y:INT;
       -- y cannot be changed from outside
       create(xvalue,yvalue:INT):POINT is
          res:POINT := new;
          res.x := xvalue;
          res.y := yvalue;
          return res
       end;
    end;

This restricts external access to the attributes in the object

    foo is
       -- some other piece of code
       a:POINT2 := #POINT2(3,5);     -- Create a new POINT2
       #OUT + a.y;                   -- Print out '5'
       -- Illegal: #OUT + a.x;
       -- Illegal: a.y := 10;
    end;

Points to note

- Privacy is on a per-class basis, rather than on a per-object basis. Thus, an object can access the private features of other objects of the same class. We actually use this fact in the `create` routine of the class `POINT2` above. Assignments to the attributes of `res` are being done outside the object being returned.

## 2.3 Class Data: shared and const

In addition to object attributes, a class definition may also contain 'shared' data, which is shared by all the objects of that class.

### 2.3.1 Shared Attributes - Restricted global variables

Shared attributes are similar to object attributes, but are shared between all the instances of a class. They are essentially global variables that reside within a class namespace. They can be accessed and modified by any instance of the class. Shareds can have the same private and readonly restrictions that regular attributes have

    private shared i,j:INT;
    readonly shared c:CHAR := 'x'

Unlike regular attributes, when only a single shared attribute is defined, a constant initializing expression may be provided.

    shared s:STR := "name";
    -- ILLEGAL shared s,p:STR := "name";
    -- cannot use initializing expression if two shareds are
    -- declared at the same time

If no initializing expression is provided, the shared is initialized to the value `'void'`.

### 2.3.2 Class Constants

Constants are accessible by all objects in a class and may not be assigned to - they must have an initializing expression from which their value is determined at compile time (there is an exception when no type is specified, as descrbed in the next subsection). If a type is specified, then the construct defines a single constant attribute which must be initialized to a constant expression. Constant expressions are recursively composed out of a combination of literals, function calls on literals, and references to other constants. More precisely, legal assignments are to

- a character, boolean, string, integer or floating point literal

- a `void` or `void` test expression

- an `and` or `or` expression, each of whose components is a constant expression

- an array literal, each of whose components is a constant expression

- a routine call applied to a constant expression, each of whose arguments is a constant expression other than void. This caveat is imporant, since `create` routines are called on void. Thus the following is illegal:[10]

      -- ILLEGAL const  a:POINT := #POINT(3,3);
      const a:POINT := void;
      -- The only legal kind of constant POINT is void

- a reference to another constant in the same class or in another class using the `'::'` notation.

      const r:FLT:=45.6;
      -- Reader routine is private r:FLT;
      private const a,b,c;
      private const d:=4,e,f
      const bar:BOOL := r > 10;
      -- Function call on constants
      const foo:ARRAY{INT} := |1,2,4,5,6|;
      -- Sather arrays are explained later
      const  baz ::= BAR::foz ;
      -- foz must be a constant expression in foz

Integer constants and Enumerated Types

If a type specifier is not provided, then no initializing expression is required and the construct defines one or more successive integer constants. The first identifier is assigned the value zero by default; its value may also be specified by a constant expression of type `INT`. The remaining identifiers are assigned successive integer values. This is the way to do enumeration types in Sather. It is an error if no type is specified and there is an assignment that is not of type `INT`.

    const a;
    -- a is of type INT and gets the value 0
    const c,d;
    -- c gets 0 and d gets 1
    const e := 3;
    -- e is also of type INT

Points to note

- There must not be cyclic dependencies among constant initializers.

      class FOO is
         const b:INT := BAR::c;

      class BAR is
         const c:INT := BAZ::d;

      class BAZ is
      -- ILLEGAL!  const d:INT := FOO::b;
      -- Introduces a cycle between b, c and d

- Since constant initialization involves permits operations on the built-in types, the operations on the built-in types are designed so that no observable side-effects can occur during constant initialization.

- The prefix readonly cannot be applied to constants, since constants cannot be modified in any case.

- Due to their definition, constants are only useful for the basic classes such as numbers, booleans and characters. All other constants can only be assigned to be void!

      class FOO is
         const a:BAR := void;
         -- only legal value

### 2.3.3 Accessing Class Data - the :: notation

It is possible to directly access the class data or features using the :: notation.

    class FOO is
       const a:INT := 3;
       private const b:INT := 5;
       readonly shared c:INT := 6;
       shared d:INT := 7;
       attr f:INT;

       create(i:INT):SAME is
          res:SAME := new;
          res.f := i;
          return res;
       end;

       method1:INT is
          return d+a;
       end;

       method2:INT is
          return f+a;
       end;
    end;

The shared and const class data can then be accessed using the :: notation

    #OUT+ FOO::a+"\n";
    FOO::d := 3;

When a method is called using the `'::'` notation, it is equivalent to calling the method on a void object. Calling a method on a void object makes sense if the feature only makes use of shared data and local state. If the method makes use of object data, a run-time error will result.

    #OUT+FOO::method1;
    -- Prints out d+a = 10
    #OUT+FOO::method2;
    -- Tries to print out self.f+a
    -- However, self (the object) is void, so trying to access 'f'
    -- results in a run-time error - Attribute access of void

- The usual privacy and modification restrictions are maintained

      a_copy:INT := FOO::a;
      -- ILLEGAL FOO::c :=3; -- c is readonly
      -- FOO::a := 7;  -- a is a constant

## 2.4 Routine definitions

The behavior of a class is specified by routines in the class body. Routines may take arguments and may return a value.

    class CALCULATOR is
       attr running_sum:INT;

       create:CALCULATOR is
          res:CALCULATOR := new;
          res.running_sum := 0;
          return res;
       end;

       add(x:INT):INT is
          res:INT := running_sum + x;
          return res;
       end;
    end;

A routine definition may begin with the keyword '`private`' to indicate that the routine may be called from within the class but is not visible from outside the class. The methods that are visible from outside the class are referred to as the class interface.

The body of a routine is a list of statements, separated by semicolons. In a routine with a return value, the final statement along each execution path must be a `return` statement. Thus, the following is not legal:

    scale_x(x:INT):INT is
       -- Illegal routine - the else clause has no return value
       if x > 0 then
          return 15;
       else
          #OUT + "Error!";
          -- last statement on this branch is not return
       end;
    end;

A `raise` statement raises an exception, and can be used wherever a return statement might be required. (See Exceptions, chapter 10 for more details) For now, we merely note that the following version of the routine '`scale_x`' does not return a value in the second branch of the if statement, but raises an exception instead, which is perfectly legal.

    scale_x(x:INT):INT is
       if x > 0 then
          return 15;
       else
          raise "An error occurred!";
       end;
    end;

Using the return value

Note that, unlike most other languages, Sather forces you to make use of the return value. This may be considered an extension of strong typing - the presence or absence of a return value is a part of the signature that should not be ignored.

    new_x:INT := scale_x(15);
    -- Legal, the return value used
    scale_x(15);
    -- ILLEGAL! Return value unused

The return value can also be used as part of an expression.

    a := scale_x(15) + 3;

### 2.4.1 Routine Arguments and Modes

The arguments to a routine are specified as a comma-separated list. Each argument must provide a name and type. The types of consecutive arguments may be declared with a single type specifier.

    create(x,y:INT):POINT ...

The scope of method arguments is the entire body of the method, and also shadows methods and attributes in the class. If a routine has a return value, it is declared by a colon and a specifier for the return type. You can get around this restriction by using the `self` expression explicitly

    class POINT is
       attr x,y:INT;
       add_x(x:INT) is
          self.x := self.x + x;
       end;
    end;

Each argument also has a mode which determines how that argument is treated when the routine is called. If no mode is explicitly stated, the argument mode is `in`. That means it is simply a value sent into the routine. The other possible modes are `out`, `inout` and `once` (which will be described in the section on iterators).

Multiple return values and `out` arguments

An out argument is really like an extra return value. An `out` argument is not set when the routine is called; rather, it is filled in by the routine itself. Consider an integer division function that returns both the divident and remainder of the two integer arguments

    divide(x,y, out dividend, out remainder:INT) is
       -- Note that the 'INT' type specifier applies to  multiple
       -- arguments while the mode qualifiers apply to only one
       -- argument.
       dividend := x/y;
       -- Integer division result
       remainder := x - y*(x/y);
       -- Remainder after the division.
       -- Could also use x.mod(y)
    end;

The divide routine may be used as shown below:

    a:INT := 15;
    b:INT := 10;
    div, rem:INT;
    -- These are defined but not assigned
    divide(a,b,out div, out rem);
    #OUT + "Divident=" + div + " Remainder=" + rem + "\n";
    -- Prints out "Divident=1 Remainder=5"

Note that the `out` argument has to be marked both where the method is defined (i.e. as a marker of the formal parameter) and at the point of call, or the compiler will complain (`once` and `in` arguments need not be mentioned at the point of call)

`inout` arguments

`inout` arguments are a combination of `in` and `out` arguments. They take a value into the function and return a value out of the function. We can thus write the swap function compactly as:

    swap(inout x, inout y:INT) is
       tmp:INT := x;
       x := y;
       y := tmp;
    end;

    a:INT := 5;
    b:INT := 10;
    -- a and b have an initial value
    swap(inout a,inout b);
    #OUT + "a=" + a + " b=" + b;
    -- Prints "a=10 b=5"

The list below describes the argument modes in more detail:

`in`
  All arguments are 'in' by default; there is no 'in' keyword. 'In' arguments pass a copy of the argument from the caller to the called method. With reference types, this is a copy of the reference to an object; the called method sees the same object as the caller.

`out`
  An '`out`' argument is passed from the called method to the caller when the called method returns. It is a fatal error for the called method to examine the value of the '`out`' argument before assigning to it. The value of an '`out`' argument may only be used after it has appeared on the left side of an assignment.

`inout`
  An '`inout`' argument is passed to the called method and then back to the caller when the method returns. It is not passed by reference; modifications by the called method are not observed until the method returns (value-result).

`once`
  Once parameters are discussed in detail in the chapter on Loops and Iterators (See Loops and Iterators, chapter 3). Only iterators may have '`once`' arguments. Such arguments are evaluated exactly once, the first time the iterator is encountered in the containing loop. '`once`' arguments otherwise behave as 'in' arguments, and are not marked at the point of call.

### 2.4.2 Local Variables - Scoping and Shadowing

Declaration Statements are used to declare the type of one or more local variables. The scope of a local variable declaration begins at the declaration and continues to the end of the statement list in which the declaration occurs. Local variables shadow routines (including the accessor routines of attributes) in the class which have the same name and no arguments.

    ... in the POINT class ...
    swap_x_y is
       temp:INT;
       temp := x;
       x := y;
       y := temp;
    end;

Within the scope of a local variable it is illegal to declare another local variable with the same name.

Points to note

- Local variables are initialized to `void` when the containing method is called.

- Local variables are not re-initialized when the declaration is encountered in the flow of control. This is particularly relevant in loop statements, which are discussed in the next chapter. The integer `'a'` is initialized to zero when the function `'compute'` is entered. It is not initialized every time through the loop.

      compute is
         loop 3.times!;
            a:INT;
            a := a + 3;
            #OUT + a + "\n"; -- Prints out successively 3, 6, 9
         end;
      end;

- Note that explicit initialization (in this case `'a:=15'` ) is performed every time it is encountered

      compute is
         loop 3.times!;
            a:INT := 15
            a := a + 3;
            #OUT + a + "\n"; -- Prints out successively 18, 18, 18
         end;
      end;

### 2.4.3 Routine calls

The most common expressions in Sather programs are method calls[11]. A routine call usually takes the form of a 'dotted' expression such as `a.foo(b).` The object on which the routine is being called (`'a'` in this example) is determined by what precedes the dot. If no object name precedes the 'dot', the `self` object i.e. the current object, is assumed. We use the following definition of the POINT class to

    class POINT is

       attr x,y:INT;

       create(x,y:INT):POINT is
          res:POINT := new; res.x := x;  res.y := y;  return res;
       end;

       add(xval,yval:INT):POINT is
          xsum:INT := x + xval;
          ysum:INT := y + yval;
          res:POINT := #POINT(xsum, ysum);
          return res;
       end;

       offset_by(val:INT):POINT is
          return add(val,val);    -- short for 'return self.add(val,val);'
       end;
    end;

illustrate different kinds of routine calls

- If nothing precedes the method name, then the form is syntactic sugar for a call on `self` If the method name is preceded by an expression and a dot '`.`', then the method is called on the object returned by the expression. In the following example, pair (3,7) is first added to `p1` and the pair (4,9) is added to that result. Note that the intermediate point that is created after the first 3,7 is added is not accessible from any variable and will be garbage collected.

      p1:POINT := #POINT(3,5);
      p2:POINT := p1.add(3,7).add(4,9);

- If the method name is preceded by a type specifier and a double colon '`::`' it is presumed to be a call on a `void` object of the specified class (`POINT` in the case below)

      a:POINT := POINT::create(3,5);

This works for the `create` routine, since it creates a new object, `res,` and then makes use of it. However, this will not work for a call on, say, `add`

    res:POINT := POINT::add(4,7);
    -- Runtime Error!

Since `xsum := x + xval;` is actually equivalent to saying `xsum := self.x + xval;` the routine accesses `self`, which is void and cannot be accessed.

### 2.4.4 Simple Overloading - Selecting a routine to call

Sather supports routine overloading. We will present a simplified version of the overloading here, as it applies to the simple reference classes we have discussed. The full overloading rule will be described in more detail lateron (See The Overloading Rule, section 5.7).

Two routines in a class may have the same name provided they differ in at least one of the following aspect:

- the number of arguments

- the presence or absence of a return value

- the type of one of the arguments (provided the types are not abstract).

Here are some examples of properly overloaded routines

    foo(a:INT, b:INT);
    foo(a:INT);            -- Different number of arguments
    foo(a:INT,b:INT):INT;  -- Has a return value

All of the above routines could co-exist in a single class interface. The right one would be selected at the point of call. The following two routines, however cannot co-exist in the same interface

    foo(a:INT,b:INT):INT;
    -- foo(a:INT,b:INT):BOOL
    -- differs only in return type, cannot overload 'foo'

## 2.5 Conditional Execution

Sather supports the standard constructs for conditional execution - if statements and multi-way case statements

### 2.5.1 if statements

if statements are used to conditionally execute statement lists according to the value of a boolean expression. In this form, the if keyword is followed by a boolean expression, the keyword then, a list of statements and the final keyword end. When the statement is executed, the boolean expression is evaluated and if the result is truethe statements in the statement list are executed. If it is false,then control passes directly to the end of the if statement.

    i:INT := -15;
    if i < 0 then
       i:=-i;
    end;
    #OUT + i;                  -- Prints out 15
    j:INT := 15;
    if j < 0 then
       j:=-j;
    end;
    #OUT + j;                  -- Prints out 15

It often happens that one wishes to perform a sequence of tests, executing only the statements which correspond to the first test in the sequence which evaluates to `true`. For example, we may want to produce a integer value `'y'` from an integer value `'x'` which has the shape of a triangular bump. It should be zero when `'x<0'`, equal to `'x'` when `'0<=x<100'`, equal to `'200-x'` when '`100 <= x<200'`, and equal to `'0'` when `'x>=200'`. This can be accomplished with a nested series of if statements:

    if x < 0 then
       y := 0;
    else
       if x < 100 then
          y := x;
       else
          if x < 200 then
             y := 200 - x;
          else
             y := 0;
          end;
       end;
    end;

Because this kind of construct is so common and the deeply nested if statements can get confusing, Sather provides a special form for it. A series of elsifclauses may appear after the statements following the `then` keyword:

    if x < 0 then
       y := 0;
    elsif x < 100 then
       y := x;
    elsif x < 200 then
       y := 200 - x;
    else
       y := 0;
    end;

There may be an arbitrary number of such elsif clauses. Each is evaluated in turn until one returns true. The statement list following this clause is evaluated and the statement finishes. If none of the expressions is true, the statements following the final `else` clause are evaluated.

### 2.5.2 case statements

Multi-way branches are implemented by case statements. There may be an arbitrary number of when clauses and an optional else clause. The initial construct is evaluated first and may have a return value of any type.

    i:INT := 7;
    switch i
    when 1,2,3 then
       j := 3;
    when 4,5,6 then
       j := 4;
    when 7,8,9 then
       j := 5;
    else
       j := 10;
    end;
    #OUT + j;           -- Prints out 5

This type must define one or more routines named `'is_eq'` with a single argument and a boolean return value.

    class POINT is
       attr x,y:INT;

       create(x,y:INT):POINT is
          res:POINT := new; res.x := x; res.y := y; return res;
       end;

       is_eq(point2:POINT):BOOL is
          -- In Sather,= is short hand for a call on 'is_eq'
          return x = point2.x and y = point2.y;
       end;

       str:STR is return "X=" + x + " Y=" + y; end
    end;

Points can then be used in a case statement as shown below

    p:POINT := #POINT(3,4);
    zero_point:POINT := #POINT(0,0);

    case p
    when zero_point then
       #OUT + "Zero point\n";
    when #POINT(1,1), #POINT(1,-1),#POINT(-1,-1), #POINT(-1,1) then
       #OUT + "Unit point: " + p.str + "\n";
    else
       #OUT + "Some other point\n";
    end;

Note that the equal sign is really short hand for the routine `is_eq.` The case statement is equivalent to an `if` statement, each of whose branches tests a call of `is_eq`. Thus the above case is equvalent to

    if p = zero_point then
       #OUT + "Zero point\n";
    elsif p = #POINT(1,1) or p = #POINT(1,-1) or ... etc. then
       #OUT + "Unit point:" + p.str + "\n";
    else
       #OUT + "Some other point\n";
    end;

The expressions tested in the branches of the `if` statement are the expressions of successive `when` lists. The first one of these calls to returns `true` causes the corresponding statement list to be executed and control passed to the statement following the case statement. If none of the `when` expressions matches and an else clause is present, then the statement list following the else clause is executed

There is one difference between the casestatement and the equivalent if statement. If none of the branches of an if statement match and no else clause is present, then execution just continues onto the next statement after the ifstatement. However, if none of the branches of the case statement matches and there is no elseclause, then a fatal run-time error will result.

Points to note

- It is a fatal error if no branch matches and there is no else clause for case statements but not for if statements.

### 2.5.3 Short circuit boolean expressions: and and or

and expressions compute the conjunction of two boolean expressions and return boolean values. The first expression is evaluated and if `false`, `false` is immediately returned as the result. Otherwise, the second expression is evaluated and its value returned. or expressions compute the disjunction of two boolean expressions and return boolean values. The first expression is evaluated and if `true`, `true` is immediately returned as the result. Otherwise, the second expression is evaluated and its value returned.

Consider the code

    p: POINT;
    if p.x > 3 then
       #OUT + p.x;
    end;
     -- Runtime error if p is void

The above block of code will work if `p` is not void. If it is void, however, the test `p.x >3` will result in a runtime error, since it is attempting to dot into a void reference type. We can catch this problem by using the following piece of code, and the semantics of the short-circuit and

    if ~void(p) and p.x > 3 then
       -- The ~ symbol indicates logical negation
       #OUT + p.x;
    end;

The above piece of code will not generate an error, even if `p` is void. The first part of the and expression tests for whether `p` is void. If it is void, then the void test returns true and the not turns this into a false. The and therefore fails before trying to evaluate the dotted expression `p.x`.

A similar behavior can be seen with the short-circuit or statement, where the second expression is not examine if the first expression evaluates to true

    a:INT := 15;
    p:POINT;
    if a>10 or p.x < 10 then
       -- Since a>10 is true, the second expression is not evaluated

- Note that booleans also define an `and_rout` routine, which does not have the same short-circuit behavior:

      if ~void(p).and_rout(p.x > 3) then
      -- May generate a run-time error, when 'p' is void
      -- The argument to the 'and_rout' routine (p.x) is evaluated
      -- even when the first condition, ~void(p) fails.
      -- Hence, if 'p' is void, p.x is still evaluated and generates a
      -- run-time error (attribute access of void)

## 2.6 Attribute Accessor Routines

The distinction between data and behavior is not as strong as has been described above. In fact, it is possible to implement a feature such that outside the class it is impossible to tell whether it is a feature or a pair of functions This section describes how this level of uniformity is achieved.

Each attribute definition adds a field to the object's state and causes the definition of a reader routine and a writer routine, both with the same name. The reader routine takes no arguments and returns the value of the attribute. Its return type is the attribute's type. The reader routine is private if the attribute is declared '`private`'. The writer routine sets the value of the attribute, taking a single argument whose type is the attribute's type, and has no return value. The writer routine is private if the attribute is declared either `private` or `readonly`.

    class INTERVAL is
       attr start:FLT;
       -- Defines the public reader start:FLT
       -- and the public writer     start(FLT)
       attr finish; INT;

       create(st,fin:INT):SAME is
          -- Create a new interval
          res:SAME := new;
          res.start(st);
          -- Equivalent to res.start := st;
          res.finish(fin);
          -- Equivalent to res.finish := fin;
       end;
    end;

Thus, the levels of privacy are defined by whether the reader and writer routines are public or private

    private attr a:FLT;   -- Defines the reader, private a:FLT
                          -- and the writer  private a(FLT);
    readonly attr b:FLT;  -- Defines the public reader, b:FLT
                          -- and the private writer b(FLT)

The same holds true for shared attribubtes. Each shared definition causes the definition of a reader routine and a writer routine, both with the same name. The reader routine takes no arguments and returns the value of the shared. Its return type is the shared's type.

    class FOO is
       shared a:INT := 3;     -- Defines a:INT and a(arg:INT);
       readonly shared b:INT; -- Defines a:INT and private a(arg:INT);
    ...

    #OUT + FOO::a;    -- Prints out 3
    FOO::a(4);        -- 'a' is set to 4, same as FOO::a := 4;
    #OUT + FOO::a;    -- Prints out 4;
    FOO::a := 7;      -- 'a' is set to '7' , equivalent to FOO::a(7);
    FOO::b(3);        -- ILLEGAL! The writer routine is private

Constants do not define a writer routine. Each constant definition causes the implicit definition of a reader routine with the same name. It takes no arguments and returns the value of the constant. Its return type is the constant's type. The routine is private if and only if the constant is declared '`private`'.

    const r:FLT:=45.6;
       -- Reader routine is r:FLT;
    private const a,b,c;
       -- Reader routine is private a:INT;
    private const d:=4,e,f
    const bar:BOOL := r > 10;
       -- Function call on constants

### 2.6.1 Attribute assignment

In order to achieve the unification of attribute assignment and routine calls, for attributes, assignment has to be given a meaning in terms of function calls.

By default, the assignment is syntactic sugar for a call of the routine with the same name as the attribute with the right hand side of the assignment as the only argument

    p:POINT := #POINT(3,5);
    p.x := 3; -- Is syntactic sugar for  p.x(3);

In the above example, the assignment to 'x' is the same as calling the routine 'x' with a single argument.

Replacing an attribute by a routine

The beauty of this treatment of assignment is that an attribute in a class can later be substituted by a pair of routines. Consider a class to represent integer intervals, where we store the first and last value in the interval

    class I_INTERVAL is
       -- Integer intervals
       attr start:INT;     -- Defines start:INT and start(INT)
       attr finish:INT;    -- Defines finish:INT and finish(INT)

       create(start,finish:INT):SAME is
          res:SAME := new;
          res.start := start;       -- Equivalent to res.start(start);
          res.finish := finish;     -- Equivalent to res.finish(finish);
          return res;
       end;

       size:INT is return finish - start + 1; end;
            -- Returns the number of integers in the interval
    end;

We can make calls on this class

    i:I_INTERVAL := #I_INTERVAL(3,10);
    i.finish := 11;
    -- Equivalent to a call i.finish(11);
    #OUT + i.finish;
    -- Prints out 11
    i.start := 15;
    -- Equivalent to the call i.start(15);

Suppose we then realize that we usually want to know the size of the interval, and rarely need to know the end point. It would then be cheaper to store the size directly, rather than computing it. The class can be changed so that we store the `first` and `size` and compute `finish`.

    class I_INTERVAL is
       -- Integer intervals
       attr start:INT;          -- Defines start:INT and start(INT)
       readonly attr size:INT;  -- Defines  size:INT and private size(INT)
       -- size is readonly, since we only need size:INT in the interface

       create(start,finish:INT):SAME is
          res:SAME := new;
          res.start := start;          -- Equivalent to res.start(start);
          res.size :=  finish-start+1; -- Store the result in res.size
          return res;
       end;

       finish:INT is return start+size-1 end;
          -- Replacement for the reader routine for 'finish'
          -- Compute finish using 'start' and 'size'

       finish(new_finish:INT) is  size:=new_finish-start+1  end;
          -- Replacement for the writer routine for 'finish'

    end;

All the calls described above will continue to work as before. The assignment to `finish` in particular will now be a call on the user-defined finish routine, instead of a call to the implicit writer routine for the attribute `finish`.

## 2.7 Static Type Inference

For the sake of convenience, Sather provides a mechanism for statically inferring the type of a variable from the context. This type-inference takes place in different situations, where the type is available from the context.

### 2.7.1 Creation Expressions

In a creation expression, it is tedious to have to repeat the type of a class on both sides of a creation expression and assignment. Hence, the # symbol may infer its type from the context.

    a:POINT;
    a := #(3,4);
    -- Equivalent to a := #POINT(3,4);

### 2.7.2 Assignments and ::=

Type inference can also take place in a declaration, if it is combined with an assignment. Since the declared type of the right hand side of the assignment is known, its type is used as the type of the variable. This combination of declaration and assignment is extremely common in Sather code.

    a ::= 3;                     -- Equivalent to a:INT := 3;
    p1:POINT := #POINT(3,5);
    p2:POINT := #POINT(4,5);
    p3 ::= p1.add(p2);           -- 'p3' is of type POINT.
    -- Assumes the function 'add' in POINT i.e. POINT::add(POINT,POINT);

When an assignment is associated with a creation, we can make use of either form of type inference

    a ::= #POINT(3,4);  -- Equivalent to a:POINT := #POINT(3,4);
    a:POINT := #(3,4);  -- Means the same

### 2.7.3 Arguments to a function call

The type of the arguments to a function call are also known and can be used to infer the type of a creation expression in a call to the function.

    foo(a:POINT) is  ...

    foo(#(3,5));
    -- The create expression infers its type
    -- from the type of the argument that 'foo' is expecting

This form of type inference may be used for closure creation expressions as well, which will be discussed in the chapter on Closures

    apply(arg:ROUT{INT}:INT) is ....
    apply(bind(3.plus(_));

If the plus routine in the INT class is overloaded, then the appropriate routine is chosen based on the declared type of the argument to 'apply' i.e. ROUT{INT}:INT. Note that if both the 'apply' routine and the 'plus' routine are overloaded, type inference may not be able to determine the type and it might be necessary to create a temporary variable with the right type

    r:ROUT{INT}:INT := bind(3.plus(_));
    apply(r);

In any case, we strongly recommend that static type inference not be used in cases where confusion might result; the extra typing is usually worthwhile!

## 2.8 Class Parameters

We will briefly describe simple parametrized classes here so that they may be used in examples through the rest of the text. For a full description of parametrized classes, please see the chapter on Parametrized Classes.

A Sather class may have various type parameters, which are basically place holders for types which are specified when the class is actually used. This allows us to write code that is generic and can be used with a different types. By convention, these type parameters are given names like `T` or `TP.` We show below a class `TUP`, which holds pairs of objects. Since we would like to be able to hold objects of any types, we just specify type parameters, `T1` and `T2`. These parameters are place-holders, which must be set to actual honest-to-goodness concrete classes when the `TUP` is actually used

    class TUP{T1,T2} is
       -- Simple version of the library tuple class
       attr t1:T1;
       attr t2:T2;
       create(t1:T1, t2:T2): SAME is
          --  Standard create routine. Arguments use the type parameters
          res ::= new;   -- Using static type inference - new returns SAME
          res.t1 := t1;  -- The types of res.t1 and the argument t1
                         -- are both T1 so the assignment is legal
          res.t2 := t2;
          return res;
       end;
    end;

We can now create a tuple object that holds, for instance, a pair consisting of a string and an integer:

    t ::= #TUP{INT,STR}(5,"this");
    -- Create a new tuple.
    -- Uses ::= to determine the type of 't'
    #OUT + t.t1 + "\n";

### 2.8.1 Arrays

A standard parametrized class is the array class, `ARRAY{T}`. (See Support for Arrays, section 6.2 for more details). When an array is actually used to hold objects, the type parameter must be instantiated to indicate the kind of objects being held.

    a:ARRAY{INT} := |2,5,7|;
    -- Special syntax for initializing an array with values 2,5,7
    #OUT+a[1];
    -- Return the second element of the array

For example, arrays are used to pass in the arguments to a program into the main procedure.

    main(args:ARRAY{STR}) is
       #OUT + args[0];
       -- On unix, args[0] is the name of the program
    end;

We can hold a collection of points using an array, as follows

    a:ARRAY{POINT} := #(3);
    a[0] := #POINT(0.0,0.0);
    a[1] := #POINT(0.0,1.0);
    a[2] := #POINT(2.0,2.0);

## 2.9 A Running Example: Employees

We will illustrate the points made above by using a simple example, which will be something of a running example to be extended in later chapters. We will start here by defining a class `'EMPLOYEE'`. Please bear in mind that this example is used to illustrate various language features, not object-oriented design.

EMPLOYEE definition

The class is composed of several attributes which hold the employee information. Various degrees of privacy are illustrated

    class EMPLOYEE is
       private attr wage:INT;
       readonly attr name:STR;
       attr id:INT;
       const high_salary:INT := 40000;

       create(a_name:STR, a_id:INT, a_wage:INT):SAME is
          res ::= new;
          res.id := a_id;
          res.name := a_name;
          res.wage := a_wage;
          return(res);
       end;

       highly_paid:BOOL is
          return wage >= high_salary;
       end;
    end;

Note the use of the special type `SAME` as the return type of the `create` routine, which denotes the current class name. `SAME` changes to mean the including class when it is included, as will be explained in the next chapter on code inclusion.

TESTEMP definition

The employee class may be exercised using the following main class.

    class TESTEMP is
       main is
          john:EMPLOYEE := #EMPLOYEE("John",100,10000);
          peter:EMPLOYEE := #EMPLOYEE("Peter",3,10000);
          john.id := 100;           -- Set the attr "id" in john to 100
          #OUT + john.name + "\n";  -- Prints "John"
          #OUT + peter.id + "\n";   -- Prints "3"
       end;
    end;

Note that the following calls would be illegal:

    #OUT + john.wage + "\n";  -- ILLEGAL! "wage" is private
    john.name := "martha";    -- ILLEGAL! "name" is readonly

A distinguished class must be specified when a Sather program is compiled (the default is to look for a class called `MAIN`). This class must define a routine named `'main'`. When the program executes, an object of the specified type is created and `'main'` is called on it.

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 TESTEMP

[back] [Abstract] [Copyright Notice] [Contents] [next]

Sather - A Language Manual