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. See Implicit this. in Methods.

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 const. See The Default 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. 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.

Implicit this. in Methods

Within a method, an identifier can implicitly refer to a field or another method. The compiler will implicitly add a this. in such cases.

Example (implicitThis.chpl).

In the below example, within proc R.method(), the identifiers field, parenlessMethod, and parenfulMethod will implicitly refer to this.field, this.parenlessMethod, and this.parenfulMethod.

record R {
  var field: int = 1;
  proc parenlessMethod { return 10; }
  proc parenfulMethod() { return 100; }
}

proc R.method() {
  var x = field + parenlessMethod + parenfulMethod();
  // the above behaves the same as the following:
  // var x = this.field + this.parenlessMethod + this.parenfulMethod();
  writeln(x);
}

When considering what an identifier might refer to in a method, the compiler will consider scopes and parent scopes in turn and choose the closest applicable match. During this process, it will consider fields and methods available from the receiver type’s definition point just after considering a method scope. This process does not apply to parenful method calls; instead those are handled through overload resolution (see Determining Most Specific Functions).

Example (shadowingAndImplicitThis.chpl).

In the below example, within proc R.method(), the identifiers a, b, and c could all refer to a field or to a variable. In the example, the variables a and b are considered closer than the fields, but the variable c is considered further away.

record R {
  var a: int = 100;
  var b: int = 10;
  var c: int = 1;
}

var c: int = 2;

proc R.method(b=20) {
  var a = 200;

  var x = a;
  // 'a' here refers to the local variable 'a', because the lookup
  // process considers the method body before considering
  // fields and methods.

  var y = b;
  // 'b' here refers to the formal argument 'b', because the
  // lookup process considers formal arguments before considering
  // fields and methods.

  var z = c;
  // 'c' here refers to 'this.c', because the lookup process
  // considers fields and methods just after reaching the method
  // declaration. Since a match is found with the field, it is used
  // before the 'var c' declared outside this method is considered.

  writeln(x+y+z);
}

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.

It is a redeclaration error to define a method without parentheses with the same name as a field.