4 ms·
What's the point of this library? A pet peeve of mine is how these libraries put the 'old' alternative in the worst possible light, creating the fake impressio
by rzwitserloot 8y ago
What's the point of this library?
A pet peeve of mine is how these libraries put the 'old' alternative in the worst possible light, creating the fake impression (at a casual glance) that the library makes your code much nicer. It doesn't.
Passing in an array is weird, so lets assume we're not working with really outdated codebases and a List<Product> is passed in instead.
You can do the snippet with bog standard Java8+ stream API like so:
List<ProductDisplayInfo> getDisplayList(List<Product> products) {
return products.stream()
.filter(p -> p.getCategory() == 2 || p.getCategory() == 5 && isProductUnderDiscount(p))
.map(ProductDisplayInfo::new)
.collect(Collectors.toList());
}
Even in old style java6 syntax it's short, sweet, and to the point (as short as this library can let you write it):
List<ProductDisplayInfo> getDisplayList(List<Product> products) {
var pdiList = new ArrayList<ProductDisplayInfo>();
for (var p : products)
if (p.getCategory() == 2 || p.getCategory() == 5 && isProductUnderDiscount(p))
pdiList.add(new ProductDisplayInfo(p));
return pdiList;
}
Separate from that, as scarface74 mentioned, part of the point of LINQ is that the structure of the expression tree can be applied elsewhere, for example, to an SQL statement.
- bunderbunder 8y agoProbably 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.
- dominotw 8y agoHopefully you can get in to underlying expression tree which can then be converted to SQL, graphql, rest ect. Code as data.
- bunderbunder 8y agoI strongly doubt it. It's only mimicking the IEnumerable<T> bits of LINQ; there's nothing like IQueryable<T> in there. I think that one thing a lot of people are missing when they talk about the Expressions bit of LINQ is that LINQ is really two different things that have two different killer features. There's the set of extensions on top of IEnumerable<T>, which are mostly used for writing declarative replacements for for-loops, and also supporting things like PLINQ. As far as I'm aware, IEnumerable<T>'s MSDN documentation doesn't even mention expression trees. And there's the (much more complicated) IQueryable<T> interface, which is the bit that supports things like the LINQ interface to Entity Framework, and is all about expression trees. The IEnumerable<T> bits aren't as fancy, but they are still quite useful.