Forwarding Methods Calls¶
Chapel 1.15 includes a preview version of a method forwarding feature.
This feature allows a record
or class
to specify that certain
method calls will be forwarded to a particular expression. The most
typical use case is to forward methods to a particular field.
Why Forwarding?¶
While Chapel supports inheritance between classes as a way to reuse method implementations, inheritance is not always appropriate. In particular, inheritance has the drawback that it affects the types of the classes and therefore changes what users can do with those classes. Due to some of these drawbacks, there is a well-known design pattern composition over inheritance.
The basic strategy for using composition instead of inheritance is to declare a member field that stores the type that would be inherited from. Then, declare methods that forward to the member field. This can allow the implementation to be more flexible and re-usable. In particular, changing how many classes participate in the implementation of a particular type need not change its public interface or impact the users of that type.
Besides these issues, composition is a more general strategy in Chapel due to the following language design decisions:
records don’t support inheritance.
multiple inheritance for classes is not supported.
inheriting a record from a class or a class from a record is not supported.
Composition can help in each of these cases where inheritance cannot apply.
Example of Forwarding¶
Suppose, for example, MyCircleImpl
is a Chapel class, but the author
of that type wishes to present it as a record (say, MyCircle
). Such
a division allows the type author to control what happens when the type
is copied and to manage the deletion of the class instances.
Consider the following example:
class MyCircleImpl {
var radius:real;
proc area() {
return pi*radius*radius;
}
proc circumference() {
return 2.0*pi*radius;
}
}
record MyCircle {
var impl: MyCircleImpl;
// forwarding methods
proc area() {
return impl.area();
}
proc circumference() {
return impl.circumference();
}
}
Since writing these forwarding methods is repetitive and boring, it’s a
good candidate for automation. The Chapel language allows automating the
creation of these forwarding methods by using the forwarding keyword.
For example, the following is equivalent to the previous MyCircle
record.
record MyCircle {
forwarding var impl: MyCircleImpl;
}
The forwarding
keyword instructs the compiler to forward method
invocations on a MyCircle
that would otherwise not resolve to
the field impl
.
Using forwarding
¶
Besides the forwarding var
style, the forwarding
syntax
can be applied to forward to a particular expression. For example,
one might wish to generate a different error in the event that impl
is nil
:
record MyCircle {
var impl: MyCircleImpl;
proc getImplOrFail() {
if impl == nil then
halt("impl is nil");
else
return impl;
}
forwarding getImplOrFail();
}
var empty = new MyCircle(nil);
empty.area(); // halts with "impl is nil"
Another direction that a user of forwarding
might go is that they
might decide to only forward certain methods. For example, the following
are equivalent ways to forward only the area()
method, assuming that
the only methods of MyCircleImpl
are area()
and
circumference()
.
record MyCircle {
var impl: MyCircleImpl;
forwarding impl only area;
}
record MyCircle {
var impl: MyCircleImpl;
forwarding impl except circumference;
}
As with use
, forwarding
supports comma-separated only
and
except
lists.
Additionally, note that multiple forwarding
declarations can be
specified.
Resolving Forwarded Methods¶
Forwarded methods resolve only when regular methods on a particular type
do not resolve. For example, in the above cases, if MyCircle
declared
a area()
method that could be called with no arguments, that method
would be called in preference to the forwarded method on MyCircleImpl
that is available. Other than that, the forwarded methods participate
normally in the function resolution process. As with other ambiguous
function declarations, ambiguity errors will be reported at the call
site.
Note that special IO methods such as writeThis
and readThis
are not
forwarded since the compiler generates these for the type by default.
Similarly, a field accessor will not get forwarded if the class defines a
conflicting field or method name. Consider the example below:
record ArrayWrapper {
var array: [1..0] int;
proc shape {
return 1;
}
forwarding array;
}
The shape
method above will resolve, meaning array.shape
will not be
forwarded.