Ref on left and right of proc name

The program below,

record tr {
   var u: [1..3] int;
   proc lft(in i: int) ref {
      return u[i];
   }
   proc ref rgt(in i: int) {
      return u[i];
   }
}
var r = new tr([1,2,3]);
var y: int;
writeln("r.u = ",r.u);
y = r.rgt(3);
writeln("after right, y = ",y);
r.lft(2) = 4;
writeln("after left, r.u[2] = ",r.u[2]);

compiles and runs correctly, but with a warning,

lftrgt.chpl:3: warning: inferring a default intent to be 'ref' is deprecated - please use an explicit 'ref' this-intent for the method 'lft'

only for the lft proc. If I insert “ref” before the name lft, ie “proc ref lft(in i: int) ref”, the warning disappears. There is an odd (to me) asymmetry between lft and rgt, because the compiler does not complain about the way rgt is declared. Any clarifications would be welcome.

Thanks

Nelson

Hi Nelson —

There currently is a ref intent in the definition of rgt in your code above, but I’m guessing you may have added that during your experimentation, as removing it doesn’t cause the warning (and is more like what you’re describing). Am I guessing correctly?

In any case, I believe what’s going on here is the following: Since lft is returning a member variable of tr by ref, the implication is that the record’s overall value (its fields) can change as a result of calling lft. The ref intent on this is intended to show that “calling this method could modify the object that you’re calling it on,” and indeed that’s true in this case. In contrast, the rgt method only returns member variables by value / copy, so does not have any chance of modifying the record and therefore the ref intent is not needed.

This is a reasonably new behavior, which is why it only generates a warning today. Historically, we always treated record methods as having ref intents on this.

Let us know if that makes sense or if you have any further questions,

-Brad

Hi Brad: thanks for the reply! I have updated my "test program" to

record tr {
   var u: [1..3] int;
   proc ref lft(in i: int) ref {
      return u[i];
   }
   proc rgt(in i: int): int {
      return u[i];
   }
}
var r = new tr([1,2,3]);
var y: int;
writeln("r.u = ",r.u);
y = r.rgt(3);
writeln("after right, y = ",y);
r.lft(2) = 4;
writeln("after left, r.u[2] = ",r.u[2]);

and it compiles and run without complaints. I have removed "ref" from the definition of rgt per your comment. But I am still a little puzzled by the need to write ref both on the left and on the right side of lft(in i: int). Why are both needed? Removing the left one produces a warning, and I guess that in an upcoming version it will become an error; removing the right one produces a compile error.

Cheers

Nelson

Hi Nelson —

Ah, I understand your question better now. The two refs serve two different purposes, but because the things they’re referring to are syntactically subtle/absent, it can be a bit confusing.

The ref that comes after the argument list says that the result being returned by the procedure should be returned by reference, permitting the caller to modify it. It is this ref that permits the assignment to r.lft(2) in your code to modify r.u directly, whereas r.rgt() would not support similar assignments to it since it uses the default const (copy-out) intent for integers.

If the declaration of lft was expanded to include the return type, it would look like:

   proc ref lft(in i: int) ref : int {

which is intended to mean “we will be returning an integer, and it should be returned by ref.” The syntax in this form is meant to mimic that of a standalone ref declaration, like:

ref xr: int = myInt;  // make 'xr' refer to 'myInt'

but without introducing a new identifier like xr since, in Chapel, the return statement doesn’t have an identifier associated with it. In your case, you’ve relied on type inference to infer the : int return type, but the ref is required since ints are returned by const (copy-out) by default (which again is why r.rgt(2) couldn’t be assigned to similarly).

On the other hand, the ref that precedes the argument list says how the this object receiver upon which the method is being called should be “passed” (or made available) to the method. In your case, the this is the record r upon which you’re calling these methods. By default for methods, this is passed by const, meaning that it can’t be modified. But since lft() returns one of the record’s fields by ref, permitting it to be modified, it’s important that this itself can be modified, since a record’s identity is the values of its fields, and if they’re modified, so is the record. This is the reason that the ref on the left-hand side is needed.

As mentioned in my previous response, this is a recent change, as this used to default to ref for records until fairly recently. And that’s also why this is merely a warning rather than an error (like “method that returns field values by ref must have a ref this intent”).

Syntactically, the placement of this ref is positioned where the this receiver would be placed in a call, which is to say, right before the argument list. Like the return value, since the this identifier doesn’t show up explicitly in the method definition, it seems to be floating there, syntactically. When using the secondary method declaration syntax to define a method outside of a record, things are slightly clearer, since the ref appears adjacent to the tr type that represents the this receiver for the method:

proc ref tr.lft2(in i: int) ref { … }

Note that in these examples, I’m not trying to suggest that secondary method declarations or explicit return types are inherently better—that’s a personal style choice. I’m just using them to demonstrate how the refs interact with other nearby syntactic elements.

Hope this helps clarify things, but definitely let us know if it doesn’t!

-Brad

Yes, much clearer now. Many thanks again.