The following function is an example that represents a more complex situation that can not be simplified:
String? intToString(int? i) {
if (i == null) return null;
return i.toString();
}
Can this function be rewritten using generics in a way that its return type is a String
instead of a String?
when the parameter is an int
?
To make things clearer, here is an example where the nullability of the return type depends on the argument:
void main() {
List<String> non_nullable = ['A', 'B'];
print(first(non_nullable).length); // Does compile
List<String?> nullable = ['A', 'B', null];
print(first(nullable).length); // Fails to compile
}
T first<T>(List<T> ts) {
return ts[0];
}
But can I do this when the parameter type and the return type are different?
You cannot. Nothing in the Dart type system allows two types to co-vary such that you get either String?
-and-int?
or String
-and-int
.
You cannot abstract over nullability. (Or asynchrony, or any other monad-like type abstraction.)
What's needed for that is higher order types, also called "modules" in some languages, which work like a function for types, so that you can apply the same type function (either the identity or applying ?
) to the type.
The Dart type system does not have higher order types in any way.
If it did, something hypothetically like a generic type argument:
M<String> first<M<T> extends T?>(M<int> value) => ...
that's still unlikely to be able to work without some functionality that works on M<T>
values in general, which is why modules both create types and have functions in them.
(And maybe a typedef Nullable<T> = T?;
would be a valid type argument to such a function. But really to make sense, it needs to also abstract over
static operations.)