Sdk: Iterable.firstWhere is hard to use

Created on 16 Jan 2013  路  9Comments  路  Source: dart-lang/sdk

I don't understand why I have to write stuff like this:

myIterable.firstMatching((o) => o.startsWith('foo'), () => null);

I think the most frequent situation is to not know if I have a match, so throwing an error should not be the default behavior when orElse is null.

area-library closed-not-planned library-core type-enhancement

Most helpful comment

_This comment was originally written by daven...@gmail.com_


The comments here helped me determine it should be:

var id = request.cookies.firstWhere((cookie) => cookie.name == 'session', orElse: () => null);

Just felt rather unintuitive, especially w/ orElse being optional when it's really not because if there's no element, an exception is thrown rather than returning null by default.

All 9 comments

I'm sympathetic to having null as a default value. You still have a way to back out of it if you know that null could be a valid value.


cc @floitschG.
_Removed Type-Defect label._
_Added Type-Enhancement, Library-Core labels._

Any update on this? Another one of my developers ran into the 'unnatural' way of writing.

_Changed the title to: "Iterable.firstWhere is hard to use"._

We debated this bug several times, but in the end we kept the current behavior. There is a nice symmetry between firstWhere(f) and where(f).first, and the work-around (the "() => null") is small.


_Added NotPlanned label._

I'd argue that firstWhere doesn't add much value compared to where(f).first.

_This comment was originally written by daveno...@gmail.com_


I'm finding firstWhere difficult to use.

var id = request.cookies.firstWhere((cookie) => cookie.name == 'session').value;

This would throw a "Bad state: No element" once I isolated it w/ a try/catch.

I thought I had to do this but still bad state:

var id = request.cookies.firstWhere((cookie) => cookie.name == 'session', orElse: null);

_This comment was originally written by daven...@gmail.com_


The comments here helped me determine it should be:

var id = request.cookies.firstWhere((cookie) => cookie.name == 'session', orElse: () => null);

Just felt rather unintuitive, especially w/ orElse being optional when it's really not because if there's no element, an exception is thrown rather than returning null by default.

_This comment was originally written by dave...@gmail.com_


So now I handle like so:

    var sessionCookie = request.cookies.firstWhere((cookie) => cookie.name == 'session', orElse: () => null);
    if (sessionCookie == null) return new Response(false);
    var id = sessionCookie.value;
    if (id == null) return new Response(false);

_This comment was originally written by @mezoni_


You can use a (slightly faster) alternative.

firstOrDefault(): 780
firstWhere(): 812
where().firstOrDefault(): 953

=================
import "package:queries/collections.dart";

void main() {
  test();
}

void test() {
  var count = 10000;
  var list = new List<int>(count);
  for (var i = 0; i < count; i++) {
    list[i] = i;
  }

  measure("firstOrDefault()", () {
    for (var i = 0; i < count; i++) {
      var collection = new Collection<int>(list);
      var value = collection.firstOrDefault((e) => e == i);
    }
  });

  measure("firstWhere()", () {
    for (var i = 0; i < count; i++) {
      var value = list.firstWhere(((e) => e == i), orElse: () => null);
    }
  });

  measure("where().firstOrDefault()", () {
    for (var i = 0; i < count; i++) {
      var collection = new Collection<int>(list);
      var value = collection.where((e) => e == i).firstOrDefault();
    }
  });
}

void measure(String msg, f()) {
  var sw = new Stopwatch();
  sw.start();
  f();
  sw.stop();
  print("$msg: ${sw.elapsedMilliseconds}");
}
=================

Also you can use the following operations on collections:

TSource aggregate
bool all
bool any
num average
bool contains
int count
Enumerable<TSource> defaultIfEmpty
Enumerable<TSource> distinct
TSource elementAt
TSource elementAtOrDefault
Enumerable<TSource> except
TSource first
TSource firstOrDefault
Enumerable<IGrouping<dynamic, dynamic>> groupBy
Enumerable<dynamic> groupJoin
Enumerable<TSource> intersect
Enumerable<dynamic> join
TSource last
TSource lastOrDefault
num max
num min
IOrderedEnumerable<TSource> orderBy
IOrderedEnumerable<TSource> orderByDescending
Enumerable<dynamic> select
Enumerable<dynamic> selectMany
bool sequenceEqual
TSource single
TSource singleOrDefault
Enumerable<TSource> skip
Enumerable<TSource> skipWhile
num sum
Enumerable<TSource> take
Enumerable<TSource> takeWhile
Dictionary<dynamic, dynamic> toDictionary
Lookup<dynamic, dynamic> toLookup
Enumerable<TSource> union
Enumerable<TSource> where

Was this page helpful?
0 / 5 - 0 ratings

Related issues

xster picture xster  路  3Comments

ranquild picture ranquild  路  3Comments

bergwerf picture bergwerf  路  3Comments

DartBot picture DartBot  路  3Comments

DartBot picture DartBot  路  3Comments