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
Comments
Bob, wouldn't this behavior be the exact same with the default Iterable though? |
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. |
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() { a[1] = 10; var c = a.mappedBy((i) => print('transforming $i')); c.first; // transforming 1 class MyIterable extends Iterable { int get length => 3; operator [](int index) { operator []=(int index, value) { get iterator => new ListIterator(this); |
@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. |
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. |
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. |
I find myself importing dart:collections-dev and writing I think I'd rather just not have MappedList pretending to be a List. |
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. |
In response to #7: You should never do an is-check for MappedList. |
Fixed in r17918. Added Fixed label. |
Woo, thanks! |
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().
The text was updated successfully, but these errors were encountered: