I am currently experimenting with the flutter framework and dart and stumbled across a seemingly strange behaviour I fail to understand. Even though the context in which the actual problem occurs is way more complicated, I was even able to replicate it in an extremely simplified showcase:
Stream<Either<String, int>> result1 = Stream.fromIterable([1, 2, 3, 4, 5])
.map((number) => number < 4 ? Right(1) : Left('error'))
.onErrorReturnWith((error) => Left('error'));
While the sample above compiles uncontradicted, I do get a compile error for the sample below:
Error: A value of type 'Left<String, dynamic>' can't be assigned to a
variable of type 'Right<dynamic, int>'
Stream<Either<String, int>> result2 = Stream.fromIterable([1, 2, 3, 4, 5])
.map((number) => Right(1))
.onErrorReturnWith((error) => Left('error'));
Is anyone capable to shed some light to this manner?
########################################################
Another example:
Future<Either<String, int>> someWebCall() {
Future<int> response = Future.delayed(Duration(milliseconds: 200), () {
throw SocketException('Well... ...it happens you know...');
});
return response.then((number) {
return number > 50.0 ? Right(number) : Left('number is too small...');
}).catchError((error) {
return Left('are you already questioning the meaning of your life?');
});
}
This compiles but ends with a runtime error:
type 'Future' is not a subtype of type 'Future<Either<String, int>>'
Then I tried to give as many hints to the compiler as I could coming up with this approach:
Future<Either<String, int>> someWebCall() {
Future<int> response = Future.delayed(Duration(milliseconds: 200), () {
throw SocketException('Well... ...it happens you know...');
});
return response.then<Either<String, int>>((number) {
return number > 50.0 ? Right(number) : Left('number is too small...') as Either<String, int>;
}).catchError((error) {
return Left('are you already questioning the meaning of your life?') as Either<String, int>;
});
}
Now I am getting:
type 'Left<String, dynamic>' is not a subtype of type 'Either<String, int>' in type cast
I really can't wrap my head around this
The type of the function (number) => Right(1) is Right<dynamic, int> Function(int), which means that the resulting stream of the map call is a Stream<Right<dynamic, int>>.
The onErrorReturnWith needs to return something of the same type as the elements of the stream it's called on, but it returns Left<String, dynamic>, not Right<dynamic, int>.
The simplest fix is to tell the map call what type to return:
...
.map<Either<String, int>>( .... )
Then the types should be what you expect (and not Either<dynamic, dynamic> like the first example likely inferred).
I finally figured out whats happening by diving into the types of dartz.
The problem is that the compiler is incapable of infering the type of Either in contexts where only either Left or Right is being used. I.e. Left('') the compiler can infer the Left part of Either as being as string and in Right(5) its capable of inferring the right part of Either as int. He is incapable however to figure out the other part respectively.
Using the code below works as indended.
Future<Either<String, int>> someWebCall() {
Future<int> response = Future.delayed(Duration(milliseconds: 200), () {
throw SocketException('Well... ...it happens you know...');
});
return response.then((number) {
return number > 50.0 ? Right(number) : Left('number is too small...');
}).catchError((error) {
return Left<String, int>('are you already questioning the meaning of your life?');
});
Related
Learning Dart and using dart_code_metrics to ensure that I write code that meets expectations. One of the rules that is active is avoid-non-null-assertion.
Note, the code below was created to recreate the problem encountered in a larger code base where the value of unitString is taken from a JSON file. As such the program cannot control what is specified in the JSON file.
From pubspec.yaml
environment:
sdk: '>=2.15.0 <3.0.0'
// ignore_for_file: avoid_print
import 'package:qty/qty.dart';
void main() {
const String unitString = 'in';
// unit.Width returns null if unitString is not a unit of Length.
if (Length().unitWith(symbol: unitString) == null) {
print('units $unitString not supported.');
} else {
// The following line triggers avoid-non-null-assertion with the use of !.
final Unit<Length> units = Length().unitWith(symbol: unitString)!;
final qty = Quantity(amount: 0.0, unit: units);
print('Qty = $qty');
}
}
If I don't use ! then I get the following type error:
A value of type 'Unit<Length>?' can't be assigned to a variable of type 'Unit<Length>'.
Try changing the type of the variable, or casting the right-hand type to 'Unit<Length>'.
Casting the right-hand side to
Unit<Length>
fixes the above error but cause a new error when instantiating Quantity() since the constructor expects
Unit<Length>
and not
Unit<Length>?
I assume there is an solution but I'm new to Dart and cannot formulate the correct search query to find the answer.
How can I modify the sample code to make Dart and dart_code_metrics happy?
Your idea of checking for null before using a value is good, it's just not implemented correctly. Dart does automatically promote nullable types to non-null ones when you check for null with an if, but in this case you need to use a temporary variable.
void main() {
const String unitString = 'in';
//Use a temp variable, you could specify the type instead of using just using final
final temp = Length().unitWith(symbol: unitString);
if (temp == null) {
print('units $unitString not supported.');
} else {
final Unit<Length> units = temp;
final qty = Quantity(amount: 0.0, unit: units);
print('Qty = $qty');
}
}
The basic reason for that when you call your unitWith function and see that it's not null the first time, there's no guarantee that the when you call it again that it will still return a non-null value. I think there's another SO question that details this better, but I can't seem to find.
Future<void> foo() {
final futureInt = Future.value(2);
return futureInt;
}
How is the above code possible? the return value is a Future of type int, while the specified value of foo is Future of type void?
Also, when adding the async keyword to the function, it doesn't even run anymore (using dartpad), why is that? (check attachment)
the error is : A value of type 'Future' can't be returned from the function 'foo' because it has a return type of 'Future'.
I tried return the Future value directly and the error disappears:
Future<void> foo() async {
final futureInt = Future.value(2);
return Future.value(2);
}
No error from this code. I can't seem to understand the difference here also between returning futureInt (which gives an error) and Future.value(2) (which works perfectly)
Thanks in advance.
In Dart, void is essentially a top-type, meaning that all values can in theory be assigned to void. This is why a Future<int> is a subtype of a Future<void> and can be returned in the non-async method.
However, when void is used as a return type it gets a special meaning: It indicates that the method should not return a value. The compiler checks this, which is why it's an error to write things like:
void foo() {
return 2;
}
As far as the compiler is generally concerned, a Future<void> is not the same thing as void, so you can return a value in the non-asynchronous function.
With async functions, the situation is a bit different. They have to return a future, and so the compiler applies something known as "flattening": In an asynchronous method returning a Future<T>, the language mandates that either a Future<T> or a direct T is returned. So, in asynchronous functions, the Future<void> behaves like a void in synchronous functions. This is why it's suddenly forbidden to return the Future<int>.
When returning a Future.value(2), you're not directly specifying the future's type. By looking at the expected return type (which is Future<void>), the compiler infers that you're returning a Future<void>.value(2). This is perfectly legal because here, the 2 is not returned directly and int is a subtype of void.
As you'd expect, the following snippet is indeed forbidden:
Future<void> foo() async {
final futureInt = Future.value(2);
return Future<int>.value(2);
}
So, to summarize:
When not used as a return value, anything can be assigned to void.
Methods declared to return void can't return non-void values.
async methods returned to Future<void> also can't return non-void values.
I have a future that has a generic parameter, which is a superclass (A) of another class (B extends A). I know for a fact that the instance of the value of the Future is of the subtype. Why can't I downcast the Future<A> to Future<B> in dart? If I unwrap the Future once and then wrap it again using async/await, it works.
Here's an example:
class A {}
class B extends A{}
void main() {
Future<A> getFuture() async { return B();}
Future<B> getBasA() { return getFuture() as Future<B>;}
Future<B> getBasAasync() async { return (await getFuture()) as B;}
print(getBasAasync()); // Works
print(getBasA()); // Throws at runtime
}
For the curious and as a motivation for the question, here's a closer-to-world example. I have a stream that emits data packets, which I filter and then get the first like this:
Future<T> getResponse<T extends ReceivedPacket>() =>
getStream<ReceivedPacket>().firstWhere((packet) => packet is T) as Future<T>; //throws
Future<T> getResponse<T extends ReceivedPacket>() async { //works
return (await getStream<ReceivedPacket>().firstWhere((packet) => packet is T)) as T;
}
PS: I've tried it out in Typescript (will happily compile and run) and C# (won't compile, but I have very limited C# knowledge). I understand that the answer to this question might be "because this is how the dart type system works". I'm just confused, because I'd have expected it either to fail at compile time like C# or work at runtime, too, like typescript.
You declared getFuture() as returning Future<A> but with the async keyword, so Dart automatically transforms return B(); to (essentially) return Future<A>.value(B());. The returned Future was never a Future<B> and therefore cannot be downcast to it.
Creating the Future explicitly would do what you expect:
Future<A> getFuture() { return Future<B>.value(B()); }
You could argue that Dart when transforms return x; in async functions, it should create a Future<T> where T is the static type of x instead of inferring it from the function's return type. As lrn explained in comments, that can't be done because you might have:
class C extends A {}
Future<A> getFuture() async {
await Future.delayed(const Duration(seconds: 1));
if (Random().nextBool()) {
return B();
} else {
return C();
}
}
The caller must get a Future back immediately, but it won't be known whether its value will be a B or C until the Future eventually completes.
I'd have expected it either to fail at compile time like C#
I too have very limited experience with C#, but I think that C# gives you a compilation error because C# does not consider Generic<SubType> to be a subtype of Generic<SuperType> whereas Dart does.
I am trying to up-cast the subclass object but it is not working.
The following program compiles without any errors.
VideoStreamModel model = VideoStreamModel("");
VideoStream entity = model;
print(model); // prints VideoStreamModel
print(entity); // prints VideoStreamModel
print(entity as VideoStream); // prints VideoStreamModel
print(cast<VideoStream>(model)); // prints VideoStreamModel
I have written a testcase to test the relation of above two classes and it passes.
test('should be a subtype of VideoStream', () async {
expect(model, isA<VideoStream>());
});
What could be the problem here?
EDIT:
[deleted]
EDIT 2:
[deleted]
Edit 3:
Here is the complete code reproducing the error.
import 'package:equatable/equatable.dart';
import 'package:test/test.dart';
class A extends Equatable {
final String x;
A(this.x);
#override
List<Object> get props => [x];
}
class B extends A {
B(String x) : super(x);
A method() {
B b = B(x); // doing A b = A(x) makes the test pass
return b;
}
}
void main() {
B b = B("");
test('test', () async {
final expected = A(b.x);
final actual = b.method();
expect(actual, expected);
});
}
It generates the following assertion error:
Expected: A:<A>
Actual: B:<B>
print is calling the toString() on the object you are pointing at (in this case VideoStreamModel) which knows what type it is. When you are casting, you are not changing anything about the object itself but only how the compiler should see the object when it determines if you are allowed to use a given typed variable to point to the object.
So when you are doing entity as VideoStream you are really just telling the compiler that you "promise" that the entity can be seen as a VideoStream. But on runtime, this cast will be tested to see if it is true.
All of this is really not an issue since you should never test for the specific type of the object when you are programming Dart but instead use the is operator which tests if a given object is compatible with a given interface.
So e,g, (entity is VideoStream) will return true.
Updated part
You problem seems to be a misunderstanding of the use of Equatable. It is important to notice that Equatable are not only using the elements from props to determine if two objects are equal but it also looks at the runtimeType. You can see this from the implementation:
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is Equatable &&
runtimeType == other.runtimeType &&
equals(props, other.props);
https://github.com/felangel/equatable/blob/master/lib/src/equatable.dart#L46
This means that:
A a = A("");
B b = B("");
print(a == b); // false
When you are using expect without any matcher, it will just make an == operation which is stated in the documentation:
matcher can be a value in which case it will be wrapped in an equals matcher
Since we (as stated before) cannot change the runtimeType of an object after its creation you need to implement your own == if you want the two object instances to be seen as equal since equatable does only see two objects as equal if they both is created from the same class and contains the same values defined with props.
I am trying to create a Dart function that essentially wraps other functions with some boilerplate error handling code, and otherwise returns the value returned by the original function. A key requirement is that it should accept functions with multiple different return types, while avoiding duplicating the common error handling logic across multiple different functions. I found one approach that seems to work by using the dynamic type, except that the compiler is not able to detect type mismatches, so they are only caught at runtime.
Is there a better way to accomplish what I'm aiming for here, and particularly in a way that catches type mismatches at compile time?
Below is a simplified example of my code, where the functions compile fine, but at runtime getAString will raise an error Dart Error: Unhandled exception: type 'List<String>' is not a subtype of type 'String'
/// Signature of API function calls
typedef APIFunctionCall = dynamic Function();
dynamic doWithErrorHandling(APIFunctionCall fn, {retries: 2}) async {
for (int attempts = 0; attempts < retries + 1; attempts++) {
try {
return await fn();
}
on Exception catch (e) {
print(
"This is just an example; actual function does a bunch of more specific error handling.");
}
}
}
Future<String> getAString() async {
// Want a function that can support multiple return types but detect type errors
String doesReturnAString = await doWithErrorHandling(() async => 'hello world'); // This runs fine
String doesntReturnAString = await doWithErrorHandling(() async => <String>['hello', 'world']); // This throws an Error
return doesntReturnAString;
}
You can abstract over the return type using a type parameter:
Future<T> doWithErrorHandling<T>(Future<T> fn(), {int retries = 2}) async {
do {
try {
return await fn();
} catch (e) {
// record error.
}
retries--;
} while (retries >= 0);
return null; // or whatever.
}
With that, you can call with any function. In most cases, the type argument can be inferred from the static type of the argument function, or from the type expected by the surrounding context, but if not, you can write it yourself.
Future<String> getAString() async {
String doesReturnAString = await doWithErrorHandling(() async => 'hello world');
// The next line has a compile-time type error!
String doesntReturnAString = await doWithErrorHandling(() async => <String>['hello', 'world']);
return doesntReturnAString;
}
(As an unrelated hint, you should never catch Exception. Dart errors do not implement Exception, they implement Error. Exception is a meaningless marker interface used by some thrown objects that the user is intended to catch and handle, but in that case, you should be catching the particular exception, like on FormatException, not the plain Exception. So, general rule: Never write on Exception).