-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Null safety feedback: How to use firstWhere? #42947
Comments
My guess is that the return type of firstWhere are
A workaround (I hope there are better solutions...) you could do is to make an extension on Iterable like this: void main() {
var list = ['a', 'b', 'c'];
String? d = list.firstWhereOrNull((e) => e == 'd');
print(d); // null
}
extension FirstWhereOrNullExtension<E> on Iterable<E> {
E? firstWhereOrNull(bool Function(E) test) {
for (E element in this) {
if (test(element)) return element;
}
return null;
}
} Alternative you could change the list to |
I am going to transfer this to dart-lang/language. |
Duplicate of dart-lang/language#836 |
My gues is: return This make sense. If you want to return null then just use the |
One way would be List<X> list = ...;
X? firstOrNull = list.cast<X?>.firstWhere((v) => test(v!), orElse: () => null); In practice, I'd go for an extension method. |
Maybe the extension can be in the SDK itself then? extension IterableExtension<E> on Iterable<E> {
E? findFirst(bool Function(E) test) {}
E? findLast(bool Function(E) test) {}
E? findSingle(bool Function(E) test) {}
} |
@lrhn can this be a option? Also Dart can take advantage of extension to create built-in helper functions like Kotlin does, it provide a bunche of extension right inside the language. |
FWIW, this situation is troublesome for the migraiton tool too. See #42382 |
Note that a similar problem applies to |
I've also run into this when migrating tests. I like the idea of the above extension methods and would be happy to see them in the core library if that's feasible. |
It's not impossible. I was targeting them for |
I've used extension methods for the following, following convention with
This makes the intent clear when reading code with these methods. |
FWIW I'm also running into this when migrating protobufs. |
Why these methods are still not in core? |
|
If you are null safe, then you can use |
@lrhn is there also a workaround for dart-async |
There is no similar method in extension <T> on Stream<T> {
Future<T?> get firstWhereOrNull async {
await for (var e in this) {
return e;
}
return null;
}
} |
I also have the issue with Streams. It feels counter-intuitive to me that null-safety is enabled by switching types ( While we're on the topic of unintuitive, I'd say that the fact that Using final int largeNumber = nums.firstWhere((int value) => value > 50) ?? 1000; is instead: final int largeNumber = nums.firstWhere((int value) => value > 50, orElse: () => 1000); or even worse: int largeNumber;
try {
largeNumber = nums.firstWhere((int value) => value > 50);
} on StateError {
largeNumber = 1000;
} and that's before null-safety became an issue. |
Adding a dependency for such basic feature is pushing it |
This is the code from api.dart.dev on
If it weren't for that |
My train of thought was I would prefer to treat the default as nullable and include a helper method for those that want to throw if an element is not found. ie/ |
The one issue with just making the return type nullable is that you can no longer distinguish a If you have Not sure how often you'll be doing |
A case where 'null' could be returned as an element of the list seems like more of a reason to make the return of 'firstWhere' nullable. Currently if I run the following -
I believe I get a runtime exception.
I believe I get a compile time exception which I would imagine is more preferable and in the spirit of null safety. |
I am having a hard time understanding why I would want to do something differently if the List contained a null vs. not finding anything. I guess if I wanted to check if null is in the list? Edit: The two scenarios I can some up with are -
In either case I would use a Second Edit : In the first case I could also do |
This is another important use-case. Using
if (myList.contains(null)) // throw or do something else
I think that E? firstWhere(bool test(E element), {bool shouldThrow = false}) {
for (E element in this) {
if (test(element)) return element;
}
if (shouldThrow)
throw IterableElementError.noElement();
} So now you have this table of return types:
So if your elements can be null ( |
In Java/Spring you would declare one method The disadvantage of having a |
I'm assuming you have an iterable of non-nullable elements, because otherwise you'd want to have an Here's what you should do when:
|
I personally don't like this approach as you either have to use an unsafe cast or you have verbosity ( |
True this would be a breaking change, but we shouldn't sacrifice long-term API clarity just for that. Imagine if this had been done during the null-safety implementation: we would have made this change in our migrations and be done with it. Now that we're past that, this would probably have to wait for the next major version, but it should still be done. Besides, by making the return type nullable the compiler can catch cases that need to be fixed (and maybe even fix them with As to your first points, both are better than what we currently have today:
To demonstrate (assuming we never had List<int> nonNullable;
List<int?> nullable;
bool Function(int) condition;
// Throw if the element is not in a non-nullable list
int element = nonNullable.firstWhere(condition)!; // new
int element = nonNullable.firstWhere(condition); // current
// Check for an element in a non-nullable list (very common)
int? element = nonNullable.firstWhere(condition); // new
int? element; // current
try {
element = nonNullable.findWhere(condition);
} catch { }
// Throw if an element is not in a nullable list
int? element = nullable.firstWhere(condition, shouldThrow: true); // new
int? element = nullable.firstWhere(condition); // current
// Checking for an element in a nullable list
int? element = nullable.firstWhere(condition); // new
int? element = nullable.firstWhere(condition, orElse: () => null); // current |
@Levi-Lesches We can also compare all three approaches. Introducing a List<int> nonNullable;
List<int?> nullable;
bool Function(int) condition;
// Throw if the element is not in a non-nullable list
int element = nonNullable.firstWhere(condition)!; // needed change with shouldThrow semantic
int element = nonNullable.firstWhere(condition); // NO change with firstWhereOrNull()
int element = nonNullable.firstWhere(condition); // current
// Check for an element in a non-nullable list (very common)
int? element = nonNullable.firstWhere(condition); // needed change with shouldThrow semantic
int? element = nonNullable.firstWhereOrNull(condition); // optionally improved with firstWhereOrNull()
int? element; // current
try {
element = nonNullable.findWhere(condition);
} catch { }
// Throw if an element is not in a nullable list
int? element = nullable.firstWhere(condition, shouldThrow: true); // needed change shouldThrow semantic
int? element = nullable.firstWhere(condition); // NO change with firstWhereOrNull()
int? element = nullable.firstWhere(condition); // current
// Checking for an element in a nullable list
int? element = nullable.firstWhere(condition); // needed change with shouldThrow semantic
int? element = nullable.firstWhereOrNull(condition); // optionally improved with firstWhereOrNull()
int? element = nullable.firstWhere(condition, orElse: () => null); // current Just to be clear, I also see |
I'll note that where you added
That's not even mentioning the potential to use I didn't include |
Just to throw a wrench in the works we could have an operator that converted errors into null values.
|
Actually, some have suggested the |
An inline "catch expression" is an interesting idea. I have thought about it a few times, but the grammar needed to express the error type and capture variables gets cumbersome. Just ignoring all of that and reacting to any throw can definitely be shorter. I fear it might not be what people actually want, and it might hide programming errors. (Also, the operator should definitely be |
inline catch is probably not the best solution for this case, but it's a great feature. Creating an object to pass to final entry = map.entries.singleWhere((entry) => false, orElse: () => MapEntry(ComplexObject1({
complexObject2: ComplexObject2({
id: ObjectId('');
}),
complexObject3: ComplexObject3({ ... }),
}), ComplexObject1({
complexObject2: ComplexObject2({
id: ObjectId('');
}),
complexObject3: ComplexObject3({ ... }),
})));
if (entry.key.complexObject2.id.asString() != '') {
// Yay, it's not `null`
} |
It would be nice to get
Yuck. |
|
The I would be backward to have to add
That's reasonable but that's such an edge case compared to the dozen times you are not in this scenario does it matter ? |
It's been a while, but I still think it would be beneficial to make E? firstWhere(bool Function(E) test, {@deprecated E Function()? orElse}) {
for (final E element in this) {
if (test(element)) return element;
}
if (orElse != null) return orElse();
return null;
} To avoid breaking changes, keep the current semantics of I thought a bit more, and I don't think we need to handle a case where List<int?> a;
int? b = myList.firstWhere((c) => c == null); There is no benefit to checking Then, the big breaking change that doesn't get caught automatically is people using List<int> a;
int? b;
// this won't be affected by making firstWhere nullable, as b will still be null
try { b = a.firstWhere((c) => c % 2 == 0); } catch (_) { /* leave b null */}
// but this will, as the catch block will never run
try { b = a.firstWhere((c) => c % 2 == 0); } catch (_) { b = 0; } But I don't think that's such a big issue either. In the first case, as the comment says, making int b = list.firstwhere((c) => c % 2, orElse: () => 0); // int? cannot be assigned to int However, this is a relatively quick fix by either changing |
Would be nice. If I had to redesign It's still not going to happen. Also, all the existing third-party implementations of |
Since Dart 3.0.0 is about streamlining the dart APIs, enabling null safety by default and contains a lot of breaking changes anyways, couldn't this please be fixed/redesigned too now? |
I'd love to, but it's still really not a viable breaking change. Too hard and work-intensive to migrate, too breaking to not migrate. I don't expect this change can happen until we actually get library versioning, where new code can use the new API, and old code can stay on the old API, on the same object and type. That's not currently on the table (we keep thinking about it, but it's not on the short-list of features we are currently working actively on.) |
Until that happens, |
I will note that in practice |
My workaround is to wrap the |
Maybe there should be a The reason I bring it up is because |
EDIT - best solutions so far:
package:collection
Original question:
I want to search a list and return null when the element is not found
https://nullsafety.dartpad.dev/2d0bc36ec1f3a5ade5550c0944702d73
With null safety, I get this error:
The
orElse
parameter offirstWhere
is declared not nullable:Why not?
What is the recommended way to search a collection with null safety enabled?
The text was updated successfully, but these errors were encountered: