-
Notifications
You must be signed in to change notification settings - Fork 203
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
Type parameters with default value for classes #4087
Comments
I like this. It's a logical extension of having a default values in functions, and i can already see many situations where it could be useful. The syntax of But since it's quite long, it means the class definition could become too long with this addition, so much so, that it could hurt the readibility of the declaration. And so, i would like to propose a sort of Examplesclass Foo<T, U(D)>
class Foo<T, U(D), V(E)>
class Foo<T, ValueT(int) extends num> Things to solveWhat needs to be solved right now, other than the exact behaviour in code, is the LSP support, type inferring and syntax highlighting. This syntax needs to have a good LSP support for quickly catching type errors, inferring the actual type, inferring when the developer needs to specify a type, when the default type and the type it extends are incompatible and so on. The syntax highlighting is important, because it can effectively prevent some readability issues from happening, without modifying the syntax. The syntax highlighting issue is about differentiating the generic type from the default type - when looking at the class definition, the generic type should be the most eye catching. The default type instead should be less visible than the generic type, and should instead serve as a hint for the developper. It's a difference between ValueT(int) and ValueT(int). The default type should be less eyecatching because, even though the type has a default value, it's still generic and the developper should still be counting on it being generic. It also prevents the earlier issue with *Imagine the bold and italic is a syntax highlighting |
This is a possibility. I agree that For instance, let's consider someone who never read a Dart code before, reading both of these: // 1
class Foo<T default int> {}
// 2
class Foo<T(int)> {} In my opinion, even if one doesn't know Dart well, it will be mostly clear what (1) means, but I couldn't tell the same for (2). I'm going to add your suggestion as an alternative in the proposal, though.
Although those are important matters, I think they matter more once (if) the proposal is refined and accepted. For now, I think we should think about the implications (I surely missed some of them) of these changes and how we can solve it properly (if they are even solvable with this approach). |
People don't do generics if they are beginners in a language or programming. Also, the problem about the syntax being more difficult to understand for beginners, could be easily mitigated by 3 minutes google search. Regardless, i was talking about syntax highlighting, because i think it can solve the issues with ambiguity, without changing the syntax (whichever syntax is gonna be implemented). I saw it as a possible solution, more than as a nice thing to have. I agree that the implications should be studied first, rather than the developer experience, but that's not my area of expertise, since i don't do generics very often (generally an interface will suffice). |
I don't agree. People who are beginners in programming in general may not use generic, but people who come to Dart with a programming background, will certainly use generics. It's not an advanced feature or anything.
This is true, and we could expand it to every syntax of the language, but I still think the team values easiness of adoption. I'm not against cryptic syntaxes myself, but as far as I know, one of the goals of Dart is to be as accessible as possible, so I'm trying to align the proposal with the overall language objective. |
I like the idea of allowing an explicit default generic type. It's very useful once in a lifetime. What should be inferred for the following class? class Foo<T> { // T extends ???
final T bar;
Foo.baz([this.bar = 42]);
Foo.qux([this.bar = "42"]);
} If the generic type was implicit, as which option would T in class Foo<T extends dynamic> {} // 1, current behavior and compile-time error.
class Foo<T extends int> {} // 2, breaking change and possibly error-prone.
class Foo<T = int> {} // 3, compile-time error.
class Foo<T extends int = int> // 4, breaking change and possibly error-prone.
// ??? 5 |
This is one of the reasons why I avoided dealing with inference in my proposal. IMO, we shouldn't infer the default value based on the constructor. But, if we do, for your case where we have many constructors, we would have to do LUB between all arguments that has static type IMO this does not scale well, so I still think that we should simply not infer the default value of a type parameter ever. |
Not really. A default constructor parameter value isn't necessarily sound just because the type argument can be omitted. It needs it to be omitted, otherwise you can write That invocation needs to be invalid, while also allowing That's a different feature: optionally optional parameters, parameters which can only be omitted if their default value is valid for the actual parameter type. One of the consequences of that is that it must be visible in function types too. |
This is a possible solution for #283 and other similar issues.
This is the first time writing a slightly formal proposal, so please be patient and let's work together to improve this.
I may not have considered potential issues, and my language is certainly not as precise as it should be, but I think the idea can be understood.
Overview
This proposal suggests that we allow for a type parameter
T
on a class to have a default value.With this proposal, we could do the following:
Motivations
Adding or removing type parameters from a class is inconvenient (#283)
Consider the following cases:
Case 1: Changing the number of type paramters of a class that already has type parameters
Then, adding a new type parameter to
Foo
is a breaking change:Case 2: If the client has
strict-raw-types
enabled, adding a type parameter to a class that has no type parameterThen, adding a new type parameter to
Foo
is a breaking change:Known workarounds
Using
typedef
For some cases, using
typedef
may be a valid workaround.Consider the following case.
Instead of simply adding a new type parameter to
Foo
, we provide another class andFoo
as atypedef
.However, this is not always desirable, because if we want
U
to be used by the client, it will have to refer to the new class:Having a default value, in this case, wouldn't be less breaking. It would still be breaking for
Baz
to support theU
parameter. However, it would be done without the need of two different classes.Allowing for default values in constructors where the field type is a generic type
Currently, the following is an error:
By having a default type parameter value, we can accept default values in the constructor where
T
is expected, as long as the type has typeU
andU <: D
, beingD
the default type forT
.Reduce "clutter" for common cases
This is more than nothing a way of making some codes terser.
For intance, consider the following library code:
Now, we don't know how the clients are going to use library, but we know that the common case is to use
DefaultTag
as parameter. The client can introduce their ownTag
s, but it's an exceptional case that's not commonly used.By providing
DefaultTag
as the default value forT
, the majority of the clients can extend from the raw typeA
, while the exceptional cases can specify their customTag
.Syntax
Considering that we are dealing exclusively with classes1, the grammar for class declaration would be changed in the following way:
This would allow us to specify the type parameter default value with the following syntax:
Alternative syntax
Using
=
instead ofdefault
For default values in a parameter,
=
is used.We could use
=
also for default type parameter values.The syntax is terser.
However, one2 could argue that it's syntatically confusing when used with
extends
.In the first case,
T extends num = int
may give the idea that we are equallingnum
toint
. In the second case,T = int extends num
may give the idea thatint
extendsnum
(which is not entirely false, but in the context we want to mean thatT
extends num).There are other shorter keywords that could be used, but I don't think any of them is semantically clearer than
default
.Using parenthesis instead of
default
As suggested by @hydro63, an alternative would be to use parenthesis, in the following way:
This approach has the following pros:
=
;=
when used withextends
.The con, however, in my opinion, is that it does not conveys the semantics as good as
default
or=
.Someone who is just starting with Dart may understand what
T default int
means, but will hardly understand whatT(int)
means without reading the documentation.Semantics
T
be a type parameter of a classC
with default valueV
:T
has a boundU
andV <: U
3 does not hold;c
inC
, any parameter of typeT
inc
can have a default valuev
iffv
is constant andv
has a static typeV2
such thatV2 <: V
3;C
is used as a raw type:T
has no bound, the instantiation to bound algotithm should inferT
to beV
instead ofdynamic
;T
has a boundU
, the instantiation to bound algotithm should inferT
to beV
instead ofU
.Other questions
Should default type parameter value be inferred from constructor?
Consider this same code as before:
With the proposed changes above, it still wouldn't work, unless we specified
T default int
.We can infer
default int
from the default parameter of the constructor.If we have more than one parameter, like:
Then we could apply the LUB algorithm. In this case, it would be inferred as
T default num
.My personal opinion is that we shouldn't do this, and I would rather prefer for a default value for the type parameter to be always explicitly stated.
Footnotes
I think it is possible to extend this to non-classes too, like regular functions methods, but it's out of the scope of this proposal. If we do, we don't have to split
typeParameter
intoclassTypeParameter
. ↩https://github.com/dart-lang/language/issues/283#issuecomment-839603127 ↩
These semantics may be changed if we have statically checked declaration-site variance. For instance, if the type parameter
T
is contravariant with a default valueU
, we may want to guarantee thatT <: U
. ↩ ↩2The text was updated successfully, but these errors were encountered: