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.
   attr running_sum:INT;

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

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

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;
      #OUT + "Error!";
      -- last statement on this branch is not return

A raise statement raises an exception, and can be used wherever a return statement might be required. (See unnamedlink 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;
      raise "An error occurred!";

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
-- 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;

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)

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;

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:


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.


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.


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 parameters are discussed in detail in the chapter on Loops and Iterators (See unnamedlink). 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 := x;
   x := y;
   y := temp;

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

Points to note

2.4.3. Routine calls

The most common expressions in Sather programs are method calls[1]. 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;

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

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

[1] We use the term 'method' here to indicate that the same description is applicable to both iterators, which have not yet been introduced, and routines.

illustrate different kinds of routine calls

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 unnamedlink).

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

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):BOOL
-- differs only in return type, cannot overload 'foo'