class parameter or class identifier query - dart

I am new in Dart and works on an app that has a class look like this:
abstract class BaseUseCase <In,Out> {}
My question is then, what is In and Out?

In and Out are type arguments. They are used to allow the code in the class to use objects of an unknown type while remaining consistent and type-safe.
For example, say you wanted to have a method in a class that would take a list of any type, perform a string conversion operation on every element, and then return a strongly typed map of results:
class Stringifier<T> {
Map<T, String> stringify(List<T> input) =>
{for (final entry in input) entry: input.toString()};
}
void main() {
Stringifier<int>().stringify([1, 2, 3]);
// Result: <int, String>{1: '1', 2: '2', 3: '3'}
}
Note that the return type and input argument type use the generic T type. This ensures that only a list of the given type can be passed in, and that the resultant map will have the correct key type.
Type arguments can be used in other declarations as well, such as function declarations - indeed, the example above can be simplified, and declared outside a class:
Map<T, String> stringify(List<T> input) { /* ... */ }
More information on generics can be found in the Dart Language Tour, as well as in the "Generics" section of the Dart 2 language specification.

Related

Restrictions on Type in dart

So, basically I need to create restrictions of which types can be used in a Type variable, something like this:
class ElementFilter<T extends Element> {
final Type<T> elementType; // What I want is something like Type<T>, but Type does not have a generic parameter
ElementFilter(this.elementType);
}
List<T> filterElements<T extends Element>(ElementFilter<T> element) {
return elements.where((el) => _isOfType(el, element.type)).toList();
}
filterElements(ElementFilter(ClassThatExtendsElement)); // Would work fine
filterELements(ElementFilter(String)); // Error, String does not extends Element
So it would only be possible to create ElementFilters with types that extend Element. Is this possible in some way?
I think you probably want:
/// Example usage: ElementFilter<ClassThatExtendsElement>();
class ElementFilter<T extends Element> {
final Type elementType;
ElementFilter() : elementType = T;
}
Unfortunately, there's no way to make the generic type argument non-optional. You will have to choose between having a required argument and having a compile-time constraint on the Type argument.
Dart doesn't support algebraic types, so if you additionally want to support a finite set of types that don't derive from Element, you could make specialized derived classes and require that clients use those instead of ElementFilter. For example:
class StringElementFilter extends ElementFilter<Element> {
#override
final Type elementType = String;
}
(You also could create a StringElement class that extends Element if you want, but at least for this example, it would serve no purpose.)
I highly recommend not using Type objects at all. Ever. They're pretty useless, and if you have the type available as a type parameter, you're always better off. (The type variable can always be converted to a Type object, but it can also be actually useful in many other ways).
Example:
class ElementFilter<T extends Element> {
bool test(Object? element) => element is T;
Iterable<T> filterElements(Iterable<Object?> elements) =>
elements.whereType<T>();
}
List<T> filterElements<T extends Element>(ElementFilter<T> filter) =>
filter.filterElements(elements).toList();
filterElements(ElementFilter<ClassThatExtendsElement>()); // Would work fine
filterElements(ElementFilter<String>()); // Error, String does not extends Element

Type Erasure with dart LinkedList?

This is the program:
import 'dart:collection';
class MyLinkedListEntry<T> extends LinkedListEntry<MyLinkedListEntry> {
T value;
MyLinkedListEntry(T this.value);
#override
String toString() => '${super.toString()}: ${value}';
}
void main(List<String> args) {
var l = LinkedList<MyLinkedListEntry>();
var s = MyLinkedListEntry("SomeString");
var p = MyLinkedListEntry(125);
l.add(s);
s.insertAfter(p);
p.insertAfter(MyLinkedListEntry(126));
l.forEach((e) => print(e));
}
And it gives this output:
Instance of 'MyLinkedListEntry<String>': SomeString
Instance of 'MyLinkedListEntry<int>': 125
Instance of 'MyLinkedListEntry<dynamic>': 126
I expected the third instance to be of type LinkedList<int> as well. Why it's not?
This is with Dart 2.13.4.
0. dynamic in type checking
Everything is a subclass of dynamic:
print(1 is dynamic); // Outputs true
print("a" is dynamic); // Outputs true
In fact, Dart even shows a warning when using the above code: Unnecessary type check; the result is always 'true'.
1. Omiting type parameters in declarations
In the declaration
class MyLinkedListEntry<T> extends LinkedListEntry<MyLinkedListEntry>
note that you're not passing the type parameter of MyLinkedListEntry in the type parameter of LinkedListEntry. From docs (emphasis mine):
When a generic class is instantiated without explicit type arguments, each type parameter defaults to its type bound [...] if one is explicitly given, or dynamic otherwise.
So Dart interprets this as
class MyLinkedListEntry<T> extends LinkedListEntry<MyLinkedListEntry<dynamic>>
2. The extends clause in type parameters
Let's look at the declaration of LinkedListEntry:
abstract class LinkedListEntry<E extends LinkedListEntry<E>>
Note that LinkedListEntry requires a type parameter named E, which must be a subclass of LinkedListEntry. When you use LinkedListEntry<E>, E must extend LinkedListEntry<E>.
When you declare MyLinkedListEntry<T>, you're passing MyLinkedListEntry<dynamic> as E. Since T always extends from dynamic, MyLinkedListEntry<T> extends LinkedListEntry<MyLinkedListEntry<dynamic>>, so this is a valid declaration.
3. Type parameters in methods
In the expression
p.insertAfter(MyLinkedListEntry(126));
you're using the insertAfter method declared in the LinkedListEntry class. Let's look at its declaration:
void insertAfter(E entry)
Since E is equal to MyLinkedListEntry<dynamic>, Dart will interpret any MyLinkedListEntry call to this method as
void insertAfter(MyLinkedListEntry<dynamic> entry)
Therefore, when you do
p.insertAfter(MyLinkedListEntry(126));
you're actually passing an upcasted MyLinkedListEntry<dynamic>, which explains the output.
The solution
Explicitly pass the type parameter of MyLinkedListEntry when inserting:
p.insertAfter(MyLinkedListEntry<int>(126));

Capture a generic runtime type of an object in a function

Consider the following minimal example:
class Element<T> {}
final elements = [
Element<String>(),
Element<int>(),
];
Element<T> transformation<T>(Element<T> element) => Element<T>();
void main() {
// prints 'Element<String>', 'Element<int>'
for (final element in elements) {
print(element);
}
// prints 'Element<Object>', 'Element<Object>',
// but I would like to get identical output as above.
for (final element in elements) {
print(transformation(element));
}
}
I can't figure out how to create a transformation of each Element<T> with its respective generic runtime type T, not the static Object type of the elements collection.
I remember having seen some complicated trick with nested closures in the past, but can't seem to find it anymore. How can I make my function capture the runtime type of T, without having to implement the transformation function in the Element class itself?
That's not possible.
The only way to get access to the type variable of an object is if the object gives it to you.
If Element had a method like
R callWith<R>(R Function<T>(Element<T>) action) => action<E>(this);
then you could get access to the E of Element<E> as a type variable.
You can't do that from outside the class.

From a String to a Type usable as a type argument

I'm creating a Money class in Dart and came up with the idea of leveraging the type system to make sure you can't subtract Swiss Francs from Dollars (as opposed to this). This works swimmingly, significantly abbreviated what I have looks as follows:
abstract class Currency {
const Currency(this.precision, this.code);
final int precision;
final String code;
}
class Chf extends Currency {
const Chf() : super(2, 'CHF');
}
class Usd extends Currency {
const Usd() : super(2, 'USD');
}
class Money<T extends Currency> {
Money<T> subtract(Money<T> other) { ... }
}
It is impossible to subtract USDs from CHFs. Great. (More complete code here.)
But I'm receiving JSON payloads with currency representations therein. I now need to go from a String 'CHF' to a Money<Chf> instance. I can't figure out how to do that.
Somewhere I need to map a (currency code) string to a Type.
final t = convertStringToType('CHF');
final m1 = Money<t>(100); // <- I can't do this: 't' isn't a type.'
The only option forward I see is having a large (colossal) switch/case statement:
switch(code) {
case 'CHF':
return Money<Chf>(100);
...
}
Clearly I don't want that. Is there a better way?
Type arguments cannot be created from scratch, they have to be actual types or other type variables. So, if you need <Chf> as a type argument, you need to have <Chf> as an actual type argument somewhere in your program. Then you can piggy-back on that.
I would do something like:
T callWithCurrency<T>(String name, T action<X extends Currency>()) {
if (name == "CHF") return action<Chf>();
if (name == "USD") return action<Usd>();
...
throw ArgumentError.value(name, "name", "Not a recognized currency");
}
and then you can use it as:
final Money m1 = callWithCurrency("CHF", <X extends Currency>() => Money<X>(100));

How to get the subtypes of a generic type using `DartType` from `analyzer` package?

How can I get the subtypes of an element using the class DartType from the analyzer package?
for example if the type is List<String>, I would like to get String. Also will be useful to get if the type is generic.
Another more complex example would be Map<String, String> where I want to get a list of the subtypes, in this case: [String, String].
This one is a little tricky - because DartType actually itself has some super types - the one that will interest you here is ParameterizedType:
import 'package:analyzer/dart/element/type.dart';
Iterable<DartType> getGenericTypes(DartType type) {
return type is ParameterizedType ? type.typeArguments : const [];
}
I don't know if it's possible to know if the type is generic - after all, it's just a type. But you can check if the type accepts generic parameters, again, using ClassElement:
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
bool canHaveGenerics(DartType type) {
final element = type.element;
if (element is ClassElement) {
return element.typeParameters.isNotEmpty;
}
return false;
}
Hope that helps!

Resources