Improved default constructors.

128 views
Skip to first unread message

Lasse R.H. Nielsen

unread,
Sep 17, 2015, 3:19:45 AM9/17/15
to core...@dartlang.org
I was thinking (idle minds are dangerous) that the "default constructor" is very primitive, and maybe it can be improved to avoid having to write as much for simple struct-like types.

Currently the default constructor (the one added if you don't add one yourself) for the class C is always "C();".

How about instead making the default constructor have parameters for all uninitialized fields.

So:
   class C {
      int foo;
      final int bar;
      int baz = 42;
   }

would get the default constructor 
  C({this.foo, this.bar});
so you can use it as "new C(foo: 42, bar: 10)".

Further, if the superclass' unnamed constructor is generative and has no positional parameters, we can forward its parameters as well, so:

class D extends C {
  int qux;
}

would get the default constructor:

  D({this.qux, foo, bar}) : super(foo: foo, bar: bar);

This should all be backwards compatible - the default constructor is still callable with no arguments and all fields without an initializer will be initialized to null.


Now, it's not entirely perfect. All the parameters are named, so you have to write  new Point(x: 42, y: 37)  instead of just  new Point(42,37) .
Also, it doesn't make the constructor a const constructor, so if you want that, you still have to write a constructor. It shouldn't make something const automatically since it becomes part of the class contract, and removing the const later is a breaking change.
  
WDYT?
/L
--
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

Rasmus Eneman

unread,
Sep 17, 2015, 3:38:38 AM9/17/15
to Lasse R.H. Nielsen, Dart Core Development
Yes please. Maintaining constructors for simple classes are just boilerplate.

I would even like to go so far that

   class C {
      int foo;
      final int bar;
      int baz = 42;
   }

would get the default constructor 
  C({this.foo, this.bar, int baz}) : this.baz = baz ?? this.baz;

--
You received this message because you are subscribed to the Google Groups "Dart Core Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to core-dev+u...@dartlang.org.



--
Rasmus Eneman

Erik Ernst

unread,
Sep 17, 2015, 4:14:26 AM9/17/15
to Rasmus Eneman, Lasse R.H. Nielsen, Dart Core Development
Nice idea! ;-)
Erik Ernst  -  Google Danmark ApS
Skt Petri Passage 5, 2 sal, 1165 København K, Denmark
CVR no. 28866984

Günter Zöchbauer

unread,
Sep 17, 2015, 4:15:02 AM9/17/15
to Dart Core Development


On Thursday, September 17, 2015 at 9:19:45 AM UTC+2, Lasse Reichstein Holst Nielsen wrote:
I was thinking (idle minds are dangerous) that the "default constructor" is very primitive, and maybe it can be

I like that a lot. You should delegate more of your work to get more idle time :)

I also like the Rasmus' extension. If you want different behavior, you can still implement a constructor manually.

Lasse R.H. Nielsen

unread,
Sep 17, 2015, 4:38:47 AM9/17/15
to Rasmus Eneman, Dart Core Development
On Thu, Sep 17, 2015 at 9:38 AM, Rasmus Eneman <Ras...@eneman.eu> wrote:
Yes please. Maintaining constructors for simple classes are just boilerplate.

I would even like to go so far that
   class C {
      int foo;
      final int bar;
      int baz = 42;
   }

would get the default constructor 
  C({this.foo, this.bar, int baz}) : this.baz = baz ?? this.baz;

That would be convenient if it could work, but currently you can't initialize a member in the constructor if it has an initializer (and you can't refer to this.baz).

We can't even change:
  class C {
     int x = something;
  }
into
  class C {
     int x;
     C({this.x = something});
  }
since the something isn't necessarily a compile-time constant.

/L

Rasmus Eneman

unread,
Sep 17, 2015, 6:20:15 AM9/17/15
to Lasse R.H. Nielsen, Dart Core Development
I see, it could be turned into
  C({this.foo, this.bar, int baz}) {
    if (baz != null) {
      this.baz = baz;
    }
  }

but that wouldn't work with finals, but you can't overwrite an initialized final anyway so that wouldn't work anyway.
Is there any problem with having an implicit body in default constructors?

If we remove the initialization at declaration this would work with both final and vars but I'm unsure about other implications

  class C {
    int foo;
    final int bar;
    int baz;

    C({this.foo, this.bar, int baz}) : this.baz = baz ?? 42;
  }

--
You received this message because you are subscribed to the Google Groups "Dart Core Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to core-dev+u...@dartlang.org.



--
Rasmus Eneman

Lasse R.H. Nielsen

unread,
Sep 17, 2015, 6:25:05 AM9/17/15
to Rasmus Eneman, Dart Core Development
On Thu, Sep 17, 2015 at 12:20 PM, Rasmus Eneman <Ras...@eneman.eu> wrote:
I see, it could be turned into
  C({this.foo, this.bar, int baz}) {
    if (baz != null) {
      this.baz = baz;
    }
  }

but that wouldn't work with finals, but you can't overwrite an initialized final anyway so that wouldn't work anyway.
Is there any problem with having an implicit body in default constructors?

If we remove the initialization at declaration this would work with both final and vars but I'm unsure about other implications
  class C {
    int foo;
    final int bar;
    int baz;

    C({this.foo, this.bar, int baz}) : this.baz = baz ?? 42;
  }

That may change the evaluation order if you are not careful. 
In any case, it changes the class in ways separate from just adding a constructor, and I don't think that's a good idea. The default constructor is just a simple *addition* to the class, the rest is still exactly as written.

(And another thing to notice is that private named fields can't be initialized this way because named parameters can't have private names).

/L
-- 

Matthew Butler

unread,
Sep 17, 2015, 8:20:57 AM9/17/15
to Dart Core Development
I like it. I did have a concern, but as you mention private variables can't be named parameters anyways. But currently it's still slightly less flexible than just using cascades at initialization.

class Foo {
 
int pNum;
 
int _myNum;
 
void set myNum(int value) { _myNum = value; }
}

main
() {
 
var foo = new Foo()
   
..pNum = 10
   
..myNum = 20;

// This feels much more awkward.
 
var bar = new Foo({pNum: 10})
   
..myNum 20;
}

Matt

Bob Nystrom

unread,
Sep 17, 2015, 1:04:03 PM9/17/15
to Matthew Butler, Dart Core Development
Nice idea!

I think it would be really useful to scrape the public packages and see how many classes could take advantage of this. Look for classes with a single generative constructor that just initializes fields.

That would give us some good data on how useful it is.

Also: +1 to you having more time for stuff like this. :)

Cheers!

- bob


--

Brian Wilkerson

unread,
Sep 17, 2015, 1:27:00 PM9/17/15
to Bob Nystrom, Matthew Butler, Dart Core Development
Lasse,

I think it would be really useful to scrape the public packages and see how many classes could take advantage of this. Look for classes with a single generative constructor that just initializes fields.

And to find out how many places currently have default constructors that would be broken by this change.

It might be hard to determine that a class has public fields that should not be settable in the constructor, but given that fields with no explicit initializer are implicitly initialized to null means that some existing code might be omitting an explicit initializer that this change would require in order to not change the semantics of that code. (On the other hand, it would be exactly as hard to write a tool to automatically convert existing code.)

Brian

Siggi Cherem

unread,
Sep 17, 2015, 1:31:18 PM9/17/15
to Bob Nystrom, Matthew Butler, Dart Core Development

Nice proposal! What would you think of the next addition: make an argument positional and mandatory if the field declaration is final and uninitialized. For example,

class C {
  int one;
  final int two;
  final int three;
  int four;
}

implies:

C(this.two, this.three, {this.one, this.four});


Two caveats:
  • unlike your initial proposal, this would technically be a breaking change (a very rare one?). In particular, unless I'm missing something obvious, if you want to use a final field, you'll likely have a constructor that initializes it (otherwise that's quite the verbose way of declaring `null`), so you wont have a default constructor in those cases in existing code.
  • the order of the parameters depends on the order of the field declarations. This worries me more, since a refactoring that reorders your fields can change the semantics of your code. Maybe what we need are mandatory named arguments?
Cheers,
Siggi

Bob Nystrom

unread,
Sep 17, 2015, 1:32:10 PM9/17/15
to Brian Wilkerson, Matthew Butler, Dart Core Development

On Thu, Sep 17, 2015 at 10:27 AM, Brian Wilkerson <brianwi...@google.com> wrote:
It might be hard to determine that a class has public fields that should not be settable in the constructor, but given that fields with no explicit initializer are implicitly initialized to null means that some existing code might be omitting an explicit initializer that this change would require in order to not change the semantics of that code. (On the other hand, it would be exactly as hard to write a tool to automatically convert existing code.)

Wouldn't it behave the same with Lasse's proposal? The optional named parameter would be omitted, defaulting to null, then the field would get initialized to that null value.

Cheers,

- bob

Bob Nystrom

unread,
Sep 17, 2015, 1:33:31 PM9/17/15
to Siggi Cherem, Matthew Butler, Dart Core Development

On Thu, Sep 17, 2015 at 10:30 AM, Siggi Cherem <sig...@google.com> wrote:
  • the order of the parameters depends on the order of the field declarations. This worries me more, since a refactoring that reorders your fields can change the semantics of your code. Maybe what we need are mandatory named arguments?

This is my worry. We treat member declaration order as having absolutely no affect on semantics, and this would be a big change to that.

+1 for mandatory named arguments. They would be super useful for bool parameters.

Cheers!

- bob

Brian Wilkerson

unread,
Sep 17, 2015, 1:53:15 PM9/17/15
to Bob Nystrom, Matthew Butler, Dart Core Development
Bob,

On Thu, Sep 17, 2015 at 10:27 AM, Brian Wilkerson <brianwi...@google.com> wrote:
It might be hard to determine that a class has public fields that should not be settable in the constructor, but given that fields with no explicit initializer are implicitly initialized to null means that some existing code might be omitting an explicit initializer that this change would require in order to not change the semantics of that code. (On the other hand, it would be exactly as hard to write a tool to automatically convert existing code.)

Wouldn't it behave the same with Lasse's proposal? The optional named parameter would be omitted, defaulting to null, then the field would get initialized to that null value.

Yes, they're equivalent in terms of the semantics of the language, but not necessarily in terms of the semantics intended by the author. Today, if I write

class C {
  String s;
}

I get a default constructor that does not allow 's' to be set. But under this proposal it could be set in the constructor. If I really don't want to allow that I have to have an explicit constructor. Maybe it's not important enough to worry about.

Brian

Bob Nystrom

unread,
Sep 17, 2015, 2:09:40 PM9/17/15
to Brian Wilkerson, Matthew Butler, Dart Core Development

On Thu, Sep 17, 2015 at 10:53 AM, Brian Wilkerson <brianwi...@google.com> wrote:
Yes, they're equivalent in terms of the semantics of the language, but not necessarily in terms of the semantics intended by the author. Today, if I write

class C {
  String s;
}

I get a default constructor that does not allow 's' to be set. But under this proposal it could be set in the constructor. If I really don't want to allow that I have to have an explicit constructor. Maybe it's not important enough to worry about.

Ah, I see. Thanks for clarifying. :)

- bob

Rasmus Eneman

unread,
Sep 17, 2015, 2:11:31 PM9/17/15
to Brian Wilkerson, Bob Nystrom, Matthew Butler, Dart Core Development
In the case of
class C {
  String s;
}


s would be settable anyway by writing
  new C()..s = 'something'
Can't see why setting it in the constructor would be a problem?

Of course private fields should not be settable as that might be dangerous but if I understand correctly this proposal doesn't wont to allow that (and can't I think, don't think named parameters can start with underscore)

--
You received this message because you are subscribed to the Google Groups "Dart Core Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to core-dev+u...@dartlang.org.



--
Rasmus Eneman

Matthew Butler

unread,
Sep 17, 2015, 2:13:09 PM9/17/15
to Dart Core Development, rnys...@google.com, butler....@gmail.com


On Thursday, September 17, 2015 at 2:53:15 PM UTC-3, Brian Wilkerson wrote:
... 
 But under this proposal it could be set in the constructor. If I really don't want to allow that I have to have an explicit constructor. Maybe it's not important enough to worry about.

Brian


Based on the wording above. Wouldn't the following also prevent S from being set in the default constructor?

class C {
  String s = null;
}

How about instead making the default constructor have parameters for all uninitialized fields.
 (emphasis mine)

Matt 

Brian Wilkerson

unread,
Sep 17, 2015, 2:30:55 PM9/17/15
to Matthew Butler, Dart Core Development, Bob Nystrom
Matt,

Based on the wording above. Wouldn't the following also prevent S from being set in the default constructor?

class C {
  String s = null;
}

Yes, but that requires a code change, and I think it would be hard to automate that change.

Brian

Alexandre Ardhuin

unread,
Oct 9, 2015, 2:55:49 AM10/9/15
to Dart Core Development
With the zengen package you can generate this kind of default constructor. See https://github.com/a14n/zengen#defaultconstructor

Alexandre

--

Cat-sushi

unread,
Oct 25, 2016, 9:13:28 AM10/25/16
to Dart Core Development
What a great idea!
I've just come from Dart News & Updates via the definition of StockRow.
I happened to thing something like this, but I am worrying that a single explicitly defined constructor breaks this story.

How do you think about my alternatives below.

Alternative #1:
Keep the improved default constructor valid even if named constructors are defined.

Alternative #2:
Make "ClassName({});" to be the explicit definition of such a constructor.
"ClassName.namedConstructor({})" also looks reasonable.

Alternative #3:
Introduce second default constructor with distinct invocation syntax, which is always valid.
If semantically possible, this invocation would be something like below.
ClassName{member1: value1, member2: value2}
This invocation syntax also have other merits below.
  • This JSON like syntax is friendly to JavaScript programmers. It is a big win, isn't it?
  • Make nested constructions a little clearer without many news.  For example,
DIV {className: 'section', children: [
  H1
{children: ['Title'],
 
Div {className: 'paragraph', chldren: [
     
'This is the contents"]}]}

I prefer alternative #3. (^^)
Reply all
Reply to author
Forward
0 new messages