-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Construction of generic types "new T()" #30074
Comments
There is the obvious problem, which you also point out, that there is no static way to know which constructors a the type of a type parameter supports. In C#, you can require a type parameter to have a zero-argument constructor, but you can't pass any parameters. In Dart, that would correspond to only allowing zero-argument unnamed constructors to be used. That wouldn't allow your If you have to handle named constructors or constructors with arguments differently anyway, I'm not sure I think the feature is worth it. |
I get your point. I guess you'd have to introduce abstract named constructors? (Not sure if possible) And require T extends SomeAbstract class. class VertexList<T extends Vertex>{
Float32List vertexData;
int vertexSize;
VertexList(){
vertexSize = T.vertexSize;
}
T getAt(int i){
var offset = i * vertexSize;
return new T.fromView(new Fload32List.view(vertexData.buffer, offset, offset + vertexSize));
}
}
abstract Vertex{
Vertex.fromView(Float32List view); /// Abstract constructor?
}
class MyVertex extends Vertex{
static int vertexSize = 8;
Vector3 position;
Vector3 normal;
Vector2 uv;
MyVertex.fromView(Float32List view){
position = new Vector3.fromView(view);
normal = new Vector3.fromView(view);
uv = new Vector2.fromView(view);
}
}
|
This would mean potentially retaining constructor information for all |
We definitely do not want type parameters to carry static members in general (constructors are somewhere between static members and instance members). To make that useful, we'd have to make "static interfaces" that abstracts over a number of static classes, and then the classes basically just become a bunch of singleton objects. |
Closing per the above comments. |
It works in C++, and C++ is compiled, so...? template<class T>
struct Foo {
static void test() {
T::some_method();
}
};
We're going to close this feature request, even though we have unanimous 13-0 votes on the parent comment? Edit: #34131 and also a unanimous 0-13 downvotes here... |
That's a C++ template, which is quite different from anything in Dart. Dart generics are retained at run-time, as run-time parameters of the instances. We still want to statically detect errors, so we can't just allow you to pass any type without somehow ensuring that it will work everywhere that the type parameter flows. In order to allow It's just not a good match for an interface based, run-time generic language like Dart. |
In that case, what about something like this? class Foo<T extends BaseClass> {
static void bar() {
T.baz();
}
}
class BaseClass {
static void baz() {
}
} If |
Dart does not inherit static members, so a subclass of |
Ah, true. In that case, dart-lang/language#356 |
C++ does not need interfaces, because it allows for multiple inheritance and pure abstract class (possibly with virtual inheritance) serves the same purpose as interface. Moreover "type interfaces" were recently introduced and they are called "concepts". So please do not refer to C++ to justify Dart missing features. |
I ended up creating a factory like this. Full usage here.
|
And why runtime error would be wrong? And why C++ can statically detect such errors and Dart can't? What's the problem in checking whether constructor parameters are valid at compile time? With such attitude of the devs this language is going nowhere. |
C++ templates are basically macros. Each application of a C++ template to a list of actual template arguments (inferred or passed explicitly) gives rise to a separate piece of code, which is compiled. This means that any two different template instantiations could give rise to completely different generated code, and also that you can have compile-time errors in the body of a template during compilation of an instantiation. The former is a major problem because a C++ template is basically a copy-paste mechanism. With 10 different usages of a template The second major problem mentioned above is that C++ templates are basically untyped. We just pass some actual template arguments and produce some code (an instantiation of the template), and the type checker doesn't get to look at the code at all before it has been instantiated. (C++ concepts are intended to help with this, and they do help somewhat, but only if you get the requirements right.) Dart generics is statically typed (if the actual type arguments satisfy the bounds then there will never be a compile-time error in the body of a generic type declaration), and it never duplicates the compiled code of a generic type declaration. I don't think it's likely that a proposal to change Dart to use a macro-like notion of generics would get much support. So it's more about having a well-defined type parameterization mechanism, and avoiding code bloat, and less about attitudes. |
C++ templates are not macros. It's a turing-complete meta-programming language and of course it generates code at compile-time, because that turns out to be useful feature and its true purpose. C++ templates enable techniques such as static polymorphism, while mechanisms such as virtual methods allow you to obtain runtime polymorphism. These are two complementary mechanisms. If you say that this is a "problem" then you don't understand what it is. If I understand you correctly, then you seem to tell me that in Dart generics basically serve the same purpose as method overriding, which in case of Dart can be with achieved through interfaces or inheritance. So Dart generics are like a competing mechanisms that serves the same purpose. Why would you need that? What makes generics useful is what people in this thread are asking for. We need some meta-programming capabilities, not just alternative syntax to create virtual methods. |
Macro expansion can certainly be Turing complete. The untyped lambda calculus is Turing complete, and it is directly based on substitution. The point here is that C++ template expansion is not type checked, type checking only occurs after expansion. (Yes, C++ concepts allow you to specify certain constraints, but it is very different from an actual, sound type checking mechanism.)
Generics in Dart (as well as in most other programming languages, C++ is the exception) serve to define type parameters on a type or function declaration D, such that it is possible to obtain generic instantiations of D. Method overriding serves to allow OO dispatch to select a method implementation that is associated with the dynamic type of the given receiver. These two concepts are completely different. Just a simple example: In a version of Dart without type parameters, how would you use method overriding to achieve a similar effect as the choice between Sure, you could write a special |
Don't pick my sentences out of a context.
That's what I'm pointing out, do not turn things upside down. It is you who made a claim that C++ is somewhat problematic and there is something wrong about it, while in fact it utilizes the concept of generic programming to the very end. I'm also not saying that Dart generics are completely useless, but the whole point of using generics is to operate on abstractions like You don't need to copy-paste the code to obtain similar results, just use delegate design pattern (to obtain iterator) and perform some type casting. Older versions of Java did something similar with Collection interface. But again, that wasn't my point. |
So does it happen or not? Is the type checked after the expansion? If so, then why do you claim that template expansion is not type checked? This looks to me like self-contradicting statements, so I have troubles with understanding you. |
Certainly C++ templates are somewhat problematic, because they are not type checked, it is only each instantiation which is type checked (as usual, I should mention that concepts are capable of performing specific checks, but only the ones that you have remembered to ask about, so that's nowhere near the same thing as a static type check of the template). C++ templates do indeed allow for maximal flexibility when it comes to handling the situation where different template arguments give rise to completely different generated code. In that sense it is optimal: Nothing can give you more flexibility than not checking. When template instantiation has taken place, the resulting C++ code is type checked as usual. This makes template instantiation similar to other code generation mechanisms: If the generated code passes the compiler then you're lucky. If the generated code is rejected by the compiler then it's because you are starting from an unchecked entity: The template itself. Almost all other languages use statically checked type parameterization mechanisms, that is: If the bounds checks on actual type arguments succeed then the resulting generic instantiation is type correct. You never need to type check the body of a type parameterized entity after instantiation. C++ templates do not have that property.
I think you made my point here, not yours. ;-) |
And what's exactly problematic with this approach? You get compile time error in both cases, right? And C++ templates are not just glorified macros, they are type-aware. Template specializations are one manifestation of this. SFINAE is another one. |
The goal of static checking is to guarantee that you won't get runtime errors. Dart generics are designed to allow the same code to run on different types. The code is generic, it works for all types (or at least all types satisfying the type parameter bounds). Since we want to allow: SomeClass<T> createSome<T>() => SomeClass<T>(); and it's undecidable (in the general case) which, and how many, type arguments can flow into abstract class Builder<T> {
T create();
}
class ValueBuilder<T> {
final T value;
ValueBuilder(this.value);
T create() => value;
}
class ListBuilder<T> implements Builder<List<T>> {
final Builder<T> elementBuilder;
ListBuilder(this.elementBuilder);
List<T> create() => [elementBuilder.create()];
}
Builder nestedList<T>(int n, Builder<T> value) =>
n <= 0 ? value : nestedList<List<T>>(n - 1, ListBuilder<List<T>>(value)); If I write Imagine a "typing JSON parser", which actually tries to type the collections it builds to match the elements. That's possible in Dart, but again, the type instantiations are created at runtime. So, Dart's generic functions are not inherently inferior to C++'s macro expansion, it's different. It's similar to C# and Java's generics (apart from the types being remembered at runtime, so we can actually do something like |
@michpolicht wrote:
C++ templates are a time-proven language design element in C++, it allows for the maximal flexibility that I mentioned, and it allows for C++ code to remain as low-level and highly performant as we'd expect from C++. So the template design is a fact of life in C++, and C++ software is written to use the properties offered by this design, and it's all good. But C++ templates are also a leaking abstraction, in the sense that it allows us to define named entities whose internal consistency has not been established. For instance: template <class T, class U>
T GetMin(T a, U b) {
return (a<b?a:b);
}
int main() {
int i = 2;
std::cout << GetMin(1, &i);
return 0;
} If you instantiate this template with types
This means that the template as such is never type checked. Type checking only takes place on each instantiation. (Remember the usual disclaimer: concepts exist, but they are not a type checker.) That in turn means that if you create a library that contains template declarations then you can't know whether they will work with all the type arguments where the template is intended to work, and your customers will not have any knowledge about which type arguments will work and which ones will cause errors somewhere deep in the body of the template. That's what I mean when I say that the template as such is untyped. It doesn't specify (and hence, of course: doesn't enforce) any constraints on the template arguments, you just have to try to pass some actual template arguments and then it may or may not work. The core point in this discussion was basically the claim that Dart ought to adopt the core design decisions that were taken for C++ templates, and then we'd be able to do various desirable things. My argument why that wouldn't work well is that "that's not a good fit for Dart, we want typed abstractions". |
Sorry, I don't have much time right now, so I will try to make a full reply later. For now I just want to address following point
In both cases: C++ type substitution and "real type checking" you get compile-time error, so I don't understand what is the real problem here. Moreover you can enforce type checks for particular types with explicit template instantiation:
Yes, there is a problem that template users and implementers had to rely on conventions, but C++ concepts are there to solve this problem and they are richer version of what you can do in Dart with template constraints like:
Then good look with light-weight storage for incoherent collections like JSON. |
You get a compile-time error for an actual instantiation where the type arguments fail to satisfy some constraints that are derived directly from every little implementation detail in the body of the template. You can't conclude anything about the correctness of an instantiation of the same template with different type arguments, so you can't promise your customers that your template is correct. You'll have to leave it at "just go ahead and use it, it might work!".
They support the specification of requirements (e.g., You may think that this is not a problem, but Dart isn't likely to choose that point in the design space. |
With Dart you tell your customer "sorry construction of generic type won't work, because Dart devs think it's not useful/unsafe", so every stick has two ends. And TBH I still don't understand your point, because there's really no value in providing a generic class that works for all types, when only certain type properties are required (and concepts are a formal mechanism to establish it). Failed template substitution acts in the same way as Dart early type checking mechanism, errors are caught at compile-time - it does not make software product buggy and I've never heard about anybody having any real problems with what you describe here. |
(I'll stop here, I won't repeat the explanations given previously.) |
With strong mode becoming the default in 2.0
I was wondering if it would then be possible to add support for calling constructors based on generic types?
An example use case would be.
The text was updated successfully, but these errors were encountered: