It would be nice to be able to closurize constructors. I find myself writing a de-serialization for DateTime that needs to be of the form
if (isUtc) {
return new Date.utc(<long parameter list...>);
} else {
return new Date(<identical long parameter list...>);
}
It's not that big a problem, but it would be nice to be able to write the parameter list once. There are some obvious questions. How would you refer to the default constructor? Could you provide type arguments? Is it an issue that invoking a closure might implicitly be doing a "new"?
_Set owner to @gbracha._
_Added Accepted label._
How would you refer to the default constructor?
new Foo
The obvious syntax for this to me is to just look like a constructor invocation without any (). That's what closurized method calls look like. So:
new Foo(1, 2, 3);
would do the same as:
var closure = new Foo;
closure(1, 2, 3);
_This comment was originally written by @seaneagan_
I like it! Only down side is that "new " is redundant for named constructors.
Consider:
new Foo
new Foo.bar
vs
Foo.new
Foo.bar
_This comment was originally written by @stevenroose_
What's the chance that this will make it into the codebase anytime soon?
I'm working on a library that may benefit from closurizable constructors. So it this change will eventually come through, I would pause a portion of the implementation and focus on another part first. If it doesn't, I'll need to find an alternative solution.
It's not likely to be very soon, since we are trying to keep things stable. I believe it will come through, but it may be quite a long time.
_This comment was originally written by @stevenroose_
Will this change cause instability then? It shouldn't break anything.
Closurizing regular methods and static methods is already possible, how
different is a constructor from a static method?
But I understand it can take a while, I'll be patient :-)
_This comment was originally written by @seaneagan_
My use case:
https://github.com/seaneagan/unscripted/issues/15
In unscripted, I would like to uniformly use closures to model command-line script behavior. The closures are annotated with command-line metadata, but in the case of sub-commands the methods are usually constructors. So my users would need to create a dummy closurizable factory method which delegates to the constructor and has the metadata. I imagine there are other scenarios where the metadata would actually have to be duplicated on manually created constructor closures, along with the entire constructor signature (including the return type, i.e. the class being constructed), and refactoring tools would be of no help when the constructor changes.
_This comment was originally written by @seaneagan_
I saw a closurization operator [1] was added to ClassMirror, but it appears to not support constructors, and doesn't specify whether the returned closure's function [2] is the actual method declaration which would need to be the case in order to get to the metadata.
_This comment was originally written by @seaneagan_
It's probably obvious, but constructor closures should be canonicalized constants, just like any other static method closures. That would be useful for using them as default factory methods, since default values must be constants.
_This comment was originally written by @seaneagan_
It would be nice if this could support type arguments:
var fooFactory = new Foo<int>;
var barFactory = new Bar.baz<String>;
_Removed Type-Defect label._
_Added Type-Enhancement label._
Issue #17907 has been merged into this issue.
Just a note that we are looking at this again.
_This comment was originally written by @stevenroose_
Nice to hear that! I have ((x) => new X(x)) code blocks everywhere.
_This comment was originally written by @kaendfinger_
See @gbracha's proposal here: https://github.com/gbracha/generalizedTearOffs/blob/master/proposal.md
I guess https://github.com/gbracha/metaclasses/blob/master/proposal.md is supposed to lead to another solution (as reference for people finding this issue first).
I think this can be closed.
void main() {
var constructor = new SomeClass#someConstructor;
var x = constructor();
print(dateConstructor(isUtc: true)(2000,1,1));
print(dateConstructor(isUtc: false)(2000,1,1));
}
Function dateConstructor({bool isUtc}) {
if(isUtc) {
return new DateTime#utc;
} else {
return new DateTime#;
}
}
class SomeClass {
SomeClass.someConstructor() {
print('constructing...');
}
}
runs fine, just the analyzer still shows warnings for #
.
output:
constructing...
2000-01-01 00:00:00.000Z
2000-01-01 00:00:00.000
var constructor = new SomeClass#someConstructor;
var x = constructor();
This doesn't work anymore. At least not in the dartpad and on my project.
Any explanation ?
@rrousselGit that feature was removed a short time after it was added. They'll try to find a better approach sometimes after Dart 2.0
Now that new
is optional it changes the way the syntax feels.
Since new
is still a keyword we're already not allowed to define a named constructed as new
, so ClassName.new
tearing off the default constructor shouldn't be breaking, and makes named constructor tearoffs look like static method tearoffs.
Any plan on this now that dart 2.0 is out for some time?
so ClassName.new tearing off the default constructor shouldn't be breaking, and makes named constructor tearoffs look like static method tearoffs.
Following that logic, default
is probably a better option than new
I have a plan! It's still mostly in my head, but it exists.
It requires allowing the id<typeargs>
syntax in more places. When we do that, a lot of things become possible:
List<int>.generate
T id<T>(T x)=> x; var intId = id<int>;
Type listOfInt = List<int>;
I hope we can use the context type to figure out whether something like List<int>
or Symbol
denotes the type or the default constructor. If the target type is a function type or Function
, it's the constructor, otherwise it's the Type
. That avoids having to add more syntax that isn't actually related to the constructor.
As a user of the language, when I need to get a constructor as a function, I would very much prefer to put it bluntly:
List<int>.generate as Function;
Map<String, String>.new as Function; // ".new" or ".default" - doesn't matter
//etc.
This kind of expression should be special-cased by a compiler, but it's as intuitive as it gets IMO.
Just want to throw in my $.02:
Is there any particular reason why Date
itself can't be used for the default ctor function reference? AFAIK there aren't any situations where there'd be ambiguity between referencing the Date
type and Date
's default ctor, and it'd mimic the call syntax much more closely, without needing an odd special syntax for it:
if (isUtc) {
d = Date(...);
} else {
d = Date.utc(...);
}
// becomes
final ctor = isUtc ? Date : Date.utc;
d = ctor(...);
I'm not at all familiar with how the Dart compiler works internally, but from what I know of compilers _in general_ it's definitely possible to say, "Hey, that typename is being used as an expression, so it _has_ to mean the default constructor".
Am I missing an edge case where there _is_ ambiguity?
It's unlikely that final ctor = isUtc ? Date : Date.utc;
can be made to work, mainly because the type system cannot guess what you mean.
The Date
expression is evaluated without any type hint, then the result of that is combined with the Date.utc
constructor tear-off result, which is likely a function type.
If Date
has type Type
, then even if it's a callable object, the type of ctor
will still be Object
.
In Dart 2, class instances are not functions. To make Date
useful as both a Type
object and function-typed tear-off of the constructor, the same object would have to both be a function and implement Type
. That's very confusing to the type system. We might be able to make Date
a Type
object that is also callable (but I'm not sure what the run-time type of that would be,)
I think it's more promising to let the meaning of Date
depend on the context type. Then you would have to write:
Function ctor = isUtc ? Date : Date.utc;`
to give the type hint needed to distinguish Date
-as-a-Type
from Date
-as-a-constructor.
@lrhn Whoops, I forgot that the type is, itself, an object (i.e. final foo = Date
is valid, and gives you the RTTI for Date). Call it a C++ hangover.
Over the past year working with Flutter. I've found several times I would like to refer to a constructor function. What was the problem with the old Someclass#someconstructor
reference? Is this issue actually scheduled to be worked on?
You can always create a closure for that () => Date()
, () => Date.utc()
This is a nice trick, but it solves only the trivial variant of problem. In the title of the issue, we have
if (isUtc) {
return new Date.utc(<long parameter list...>);
} else {
return new Date(<identical long parameter list...>);
}
Not sure which case is more common though. Maybe constructors with identical long parameter lists are rare? If so, spending time on it would have very low ROI.
That's one of the reasons why I think spread operator for classes would be lovely.
() => Foo()
is fine, but if it has tons of arguments, then it's not anymore.
I assume tear-offs for constructors and getters/setters are still planned.
Closures are only a workaround until it's done.
I think we should also consider the utility in functions like map
as another case. For example, let's say you make a Flutter widget FancyText
which renders text... fancily. You have an array of strings to put into a Column
so they all render in a nice, er, column. Right now you'd have to write:
Column(
children: textArray.map((s) => FancyText(s))
)
With this, you could write
Column(
children: textArray.map(FancyText.new)
)
That's obviously a trivial example, but it is a (very nearly) real one that I recently had to use, and in my experience with other languages -- typically more functional ones, though not exclusively -- it's _very_ common. Letting ctors be called like any other function taking arguments and returning a FancyText
would be useful.
In similar situations we've moved an SDK issue to the language repository, but in this case https://github.com/dart-lang/language/issues/216 contains a more recent version of the same proposal, so I'll close this one. Please go there for additional discussion!
Most helpful comment
Now that
new
is optional it changes the way the syntax feels.Since
new
is still a keyword we're already not allowed to define a named constructed asnew
, soClassName.new
tearing off the default constructor shouldn't be breaking, and makes named constructor tearoffs look like static method tearoffs.