-
Notifications
You must be signed in to change notification settings - Fork 205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Constructor tear-off syntax discussion. #1564
Comments
I would broaden this question a bit. Specifically, given a generic class
In other words, we can allow either or both of |
Wouldn't it be possible to disambiguate
Unless there are clear technical reasons for don't allow it, I think this is the clearest option. |
@srawlins pointed out to me that dartdoc already made a syntax choice here long ago, allowing dartdoc references to unnamed constructors as Dartdoc is also making a subtle assumption here in the output file structure that the unnamed constructor is named the same as its defining class. Dartdoc complains very loudly (fails with error by default) if you somehow have or inherit a member named the same as a class it is contained in for this reason. Fortunately, people don't seem to do this in practice, so another option you could consider might be unnamed constructors with an implicit name the same as the defining class. The unnamed constructor then references as: (edit: Not as clear to me that this would have a broad impact after thinking about it some more.). That could have some big ripples in the language specification, and there may be subtleties I don't get here which make this a horrible notion. As a matter of practice though, I'd be surprised if this caused any ecosystem disruption to change because dartdoc already does enforce this. I don't otherwise have a strong opinion. These are just the prevailing winds from dartdoc's perspective. |
I know it's not a strict issue, but I'm personally in favor or |
Are you suggesting this? class Foo<T> {
Foo.bar(T value);
}
Foo<T> Function<T>(T) a = Foo.bar;
Foot<T> b = a<int>(42); Because, to your point, this is how you would currently do List<num> x = [1, 2, 3];
List<R> Function<R>() y = x.cast;
List<int> z = y<int>(); |
The issue with
is that it doesn't interact well with generic constructors (which is another feature we do want, #1510). If you can write class Foo<T> {
Foo.baz<R>(Iterable<R> elements, T convert(R)) { ... }
} to declare a generic constructor (on a class which is generic or not), then tearing off that So, making It can be I still we risk being inconsistent, or at least confusing, and would prefer if we always instantiate the class when we tear off a constructor. |
@lrhn: class Foo<T extends num> {
Foo.bar<R>(T value1, R value2);
}
Foo<int> Function<R>(int, R) tearOff1 = Foo<int>.bar;
Foo<num> Function<R>(num, R) tearOff2 = Foo.bar; So basically refer to the |
@tatumizer A const constructor is just a normal constructor when used at run-time. The fact that it can be called using A more interesting thing is that a constructor tear-off can be a constant function value if the class is not generic, and maybe if the class is generic (depending on whether the instantiation type is constant). |
I was hoping we could change the type literal syntax to something else and use the bare class name for the unnamed constructor tear-off. Unfortunately, I don't think that will fly. The Flutter new ClassNameI think we should not use
ClassName.newI think we should use
In general, I think postfix high-precedence method-like syntax plays better with the rest of the language. We have often been frustrated by having Wildcard!Another option is to not add specific constructor tear-off syntax. Instead, do some kind of more general partial application syntax. Here's a strawman: A function(a, b, c, d) => a + b + c + d;
var examples = [
function(...),
function(1, ...),
function(1, ..., 4),
function(1, 2, ..., 4),
]; Is equivalent to: function(a, b, c, d) => a + b + c + d;
var examples = [
(a, b, c, d) => function(a, b, c, d),
(b, c, d) => function(1, b, c, d),
(c, d) => function(1, b, c, 4),
(c) => function(1, 2, c, 4),
]; We would then allow this syntax in constructor invocations as well. With this, the unnamed constructor tear-off that forwards all parameters is just: SomeClass(...) That's only one character longer than |
Yeah, this is a good point. |
I like the idea from DartDoc, using For consistency, we should then probably make it legal to use that as a constructor call (e.g. |
Well, named constructors don't typically begin with a capital letter. |
It's not consistent with how unnamed constructors are declared or invoked, which is probably the comparison that matters most.
Mainly that when the class name is long (which is pretty common) it's likely to be even more verbose than making a closure manually, which users can already do today. I did a really hacky scan over a bunch of pub packages looking for existing closures that could be constructor tear-offs if we supported them. Of the ones I found, 31 would be improved using constructor tear-offs with dartdoc style syntax like // Cases where Foo.Foo is shorter:
_Node._Node
() => _Node()
Address.Address
(a) => Address(a)
StoreScreen.StoreScreen
(childRouter) => StoreScreen(childRouter)
Text.Text
(e) => Text(e)
// Cases where the existing explicit closure is shorter:
_Example._Example
() => _Example()
GlobalKey.GlobalKey
() => GlobalKey()
GetReplicatorPendingDocumentIDs.GetReplicatorPendingDocumentIDs
(address) => GetReplicatorPendingDocumentIDs(address)
ImmediateMultiDragGestureRecognizer.ImmediateMultiDragGestureRecognizer
() => ImmediateMultiDragGestureRecognizer()
AdvanceMsgItem.AdvanceMsgItem
(e) => AdvanceMsgItem(e)
StandardDialogsLocalizations.StandardDialogsLocalizations
(locale) => StandardDialogsLocalizations(locale)
AsyncIoRequestClient.AsyncIoRequestClient
(prefix) => AsyncIoRequestClient(prefix)
IdbStoreRecordSnapshotSqflite.IdbStoreRecordSnapshotSqflite
(row) => IdbStoreRecordSnapshotSqflite(row) Note that using |
Also note that, as unlikely as it is, |
This too.
I don't expect we'll ever "unreserve" the |
People seem to be gravitating towards |
The "bind"/"curry" operation you describe would partially apply the provided parameters (which must match a prefix of positional parameters and subset of named parameters), and then evaluates to function which expects the rest. int foo(int x, [int? y, int z = 0]) => ...
int bar(int x, int y, {int? z, int w = 0}) => ...
var f1 = foo**(1, 2); // ([int z = 0]) => foo(1, 2, z)
var f2 = bar**(1, z: 2); // (int y, {int w = 0}) => bar(1, y, z: 2, w: w) If we extend that to type parameters, then we'd get: R baz<R, T>(T x, [T? y]) => ...;
var f3 = baz**<int, int>(); // (int x, [int? y]) => baz<int, int>(x, y)
var f4 = baz**<int>(2); // <T>([int? y]) => baz<int, T>(2, y) (I put the It can express any kind of partial application. You need the It doesn't work for constructor tear-offs, though, because the type arguments are on the class, The question is what type we base the specialization on: The static type or the run-time type of the function in the partially applied invocation. That is, if we have: int Function(int) f = (int x, [int? y]) => y ?? x;
var g = f**(2); will Can you specialize a dynamic function? (Another strawman syntax: |
I guess this can be closed? |
I think so. I'll go ahead and close it and let @lrhn re-open if there's something unresolved that I missed. |
A way to tear off constructors as functions, like you can for all kinds of instance methods and function declarations, would be nice. One request for it is #216, but there are more.
The perfect syntax for tearing off a named constructor is
ClassName.constructorName
, just like you would write the tear-off of a static function declaration.The perfect syntax for tearing off an unnamed (empty-named) constructor, one invoked as
ClassName(args)
, would seem to beClassName
, but that syntax is already used as aType
literal.Unnamed constructors
Taking the syntax.
We can, technically, change the syntax for type literals to something else (
Foo.class
,Type.of<Foo>
,~~Foo
, or something), force everybody to migrate to that syntax, and then use the unqualified name as the constructor tear-off.That's unlikely to be viable in practice. A lot of people are very used to writing type literals as literals, and a lot of code will need to be migrated, even if it's completely automatable.
Probably not viable.
Sharing the syntax.
We could make the context type decide whether
Foo
means theType
object or the constructor tear-off, just like we do for callable objectcall
method tear-offs.It's overloading and likely to be confusing. For callable object
call
method tear-off, no doing the tear-off immediately is not necessarily an issue, if the object keeps having the callable-class type, you can always tear the method off later.A
Type
object is not itself callable, so we would need to get the tear-off right at the literal, not when it's later used in a callable context. That makes it much more fragile and likely to give the wrong result. As such, probably not viable either.Make the
Type
object callable then?That'd be a big change. Currently all
Type
objects have the same type, the opaqueType
type.In order to be able to later tear off a constructor from a
Type
object, we need a subtype corresponding to the class itself (which then needs a name too).Say the (meta-)type of the
Foo
type isFoo.class
. Then you can writeFoo.class x = Foo;
. That object implementsType
and is callable. But then, why doesn't it have all the constructors and static members? We'll have introduced proper meta-classes just to tear off a constructor.Not viable as a solution for something which is a smaller issue than introducing metaclasses.
Separate syntax.
If we introduce new syntax just for unnamed constructor tear-offs, the current proposals are:
Foo.new
new Foo
(Foo.)
(slightly tongue-in-cheek, but it could work).Any other ideas?
The
Foo.new
syntax suggests that you should also be allowed to invoke the constructor asFoo.new(args)
, which you currently can't. Can you declare the constructor asFoo.new(...)
, andFoo(...)
is just a shorthand? That might be useful if we want to introduce generic constructor (we do, #1510), then you can declareFoo.new<T>(...)
as generic without it looking like a type argument onFoo
.The
new Foo
syntax suggests that you can also invoke the constructor asnew Foo(args)
, which you can. It's just that we're generally recommending that you don't, so it feels weird reintroducing it as mandatory here.Instantiated tear-offs.
A class can be generic. You can invoke a constructor as
Foo<int>.bar()
. Should you also be allowed to tear it off asFoo<int>.bar
?If not, we can allow implicitly instantiated tear-offs like we do for generic functions:
So there is precedence for only allowing implicit instantiation, and we can do the same for constructors.
But the latter is so much shorter. Because you have to write the entire function type just to specify one part of it, you can end up writing a lot more to get correct implicit instantiated tear-offs.
What about the unnamed constructor? All of
Foo<int>.new
,new Foo<int>
and(Foo<int>.)
are new syntaxes that would otherwise be invalid, so they can work.Should there be explicitly instantiated tear-offs:
Set<int>.new
,List<int>.filled
?If so, should we make
List<int>
a valid type literal while we are at it? (#123).The text was updated successfully, but these errors were encountered: