Object literal in the form of Map literal prefixed by ClassName

96 views
Skip to first unread message

Cat-sushi

unread,
Oct 30, 2016, 9:55:34 AM10/30/16
to Dart Core Development

How do you think my proposal?


Summary

  • Introduce Object literal in the form of Map literal prefixed by ClassName as a constructor invocation
    ClassName {member1: value1, member2: value2}
  • Make the default constructor having optional named parameters to initialize all members
  • Make the default constructor always valid even if other constructors are defined explicitly

Motivation
  • Object literal similar to JSON is useful 
    • JS code will be easier to be convert to Dart
    • Dart will be more familiar to JS programmers
    • Affinity for literals of String, List and Map
  • Nested invocation of constructors will be clearer without noisy new
    • Application with frameworks such as Flutter will be easier to be developed
    • Such applications will be as readable as marked up texts
  • Boilerplate of constructor definition can be omitted. 
    • Don’t repeat yourself!
    • Frameworks like Flutter will be easier to be developed
  • Consequently, inappropriate use of Map could be avoided 
    • Keys of Map<Symbol, dynamic> doesn’t have tool support for code completion and highlighting
    • Values of Map<Symbol, dynamic> doesn’t have tool support for static type checking and highlighting
    • Values of Map<Symbol, dynamic> prevent type inference by the system

Examples

Example 1

Before
class Div {
 
String className;
 
List children;
 
Div({this.className, this.children}); // boilerplate
}


class H1 {
 
List children;
  H1
({this.children}); // boilerplate
}


var dom =
 
new Div(className: 'section', children: [ // noisy new
   
new H1(children: ['Title']), // noisy new
   
new Div(className: 'sectionbody', children: [ // noisy new
     
'This is the section body'
   
])
   
]);

After
class Div {
 
String className;
 
List children;
}

class H1 {
 
List children;
}

var dom =
 
Div { className: 'section', children: [
    H1
{ children: ['Title']},
   
Div { className: 'sectionbody', children: [
     
'This is the section body'
   
]}
 
]};

Before
class StockRow extends StatelessWidget {
 
StockRow({
   
Stock stock,
   
this.onPressed,
   
this.onDoubleTap,
   
this.onLongPressed
 
}) : this.stock = stock, super(key: new ObjectKey(stock));


After
/* StockRow({}) is a explicit definition of "default" constructor to specify initialization list. In addition, implicit "this.stock" must be able to be referred in initialization list as "stock" */
class StockRow extends StatelessWidget {
 
StockRow({}): super(key: new ObjectKey(stock));

Example 3

over_react 1.1.1 | Pub Package Manager
OverReact could be even more fluent.


Proposal
  • Improve default constructor 
    • Always valid even if other constructors are explicitly defined
    • Final member will be internally marked as mandatory at compile time of it
    • ClassName({}) is a optional explicit definition of default constructor for supplying initializer list 
      • Members initialized in initializer list can’t be specified as named arguments 
      • Implicit parameter this.menber1 can be referred in initializer list as member1
    • The same goes for the super class and the mixins
  • Introduce object literal as a invocation of the default constructor 
    • The syntax is in forms of Map literal prefixed by ClassName
      ClassName { member1: value1, member2: value2}
    • Needless to specify the keyword of new
    • Final members marked as mandatory can not be omitted
Alternatives

Filipe Morgado

unread,
Oct 30, 2016, 1:13:03 PM10/30/16
to Dart Core Development
Overall, I like the idea. But there are a few downsides.

Currently, if we forget to initialize any final field in any constructor, it's an error.
With "Map-like class literal", it would delay the error until invocation.

I often work with const classes that have constructor parameters that are not publicly available after initialization.
I would have to declare that I expect an extra argument in the Map-like literal.

In a lot of cases, we would need a way to disable Map-like literals for a given class.

How would we declare internal fields initialization?
class MyClass {
 
final int _myInt;
 
const MyClass(this._myInt);
}

I see this possibly working only in 2 mutually-exclusive ways:
Either as a "shortcut" for constructor arguments:
class MyClass {
 
MyClass({param1, param2, param3}) { ... }
}
// Before ...
var instance = new MyClass(
  param1
: 0,
  param2
: 0,
  param3
: 0
);
// After
var instance = MyClass {
  param1
: 0,
  param2
: 0,
  param3
: 0
};
(Not much of an improvement).

... Or as a shortcut for setting public properties:
class MyClass {
 
MyClass(param1, param2, param3) { ... }
}
// Before
var instance = new MyClass(0, 0, 0)
 
..var1 = 1
 
..var2 = 2
  ..var3 = 3;
// After
var instance = MyClass(0, 0, 0) {
  var1
: 1,
  var2
: 2,
  var3: 3
}
(Not much of an improvement either).

I'm all for removing the "new" keyword (and inferring "const" when needed) and for Improved Default Constructors.
But, except for removing the need to define default constructors, I fail to see any real advantage in this proposal.
Specially when constructor parameters offer much more flexibility.

Removing the "new" keyword would make a huge difference by itself.

Well, my 2 cts ...

Cat-sushi

unread,
Oct 31, 2016, 7:03:52 AM10/31/16
to Dart Core Development
Currently, if we forget to initialize any final field in any constructor, it's an error.
With "Map-like class literal", it would delay the error until invocation.

As I mentioned, I think the system can internally mark the (implicit) parameters for the final fields as mandatory at the point of compilation of the constructors.
I think also checking the marks at the call sites costs just as much as binding conventional mandatory arguments does.

I often work with const classes that have constructor parameters that are not publicly available after initialization.
I would have to declare that I expect an extra argument in the Map-like literal.

Sorry, I'm not sure what you are meaning.
Would the following example help?

const ClassName { member1: const1, member2: const2 }

How would we declare internal fields initialization?

Do you mean "internal" is "private"?
I think private fields should be visible in the object literals at lest in the same library.
For library users, I think private fields should be initialized in initializer lists.
That said, some classes would be required to have initialization method such as call() of Div in OverReact, but this proposal mainly targets data intensive classes which have limited internal statuses and logic such as Color, Light, Sphere and Camera in RayTracer.

(Not much of an improvement).

As you may think, my proposal doesn't save the count of typing much at the call sites.
But I think the improvement of readability is non-trivial especially in case of nested constructions.
For example, at my first glance, I couldn't get the mechanism of OverReact because of unobvious constructions and the strange parentheses around cascade operations (and implicit call() invocations).
The cascade operations for initialization sometime require extra outer parentheses to handle the objects initialized.
In contrast, under the syntax I'm proposing, constructions are self-describing and no extra parenthesis is required to handle the object initialized.
At the same time, I don't prefer the keyword new in such contexts, because it prevent the code from being readable as marked up text or JSON like expression.

Removing the "new" keyword would make a huge difference by itself.

Yes, this is one of the reasons why I am proposing distinct syntax.
I'm not insisting that new for conventional construction should be able to be omitted, but I indeed like it.
Anyway, the syntax of current specification doesn't require keyword new for literals of Map, List, String, int, and so on.

BTW, what are the real downsides?
The complexity of the system might be the one.
But I'm not sure how much my proposal increase the complexity of the system.


2016年10月31日月曜日 2時13分03秒 UTC+9 Filipe Morgado:

Cat-sushi

unread,
Nov 2, 2016, 9:12:03 AM11/2/16
to Dart Core Development
I found this.member1 can be access under the option of --initializing-formal-access.

I found also that with current specification, this.memberOfSuper can't be access as formal nor in initializer list.
this.memberOfMixin neither can.

For my proposal, those should be accessed.
I think this modification isn't difficult, because default constructors of super classes and mixins can't have bodies even under my proposal.

2016年10月30日日曜日 22時55分34秒 UTC+9 Cat-sushi:
Reply all
Reply to author
Forward
0 new messages