Routine definitions

Sather - A Language Tutorial
Prev | Chapter 2. Classes and Objects | Next

# 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 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;
       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 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: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[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;
       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;

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

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

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'

Prev | Home | Next
Class Data: shared and const | Up | Conditional Execution