Is there a neater way to map member functions in dart? - dart

Dart has a handy map function on iterables, and it accepts a lambda. So I can write something like:
// Stupid example class
class Foo {
int v;
int v2() { return v*v; }
}
List<int> mapFoos(List<Foo> foos) {
return foos.map( (Foo f) => f.v2() );
}
But this feels a little clunky to me. I'm used to being able to tell map to use the member function directly, something that would look more like:
// does not compile
List<int> mapFoos(List<Foo> foos) {
return foos.map(Foo.v2);
}
But this fails to compile with the error:
The argument type '() → int' can't be assigned to the parameter type '(Foo) → int'
Is there some way to turn the member function into a lambda in a succinct way, so that
we can have something closer to the second example.
I could write
int applyV2(Foo f) {
return f.v2();
}
List<int> mapFoos(List<Foo> foos) {
return foos.map(applyV2);
}
but then I'd need to create that for each member function I want to map, which isn't really any better than using the lambda function.
If it makes any difference I'm using dart 1 due to "legacy reasons", if this has changed in recent versions I'd love to know that too.

No.
There is no shorter way to create a function which takes a Foo and calls its v2 method, than (f) => f.v2().
You can omit the Foo type on the parameter, because it can be inferred from the context (a List<X>.map<R> requires an R Function(X) as argument).
You cannot tear off Foo.v2 because v2 is an interface method, not a static method.
Just to elaborate on why Dart doesn't allow that, you can stop reading now if you just want to know what works:
Some languages allow you to tear off instance methods, so Foo.v2 becomes a function which expects its this object as an argument, in Dart a function of type int Function(Foo). Dart does not allow that. Probably for many different reasons, but most importantly because it cannot work. Dart types are interfaces, all class types can be implemented by another class without inheriting any implementation.
If you then tear off Foo.v2, you can call it with an instance of another class which implements Foo, but which won't necessarily find the private fields that Foo has, and which v2 could depend on.
Also, the tear-off would be covariant in its this-parameter.
Take SubFoo which extends Foo and has its own v2 method. If you do Foo foo = SubFoo(); var vtoo = foo.v2; then the static type of vtoo will be int Function(Foo), but the implementation from SubFoo will necessarily have runtime type int Function(SubFoo), which is not a subtype of the static type. That means it's unsound. The torn off function will have to do a run-time type check that its argument is actually a SubFoo, and throw if it's not. (So, that feature is not a good match for Dart.)

Related

Type safety in Dart using Container

I found something strange in dart. If there is a list that contains instances of a base class (in this example Super), the list can be set with a list of inherited instances. It seems that this changes the list type at runtime.
Is this intended behavior or is this a bug in Dart?
abstract class Super {}
class A extends Super {}
class B extends Super {}
class Container {
List<Super> mylist = [];
Container(this.mylist);
}
void main() {
// 1. dont't works
final container = Container(<A>[A(), A()]);
// 2. works
final container = Container([A(), A()]);
print(container.mylist.runtimeType);
container.mylist.add(B());
print(container.mylist);
}
If case 1 is used in the code above I get the following error:
JSArray<A>
Uncaught Error: TypeError: Instance of 'B': type 'B' is not a subtype of type 'A'
The error is at the line where I try to add an instance of B:
container.mylist.add(B());
Dart has a system called type promotion, where it can promote the type of a variable, similar to type inference.
It works as a cast. On the first example you've explicit promoted the type of your list to be of type A, so there's nothing strange about this.
Take a look at the first article that explains this mechanism.
When you do:
final container = Container(<A>[A(), A()]);
you explicitly create a List<A> object. Although Container's constructor expects a List<Super>, it accepts a List<A> argument because Dart considers Generic<Derived> to be a subtype of Generic<Base> if Derived is a subtype of Base. Your later attempt to do container.mylist.add(B()); will fail because container.mylist is actually a List<A> and therefore cannot legally store any B elements.
When you instead do:
final container = Container([A(), A()]);
then, because the List literal is not given an explicit type, its type is inferred to be List<Super> from Container's expected construction parameter. container.mylist.add(B()); will succeed since container.mylist is actually a List<Super> and therefore can legally store B elements.

Adding the generic type to a comparable type in Dart

This is a followup question after reading this Q&A:
Generic Sorting function accepts T, but want to ensure T is comparable
I have a class like so:
class BinarySearchTree<E extends Comparable> { ... }
so I can create an instance like this:
final tree = BinarySearchTree<int>();
My question is about using Comparable vs Comparable<E>. When I do this:
class BinarySearchTree<E extends Comparable> { ... }
then the type defaults to E extends Comparable<dynamic>. I normally try to avoid dynamic, so in order to be more explicit about the type that is being compared, it seems like I should write it this:
class BinarySearchTree<E extends Comparable<E>> { ... }
But in that case I get an error here:
final tree = BinarySearchTree<int>();
// 'int' doesn't conform to the bound 'Comparable<int>' of the type parameter 'E'.
// Try using a type that is or is a subclass of 'Comparable<int>'.
This demonstrates my lack of understanding of generics. What am I missing?
In Dart, a class cannot implement 2 different concrete instances of a generic interface:
abstract class Foo<T> {}
// error: Foo can only be implemented once
class Bar implements Foo<String>, Foo<int> {}
num implements Comparable<num>, because it would be slightly absurd for the built-in number types to not be comparable. However, since int is a subtype of num (and therefore inherits Comparable<num>, it cannot have Comparable<int>.
This leads to the slightly weird consequence that int does not implement Comparable<int>.
The problem you're facing is that from the language's point of view, there are 2 types involved: the type of the elements being compared, and the type of the elements they are being compared to.
As such, your type will need 2 type parameters:
class Tree<T extends Comparable<S>, S> {
T get foo;
}
final intTree = Tree<int, num>();
final foo = intTree.foo; // returns an int
Admittedly, this isn't a super clean solution, but if you're using Dart 2.13 or higher, you can use typedefs to make it a bit nicer:
typedef IntTree = Tree<int, num>;
typedef RegularTree<T> = Tree<T, T>;
final intTree = IntTree();
final stringTree = RegularTree<String>();
intTree.foo // is an int
stringTree.foo // is a String
There is another option, which is to just drop some type safety and use Comparable<dynamic>, but personally I'd recommend against it. BTW, if you want to avoid accidentally missing type parameters you can disable implicit-dynamic as described here: https://dart.dev/guides/language/analysis-options#enabling-additional-type-checks
This will give an error any time the type dynamic is inferred from context without the programmer actually typing the word dynamic

Dart smartcast not working with two generics

I have class that takes to generics (for example A and B) and then i have a subclass which specifies one of those generics, like that:
class Foo<A, B>{
void f() {}
}
class Boo<A> extends Foo<A, String> {
void b() {}
}
When i am trying to cast a Foo variable to Boo the smart cast some how is not working:
void test<A, B>(Foo<A, B> foo) {
if (foo is Boo<A>)
foo.b();
}
The error message is: Error: The method 'b' isn't defined for the class 'Foo<A, B>'.
But when i drop the generic types the function compiles:
void test(Foo foo) {
if (foo is Boo)
foo.b();
}
Is there any way i can get the smart cast to work without dropping the generic types?
This is working as intended.
Dart only promotes variables to subtypes (a more precise type) to ensure that you don't lose information. That's why it's called "promotion", not just "casting".
When you do foo is Boo<A>, then it's checking whether Boo<A> is a subtype of the currently known type of the foo variable, which is Foo<A, B>.
It is not.
The type Boo<A> a subtype of Foo<A, String> (because it extends that), but String is not (statically known to be) a subtype of the type variable B. So as static types, Boo<A> is not a subtype of Foo<A, B>, and the promotion doesn't promote the static type of the foo variable.
There is not a lot you can do about this as written. The type B could be bound to Never at runtime, so no other type would be sound to assume to be a subtype of B at compile time.
When you drop the generics, the type of foo becomes Foo<Object?, Object?>, and any type implementing Foo is a subtype of that, so you do get promotion to Bar<whatever>.
If you declared test as:
void test<A>(Foo<A, String> foo) {
then the promotion would succeed.

A few questions about Dart generics and type safety

I have the following Dart 2 code with null-safety.
extension Foo<T> on List<T> {
List<U> bar<U>({
U Function(T)? transform,
}) {
final t = transform ?? _identityTransform;
return map(t).toList();
}
}
U _identityTransform<T, U>(T t) => t as U; // #1, #2
void main() {
final strings = ['a', 'b', 'c'].bar<String>(); // #3
final ints = ['1', '2', '3'].bar(transform: int.parse);
print(strings);
print(ints);
}
It is an extension method on List<T> with a custom method that is basically a map with the
difference that it can return a new list of the same type if no transform is specified. (My real code is more complex than this, but this example is enough to present my questions.)
I want to be able to call bar() on a List with transform or without; if called without it, _identityTransform should be used.
The code above works, but I have a few reservations as to its quality, and questions, as I haven't really come to terms with Dart generics yet:
In the line marked #1 - the _identityTransform takes two generic parameters as I need access to them, but when the function is used the generic types are not used because I don't think it is possible to write something like _identityTransform<T, U> there. Is there a better way of defining _identityTransform? Am I losing any type safety with my current code?
In the line marked #2 I need a cast as U for the code to compile, I haven't managed to make the code work without it. Is there a way to do it without the cast?
In the line marked #3, when I call the extension method without any transform (i.e. I want the identity transform to kick in) I need to explicitly pass the generic type, otherwise the compiler complains about missing generic type (in strong mode) or infers strings to be List<dynamic> (strong mode turned off). Is some generics magic possible to be able to call .bar() and still have strings be inferred to List<String>?
I would make _identityTransform a nested function of bar so that you can remove its type arguments and instead use the same T and U as bar:
extension Foo<T> on List<T> {
List<U> bar<U>({
U Function(T)? transform,
}) {
U _identityTransform(T t) => t as U;
final t = transform ?? _identityTransform;
return map(t).toList();
}
}
Alternatively if you want to explicitly use _identityTransform<T, U>, then you could use a closure: t = transform ?? (arg) => _identityTransform<T, U>(arg), but that seems like overkill.
You need the cast. T and U are independent/unrelated types. Since you don't know that you want T and U to be the same until bar checks its argument at runtime, you will need the explicit cast to satisfy static type checking.
If the caller passes nothing for the transform argument, there is nothing to infer U from, so it will be dynamic. I can't think of any magical way make U default to T in such a case (again, that would be known only at runtime, but generics must satisfy static analysis).

Type inference behaves differently for similar cases

Running the following code (Dart 2.3) throws the exception:
type 'List<dynamic>' is not a subtype of type 'List<bool>'
bar() => 0;
foo() => [bar()];
main() {
var l = [1, 2, 3];
l = foo();
}
However, this slightly altered example runs correctly:
main() {
bar() => 0;
var l = [1, 2, 3];
l = [bar()];
}
As does this:
main() {
bar() => 0;
foo() => [bar()];
var l = [1, 2, 3];
l = foo();
}
What is it about Dart's type inference algorithm that makes these cases behave differently? Seems like the types of the functions foo and bar should be pretty easy to infer, since they always return the same value. It also isn't obvious to me why moving around the site of the function declaration would change type inference in these cases.
Anyone know what's going on here?
Leaf Petersen explains it in a comment to dart-lang/sdk issue #33137: Type inference of function return value:
This is by design. We do infer return types of non-recursive local
functions (functions declared inside of the scope of another function
or method), but for top level functions and methods, we do not infer
return types (except via override inference). The reasons are as
follows:
Methods and top level functions are usually part of the API of a program, and it's valuable to be able to quickly read off the API of a
piece of code. Doing method body based return type inference means
that understanding the signature of the API requires reading through
the method body.
Methods and top level functions can be arbitrarily mutually recursive, which makes the inference problem much harder and more
expensive.
For primarily these reasons, we do not infer return types for top level functions and methods. Leaving off the return type is just another way of saying dynamic.
If you set
analyzer:
strong-mode:
implicit-dynamic: false
in your analysis_options.yaml file, then dartanalyzer will generate errors when top-level functions have an implicit dynamic return type:
error • Missing return type for 'bar' at example.dart:1:1 • strong_mode_implicit_dynamic_return
error • Missing return type for 'foo' at example.dart:2:1 • strong_mode_implicit_dynamic_return
It looks like nested functions are treated differently than top-level functions. It is probably a bug. I get the following from Dartpad on Dart 2.3.1.
foo() => 0;
bar() => [foo()];
main() {
baz() => 0;
qux() => [baz()];
print(foo.runtimeType);
print(bar.runtimeType);
print(baz.runtimeType);
print(qux.runtimeType);
}
// () => dynamic
// () => dynamic
// () => int
// () => List<int>
Explanation here:
This is expected behavior.
Local functions use type inference to deduce their return type, but top-level/class-level functions do not.
The primary reason for the distinction is that top-level and class level functions exist at the same level as type declarations. Solving cyclic dependencies between types and functions gets even harder if we have to also analyze function bodies at a time where we don't even know the signature of classes yet.
When top-level inference has completed, we do know the type hierarchies, and where top-level functions are unordered, they can refer to each other in arbitrary ways, local functions are linear and can only depend on global functions or prior local functions. That means that we can analyze the function body locally to find the return type, without needing to look at anything except the body itself, and things we have already analyzed.

Resources