dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.21k stars 1.57k forks source link

Constructor inheritance #9468

Open DartBot opened 11 years ago

DartBot commented 11 years ago

This issue was originally filed by efesak...@gmail.com


Hi,

i am coming from PHP world and i am curious why developers choose the way not adding constructor (with arg) to inheritance. From my view it violates DRY principle by repeating code. I did little research - PHP, Ruby, Python inherits constructor. Java, C#, C++ not. C++0x has new feature - method explicitly defining constructor inherition.

Is there any reason or advantage for programmer not having constructor inherited?

(i am not experienced dart nor java programmer, but this makes me no sense in modern language)

lrhn commented 11 years ago

Removed Type-Defect label. Added Type-Enhancement, Area-Language, Triaged labels.

lrhn commented 11 years ago

This could work: If you extend a class and specify no constructors, you currently get a default constructor "myClass(): super();". You could instead get default constructors forwarding to all of the superclass' generative constructors. The moment you declare your own constructor, you inherit nothing, as usual. This matches what is currently specified for mixins anyway. If you do

class C {
  C.foo();
}
class D extends C {} // no constructor inherited, and it's an error.
class E {}
class F = C with E;  // inherited constructor!

It would be consistent to make the constructors inherit everywhere. It should not be a breaking change (but maybe an inconvenient one), since any class that currently gets a default constructor added will have a superclass with a unnamed zero-argument constructor, and so it will still get the same constructor. It might inherit more constructors too, which is the inconvenient part.

DartBot commented 11 years ago

This comment was originally written by ras...@mindplay.dk


For the record, I also have a long history with PHP, as well as many other languages.

From my perspective, explicit constructors do not violate the DRY principle, because you're not simply repeating yourself.

The constructor in a base-class defines the needs of the base-class. A similar constructor in a sub-class defines the needs of the sub-class, which aren't automatically the same as those of the base-class - to assume so, is to assume that the author of the base-class can predict the future, which may be the case in a closed system, but in the case of open systems (such as frameworks) you can't make those assumptions.

I wrote a bit of a dissertation on the same subject on stackoverflow:

http://stackoverflow.com/questions/15665354/why-dart-does-not-inherit-constructor/17472936#­17472936

gbracha commented 10 years ago

Much could be said about the disadvantages of constructors, and in fact I have:

http://gbracha.blogspot.com/2007/06/constructors-considered-harmful.html

Dart constructors solve some but not all of the issues.

Constructors must deal with the task of initializing the instance, and thus are specialized in subclasses. In general, it is not valid to inherit a constructor, as it will not properly initialize the subclass. So you should not have constructor inheritance by default. In languages where class methods act as constructors (like Ruby) you get inheritance by default, and it is often a problem because you can instantiate a class and not initialize it probably.

 In those cases where the a subclass requires no further initialization, and no change to the incoming parameters, it would be convenient to not have to introduce special constructors. In some cases, this actually works (if you only use default constructors). Those situations where you really have to define fairly pointless constructors to forward the constructor calls to the superclass are the minority. For those cases, one could imagine a sugar saying you want to inherit the constructors from the superclass. You'd have to state that, and it would only be worthwhile if it were substantially shorter than the new constructors.

It seems an exceeding amount of sugar to me, and I would not expect it any time soon.


Set owner to @gbracha. Added Accepted label.

crtl commented 4 years ago

@gbracha

Constructors must deal with the task of initializing the instance, and thus are specialized in subclasses. In general, it is not valid to inherit a constructor, as it will not properly initialize the subclass.

The parent does not know about the child but wise versa. Therefore the child is also responsible for initializing itself and the parent correctly. The parent constructor works in itself and describes its parameters in its signature. If the child fails to pass the required arguments its the childs fault. Constructors are specialized in nothing but the class that they belong to. What is the opposite of a "specialized" constructor? A "generalized" constructor? This doesnt make sense.

Those situations where you really have to define fairly pointless constructors to forward the constructor calls to the superclass are the minority.

No they are not. Basically you have to to this in every class you extend which accepts constructor arguments. I had multiple occurences where I did not want to use inhertance because I have to copy all the parameter lists for constructors just to override a method.

For those cases, one could imagine a sugar saying you want to inherit the constructors from the superclass. You'd have to state that, and it would only be worthwhile if it were substantially shorter than the new constructors.

Why do I dont want to inherit the constructor? The constructor is an required integral piece of logic tied to the parent class. Just extending the parent doesnt remove the need for it and if it is not needed anymore you override it.

Here is a usecase:

class ListBloc<T> {

  final ListRepositoryInterface repository;

  compareItems(T element) => element == item;

  ListBloc(@required this.repository);
}

class EntityListBloc extends ListBloc<Entity> {

  /// Here if have to copy/paste the complete constructor for no reason because if not I cant pass [repository]
  EntityListBloc(@required ListRepositoryInterface repository): super(repository);

  compareItem(Entity element) => this.item.id == element.id;
}

I also have PHP experience so heres an working example:


class Superclass {
  protected $prop;

  public function __construct($prop) {
    $this->prop = $prop;
  }

  public function printProp() {
    echo $prop;
  }
}

/**
 * Override method, constructor is inherited
 */
class FirstChild extends Superclass {
  printProp() {
    echo "Hello: $prop";
  }
}

/**
 * Override constructor
 */
class SecondChild extends Superclass {
  public function __construct($prop) {
    // Do stuff
   parent::__construct($prop);
  }
}

$super = new Superclass("World");
$child1 = new FirstChild("World");
$child2 = new SecondChild("World");

$super->printProp(); // World
$child1->printProp(); // Hello: World
$child2->printProp(); // World

I cannot really understand the disadvantages of constructor inheritance. Also it does not add any BC issues.

EDIT: Im currently working on an event bus and this example show perfectly how constructor inheritance can improve code quality. Given an Entity which can be created, viewed and updated we need diffrent types of events. The generic type system allows us to use Type as an event identifier. Now when we create our events we have to duplicate all constructors:

abstract class EntityEvent {
  final Entity entity;

  EntityEvent(this.entity);
}

class EntityCreatedEvent extends EntityEvent {
  EntityCreatedEvent(Entity entity): super(entity);
}

class EntityViewedEvent extends EntityEvent {
  EntityViewedEvent(Entity entity): super(entity);
}

class EntityUpdatedEvent extends EntityEvent {
  EntityUpdatedEvent(Entity entity): super(entity);
}

This could be written like the following:

abstract class EntityEvent {
  final Entity entity;

  EntityEvent(this.entity);
}

class EntityCreatedEvent extends EntityEvent { }

class EntityViewedEvent extends EntityEvent { }

class EntityUpdatedEvent extends EntityEvent { }
idkq commented 3 years ago

I have to copy all the parameter lists for constructors just to override a method.

Agree. This is utterly insane. There should be a way to override constructors (at least) and inherit the list of parameters. If you are extending a class, by default it implies that you will be reusing those same params.

esDotDev commented 2 years ago

Just as an example, working in a tween lib right now, and every single sub-class needs this boilerplate:

class Move extends Tween<Offset> {
   const Move({required Offset from, required Offset to, Curve? curve})
        : super(from: from, to: to, curve: curve);

Which ends up being about 50% of the body of the class.

Where base class should really be handling it already for most tweens w/

abstract class Tween<T> {
  const Tween({required this.from, required this.to, this.curve});
  final T from;
  final T to;
  final Curve? curve;

It would be really nice if we could optionally change some defaults, but still inherit the overall signature somehow, as that is a common use case.

Another issue with duplicating params, is you often have to duplicate the default values. This is definitely not dry.

A good use case for this is MinSizeRow/MinSizeCol in flutter. It would be a very useful class, it only changes 1 default param value. but creating the class is like 20 lines of boilerplate, and (worse) we have to duplicate the other default params from Row, when we really just want to inherit them.

Instead of this:

class MinSizeRow extends Row {
  MinSizeRow({
    Key? key,
    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
    MainAxisSize mainAxisSize = MainAxisSize.min, //           <!-----   only change we want to make
    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
    TextDirection? textDirection,
    VerticalDirection verticalDirection = VerticalDirection.down,
    TextBaseline? textBaseline,
    List<Widget> children = const <Widget>[],
  }) : super(
          children: children,
          key: key,
          mainAxisAlignment: mainAxisAlignment,
          mainAxisSize: mainAxisSize,
          crossAxisAlignment: crossAxisAlignment,
          textDirection: textDirection,
          verticalDirection: verticalDirection,
          textBaseline: textBaseline,
        );
}

Would be really nice if we could instead write something closer to:

class MinSizeRow extends Row {
  MinSizeRow(inherited) : super(this.mainAxisSize = MainAxisSize.min);
}
eernstg commented 2 years ago

There is a new language feature coming up (probably 2.17, no promises) known as super parameters, and it will allow you to do it in the following way:

class MinSizeRow extends Row {
  MinSizeRow({
    super.key,
    super.mainAxisAlignment,
    super.mainAxisSize = MainAxisSize.min, // Default value can be included, useful if different from super.
    super.crossAxisAlignment,
    super.textDirection,
    super.verticalDirection,
    super.textBaseline,
    super.children,
  });
}

It isn't entirely as concise as inherited, but it does remove a lot of boilerplate.

esDotDev commented 2 years ago

Nice, ty!

crtl commented 2 years ago

There is a new language feature coming up (probably 2.17, no promises) known as super parameters, and it will allow you to do it in the following way:

class MinSizeRow extends Row {
  MinSizeRow({
    super.key,
    super.mainAxisAlignment,
    super.mainAxisSize = MainAxisSize.min, // Default value can be included, useful if different from super.
    super.crossAxisAlignment,
    super.textDirection,
    super.verticalDirection,
    super.textBaseline,
    super.children,
  });
}

It isn't entirely as concise as inherited, but it does remove a lot of boilerplate.

Seems like a neat workaround but still requires to copy the constructor.

FMorschel commented 5 months ago

Could this be considered with the new macros? I'm not entirely familiar with them, but if I understand them enough this is possibly true.

eernstg commented 5 months ago

Here's an update. The first example from this comment could be expressed as follows today:

class ListBloc<T> {
  final ListRepositoryInterface repository;
  ListBloc(this.repository);
  T get item => throw 0; // Was undefined, let's just throw.
  compareItem(T element) => element == item;
}

class EntityListBloc extends ListBloc<Entity> {
  EntityListBloc(super.repository);
  compareItem(Entity element) => this.item.id == element.id;
}

This is already better, mainly because of the super parameters feature.

We are very likely to add another relevant feature to Dart, namely primary constructors. They would help, too:

// Using primary constructors, not yet in the language.

class ListBloc<T>(final ListRepositoryInterface repository) {
  T get item => throw 0;
  compareItem(T element) => element == item;
}

class EntityListBloc(super.repository) extends ListBloc<Entity> {
  compareItem(Entity element) => this.item.id == element.id;
}

The second example gets better, too:

// Using primary constructors.

abstract class EntityEvent {
  final Entity entity;
  EntityEvent(this.entity);
}

class EntityCreatedEvent(super.entity) extends EntityEvent;
class EntityViewedEvent(super.entity) extends EntityEvent;
class EntityUpdatedEvent(super.entity) extends EntityEvent;

These changes are all about making constructor declarations more concise, they don't allow us to omit the subclass constructor declarations entirely. However, we can do that using a mixin application class (that is, the kind of class declaration that uses =):

mixin _EntityListBlocMixin on ListBloc<Entity> {
  compareItem(Entity element) => this.item.id == element.id;
}

class EntityListBloc = ListBloc<Entity> with _EntityListBlocMixin;

The point is that a mixin application class will have an implicitly induced generative constructor with the same name and formal parameter list as each generative constructor in the superclass (that is, ListBloc<Entity>), and each of these implicitly induced constructors will just invoke the corresponding constructor in the superclass.

This idiom will work in the examples mentioned in this issue, and it could be useful in the cases where there are many constructors with complex formal parameter lists.

It will not work if the increment from ListBloc<Entity> to EntityListBloc cannot be expressed as a mixin. In particular, you can't add a new instance variable and have it initialized using an extra argument to some constructors. However, that's exactly the case where you can't "inherit" the constructors anyway, so that shouldn't be much of a problem in practice.

That said, it should certainly also be possible to implement a macro which will create forwarding constructor declarations automatically, and this approach could be much more flexible and expressive than the mixin application class idiom because the macro implementation can introspect on the superclass and generate whatever we want.