Skip to content
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

List.mappedBy should not override Iterable.mappedBy #7982

Closed
munificent opened this issue Jan 18, 2013 · 11 comments
Closed

List.mappedBy should not override Iterable.mappedBy #7982

munificent opened this issue Jan 18, 2013 · 11 comments
Assignees
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core

Comments

@munificent
Copy link
Member

The current implemented of mappedBy in List does something unintuitive: it returns a live functional reactive pseudo-collection that continues to monitor the original list:

  var a = [1, 2, 3];
  var b = a.mappedBy((i) => i * 2);
  print(b); // [2, 4, 6] ok...

  a[1] = 10;
  print(b); // [2, 20, 6] !!!

Also, it will invoke the transformation each time the element is accessed:

  var c = a.mappedBy((i) => print('transforming $i'));

  c[0]; // transforming 1
  c[0]; // transforming 1
  c[0]; // transforming 1
  c[0]; // transforming 1

So this isn't a "lazy" list, it's a "call by name" one. I believe this behavior is not what most users would intuit or want. (It's certainly not what I expected.)

It may be useful in some corner cases but if so I think that should be a separate type users construct specifically. It's currently located at one of the most-used methods on one of the most-used types.

One simple solution would to just not override at all. If users want a list back, they can just call .toList().

@jmesserly
Copy link

Bob, wouldn't this behavior be the exact same with the default Iterable though?

@munificent
Copy link
Member Author

At an abstract level, yes, the iterable you get back from Iterable.mapped by is also lazy and pointing to the original object.

In practice, though, because Iterable supports few operations for accessing elements, you usually relatively quickly call .toList() on it, or walk it imperatively, or add it to a list or do some other operation to force the lazy evaluation once and then you're done.

This gives you back what appears to be a list (which the user will likely think of as an object that stores data) but which has this quantum entangling back to the original list.

I think this will burn people. They'll see the result is a list so they'll think "ok, the mapping's done" and then store it in a field, or clear the original list etc.

@jmesserly
Copy link

Here's what I mean. The code below behaves identically to your verison. So even if they deleted the "mappedBy" overload, it wouldn't change the behavior you find confusing. You appear to be expecting an eager evaluation.

(Iterable doesn't have toString -- but it probably should)

main() {
  var a = new MyIterable(1, 2, 3);
  var b = a.mappedBy((i) => i * 2);
  print(b.toList()); // [2, 4, 6] ok...

  a[1] = 10;
  print(b.toList()); // [2, 20, 6]

  var c = a.mappedBy((i) => print('transforming $i'));

  c.first; // transforming 1
  c.first; // transforming 1
  c.first; // transforming 1
  c.first; // transforming 1
}

class MyIterable extends Iterable {
  int first, second, third;
  MyIterable(this.first, this.second, this.third);

  int get length => 3;

  operator [](int index) {
    switch (index) {
      case 0: return first;
      case 1: return second;
      case 2: return third;
      default: throw new RangeError('index $index should be >= 0 and <= 2');
    }
  }

  operator []=(int index, value) {
    switch (index) {
      case 0: first = value; return;
      case 1: second = value; return;
      case 2: third = value; return;
      default: throw new RangeError('index $index should be >= 0 and <= 2');
    }
  }

  get iterator => new ListIterator(this);
}

@jmesserly
Copy link

@bob, posted that before seeing your updated comment. Yes, I agree returning List is confusing. But I think the problem is more that we don't have distinction between read-only list and List. Maybe if the type annotation said ListView?

Otherwise, you're essentially arguing for removing functionality -- in particular the [] and toString. I'm not sure that is a big deal.

@munificent
Copy link
Member Author

Right, your comment is spot-on. The behavior here isn't inconsistent with Iterable. My belief, though, is that the behavior of something whose type is List should be inconsistent with Iterable because users expect those types to work differently.

I think user's expect Lists to work like data structures. They have a literal form, and the majority of lists they encounter will be data structures.

It takes a little getting used to the idea that Iterables are not always data structures, but once people understand that things like infinite sequences, generators, etc. can all be Iterables I think that enforces the right mental model that Iterable is a thing you can dynamically request values from and that computation may be involved in that.

I also think it's relatively rare that users iterate the same iterable twice (though I would definitely be interested in real data gathering here). I think they typically either call another transforming method on it, call .toList(), use it in a for loop, or occasionally add it to an existing collection.

@alan-knight
Copy link
Contributor

I agree that it's surprising for the result to identify itself as a List and behave mostly like a List, but with slightly different semantics. This seems at least related to 7940.

@alan-knight
Copy link
Contributor

I find myself importing dart:collections-dev and writing
  if (result is MappedList) {
    result = result.toList();
  }

I think I'd rather just not have MappedList pretending to be a List.

@jmesserly
Copy link

Alan, you raise a good point. At the very least, if something returns a list, I want a way to tell if it's read only. I filed http://dartbug.com/8113 for that


Added Area-Library, Library-Core labels.

@floitschG
Copy link
Contributor

In response to #­7: You should never do an is-check for MappedList.
If you get a MappedList, then use it this way. The copying would probably be more expensive than doing repeated computations of the elements. In any case it's not up to you to decide.

@floitschG
Copy link
Contributor

Fixed in r17918.


Added Fixed label.

@munificent
Copy link
Member Author

Woo, thanks!

@munificent munificent added Type-Defect area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core labels Jan 31, 2013
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core
Projects
None yet
Development

No branches or pull requests

4 participants