3 ms·
Probably the biggest difference between this and Java Streams is that it produces an Iterable<T>, which implies that, like LINQ, the thing can be iterated multi
by bunderbunder 8y ago
Probably the biggest difference between this and Java Streams is that it produces an Iterable<T>, which implies that, like LINQ, the thing can be iterated multiple times. For better or for worse, Java 8's Streams can only be iterated once. The difference can potentially be a big deal, depending on your use case.
- barrkel 8y agoRepeatability is a bonus, but it's also a limitation - not all sources of data can be restarted. Overall, I think restartable enumerations are ok to lose given that they are rarely restarted in practice - they tend to be expensive, or at least seem expensive.
- bunderbunder 8y agoWhen you're working with external data, yes, they can be expensive. But most of what I've seen LINQ to Objects used for is simply mapping over data that already lives in memory. Oftentimes you want to just pass along a lazy projection of that data, on the grounds that re-running the operation is less expensive than allocating a bunch more memory in order to cache the result to a list. In the cases where a source can't be restarted, it's always seemed easy enough to me to just build up your LINQ query and then pass its enumerator back rather than the original enumerable. Returning an IEnumerator<T> should be sufficient to communicate that you only get to enumerate the thing once, and tacking a ".GetEnumerator()" on the end of things requires barely more typing than tacking ".ToList()" on the end. By contrast, with streams, I feel like the Java maintainers gave me an API that's less powerful than the standard I'm used to on other platforms, on the grounds that they think I'm not a smart enough developer to handle a more powerful API. I don't really appreciate that. All it's really meant is that I now need to work with two different libraries depending on whether I'm working with data that lives in memory or not, which does worse things to the cognitive load it takes to understand the codebase than simply having to keep track of the difference between an Iterable<T> and an Iterator<T> would have.
- barrkel 8y agoI find it hard to get worked up about it. You can just hand around a Supplier of your stream if you need restarting. Monadic composition of further operations is a bit clumsier then, but it's still very doable.
- twic 8y agoYou can turn a Stream<T> into an Iterator<T>, and since Iterable<T> is a functional interface whose single method supplies an Iterator<T>, you can make an Iterable<T> by wrapping a Stream<T> in a simple lambda: Iterable<ProductDisplayInfo> getDisplayList(List<Product> products) { return () -> products.stream() .filter(p -> p.getCategory() == 2 || p.getCategory() == 5 && isProductUnderDiscount(p)) .map(ProductDisplayInfo::new) .iterator(); } (E&OE - i haven't actually compiled this, but do this all the time!)
- bunderbunder 8y agoWhat happens when you try to iterate that iterable twice?
- jcheng 8y agoWhy wouldn't that work? The iterable returns a different iterator, backed by a different stream, each time.