Is there a way to pass an extension method as a function argument?
I'd like to pass an extension method to a .map method like a static function if it's possible.
Something like this:
extension Add10 on int {
int add10() {
return this + 10;
}
}
int add100(int x) => x + 100;
// I'd like to do something like this
[1, 2, 3, 3, 5].map(Add10.add10);
// Like I could do with a regular function
[1, 2, 3, 4, 5].map(add100);
// instead of
[1, 2, 3, 4, 5].map(x => x.add10());
Is this possible?
In other words, can an extension method on int that returns an int satisfy the signature
int Function(int)
like my add100 function can?
Or are extension methods always treated more like a class that must wrap the value before making the call?
No, not like that.
An extension method is like an instance method in this regard. You can't extract an instance method from int int to a function, pass it somewhere else, and then call that method on other integers.
Just like an instance method, you need to do:
[1, 2, 3, 3, 5].map((x) => x.add10())
That's one situation where extension methods are less usable than the static helper function they're intended to replace. As #eugene-kuzmenko points out, you can make the extension method a static function instead (not necessarily inside an extension declaration), and then you can tear it off an pass it as an argument, because then it's not inherently tied to a single receiver, but it takes the number to act on as an argument, like Iterable.map expects.
If you are going to turn the result of the map into a List anyway, I'd probably just use a literal:
var numbers = [1, 2, 3, 3, 4];
var newNumbers = [for (var x in numbers) x.add10()];
It looks slightly more cumbersome, but in practice I find it much more convenient to not have to write functions literals (and it also works with asynchronous operations then).
Technically you can use a static function inside extension
[1, 2, 3, 3, 5].map(**Add10.add10**);
So you can change your extension method(and function signature)
extension Add10 on int {
static int add10(int i) {
return i + 10;
}
}
But I don't think it's a good idea
Related
I tried writing a simple generic method that would iteratively copy a nested List, for example a List<List<int>>. But unfortunately, the recursive call seems to always return List<dynamic>, so I get the following error
The argument type List<dynamic> can't be assigned to the parameter type T
List<T> listDeepCopy<T>(List<T> list){
List<T> newList = List<T>();
list.forEach((value) {
if( value is List ){
newList.add(listDeepCopy(value)); // <-- listDeepCopy() always returns List<dynamic>
}
else{
newList.add(value);
}
});
return newList;
}
So if I call
List<List<int>> list = [[1,2],[3,4]];
List<List<int>> copy = listDeepCopy(list);
T is List<int>
value is T - i.e. List<int>
listDeepCopy(value) should equal listDeepCopy<List<int>>, which would return a List<int>, which should be possible to add to newList, which is a List<List<int>>
Where am I going wrong here, and how can I make something like this work?
I probably would implement it as:
List<T> listDeepCopy<T>(List<T> list) {
var copy = list.toList();
for (var i = 0; i < copy.length; i += 1) {
var element = copy[i];
if (element is List) {
copy[i] = listDeepCopy(element) as T;
}
}
return copy;
}
void main() {
List<List<int>> list = [
[1, 2],
[3, 4]
];
List<List<int>> copy = listDeepCopy(list);
list[0][0] = 99;
print(copy); // Prints: [[1, 2], [3, 4]]
}
A problem with your approach is that Dart cannot properly infer the generic type parameter for that recursive listDeepCopy(value) call. value is of type T that is known to be a List (which is shorthand for List<dynamic>), and I am not aware of a way to extract the static element type. (Maybe #lrn will see this and provide a better, more complete explanation.)
In such a case, it's better to rely on polymorphism by calling a method on the List that returns a copy of itself: .toList().
(As an example where this matters, consider a shallow copy scenario:
List<T> shallowCopy1<T>(List<T> list) => <T>[...list];
List<T> shallowCopy2<T>(List<T> list) => list.toList();
extension StaticType<T> on T {
Type get staticType => T;
}
void main() {
List<num> list = <int>[1, 2, 3];
var copy1 = shallowCopy1(list);
var copy2 = shallowCopy2(list);
print('original: staticType: ${list.staticType}, runtimeType: ${list.runtimeType}');
print('copy1: staticType: ${copy1.staticType}, runtimeType: ${copy1.runtimeType}');
print('copy2: staticType: ${copy2.staticType}, runtimeType: ${copy2.runtimeType}');
}
Although both copies preserve the static type of the original List, only copy2 preserves the object's actual (runtime) type. A proper copy depends on the runtime type of the object being copied, and the only robust way to do that is for the object to create a copy of itself.)
You can't do it the way you are trying to do it.
The problem is that deepClone<T> converts a List<dynamic> to a List<T> (which is fine) and then tries to convert elements that are themselves lists into typed list ... but you don't know the type.
In effect, when you check that value is List, you don't know what kind of list to convert it to.
There are two cases:
Either T is List<X> or Iterable<X> for some type X, but you have no way to get your hands on that X. Dart doesn't allow you to destructure types at runtime.
Or T is Object or another general supertype with no "list element" type inside it, and then you simply do not have any information about what List type to convert the nested list to. (That's actually the simplest case, because then you should simply not deepClone the list at all).
There is a way to figure out which case you are in (<T>[] is List<Iterable<Object?>>), but it won't help you in the former case, unless you want to do an exhaustive search of all the possible types that X might be.
What I'd do instead is to build a converter, instead of using a single function.
abstract class Cloner<T> {
const factory Cloner() = _ValueCloner<T>;
T clone(dynamic source);
Cloner<List<T>> get list => _ListCloner(this);
}
abstract class _BaseCloner<T> implements Cloner<T> {
const _BaseCloner();
Cloner<List<T>> get list => _ListCloner<T>(this);
}
class _ValueCloner<T> extends _BaseCloner<T> {
const _ValueCloner();
T clone(dynamic source) => source as T;
}
class _ListCloner<T> extends _BaseCloner<List<T>> {
final Cloner<T> _base;
_ListCloner(this._base);
List<T> clone(dynamic source) =>
<T>[for (var o in source as List<dynamic>) _base.clone(o)];
}
Then, if you actually know the type of the data, you can build your cloner as:
var typedList =
Cloner<int>().list.list.clone(
<dynamic>[<dynamic>[1, 2], <dynamic>[3, 4]]);
which yields a List<List<int>> with the value <List<int>>[<int>[1, 2], <int>[3, 4]].
The code below defines a generic myFirstWhereFunction with 3 arguments:
Generic list
Generic value to search in the list
Generic default value to return if the searched value is not in the passed generic list
The code:
void main() {
const List<int> intLst = [1, 2, 3, 4];
print(myFirstWhereFunc(intLst, 4, -1));
print(myFirstWhereFunc(intLst, 5, -1));
const List<String> strLst = ['coucou', 'go', 'bold', 'tooltip'];
print(myFirstWhereFunc(strLst, 'go', 'not exist'));
print(myFirstWhereFunc(strLst, 'ok', 'not exist'));
}
T myFirstWhereFunc<T>(List<T> lst, T searchVal, T defaultVal) {
return lst.firstWhere((element) => element == searchVal, orElse: <T> () {
return defaultVal;
});
}
But this code generates an exception.
One solution is to replace the generic myFirstWhereFunc return type by dynamic (code below):
dynamic myFirstWhereFunc<T>(List<T> lst, T searchVal, T defaultVal) {
return lst.firstWhere((element) => element == searchVal,
orElse: () => defaultVal);
}
But is there another way of solving the problem ?
I believe that the problem is that when you do:
print(myFirstWhereFunc(intLst, 4, -1));
there are two possible ways to infer the type of myFirstWhereFunc:
Bottom-up (inside-out): myFirstWhereFunc is called with a List<int> and with int arguments, so its type could be myFirstWhereFunc<int>. This is what you want.
Top-down (outside-in): print has an Object? parameter, so myFirstWhereFunc could be myFirstWhereFunc<Object?> so that it returns an Object?. This is what actually happens and is what you do not want.
Dart ends up with two possible ways to infer the generic type parameter, both seem equally valid at compilation-time, and it picks the one that you happen to not want. Picking the other approach likely would result in undesirable outcomes for different code examples. Arguably inference could try both ways and pick the narrower type if one approach leads to a subtype of the other. I'm not sure offhand if that would break code, but I wouldn't be surprised. (I also suspect that it's been suggested in https://github.com/dart-lang/language/issues somewhere...)
Changing myFirstWhereFunc's return type to dynamic is a crude workaround to the problem because it makes myFirsyWhereFunc's type no longer inferrable from print's parameter type.
If you split the line up and use a temporary variable, inference can infer myFirstWhereFunc independently of print and then should do what you want:
var intResult = myFirstWhereFunc(intLst, 4, -1);
print(intResult);
I have a question on how to easily use map as a parameter in Dart. Is there any easy way of passing all the key-values pairs of a map object to a function?
For example, I have a map and a function like this:
const testMap = {"a": 1, "b":2};
int testFunc(a, b){
return a + b;
}
and I want to use them like this:
testFunct(**testMap) // not possible in dart (though possible in python)
which should give us 3 as the result.
Any smart solution like this? Thanks!
Please note that ** is the python-way of passing the parameters inside dictionary: docs
It's not directly possible the way you write it, but you can do something similar using Function.apply.
You have to either pass the arguments as positional, and then you need to know the order:
const testMap = {"a": 1, "b":2};
int testFunc(a, b){
return a + b;
}
void main() {
print(Function.apply(testFunc, testMap.values.toList()));
}
or you have to use named parameters and then the map keys should be symbols:
const testMap = {#a: 1, #b:2};
int testFunc({required int a, required int b}){
return a + b;
}
void main() {
print(Function.apply(testFunc, [], testMap));
}
Dart distinguishes positional and named parameters, so if you want to refer to a parameter by name, it should be named.
The former approach is dangerous because you don't use the names of the map. It should really just be const testArguments = [1, 2]; and not include a map which doesn't help you.
Using Function.apply is not type safe. It's as type-safe as calling something with type dynamic or Function — which means not safe at all — so use it carefully and only when necessary.
the normal list in dart 1d can use with it many methods like insert remove indexOf etc . but in 2d list many of list methods can't work with it ? why an example below
var lista = [1,2,3,4,5] ;
lista.add(6);
print(lista);
Works fine
var listb = [[1,2,3],
[4,5]
];
listb.add(6); // wont add cause the argument type 'int' can't be assigned to the parameter type 'List'.
accept it only when listb.add([6]); but add this as new element in list i want to add in [4,5] a number only ?!
If you want to add to the second list of the lists, just do so:
void main() {
var listb = [[1,2,3], [4,5]];
listb[1].add(6);
print(listb);
}
This prints
[[1, 2, 3], [4, 5, 6]]
You wanted the first list to know that you want to add to the second of it's elements. That is not how programming works, the compiler does not guess what you may want. You have to explicitly tell it what to do.
I need to add or overwrite items to a LinkedHashMap and at the same time it should return the map modified.
This is the code I have:
MyModel.fromJson(json);
json variable is LinkedHashMap, and I need to add items before calling fromJson function.
I tried with addAll function:
MyModel.fromJson(json.addAll({ ... }));
but it returns void so I can use for calling fromJson function.
You can use the cascade notation for calling a method on a object and still return the same object reference instead of the result of the method:
void main() {
final map = {"A": 1};
print(map..addAll({"B": 2})); // {A: 1, B: 2}
}