How to return two specific types from a generic method? - dart

T getValue<T>(int i) {
if (T == String) return '$i'; // Error
return i; // Error
}
void main() {
var s = getValue<String>(1);
var i = getValue<int>(1);
}
I want getValue to return string if T is String and int otherwise. How to do that?

You can't restrict the type parameter to just int or String, so it will have to accept more than that (at least their least common supertype, Object, so basically any type).
It's not a particularly helpful way to code. It's possible, but not recommended:
T getValue<T>(int i) {
if (i is T) return i;
return "$i" as T;
}
This will return the int if T allows it (so T being any of int, or a super type of int, which is num, Object, dynamic or void, or any number of Comparable<X> wrappings around any any of those supertypes), and otherwise try to return a string. That will fail with a type error unless T is String (since we've already ruled out all supertypes of String).
You can still call it as getValue<bool>(42) and watch it fail, so the type argument doesn't help with correctness.
It's not particularly effective. I'd rather do:
dynamic getValue(int i, {bool toString = false}) {
if (toString) return "$i";
return i;
}
and call it as:
String x = getValue(42, toString: true); // Add `as String` if you disable downcasts.
int y = getValue(42); // And `as int` here.
The type parameter is really just making things harder. You are going to cast or type-check the result anyway, so might as well do it at the call point, rather than introduce type variables that aren't actually preventing misuse anyway.
(I'd probably just do two different functions, but I assume that there is a reason for wanting one function).

As I mentioned in the comments, I don't see any way that you could use your generic as the return type of your getValue function. Even assuming the return under the if statement worked, there is nothing that can be done about trying to return int i when List is passed as the type. You'll be trying to return an int as a List.
If you change it to dynamic, your code will work fine as it's just using the generic as another parameter.
dynamic getValue<T>(int i) {
if (T == String) return '$i';
return i;
}
void main() {
var s = getValue<String>(1);
var i = getValue<int>(1);
}

Related

How to convert int to double and vice-versa when parsing using num.parse?

Minimal reproducible code:
void main() {
print(foo<int>('1'));
print(foo<double>('1.0'));
print(foo<double>('1'));
print(foo<int>('1.0'));
}
T foo<T extends num>(String s) {
final res = num.parse(s);
return (T == double ? res.toDouble() : res.toInt()) as T;
}
As you can see I'm manually handling the types. Is there any better way to do it?
I don't see a much better solution. You have a function which changes behavior entirely based on the type argument. Since all you can do with a type argument is subtype-checks, or comparing it to a constant type literal, you need to do something like that.
I'd prefer subtype-checks, because that also allows promotion, but for something limited like this, where there are only four possible types for T, checking Type object equality can also work. There'll just need to be at least one as T before returning.
Either approach also only works for type hierarchies which are finite, and you account for all the possible types. Even here, the current code is not covering <num> and <Never> which are also valid type arguments for the bound num. So, be vigilant.
Maybe, using subtype checks for promotion:
T parse<T extends num>(String source) {
var value = num.parse(source);
if (value is T) return value;
// T is not `num`.
num d = value.toDouble();
if (d is T) return d; // T was double.
try {
num n = value.toInt(); // Can fail for Infinity/NaN
if (n is T) return n; // T was int
} catch (_) {
// T was `int` after all, so throwing was correct.
if (1 is T) rethrow; // T was int, but the input was not valid.
}
// T was neither num, double, nor int, so must be `Never`.
throw ArgumentError.value(T, "T", "Must not be Never");
}
Or. using Type object equality:
T parse<T extends num>(String source) {
var value = num.parse(source);
switch (T) {
case num: return value as T;
case int: return value.toInt() as T;
case double: return value.toDouble() as T;
default:
throw ArgumentError.value(T, "T", "Must not be Never");
}
}

Dart: Reference a variable name from the variable itself

I’m trying to get the variable name instead of the value to pass it to another method
eg.
String myString = "xyz";
String getVariableName(String s){
// What i want is if the above string is passed [getVariableName(myString)]
//i want it to return ['myString']
}
This is generally not possible. At best, if there are a finite number of String variables you care about, you could manually make a collection of them, and then search it:
final stringVariables = <String, String Function()>{
'myString': () => myString,
'myOtherString': () => myOtherString,
};
var myString = 'xyz';
var myOtherString = 'abc';
String getVariableName(String s) {
for (var entry in stringVariables.entries) {
if (entry.value() == s) {
return entry.key;
}
}
return '';
}
void main() {
print(getVariableName('xyz')); // Prints: myString
myString = 'foo';
print(getVariableName('foo')); // Prints: myString
}
Note that stringVariables's above must use a Function as a thunk to delay evaluation of the variable; otherwise the variable name would be associated with whatever value it happened to have when stringVariables is first accessed and wouldn't work if your variables are reassigned.
All that said, I don't really recommend doing any of this. This sounds like an XY problem. You should be asking about whatever it is you ultimately want to do, and there probably is a better way to accomplish that task.

+ isn't defined for the type 'Object

I keep getting this error => "The operator '+' isn't defined for the type 'Object'. (view docs). Try defining the operator '+'."
How could I define it?
import 'dart:math';
bool isArmstrongNumber(int number) {
var numberString = number.toString();
return number ==
numberString.split("").fold(0,
(prev, curr) => prev! + pow(int.parse(curr), numberString.length));
}
main() {
var result = isArmstrongNumber(153);
print(result);
}
fold in Dart can have some problems when it comes to automatically determine what type it should return and handle. In these cases, we need to manually enter the type like this (fold<int>()):
import 'dart:math';
bool isArmstrongNumber(int number) {
final numberString = number.toString();
return number ==
numberString.split("").fold<int>(
0,
(prev, curr) =>
prev + pow(int.parse(curr), numberString.length).toInt(),
);
}
void main() {
final result = isArmstrongNumber(153);
print(result); // true
}
I also fixed a problem where pow returns num which is a problem. In this case, we can safely just cast it to int without issues.
Details about this problem with fold
The problem here is that Dart tries to guess the generic of the fold based on the expected returned type of the method. Since the == operator expects an Object to compare against, fold will also expect to just return Object and the generic ends up being fold<Object>.
This is not a problem for the first parameter since int is an Object. But it becomes a problem with your second argument where you expect an int object and not Object since Object does not have the + operator.

Using an 'is' expression when the right-hand operand is a variable?

I am trying to write a function that takes two arguments: givenType and targetType. If these two arguments match, I want givenType to be returned, otherwise null.
For this objective, I am trying to utilize Dart's is expression (maybe there is a better way to go about it, I am open to suggestions). Initially, I thought it would be as simple as writing this:
matchesTarget(givenType, targetType) {
if (givenType is targetType) {
return givenType;
}
return null;
}
But this produces an error:
The name 'targetType' isn't a type and can't be used in an 'is'
expression. Try correcting the name to match an existing
type.dart(type_test_with_non_type)
I tried looking up what satisfies an is expression but cannot seem to find it in the documentation. It seems like it needs its right-hand operand to be known at compile-time (hoping this is wrong, but it does not seem like I can use a variable), but if so, how else can I achieve the desired effect?
I cant guess the purpose of the function (or the scenario where it would be used, so if you can clarify it would be great). First of all, I don't know if you are passing "types" as arguments. And yes, you need to specify in compile time the right hand argument of the is function.
Meanwhile, if you are passing types, with one change, you can check if the types passed to your function at runtime.
matchesTarget(Type givenType, Type targetType) {
print('${givenType.runtimeType} ${targetType.runtimeType}');
if (givenType == targetType) {
return givenType;
}
return null;
}
main(){
var a = int; //this is a Type
var b = String; //this is also a Type
print(matchesTarget(a,b)); //You are passing different Types, so it will return null
var c = int; //this is also a Type
print(matchesTarget(a,c)); //You are passing same Types, so it will return int
}
But if you are passing variables, the solution is pretty similar:
matchesTarget(givenVar, targetVar) {
print('${givenVar.runtimeType} ${targetVar.runtimeType}');
if (givenVar.runtimeType == targetVar.runtimeType) {
return givenVar.runtimeType;
}
return null;
}
main(){
var a = 10; //this is a variable (int)
var b = "hello"; //this is also a variable (String)
print(matchesTarget(a,b)); //this will return null
var c = 12; //this is also a variable (int)
print(matchesTarget(a,c)); //this will return int
}
The Final Answer
matchesTarget(givenVar, targetType) {
print('${givenVar.runtimeType} ${targetType}');
if (givenVar.runtimeType == targetType) {
return givenVar;
}
return null;
}
main(){
var a = 10; //this is a variable (int)
var b = String; //this is a type (String)
print(matchesTarget(a,b)); //this will return null because 'a' isnt a String
var c = int; //this is also a type (int)
print(matchesTarget(a,c)); //this will return the value of 'a' (10)
}
The as, is, and is! operators are handy for checking types at runtime.
The is operator in Dart can be only used for type checking and not checking if two values are equal.
The result of obj is T is true if obj implements the interface specified by T. For example, obj is Object is always true.
See the below code for an example of how to use the is operator
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
Even the error message that you're getting says that
The name 'targetType' isn't a type and can't be used in an 'is'
expression.
So the bottomline is that you can use is only for checking if a variable or value belongs to a particular data type.
For checking equality, you can use the == operator if comparing primitive types, or write your own method for comparing the values. Hope this helps!

How to check if value is enum?

I would like to check if a value in dart is an Enum, and if so, serialize it automatically.
My sticking point is how to test if a value is an enum.
This is how I am testing for other types
if (T == int)
val = prefs.getInt(this._key);
else if (T == double)
val = prefs.getDouble(this._key);
else if (T == bool)
val = prefs.getBool(this._key);
else if (T == String)
val = prefs.getString(this._key);
else if ([""] is T)
val = prefs.getStringList(this._key);
but I cannot seem to compare T to an enum
Since all enums have the same methods I was hoping to sense them and handle them accordingly
As of late 2021, all enums extend Enum
You can now finally use is Enum directly:
enum MyEnum {one, two, three}
var x = "a";
var y = MyEnum.one;
print(x is Enum); // false
print(y is Enum); // true
Which also means you can now create extensions for enums:
extension EnumExtension on Enum { ... }
If you need to check is dynamic value enum type. You can use the next approach. The main idea is quite similar to #Oniya Daniel's answer.
enum Fruit {
banana,
apple,
orange,
}
Next method to check isEnum condition
bool isEnum(dynamic data) {
final split = data.toString().split('.');
return split.length > 1 && split[0] == data.runtimeType.toString();
}
Test result below
test('check enum runtime', () {
expect(isEnum(Fruit.banana), true);
expect(isEnum(null), false);
expect(isEnum(''), false);
expect(isEnum('banana'), false);
});
P.S.: to get enum String value good to use describeEnum(<enum_value>) from the package:flutter/foundation.dart
I don't have enough reputation to comment, or I would have added a statement to the answer by #Dmitry Puzak. In Dmitry's method, if data is passed as the String "String.3.0.1", for instance, the method will incorrectly identify the parameter as an enum. The flutter-provided method, describeEnum, attempts to check that it's parameter is an enum and throws an exception if it's not, but in my experience the exception isn't always thrown when the parameter to describeEnum is a String value containing dots.
For a project I'm working, I modified Dmitry's isEnum method to the following, adding the first if block inside the method:
bool isEnum(dynamic data) {
if (data.runtimeType == "".runtimeType) {
return false;
}
final split = data.toString().split('.');
return split.length > 1 && split[0] == data.runtimeType.toString();
}
This makes sure to return false for any String parameter.
I can't offhand think of any other instance where the runtime type of a variable could possibly match the first part of the split done on the toString() return value.
Convert the enum to a list and check if the list contains the value you intend to check.
enum ObjectType {
TypeA, TypeB
}
// checking against enum values
print(ObjectType.values.contains(ObjectType.TypeA)); // true
print(ObjectType.values.contains(ObjectType.TypeB)); // true
// checking against string value
print(ObjectType.values.contains("ObjectType.TypeA")); // false
// checking against boolean
print(ObjectType.values.contains(false)); // false
// checking against int
print(ObjectType.values.contains(234)); // false
// checking against double
print(ObjectType.values.contains(2.345)); // false

Resources