Methods

A method is a procedure or iterator that is associated with an expression known as the receiver.

Methods are declared with the following syntax:

method-declaration-statement:
  procedure-kind[OPT] proc-or-iter this-intent[OPT] type-binding[OPT] identifier argument-list[OPT]
    return-intent[OPT] return-type[OPT] where-clause[OPT] function-body

proc-or-iter:
  'proc'
  'iter'

this-intent:
  'param'
  'type'
  'ref'
  'const ref'
  'const'

type-binding:
  identifier .
  '(' expression ')' .

Methods defined within the lexical scope of a class, record, or union are referred to as primary methods. For such methods, the type-binding is omitted and is taken to be the innermost class, record, or union in which the method is defined.

Methods defined outside of the lexical scope of a class, record, or union, but within the scope where the type itself is defined are known as secondary methods. Methods defined outside of both the lexical scope and the scope where the type itself is defined are known as tertiary methods. Both secondary and tertiary methods must have a type-binding (otherwise, they would simply be standalone functions rather than methods). Note that secondary and tertiary methods can be defined not only for classes, records, and unions, but also for any other type (e.g., integers, reals, strings).

Secondary and tertiary methods can be declared with a type expression instead of a type identifier. In particular, if the type-binding is a parenthesized expression, the compiler will evaluate that expression to find the receiver type for the method. In that case, the method applies only to receivers of that type. See also Creating General and Specialized Versions of a Function.

Method calls are described in Method Calls.

The use of this-intent is described in The Method Receiver and the this Argument.

Method Calls

A method is invoked with a method call, which is similar to a non-method call expression, but it can include a receiver clause. The receiver clause syntactically identifies a single argument by putting it before the method name. That argument is the method receiver. When calling a method from another method, or from within a class or record declaration, the receiver clause can be omitted.

method-call-expression:
  receiver-clause[OPT] expression ( named-expression-list )
  receiver-clause[OPT] expression [ named-expression-list ]
  receiver-clause[OPT] parenthesesless-function-identifier

The receiver-clause (or its absence) specifies the method’s receiver The Method Receiver and the this Argument.

Example (defineMethod.chpl).

A method to output information about an instance of the Actor class can be defined as follows:

proc Actor.print() {
  writeln("Actor ", name, " is ", age, " years old");
}

This method can be called on an instance of the Actor class, anActor, with the call expression anActor.print().

The actual arguments supplied in the method call are bound to the formal arguments in the method declaration following the rules specified for procedures (Procedures). The exception is the receiver The Method Receiver and the this Argument.

A primary or secondary method is always visible when the receiver is of the type to which the method is bound or of a subtype of such type. Tertiary methods are only visible if the module which defines them has been imported or used in such a way that allows these methods to be called (see Using Modules and Importing Modules).

Note

Future work: the semantics of privacy specifiers for methods are still under discussion.

The Method Receiver and the this Argument

A method’s receiver is an implicit formal argument named this representing the expression on which the method is invoked. The receiver’s actual argument is specified by the receiver-clause of a method-call-expression as specified in Method Calls.

Example (implicitThis.chpl).

Let class C, method foo, and function bar be defined as

class C {
  proc foo() {
    bar(this);
  }
}
proc bar(c: C) { writeln(c); }

Then given an instance of C called c1, the method call c1.foo() results in a call to bar where the argument is c1. Within primary method C.foo(), the (implicit) receiver formal has static type borrowed C and is referred to as this.

Methods whose receivers are objects are called instance methods. Methods may also be defined to have type receivers—these are known as type methods.

Methods on a class C generally use a this type of borrowed C but this will be more generic in some cases. See Class Methods.

The optional this-intent is used to specify type methods, to constrain a receiver argument to be a param, or to specify how the receiver argument should be passed to the method.

When no this-intent is used, a default this intent applies. For methods on classes and other primitive types, the default this intent is the same as the default intent for that type. For record methods, the intent for the receiver formal argument is ref or const ref, depending on whether the formal argument is modified inside of the method. Programmers wishing to be explicit about whether or not record methods modify the receiver can explicitly use the ref or const ref this-intent.

A method whose this-intent is type defines a type method. It can only be called on the type itself rather than on an instance of the type. When this-intent is param, it specifies that the function can only be applied to param objects of the given type binding.

Example (paramTypeThisIntent.chpl).

In the following code, the isOdd method is defined with a this-intent of param, permitting it to be called on params only. The size method is defined with a this-intent of type, requiring it to be called on the int type itself, not on integer values.

proc param int.isOdd() param {
  return this & 0x1 == 0x1;
}

proc type int.size() param {
  return 64;
}

param three = 3;
var seven = 7;

writeln(42.isOdd());          // prints false
writeln(three.isOdd());       // prints true
writeln((42+three).isOdd());  // prints true
// writeln(seven.isOdd());    // illegal since 'seven' is not a param

writeln(int.size());          // prints 64
// writeln(42.size());        // illegal since 'size()' is a type method

Type methods can also be iterators.

Example (typeMethodIter.chpl).

In the following code, the class C defines a type method iterator which can be invoked on the type itself:

class C {
  var x: int;
  var y: string;

  iter type myIter() {
    yield 3;
    yield 5;
    yield 7;
    yield 11;
  }
}

for i in C.myIter() do
  writeln(i);

When this-intent is ref, the receiver argument will be passed by reference, allowing modifications to this. If this-intent is const ref, the receiver argument is passed by reference but it cannot be modified inside the method. The this-intent can also describe an abstract intent as follows. If it is const, the receiver argument will be passed with const intent. If it is left out entirely, the receiver will be passed with a default intent. For records, that default intent is ref if this is modified within the function and const ref otherwise. For other types, the default this intent matches the default argument intent described in The Default Intent.

Example (refThisIntent.chpl).

In the following code, the doubleMe function is defined with a this-intent of ref, allowing variables of type int to double themselves.

proc ref int.doubleMe() { this *= 2; }

Given a variable x = 2, a call to x.doubleMe() will set x to 4.

The this Method

A procedure method declared with the name this allows the receiver to be “indexed” similarly to how an array is indexed. Indexing (as with A[1]) has the semantics of calling a method named this. There is no other way to call a method called this. The this method must be declared with parentheses even if the argument list is empty.

Example (thisMethod.chpl).

In the following code, the this method is used to create a class that acts like a simple array that contains three integers indexed by 1, 2, and 3.

class ThreeArray {
  var x1, x2, x3: int;
  proc this(i: int) ref {
    select i {
      when 1 do return x1;
      when 2 do return x2;
      when 3 do return x3;
    }
    halt("ThreeArray index out of bounds: ", i);
  }
}

The these Method

An iterator method declared with the name these allows the receiver to be “iterated over” similarly to how a domain or array supports iteration. When a value supporting a these method is used as the iteratable-expression of a loop, the loop proceeds in a manner controlled by the these iterator.

Example (theseIterator.chpl).

In the following code, the these method is used to create a class that acts like a simple array that can be iterated over and contains three integers.

class ThreeArray {
  var x1, x2, x3: int;
  iter these() ref {
    yield x1;
    yield x2;
    yield x3;
  }
}

An iterator type method with the name these supports iteration over the class type itself.

Example (typeMethodIterThese.chpl).

In the following code, the class C defines a type method iterator named these, supporting direct iteration over the type:

class C {
  var x: int;
  var y: string;

  iter type these() {
    yield 1;
    yield 2;
    yield 4;
    yield 8;
  }
}

for i in C do
  writeln(i);

Methods without parentheses

Similarly to Functions without Parentheses, it is possible to create methods that do not have parentheses. Such methods look similar to field access when they are called. As a result, a method without parentheses can be used to replace a field that was removed or renamed while providing the same interface as the field accessor.

Example (parenlessMethod.chpl).

For example, the following code shows a record myType that has a field x. It also shows some code that operates on the field.

record myType {
  var x: int;
}

var v: myType;
writeln(v.x);

Now, suppose that as myType evolves, it is adjusted to compute the value of x without storing it at all. In that case, a method without parentheses can allow the code using myType to function as it did before:

record myType {
  // this parentheses-less function supports
  // a field-access syntax
  proc x : int {
    return 0; // compute ``x`` and return it
  }
}

var v: myType;
writeln(v.x);

One can create a method without parentheses to replace a field or parenless method in a parent class. Such methods require the override keyword (see Overriding Base Class Methods).

Note that class methods without parentheses that return with type or param intent use a generic type for the this argument. See Class Methods for more details.