Dart 2.0 suggestion: Remove all the constructors from Dart

722 views
Skip to first unread message

Kasper Peulen

unread,
Aug 18, 2016, 7:17:45 AM8/18/16
to mi...@dartlang.org
Okay, this may sound a bit outrageous hehe. It is probably way too much of change for Dart 2.0, but I find this idea in general interesting for a language and I would like to discuss it. 


I think I also heard Gilad Brach say something that the constructors in Dart are a mess, and I agree. 
I think there is just too much ways to do essentially the same thing, and I think we can do much better.

So I think instead of having named constructor, factory constructors, initializer lists, etc. I think you should all remove it from the language. And there should be just one way to create a new instance of an object, and that is automatic determined from the public properties of the object.

So say if I have the class 

class State {
 
final String value;
 
final int length;
 
bool show;
}

Now the way to create an instance is simply:

var state = new State(value:"bla", length: 10, show: true);

If you want default values, just write:

class State {
 
final String value = "default";
 
final int length = 3;
 
bool show = false;
}

var state = new State(value:"bla");

So a final field, is only really final after the object is initialized, may sound a bit strange, but I think it is the most sensible way of doing it, and makes sure there is still just one good way of doing this.

And yes, every parameter is named, because it is good to explicitly state which field you are setting.

So how do you say if a field could be null, or must be initialized. Well, I propose the exact same as I do here:

NNBD would really help there, otherwise, you could do:

class State {
  
@required String value;
  @required int length;
  bool show;
}

But I would prefer instead:

class State {
 
String value;
 
int length;
 
bool? show;
}

Now if you want to do some calculation, side effects, error checking or whatever before you initialize an object, I propose that you use just a function for that. So there is just one way to `new` an instance. You can not new it with a factory, because that just don't make sense. However, you can implement this function of course as a static method of the class. And I propose to have some syntax to write a default static method of a class. For example:


class
 Point {
  
final num x;
  
final num y;
  
final num distanceFromOrigin;
  
  
static default(num x, num y) {
    
return new Point(
      x: x, 
      y: y, 
      distanceFromOrigin: sqrt(pow(x, 2) + pow(y, 2))
         )
  }
}

So you call this static method like this:

var point = Point(2, 3);

Now all the fields are always the things that you can set while creating a new object. So it is just the input for the `new MyObject` function. If it is not final, you can also change it later, or set the value later. 

But what if you want a field, that you can not set in the initializer? Well, you can make it private, and optionally create a getter for it.

I'm sure I have not thought about all the edge cases. Actually, I didn't really think how this would work with inheritance. 
But I do think that Dart's constructor syntax is just too messy, and too much ways to do the same thing. Just putting in some ideas.

Lasse R.H. Nielsen

unread,
Aug 18, 2016, 8:57:16 AM8/18/16
to mi...@dartlang.org
Factory constructors are generally useless (apart from const factory constructors which are just forwarding constructors, not really "factory" ones, and the fact that you can supply type parameters, but when we get generic methods, that isn't a distinguishing feature any more).

Generative constructors have two purposes: Initializing new instance state and initializing an inherited superclass state.

That means that the constructor is API that clients of the class uses. 
If you decide to change the internal representation of a class, say storing multiple bool fields in a single integer, or changing a field to a getter, having a constructor that is directly linked to actual state means that you change your API.
The constructors that we have now are abstractions that encapsulate the actual state. You can change the object state without changing the API exactly because the constructor isn't linked directly to the state.

That's not really a problem, we can just make the "physical constructor" you propose be private and then you just expose factory functions returning new instances as the API, using the real constructor internally.

That hits the other usage - calling the superclass generative constructor.
You can't use plain functions for that (at least not the way most OO languages are wired up, but that's another story). So, you need to expose a real generative constructor to the subclass, and you still need to keep that API constant when you refactor the class. You need the generative constructor *and* you need the abstraction layer, which is how we got to where we are now.

Now, I'm personally not particularly happy about generative constructors being unchangeable API. You can't change a generative constructor to a factory constructor if someone is subclassing your class - and that was really the reason for having factory constructors to begin with.
I don't think we'll change that though. The alternatives are a little too weird for a mainstream language :)

/L

On Thu, Aug 18, 2016 at 1:17 PM, Kasper Peulen <kasper...@gmail.com> wrote:
Okay, this may sound a bit outrageous hehe. It is probably way too much of change for Dart 2.0, but I find this idea in general interesting for a language and I would like to discuss it. 


I think I also heard Gilad Brach say something that the constructors in Dart are a mess, and I agree. 
I think there is just too much ways to do essentially the same way, and I think we can do much better.

So I think instead of having named constructor, factory constructors, initializer lists, etc. I think you should all remove it from the language. And there should be just one way to create a new instance of an object, and that is automatic determined from the public properties of the object.

So say if I have the class 

class State {
 
final String value;
 
final int length;
 
bool show;
}

Now the way to create an instance is simply:

var state = new State(value:"bla", length: 10, show: true);

If you want default values, just write:

class State {
 
final String value = "default";
 
final int length = 3;
 
bool show = false;
}

var state = new State(value:"bla");

So a final field, is only really final after the object is initialized, may sound a bit strange, but I think it is the most sensible way of doing it, and makes sure there is still just one good way of doing this.

And yes, every parameter is named, because it is good to explicitly state which field you are setting.

So how do you say if a field could be null, or must be initialized. Well, I propose the exact same as I do here:

NNBD would really help there, otherwise, you could do:

class State {
 
String value;
 
int length;
 
@required bool show;
}

But I would prefer instead:

class State {
 
String value;
 
int length;
 
bool? show;
}

Now if you want to do some calculation, side effects, error checking or whatever before you initialize an object, I propose that you use just a function for that. So there is just one way to `new` an instance. You can not new it with a factory, because that just don't make sense. However, you can implement this function of course as a static method of the class. And I propose to have some syntax to write a default static method of a class. For example:

class Point {
 
final num x;
 
final num y;
 
final num distanceFromOrigin;
 
 
static default(num x, num y) {
   
return new Point(
 x: x, y: y, distanceFromOrigin: sqrt(pow(x,2) + pow(y, 2))
  }
}


class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;


  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(pow(x, 2) + pow(y, 2));

}

--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.



--
Lasse R.H. Nielsen - l...@google.com  
'Faith without judgement merely degrades the spirit divine'
Google Denmark ApS - Frederiksborggade 20B, 1 sal - 1360 København K - Denmark - CVR nr. 28 86 69 84

Kasper Peulen

unread,
Aug 18, 2016, 10:39:40 AM8/18/16
to Dart Misc
Not sure if I understand your point precisely. I'm taking an example, from Emil's article.

This is how I would picture inheritance:

class Super {
  A
a;
  B b;
 
static default(String input) {
 
  A a = // calculation
    B b
= // calculation
    return new Super(a: a, b: b);
 
}
  method
() {
   
return a.doSomething(b);
 
}
}

class Sub extends Super {
  C c
;
  D d
;
 
static default(A a, B b, C c, D d) {
   
return new Sub(a: a, b: b, c: c, d: d);
 
}
}

So now you say, you loose the object state you would get by calling the default static method? I guess that is a point.

You can maybe do this:

class Sub extends Super {
  C c
;
  D d
;
 
static default(String input, C c, D d) {
   
var sup = Super(input);
   
return new Sub(a: sup.a, b: sup.b, c: c, d: d);
  }
}

where Super() is the default static method. I guess this can become very painfull though, in super large classes.

Emil Persson

unread,
Aug 18, 2016, 11:00:23 AM8/18/16
to Dart Misc
As the author of that Medium-post I should explain.

The main idea is that every class has a list of fields (you could easily think of methods like fields that happen to have function types). If you want to make a new instance of a class, you have to provide all the fields that are not already implemented. That is generally all we do in our constructors.

class A {
 
final B b;
  A
(this.b);
}

When we extend a class, we not only have to initialize our own fields, but our superclass':

class C extends A {
  D d
;
  C
(this.d, B b) : super(b);
}

Consider a world without constructors, where abstract fields dictate how we initialize the class. I'm going to indicate them with an abstract keyword, to make it more clear.

class A {
 
abstract B b;
}

class C extends A {
 
abstract D d;
}

To initialize A, we need to supply the implementation of the abstract field:

new A(b: ...);

To initialize C, we need to supply the implementations of both the abstract fields of A and C:

new C(b: ..., d: ...);

If a subclass doesn't want to "forward" the abstract class, but instead provide its own implementation, it can do that:

class C extends A {
 
@override B b = ...;
  D d
;
}
new C(d: ...);

With constructors, a subclass can use its constructor to modulate arguments before sending them to the super constructor:

class C extends A {
  D d
;
  C
(this.d, String somethingElse) :
   
super(bFromSomethingElse(somethingElse));
}

This is what my post considered to be "harmful", since it's the source of many issues with multiple inheritance, like the diamond problem.

Without constructors, the top of the diamond never receives two instances of a dependency, because it has no constructor that can be called twice. Instead, when initializing the bottom of the diamond, the user must pass along the implementation of the field that the top-class has left abstract.

If one of the subclasses in the diamond provides that implementation itself, you don't need to. If both subclasses implement the same field, then it's a compile time error.

If a base class changes what fields are abstract, that is, in fact, a breaking change to its "constructor". But that is just like adding another generative constructor argument.

Daniel Davidson

unread,
Aug 18, 2016, 11:21:09 AM8/18/16
to Dart Misc


On Thursday, August 18, 2016 at 7:57:16 AM UTC-5, Lasse Reichstein Holst Nielsen wrote:
Factory constructors are generally useless (apart from const factory constructors which are just forwarding constructors, not really "factory" ones, and the fact that you can supply type parameters, but when we get generic methods, that isn't a distinguishing feature any more).


Curious why you say that? This is from Classes: Constructors:

One popular pattern is the Factory Pattern, with examples in many frameworks or toolkits. However, without native language support for the factory pattern, most implementations have to implement it with combinations of static methods and or utility classes. While this works, it exposes the pattern to the consumer of the code. Luckily, the designers of Dart added the factory pattern right into the language. With Dart’s factory constructors, you can natively use the factory pattern and make it appear like a regular constructor.

One great use case for factory constructors is to return objects from a cache. 

Could you elaborate? Is this no longer accurate?

One thing factory constructors seem to aid with is allowing fields to more easily be final. The factory constructor can dynamically create fields to pass to another constructor. For instance, a factory constructor can take fields a and b, and compute a reasonable default for c based on a and b then pass them to a ctor allowing all three to be final. But regular constructors don't seem to allow references to or computation on  previous final fields passed in.

Thanks
Dan

Lasse R.H. Nielsen

unread,
Aug 18, 2016, 12:21:14 PM8/18/16
to mi...@dartlang.org
On Thu, Aug 18, 2016 at 5:21 PM, Daniel Davidson <phyto...@gmail.com> wrote:


On Thursday, August 18, 2016 at 7:57:16 AM UTC-5, Lasse Reichstein Holst Nielsen wrote:
Factory constructors are generally useless (apart from const factory constructors which are just forwarding constructors, not really "factory" ones, and the fact that you can supply type parameters, but when we get generic methods, that isn't a distinguishing feature any more).


Curious why you say that? This is from Classes: Constructors:

One popular pattern is the Factory Pattern, with examples in many frameworks or toolkits. However, without native language support for the factory pattern, most implementations have to implement it with combinations of static methods and or utility classes. While this works, it exposes the pattern to the consumer of the code. Luckily, the designers of Dart added the factory pattern right into the language. With Dart’s factory constructors, you can natively use the factory pattern and make it appear like a regular constructor.

One great use case for factory constructors is to return objects from a cache. 

Could you elaborate? Is this no longer accurate?

You don't need a factory *constructor* to create and cache instances. All you need is a factory *function*.
The real reason for having factory constructors in Dart is that it allows you to change from having a generative constructor (which always create a new instance) to a factory constructor (which could be caching) without changing the class API.

The problem is that it does change the class API wrt. extension because a subclass must always call generative constructor of the superclass. Changing from generative to factory is therefore a breaking change. The other direction isn't, though, so you can always start out with only public factory constructors.
 

One thing factory constructors seem to aid with is allowing fields to more easily be final. The factory constructor can dynamically create fields to pass to another constructor. For instance, a factory constructor can take fields a and b, and compute a reasonable default for c based on a and b then pass them to a ctor allowing all three to be final. But regular constructors don't seem to allow references to or computation on  previous final fields passed in.

That is true. Again, you don't need a constructor for that, any static function will do. You just don't get to (have to) write `new` in front of it.

I'm not saying factory constructors are necessarily a bad idea. Personally, I think it's the generative constructors that are the problem - if we didn't have them, there would be no need for factory constructors either, it'd all just be static functions.

Given that we do have constructors, factory constructors are useful in order to get a kind of symmetry in the APIs, so similar functions are all constructors or all functions, and not a mismatch were some are generative constructors and some are static factory functions.

/L
 

tatumizer-v0.2

unread,
Aug 18, 2016, 2:30:00 PM8/18/16
to Dart Misc
As soon as "new" is gone, dart can support the view on constructor as static function. All you need for that is to allow expressions like
var myFooContructor=Foo.constructor;  // for default constructor
var foo=myFooConstructor("bar", "baz");
var myFooConstructor1=Foo.someFactoryConstructor;
var foo1=myFooConstructor1("bar", "baz");
etc

IMO, the problem is not with generative constructor, and not with factory constructor, and not with multiple inheritance (which is a dead horse already) - it's a problem with "new", which is an arbitrary idea to begin with, and cannot be even consistently implemented (e.g. we never write "new str.Substring(0)").



Lasse R.H. Nielsen

unread,
Aug 18, 2016, 4:32:55 PM8/18/16
to mi...@dartlang.org
On Thu, Aug 18, 2016 at 8:29 PM, tatumizer-v0.2 <tatu...@gmail.com> wrote:
As soon as "new" is gone, dart can support the view on constructor as static function.

I tried to avoid that discussion, but yes.
If you don't have a syntactic difference between constructor calls and non-constructor calls, then you don't need factory constructors. Factory methods are completely equivalent (assuming generic methods).
 
All you need for that is to allow expressions like
var myFooContructor=Foo.constructor;  // for default constructor
var foo=myFooConstructor("bar", "baz");
var myFooConstructor1=Foo.someFactoryConstructor;
var foo1=myFooConstructor1("bar", "baz");
etc

IMO, the problem is not with generative constructor, and not with factory constructor, and not with multiple inheritance (which is a dead horse already) - it's a problem with "new", which is an arbitrary idea to begin with, and cannot be even consistently implemented (e.g. we never write "new str.Substring(0)").

There are still problems with generative constructors and the fact that class extension depends on them. Not easily solvable problems, sadly.

/L

tatumizer-v0.2

unread,
Aug 18, 2016, 4:48:37 PM8/18/16
to Dart Misc
> There are still problems with generative constructors and the fact that class extension depends on them. Not easily solvable problems, sadly.
Sorry, I'm not aware of these problems. Could you provide an example?
super() calls can remain the same as they are now, what's wrong with that?
Adding a "static perspective" (as in the example above) doesn't seem to destroy anything, it's just an "extra", no?




Lasse R.H. Nielsen

unread,
Aug 19, 2016, 3:04:02 AM8/19/16
to mi...@dartlang.org
On Thu, Aug 18, 2016 at 10:48 PM, tatumizer-v0.2 <tatu...@gmail.com> wrote:
> There are still problems with generative constructors and the fact that class extension depends on them. Not easily solvable problems, sadly.
Sorry, I'm not aware of these problems. Could you provide an example?

The only real problem I have is that whether something is a generative constructor is a part of your API due to subclasses requiring it. Changing it is a breaking change, independently of which syntax you use to call constructors.
 
super() calls can remain the same as they are now, what's wrong with that?

That super-calls in subclass constructors require a generative constructor that is also publicly visible.
If we had protected generative constructors, then we could make the public API independent of whether something is a generative constructor or not, and change between them without breaking clients, as long as we keep the protected constructor the same.

Right now your subclass API and your client API are mixed and that locks you in to some choices that you have to make.

Adding a "static perspective" (as in the example above) doesn't seem to destroy anything, it's just an "extra", no?

The public client API of a class can easily be abstract, and that works well.
There doesn't need to be any difference between static functions and generative constructors from that perspective.

There do need to be a difference in the subclass API, and currently we can't distinguish the two APIs because they are all public. That's my problem.

tatumizer-v0.2

unread,
Aug 19, 2016, 12:19:44 PM8/19/16
to Dart Misc
Now I remember we discussed that, but I don't remember why simply prefixing protected members with "protected" (e.g. protectedFoo, protectedBar, Baz.protected for constructor etc) is not a good idea. Basically, this all fits into the great discovery we made in a parallel thread (things are not the same if you look from outside vs from inside). Those protected methods can be kinda public, but analyzer will complain if used by somebody other than subclass.

BTW, I don't think dart has much choice in that, b/c private members are already distinguished by prefix _, so for protected we just need another prefix, and it's better be BIG prefix (protected methods are rare).

Reply all
Reply to author
Forward
0 new messages