Methods

A function declared with a receiver type defines a method for this type.

Purpose of methods

Methods are functions that perform on a variable with a specific user-defined type, and cannot be used for another type.

Use methods to implement the interface (the access methods) for a type.

Note: The concept of methods allows you to write robust code like in Object-Oriented Programming languages, without the complexity and traps of OOP.

Defining a method

A method is a FUNCTION defined with a receiver argument specified in parentheses before the function name.

The receiver consists of an identifier followed by a user-defined type. The receiver identifier is then referenced in the function body as the target of this method:
TYPE Rectangle RECORD
    height, width FLOAT
END RECORD

FUNCTION (r Rectangle) area() RETURNS FLOAT
    RETURN r.height * r.width
END FUNCTION

Method definition rules

The receiver type must be a TYPE defined in the same module as the method, and it must define a structured RECORD type (it cannot be a flat type defined with a primitive type such as INTEGER).

Important: Receiver types and methods for this type must be defined in the same module. If the receiver type is not defined in the same module as the method, the compiler produces the error -8426.
Method names and receiver type field names must be different: It is not legal to define a method with the same name as a field of the receiver type:
PUBLIC TYPE Rectangle RECORD
    height, width FLOAT
END RECORD
...
PUBLIC FUNCTION (r Rectangle) width () RETURNS INTEGER
| Method and field names must be different. Type rectangle.Rectangle has field width.
| See error number -8427.
    RETURN r.width
END FUNCTION
The compiler is more strict regarding methods definitions, compared to regular functions. For example, when a method returns values, it must be defined with the RETURNS clause:
PUBLIC TYPE Rectangle RECORD
    height, width FLOAT
END RECORD
...
PUBLIC FUNCTION (r Rectangle) getWidth ()
    RETURN r.width
| A method without return type (RETURNS) can not return values.
| See error number -8431.
END FUNCTION

Invoking a method

To invoke a method, prefix the method with the variable of the type used as receiver for the method:
DEFINE r1, r2 Rectangle
DISPLAY r1.area()
DISPLAY r2.area()

Polymorphism

The same function name can be reused for a different receiver type. Using a different receiver type for the same function name implies another method:
FUNCTION (r Rectangle) area() RETURNS FLOAT
    RETURN r.height * r.width
END FUNCTION

FUNCTION (r Triangle) area() RETURNS FLOAT
    RETURN ( r.height * r.base ) / 2
END FUNCTION

Receiver variable is passed by reference

A method for a type can implicitly modify the receiver (the receiver variable is passed by reference to the method):
FUNCTION (r Rectangle) setDimensions(w FLOAT, h FLOAT)
    CALL myAssert( (w>0 AND h>0), "Invalid dimensions!" ) -- stops program
    LET r.width = w
    LET r.height = h
END FUNCTION

Methods with record parameters

When defining a method taking a structured record type as parameter, you must call the method with a record variable name. The record will be passed as a copy: The original record cannot be modified, unless the INOUT clause is used.
Note: The .* dot star notation (expending the record fields on the stack) is not allowed when calling a method with a record as parameter. This technique is supported with regular functions for backward compatibility.
TYPE t_rec1 RECORD
         f1 INT
     END RECORD

TYPE t_rec2 RECORD
         f1 INT
     END RECORD

FUNCTION (r t_rec1) method1(p t_rec2)
    LET p.f1 = 999
END FUNCTION

FUNCTION (r t_rec1) method2(p t_rec2 INOUT)
    LET p.f1 = 999
END FUNCTION

FUNCTION main()
    DEFINE r1 t_rec1, r2 t_rec2
    CALL r1.method1( r2 )
    DISPLAY r2.f1 -- not modified
    CALL r1.method2( r2 )
    DISPLAY r2.f1 -- 999
    -- CALL r1.method1( r2.* ) -- invalid!
END FUNCTION