From a String to a Type usable as a type argument - dart

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));

Related

class parameter or class identifier query

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.

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

`nameof` operator in flutter

There is nameof operator in C#, it allows to get property name at compile time:
var name = nameof(User.email);
Console.WriteLine(name);
//Prints: email
It is not possible to use reflection in flutter and I do not want to hardcode names of properties i.e. to be used for querying SQLite tables. Is there any workaround?
***Currently I'm using built_value library.
For the archives, I guess, this isn't possible as Dart doesn't store the names of variables after compiling, and as you mentioned, Flutter doesn't support reflection.
But you can still hardcode responsibly by grouping your properties as part of the object that they belong to, like with JSON:
class User {
final String email;
final String name;
const User({required this.email, required this.name});
Map toJson() => {
"email": email,
"name": name,
};
}
Instead of remembering to type out "email" and "name" whenever you use User, just call User.toJson(). Then, when you want to rename your variables, you can use your IDE's "rename all", or just skim over your User class to quickly change all of the names without missing any.
I'm currently monitoring the progress on the dart:mirrors package, which offers some neat reflective properties and methods, though, I hadn't found a simple way to just get the name of a symbol like nameof() does.
Example:
import 'dart:mirrors';
class User {
final String email;
final String name;
const User({required this.email, required this.name});
}
void main() {
reflectClass(User).declarations.forEach((key, value) {
print(value.simpleName);
});
}
Output:
Symbol("email")
Symbol("name")
Symbol("User")
These are of type Symbol.
More here: https://api.dart.dev/stable/2.4.0/dart-mirrors/dart-mirrors-library.html
So, until they develop a nameof, I've created an extension on symbol:
extension SymbolExtensions on Symbol {
String get nameof =>
RegExp(r'"(.*?)"').firstMatch(toString())!.group(1).toString();
}
So, you could do:
print(reflectClass(User)
.declarations[#email)]!
.simpleName
.nameof);
Output:
email
It's a start. Dart is still growing.
You can use code generation.
The basic idea is to create a nameof annotation class and mark parts of your code with it. You also need to create a code generator that handles your annotated code. Look at the json_serializable package for an example and create your own code generator.
If you do not want to create your own generator, use a ready-made package nameof: https://pub.dev/packages/nameof
Short how-to with this package.
Mark your class with nameof annotation.
#nameof
class Car {
final double price;
final double weigth;
final int year;
final String model;
Car(this.price, this.weigth, this.year, this.model);
Car.sedan(double price, double weigth, int year)
: this(price, weigth, year, 'Sedan');
}
Run the code generator.
flutter pub run build_runner build
Then use the generated class, which will look something like this.
/// Container for names of elements belonging to the [Car] class
abstract class NameofCar {
static const String className = 'Car';
static const String constructor = '';
static const String constructorSedan = 'sedan';
static const String fieldPrice = 'price';
static const String fieldWeigth = 'weigth';
static const String fieldYear = 'year';
static const String fieldModel = 'model';
}
You can implement your own nameOf function:
String? nameOf(dynamic o) {
if (o == null) return "null";
try {
if (o is List) {
var first = o.length > 0 ? o[0] : null;
if (first != null) {
var elementType = nameOf(first)!;
Log.debug("nameOf: List<$elementType>");
if (!isMinified(elementType))
return "List<$elementType>";
}
} else {
Function? getTypeName = o.getTypeName;
if (getTypeName != null) return getTypeName();
}
} catch (e) {
Log.debug("ignored nameOf error: $e, falling back to o.runtimeType: ${o.runtimeType}");
}
return o.runtimeType.toString();
}
bool isMinified(String type) => type.startsWith("minified:");

Generics type parameter wildcard

I would like to create an abstract class which takes a type parameter and the constructor of that class should be passed another Action eg.
abstract class Action<Tc> {
public function __construct(private ?Action<*> $onSuccess = null) {}
}
How can I express a type parameter wildcard ie. "?" (Java) or "_" (Scala) in Hack?
Hack doesn't have wildcard type parameters right now, so the closest you can get is actually specifying a dummy type parameter that you don't actually need, e.g.,
abstract class Action<Tc, Ta> {
public function __construct(private ?Action<Ta> $onSuccess = null) {}
// ...
}
Depending on how exactly you use the $onSuccess member variable, you may want it to be some specific subclass of Action<T> to be determined later, and so you may want something like this:
abstract class Action<Tc, Ta, To as Action<Ta>> {
public function __construct(private ?To $onSuccess = null) {}
// ...
}
However, I question whether the "dummy" types above above are really a dummy -- the vast, vast majority of use cases of Action<T> are going to care what exactly the T is, otherwise how exactly would you use the Action<T>? (There are certainly rare cases where you don't care about the T at a callsite, but they are, well, rare and so I encourage you to consider whether that is actually your case as you build out this functionality.)
Not sure about a wildcard, but could this achieve what you want?
<?hh
abstract class Action<T1 as Action, T2> {
public function __construct(private ?T1 $onSuccess = null, private ?T2 $bla = null) {}
}
class ActionA<T1 as Action, T2> extends Action<T1, T2> {}
class ActionB<T1 as Action, T2> extends Action<T1, T2> {}
class ActionC<T1 as Action, T2> extends Action<T1, T2> {}
$action = new ActionA(new ActionB(new ActionC(null)));
var_dump($action);
When I run this against HHVM 3.1.0, I get:
object(ActionA)#1 (2) {
["onSuccess":"Action":private]=>
object(ActionB)#2 (2) {
["onSuccess":"Action":private]=>
object(ActionC)#3 (2) {
["onSuccess":"Action":private]=>
NULL
["bla":"Action":private]=>
NULL
}
["bla":"Action":private]=>
NULL
}
["bla":"Action":private]=>
NULL
}
And the 3.1.0 type checker also returns "No errors!".
However, the T1 as Action statement on the abstract class doesn't appear to be enforcing. For instance, I can change the instantiation line to:
$action = new ActionA(new ActionB(new ActionC(new DateTime())));
And it hums along fine, with the typechecker returning no errors still. And this is after taking the class definitions out into their own file with <?hh // strict.
So not really your answer, but perhaps close? The behavior above might suggest Hack has some issues with this sort of pattern?

Syntax error when trying to determine if a variable is of a certain type

Pretty much as the title says: If you have a Type stored in a variable, there's no way to compare your actual object to this type variable, as far as I can tell. I can probably accomplish what I'm trying to do with mirrors, but I'd prefer not to if at all possible.
void example() {
Type myType = String;
String myExample = "Example";
//Syntax error here: The name 'myType' is not a type and cannot be used in an 'is' expression
if (myExample is myType) {
}
}
You can't generally test if a value is of a type using the Type object.
Type objects are reflected types, not real types. They represent the real type, but you can't use them in the code where you need a type: as type assertions, as generic type parameters or with the is/as operators. You must use the name of a type in those places, and not the name of a normal variable that happens to hold a Type object.
Clever stuff using mirrors might get there, but it's likely overkill for most cases (and I understand that you don't want it).
What you might be able to do instead, is to not pass around raw Type objects. You could instead make your own type abstraction, something like:
class MyType<T> {
const MyType();
Type get type => T;
bool isA(Object object) => object is T;
}
Then you can use that to represent types, not a Type object, and do something like:
void main(List<String> args) {
MyType myType = const MyType<String>();
String myExample = "Example";
if(myType.isA(myExample)) {
print('is');
} else {
print('is not');
}
}
That does require that your entire program uses your type objects to pass around types, but it also gives you a lot of control over those objects, so you can implement the functionality that you need.
I tried
library x;
void main(List<String> args) {
Type myType = String;
String myExample = "Example";
if(myExample.runtimeType == myType) {
print('is');
} else {
print('is not');
}
}
and it worked.
I have not much experience with such code in Dart though. Maybe that is not a fail-safe approach.
import 'package:reflection/reflection.dart';
void main() {
var childType = typeInfo(Child);
var baseType = typeInfo(Base);
if(childType.isA(baseType)) {
print("Child is Base");
}
if(baseType.isAssignableFrom(childType)) {
print("Base is assignable from Child");
}
}
class Base {
}
class Child extends Base {
}
Child is Base
Base is assignable for Child
P.S.
The "reflection" package incompatible with dart2js. It work only when used in Dart language.

Resources