Skip to content
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

Generics & Types that implement interfaces #2570

Closed
niteshb opened this issue Oct 13, 2022 · 6 comments
Closed

Generics & Types that implement interfaces #2570

niteshb opened this issue Oct 13, 2022 · 6 comments
Labels
request Requests to resolve a particular developer problem state-duplicate This issue or pull request already exists

Comments

@niteshb
Copy link

niteshb commented Oct 13, 2022

Please check the code at here

On line 13, the compiler reports the following error:
The method 'fromString' isn't defined for the type 'Type'.
Try correcting the name to the name of an existing method, or defining a method named 'fromString'.

I have tried to replace "extends" with "with" or "implements". Compiler doesn't like "with" or "implements". "extends" it tolerates.
I have tried mixins, abstract classes...

Yeah, won't work. That is trying to attack at the wrong place.
Because the error says "isn't defined for the type 'Type'". It just goes to "Type", checks its members, doesn't find "fromString", complains. Compiler doesn't see that this type may have static methods which are guaranteed to there because of "extends/with/implements". I would prefer "implements".

I think this should be easy to implement. Please enlighten me if I am wrong.
May be this is a duplicate of #356, but the discussion there seems to be going in multiple directions.

@niteshb niteshb added the request Requests to resolve a particular developer problem label Oct 13, 2022
@lrhn
Copy link
Member

lrhn commented Oct 13, 2022

You cannot call static methods on type parameters like T.

Static methods belong to the namespace introduced by the class/mixin/extension declaration they are inside, but not to the type.
(We make a few exceptions to that in type aliases, but only for type aliases which directly denote the type of a specific declaration, it's like we expand the type alias textually before calling the static member. It's not really on the type.)

Dart is statically typed. Since static members are not part of any type or interface, and are not inherited in any way. There is no way to statically know whether a static T.fromString exists in the declaration of the type bound to T. Even if it exists in the declaration of the type used as bound for the type variable, it doesn't necessarily exist in the declarations of subtypes.

Even if we allowed you to invoke a static method from the declaration of the type bound to T, we couldn't know the type of it.
That is what #356 is about.
We'd have to add a handful of new concepts to the language (static interfaces, static inheritance, virtual static methods, likely self-types) in order to make this work in any reasonable way. That's not high on our current priority list.

It's not easy to implement. I would defer to #356.

@lrhn lrhn closed this as completed Oct 13, 2022
@lrhn lrhn added the state-duplicate This issue or pull request already exists label Oct 13, 2022
@niteshb
Copy link
Author

niteshb commented Oct 14, 2022

Thanks for the response, Lasse.

May be I am wrong, but T.fromString is just a template. Compiler will need to know only when the template is used somewhere, and there it would/can know if the type invoking the template has fromString or not. Also not enforcing static members in subtypes feels very wrong and root of all these problems.

It seems this is more of a prioritization issue, but to me it also seems like a language design issue. Something is not feeling right with the language as it did with C++, Java & Python as I learnt them. Here the language seems messed up. Java was pain to code in, but didn't feel messed up in design.

I hope it doesn't go to dustbin because of these issues. There are so many competitors in market today.
Sorry for the rant but I liked Dart initially but it is getting harder to know where the boundaries are. With Java one knew the boundaries were all around, well defined. With Python there were none, I could essentially do anything. With Dart, one thinks something should work, only to be bitten to know and learn.

Again, really sorry for the rant, but am trying to iron out Future/then(onValue, onError)/value/error/catchError since morning. Finding random behaviour at different places. Never had that in any other language. Going after the mystical FutureOr now. Hopefully I get a hold of what really is going on there.

@Levi-Lesches
Copy link

May be I am wrong, but T.fromString is just a template. Compiler will need to know only when the template is used somewhere, and there it would/can know if the type invoking the template has fromString or not. Also not enforcing static members in subtypes feels very wrong and root of all these problems.

Consider the following:

class A { 
  A.fromString(String _);
}

class B extends A { /* Notice the lack of .fromString constructor */ }

T parseData<T extends A>(String data) => T.fromString(data);

void main() {
  A a = parseData<A>("hello");  // okay, calls A.fromString("hello")
  B b = parseData<B>("hello");  // B extends A but does not have a .fromString!
}

The compiler can see that this is a problem and tries to warn you: Just because B extends A (and therefore contains all its instance members, doesn't mean it also contains all its static members. Now, you could say that B extending A should force it to implement those static members, and that's what #356 is about. See the discussion there for some issues you can run into. To be clear, your feature request wasn't closed because it wasn't important, it was closed because #356 already covers your request and you should upvote that issue instead.

Sorry for the rant but I liked Dart initially but it is getting harder to know where the boundaries are. With Java one knew the boundaries were all around, well defined. With Python there were none, I could essentially do anything. With Dart, one thinks something should work, only to be bitten to know and learn.

This is a matter of getting to know the language. For someone who doesn't know private/protected/public, or the difference between a class and an interface, Java can seem weird and confusing. Give yourself some more time with Dart, you'll get the feel for it eventually.

Again, really sorry for the rant, but am trying to iron out Future/then(onValue, onError)/value/error/catchError since morning. Finding random behaviour at different places. Never had that in any other language. Going after the mystical FutureOr now. Hopefully I get a hold of what really is going on there.

The semantics of Future are pretty precisely defined in the documentation and are overall pretty similar to JavaScript's Promise, but I'd highly suggest you stay away from then, catchError, and FutureOr. Use async/await instead.

// pretend this does real stuff
Future<void> connectToServer() => Future.delayed(Duration(seconds: 1));

// instead of 
Future<String> getData() { 
  return connectToServer().then((_) => "Data").catchError((error) => "Error");
}

// try this
Future<String> getData() async {
  try {
    await connectToServer();
    return "Data";
  } catch (error) {
    return "Error";
  }
}

@lrhn
Copy link
Member

lrhn commented Oct 14, 2022

May be I am wrong, but T.fromString is just a template.

It's not.
The Dart language does not use the word "template" anywhere, it's not a concept that applies to Dart at all.
A Dart compiler does not copy the code for each type instantiation, and checks the code independently.

Instead it treats generic code as being actually generic, aka. working the same for all possible type instantiations.

In Dart, a type parameter represents an actual runtime argument to the generic thing. It exists at runtime (unlike Java, which erases type arguments).

A generic class which is instantiated carries along a concrete representation of the type argument, as if it was an instance member of the object. There is only one class, no templating, and that class has a type argument as part of its state.

A generic function can access the concrete type argument passed to it at runtime (and can pass it on to other functions or class instantiations). There is only one function body, no templating, and the function body can access the type argument passed to it.

Because of that, and Dart being statically typed, the only thing you can do with a type parameter is what you can do with all types. That's is to do subtype tests and use as a type argument again.

Dart is different from most other, otherwise similar, languages precisely in that it keeps the type arguments at runtime.
That allows doing some things that those other language cannot do (at least not soundly).

That sometimes get in the way, like

Future<int> f = Future<num>.value(1);
// or
List<int> l = <num>[1];

Those assignments are not allowed. Even though the created future/list definitely contains just integers, it's still a Future<num>/List<num> at runtime, which is-not a Future<int>/List<int>. The value cannot forget the type it was created with, because that type is part of the object.

But it also allows you to do something like:

class Collector<T> {
  List<T> values = [];
  bool tryAdd(Object? value) {
    if (value is T) { 
      values.add(value);
      return true;
    }
   return false;
}
void main() {
  var ints = Collector<int>();
  var strings = Collector<String>();
  var nulls = Collector<Null>();

  sort([ints, strings, nulls], [1, "a", 2, null, "b", null, 3.5]);

  print(ints.values); // [1, 2]
  print(strings.values); // [a, b]
  print(nulls.values); // [null, null];
}
void sort(List<Collector<Object?>> collectors, List<Object?> values) {
  for (var value in values) {
    for (var collector in collectors) {
      if (collector.tryAdd(value)) break;
    }
  }
}

(I'm sure it's possible in some other languges, but not in Java, and likely not in C#).

For now, accept that Dart is different. You cannot assume that you can carry over behavior from, e.g., C++ to Dart (or to Java or C# for that matter). I believe C# is closer to C++ in that it does do template instantiation, but mainly as an implementation trick. It also tries to be statically sound, which is why they're now introducing static virtual members. Dart would need something like that too, to support calling methods on type variables.

@niteshb
Copy link
Author

niteshb commented Oct 16, 2022

May be I am wrong, but T.fromString is just a template.

It's not. The Dart language does not use the word "template" anywhere, it's not a concept that applies to Dart at all. A Dart compiler does not copy the code for each type instantiation, and checks the code independently.

Ah! This is what I was missing. Thank you from the bottom of my heart. I assumed that with all the "type checking", templating would be happening in the background.

Because of that, and Dart being statically typed, the only thing you can do with a type parameter is what you can do with all types. That's is to do subtype tests and use as a type argument again.

Dart is different from most other, otherwise similar, languages precisely in that it keeps the type arguments at runtime. That allows doing some things that those other language cannot do (at least not soundly).

... and that is a design choice I guess, which, for now, is making me very uncomfortable. As I am imagining this with all the other choices Dart has made, and looking at your examples, I am thinking that this doesn't seem to go well with the love of programming. Basically it feels like Dart language designers are not thinking like users of language but just designers of language, focusing too much on tough optimization and patting their own backs. In this specific case, I strongly think that this should be a runtime-error, not compile-time error, at worst. If the programmer is already mentioning extends/with/implements, what is the compiler trying to save the programmer from? (Her/Him)self? No smart language should try to do that. I would say this is a bad choice made by dart-lang-designers. If it has come to this then may be it is time to rethink the set of design choices.

Again thanks for the detailed explanations, and it is early days for me with Dart, but I have already discovered many weird asks from programmers by designers (like requiring to mention void in some places and not others).

Thanks as well for the suggestions on onError/FutureOr/catchError. I was able to conquer that mess and boy it is a mess. Will post some bugs/suggestions around it after rethinking along the points made by you in your replies above.

@lrhn
Copy link
Member

lrhn commented Oct 17, 2022

The one feedback we have consistently received from our users so far, is that they prefer compile-time errors over runtime errors. Dart has moved quite some way in that direction. Dart 1.0 was much, much more dynamic and made most things runtime errors. You could get away with anything. Even checking the types of variables was optional.
The actual users were not amused.

If anything, retaining type arguments at runtime was a consequence of that choice, because Dart 1.0 didn't use static types for much, or anything unless you asked for it. Checking them at runtime was all you could do (if you asked for it).
It has turned out to actually be a very powerful feature, because of the things it allows by using the type at runtime.

If you consider the goal to be "no runtime errors", then we're still some way off. Dart is not Haskell or SML, it doesn't necessarily run just because it compiles. However, the unsafe places are well known and statically detectable, which is why Dart can have a fairly efficient sound runtime model, because it knows where to insert the checks that prevent unsoundness, and where it can just let well-typed values pass through.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem state-duplicate This issue or pull request already exists
Projects
None yet
Development

No branches or pull requests

3 participants