chapel-lang / chapel

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

How should compiler-generated writeThis routines interact with user-defined ones in the presence of inheritance? #7118

Open mppf opened 7 years ago

mppf commented 7 years ago

There are some problems with the current writeThis strategy when combined with class inheritance.

Right now, buildDefaultFunctions constructs a writeThis for any type that doesn't have a writeThis or a readThis method. The trouble is, a parent class might have such a method and buildDefaultFunctions does not notice that.

Consider this example:

class Parent {
  var _parentPrivateField: int;
  var parentField: int;
  // Author of Parent creates a writeThis to hide _parentPrivateField,
  // since maybe it doesn't make sense to output it.
  proc writeThis(f) {
    f <~> "{parentField=" <~> parentField <~> "}";
  }
}
class Child : Parent {
  var childField: int;
}
var c = new Child();
writeln(c);

This program prints out

{_parentPrivateField = 0, parentField = 0, childField = 0}

In this case, the author of Parent wants to hide a field from the I/O. But, more generally, how can the author of Parent write a custom writeThis that has any impact on subclasses?

It seems tempting to have buildDefaultFunctions generate Child.writeThis in a way as to call Parent.writeThis. However, that strategy isn't compatible with the current formatting for classes, namely that the child class, when output, includes a mix of parent and child field names. We could address that by outputting in a different format, like this (when outputing a Child above):

{ super = {parentField = 0}, childField = 0}

(But I am also always wondering if we should include the class type name in such output).

benharsh commented 6 years ago

Would support for private fields help at all, or was that just an example of the general problem?

My initial instinct is to try and keep this simple, and that we should generate the naive writeThis that prints the first example. I feel like the nested nature of {super = { super = { ... } } } isn't human-friendly, but perhaps that's not the point.

mppf commented 6 years ago

It was just an example of a general problem. The point is that the child class shouldn't necessarily know about all of the parent class fields and in fact the parent class should be able to control which of them print.

benharsh commented 6 years ago

The "shouldn't necessarily know about all of the parent class fields" point still feels to me like something that should be controlled by language visibility constructs.

Approaching this from a different angle, it's possible a class could have all private fields and a custom writeThis. If a child class' writeThis ignored private fields, then we'd end up not printing any information about the parent. That doesn't seem like good default behavior.

I guess I'd be more open to the "super = {}" approach if it spanned multiple lines:

Child {
  super : Parent = {parentField = 0}
  childField = 0
}

But that may lead into issues with whitespace alignment and brace styles...

bradcray commented 3 years ago

I think that one of two things should happen here:

1) the presence of a parent class writeThis() routine prevents the compiler from generating a default writeThis() routine on the child class, and if the child class doesn't have an explicit override, it simply invokes the parent class version as with any other inherited method (i.e., if the child class wants to specialize its output beyond what the parent would do, it has to do so).

2) the presence of a parent class writeThis() routine causes the compiler to generate a default writeThis() for the child that invokes the parent class writeThis() before writing out the child-specific fields in something like the usual way.

My intuition would be to do option 1 here because it seems simplest and consistent with how other dynamically dispatched, partially overridden methods behave.

Option 2 might seem "more productive" in some way, but it's also problematic as Michael notes above, due to questions like "where do the curly brackets go?" I also think that there's an argument that if the parent class really wants to specialize its output, it seems unlikely that the child class would not want to as well (i.e., that any compiler-generated default would do something that was more reasonable than surprising).

bradcray commented 3 years ago

PR #16920 adds some futures around this topic.