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 expressionanActor.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
, methodfoo
, and functionbar
be defined asclass C { proc foo() { bar(this); } } proc bar(c: C) { writeln(c); }Then given an instance of
C
calledc1
, the method callc1.foo()
results in a call tobar
where the argument isc1
. Within primary methodC.foo()
, the (implicit) receiver formal has static typeborrowed C
and is referred to asthis
.
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 athis-intent
ofparam
, permitting it to be called on params only. Thesize
method is defined with athis-intent
oftype
, requiring it to be called on theint
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 athis-intent
ofref
, allowing variables of typeint
to double themselves.proc ref int.doubleMe() { this *= 2; }Given a variable
x = 2
, a call tox.doubleMe()
will setx
to4
.
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 identifiersfield
,parenlessMethod
, andparenfulMethod
will implicitly refer tothis.field
,this.parenlessMethod
, andthis.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 identifiersa
,b
, andc
could all refer to a field or to a variable. In the example, the variablesa
andb
are considered closer than the fields, but the variablec
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 namedthese
, 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 fieldx
. 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 ofx
without storing it at all. In that case, a method without parentheses can allow the code usingmyType
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.