chapel-lang / chapel

a Productive Parallel Programming Language
https://chapel-lang.org
Other
1.78k stars 419 forks source link

CG: how to specify an interface's parameter(s)? #16966

Open vasslitvinov opened 3 years ago

vasslitvinov commented 3 years ago

main issue: #8629

Which of the following styles do we want to support for an interface declaration?

(implicit) the type Self is implicitly the formal This would be particularly useful if we adopt (only-methods) from #16800 and/or disallow free functions, discussed in #16851

interface IFC {...}  // the type 'Self' is implicitly the formal

(explicit) the formal(s) are listed explicitly

interface IFC(T) {...}  // the type 'T' is the formal

(with-intent) the formal(s) are listed explicitly with type or param intent(s)

interface IFC(type T, param P) {...}  // the type 'T' and the param 'P' are the formals

Related questions:

bradcray commented 3 years ago

I'm not sure I'm sold on Self as the keyword for the implicit self-reference. My intuition goes to this because within the context of an interface it seems pretty obvious it would refer to the type itself (similar to how this within a method refers to the type or value to which the method is being applied). But I recall there being a slight subtlety or potential point of confusion to this choice in past discussions (but don't recall what it was). I'd probably pick self over Self if we stuck with that term. If we pick an identifier other than this for this, should we reserve it as a keyword, similar to how this is?

vasslitvinov commented 3 years ago

We have not, so far, chosen this exactly because of the ambiguity with this inside a method. Especially when an interface contains a default implementation for a required method, we want to be able to refer both to this-the-receiver and this-the-interface formal.

I agree, our identifier of choice should be reserved, similarly to this.

bradcray commented 3 years ago

Especially when an interface contains a default implementation for a required method, we want to be able to refer both to this-the-receiver and this-the-interface formal.

Can you give an example here illustrating this?

vasslitvinov commented 3 years ago

when an interface contains a default implementation for a required method, we want to be able to refer both to this-the-receiver and this-the-interface formal

Here is an example:

interface Ifc(This) {
  proc doit(arg: This);
  proc This.helper() { doit(this); }
}
bradcray commented 3 years ago

Eww, yeah, that is a bit unfortunate. Is it also the case that we can/might want to refer to This within the body of a default implementation? I.e., could one write:

interface Ifc(This) {
  proc This.helper() { writeln(This:string); }
}
vasslitvinov commented 3 years ago

Right, we could potentially. As of now, we have not worked out how to deal with types and params within interface-constrained functions.

Even if we draw the line "function header ==> this means the interface formal, function body ==> this means the receiver" and make it work, that would be too confusing to my eye.

bradcray commented 3 years ago

Even if we draw the line "function header ==> this means the interface formal, function body ==> this means the receiver" and make it work, that would be too confusing to my eye.

That's exactly why I was asking. Though I agree it's subtle, if we didn't think such patterns would be written very often, I would consider it to avoid having to introduce a new keyword for this single purpose.

bradcray commented 3 years ago

Just as a starting sanity-check, when you wrote:

interface Ifc(This) {
  proc doit(arg: This);
  proc This.helper() { doit(this); }
}

did you mean this:

interface Ifc(type This) {
  proc doit(arg: This);
  proc This.helper() { doit(this); }
}

(maybe the type is optional? I'm not wild about that...), and is it correct that that's equivalent to this today:

interface Ifc {
  proc doit(arg: Self);
  proc Self.helper() { doit(this); }
}

Follow-up question: Within the body of an interface's method on Self/This, am I correct that this.type == This/Self? That is, if we didn't have separate identifiers for this and This/Self would we still be able to refer to both things within the body of the function?

Brainstorming other ways to express the type of a single-type interface, what about:

interface T?.Ifc {   // query the type to which the interface is applied rather than relying on an implicit type
  proc doit(arg: T);
  proc T.helper() { doit(this); }
}

I'm feeling more allergic to the notion of having this in an interface function's argument list refer to the interface's type because it seems like we probably ought to permit formal argument lists in normal methods to refer to this like they can refer to other preceding arguments. That is, if I can write proc foo(x, y: x.type) { ... }, it seems like I should probably be able to write proc R.foo(x, this.eltType) { ... } where this refers to the R value on which the method was called, essentially... And if I can do it on a method, I'd want to be able to do it on an interface function as well.

That said, I could imagine writing your example as:

interface Ifc {
  proc doit(arg: this.type);  // doit is not a method, so `this` refers to Ifc's type
  proc this.helper() { doit(this); }  // this is a method on type `this`, so the first `this` refers to Ifc's type; the second refers to the value, like it always does within a method
  proc type this.helper() { doit(new this()); }  // this is a type method on type `this`, so the first `this` refers to Ifc's type again; and so does the second, since it's a type method
}

Is this subtle and potentially confusing? Yes. Does it make consistent sense with other aspects of the language? I'd argue also yes. Is it a preferred form? Probably not, in which case we'd suggest the user rewrite it using an explicit type argument:

interface Ifc(type T) {
  proc doit(arg: T);
  proc T.helper() { doit(this); }
  proc type T.helper() { doit(new T()); }
}

or the query format proposed above, if considered sufficiently attractive/clear:

interface T?.Ifc {
  proc doit(arg: T);
  proc T.helper() { doit(this); }
  proc type T.helper() { doit(new T()); }
}