Callback with generic type parameter in Dart
Asked Answered
D

2

12

I'm trying to define a callback function that needs to accept a generic parameter and return a value of the same type. Keep in mind that the following example is an oversimplified version of what I actually need.

final T Function<T>(T value) self = (value) => value

This results in the following error, that I can't seem to get rid of.

The argument type '(dynamic) → dynamic' can't be assigned to the parameter type '<T>(T) → T'

dart(argument_type_not_assignable)

The only thing that seems to work is to give the value a type, but that defeats the purpose of using a type parameter in the first place.

final T Function<T>(T value) same = <String>(value) => value;

I need it to be generic so that the caller can pass the type it expects in return. I also need it to be stored in a variable, so I can pass it around as a callback function.

If this isn't directly possible, do you know of any workarounds? Thank you in advance.


Here's a more complete example if the requirements aren't clear.

abstract class Provider<T> {
  T get bar;
}

class StringProvider extends Provider<String> {
  String get bar => 'bar';
}

class NumberProvider extends Provider<int> {
  int get bar => 42;
}

class Foo {
  final T Function<T>(Provider<T> provider) provide;

  const Foo({ this.provide });
}

test() {
  final foo = Foo(provide: (provider) => provider.bar);

  String strValue = foo.provide(StringProvider()); // should be 'bar'
  int numValue = foo.provide(NumberProvider()); // should be 42
}

The annoying thing is that Dart actually understands that foo.provide(StringProvider()) will return a string and that using NumberProvider will indeed return an integer, and yet, the error is still risen for the line where the variable is actually given a value.

final foo = Foo(provide: (provider) => provider.bar);
The argument type '(dynamic) → dynamic' can't be assigned to the parameter type '<T>(Provider<T>) → T'
Drumfish answered 25/4, 2019 at 11:17 Comment(0)
D
12

It turns out that I can cheat the type checker by giving any concrete type when defining the value. Note that dynamic is not allowed, but anything else goes.

final foo = Foo(provide: <int>(provider) => provider.bar);

This both gets rid of the error and allows for the provide method to return the correct type when called.

In conclusion, this seems like a simple shortcoming of the type checker, not something that's actually impossible or difficult to achieve using the already existing language features. I will be raising an issue on the language's GitHub repository to allow for further investigation and discussion.


Update #1: the issue has been opened on GitHub.


Update #2: the issue has been resolved, and it turns out that the behavior is by design. Quoting Erik Ernst from the SDK team:

Try this: final foo = Foo(provide: <T>(Provider<T> provider) => provider.bar);!

The problem is that you're passing a non-generic function to the Foo constructor, and you should pass a generic function. There is no subtype relationship between a generic function type and a non-generic function type, so according to the type checker you might as well pass a String, and that's the reason for the 'can't be assigned to' message.

It turns out that simply adding <T> before the parameter list (instead of int as in the original workaround) solved the issue.

final foo = Foo(provide: <T>(provider) => provider.bar);

This forces Dart to understand that provider is of type Provider<T> and that the method will return a value of type T, saving us from using a concrete type and still getting rid of the error.

Drumfish answered 25/4, 2019 at 13:23 Comment(0)
D
0

You have to define a standalone generic function:

class Foo {
  final T Function<T>(Provider<T> provider) provide;

  const Foo({ this.provide });
}

main() {
  T f<T>(Provider<T> provider) => provider.bar;
  final foo = Foo(provide: f);

  String strValue = foo.provide(StringProvider()); // should be 'bar'
  int numValue = foo.provide(NumberProvider()); // should be 42
}
Didache answered 25/4, 2019 at 12:30 Comment(4)
Unfortunately, this defines a function - one that you would use in a class, for example - not a standalone variable. I need this to be the latter so I can store it and pass it around as a callback function. I've added this remark to my original question.Drumfish
Without your addition I had guessed that self was a top-level variable.Didache
Sorry for the confusion then, I appreciate the heads up. Your updated answer gets me closer, but still not all the way. Giving Foo the type parameter gets rid of the errors, but this way foo.provide just returns dynamic regardless of the passed parameter.Drumfish
I've added my own answer as I've managed to find a way to cheat the type checker that gets me the results I needed. I appreciate you trying to help, in any case.Drumfish

© 2022 - 2024 — McMap. All rights reserved.