Chen Li Yong
Chen Li Yong

Reputation: 6087

How to specify generic type such that we can use some operation on the generic type in Flutter?

For example, I expect <T extends type> can works like this:

Class Parent {
  String data;
  Parent({ this.data });
}

Class Child extends Parent {
  Child({ this.data }) : Parent(data: data);
  void showData() { print(data); }
}

T wrapper<T extends Parent>(String value) {
  var result = T(data: value);
  return result;
}

void main() {
  var trial = wrapper<Child>("Hello world");
  trial.showMessage(); // print "Hello world"
}

But turns out it gives me error at var result = T(data: value);, saying that T is not a function. When I specified , I expect that T can be operated like Parent class, and if I supplied its descendant like Child, the operation done will be Child instead. But the constructor will work either way because T extends Parent. Is such thing possible?

Upvotes: 1

Views: 979

Answers (1)

nvoigt
nvoigt

Reputation: 77285

Constructors are not inherited. You know that already, because you wrote one yourself in your child class that does nothing but call the base class with the same parameters.

One could as well write a different constructor. So "X extends Y" says a lot about X, but it does not say anything about how the constructor of X looks (or whether it even has a constructor accessible in that scope). So a constructor call is not in the properties available to you when you specify your generic to "extend Y", because Y can do exactly nothing to make sure all it's derivates follow a specific construction method.

Different languages deal with the problem of "but how do I construct a new instance of my generic type" in different ways, but the underlying concept is common to almost all concepts of generics where the generic code is compiled before knowing the specific types of all T's handled. A constructor is not inherited in most OOP languages, therefor it is not guaranteed to be there for any "X extends Y" even if Y has it.

It might be easy to overlook when you have all your code in one compilation unit. The compiler should be able to figure it out, right? But your code might not be in a single compilation unit:

Codebase one:

Class Parent {
  String data;
  Parent({ this.data });
}

T wrapper<T extends Parent>(String value) {
  var result = T(data: value);
  return result;
}

At this point, the compiler has no idea what "Child" might look like. It cannot possibly determine that the child class that will be used in the Future has a constructor like that.

Codebase 2:

Class Child extends Parent {
  Child({ this.data }) : Parent(data: data);
  void showData() { print(data); }
}

void main() {
  var trial = wrapper<Child>("Hello world");
  trial.showMessage(); // print "Hello world"
}

Now, at this point, a compiler could figure out that the program it's given would actually work. Some concepts of generics do that, where generics cannot be compiled into independent libraries, they always come as source code, because only the final compiler producing the executable can determine whether it would work with a specific class. Flutter does not do this. Flutter needs the generic itself be valid for the constraints given.

All newer language's versions of generics have followed the path of knowing the constraints beforehand and only allowing code operating inside those constraints. And I think it's good because while it has it's shortcomings, it leaves less room for errors or cryptic error messages.

Upvotes: 1

Related Questions