4 ms·
long numberOfWords = Stream.of("the", "", "fox", "jumps", "over", "the", "", "dog") // (1) .filter(Predicate.not(String::isEmpty))
by mjburgess 3y ago
long numberOfWords =
Stream.of("the", "", "fox", "jumps", "over", "the", "", "dog") // (1)
.filter(Predicate.not(String::isEmpty)) // (2)
.collect(Collectors.counting()); // (3)
This programming style is both expressive and efficient.
Err... I assume the author here is aware that it is neither, but perhaps struggles for something else to say in motivating this API. It's certainly an amusing remark.
I'd prefer either an imperative,
for(w <- words) if(!w.empty) count++
or, an ergonomic dsl,
words.filter( w -> !w.empty ).count
The strange namespacing of functional primitives, glued into an api via 'gather', 'collect' etc. is neither expressive not efficient. Though I concede it may be a necessary route for a JEP
- bmacho 3y agoSeems expressive and efficient to me.
- marginalia_nu 3y agoExpressive, sure. Efficient? Not so much. It's easy to find cases where performing an operation using the streams API comes at a 90% performance hit.
- PhilipRoman 3y agoTSMT Fingers crossed that the upcoming Valhalla project can improve the situation w.r.t. number of allocations. I've noticed a general trend where languages without support for proper compile time metaprogramming (and the corresponding optimizations) often create uglier code (despite being higher level) to avoid the performance hit. For example the mutable Vec2D type in various physics related libraries. Last time I tested it was still faster to implement various string operations using Unsafe, than with standard APIs. I want to be able to trust my compiler/JIT.
- qsort 3y agoThe do notation would be ideal here, but it's probably a bit too much to ask...
- Traubenfuchs 3y agoIs it that fun to shit on Java with cherrypicked worst-case and unfair examples? You could use any libraries StringUtils.hasText (any serious app has one) to avoid the Predicate.not and do static imports (StringUtils, Collectors) to avoid explicitedly naming the classes containg those static methods... Also your example misses the string collection creation part. Ultimately we should get, put on one line like your code: words.stream().filter(hasText).collect(counting());
- mrkeen 3y ago> Is it that fun to shit on Java with cherrypicked worst-case and unfair examples? The person doing the shitting did not pick the examples.
- d35007 3y agoThe kids hate Java, and that's ok. No sense arguing with them. I am happy a tool I use frequently is getting better. I am also happy there are other tools out there.
- mike_hearn 3y agoThat example could be easily written like this: var num = words.stream().filter(w -> !w.isEmpty()).count(); This is shorter and closer to what you're expecting. The reason it's written the long way is because of the labels. The prior sentence is: A stream pipeline consists of three parts: a source of elements, any number of intermediate operations, and a terminal operation. For example: ... so the goal here is to make the different components of the pipeline clear conceptually for the explanation of what's to come, not to write the shortest code possible. It therefore uses code that makes the underlying abstractions explicit rather than the sugar that avoids typing. The reference to "expressive and efficient" meanwhile is likely about the fact that Java can parallelize streams in many cases (efficient). As for "expressive", well, that's somewhat debatable but for sure there are some cases where the functional pipeline style is easier to read and more expressive than the imperative style. In my own code I use both because indeed especially for short operations over lists and the like, sometimes a for loop is just clearer to my eye. But it's a matter of taste. BTW the same code written in Kotlin: val num = words.filter { !it.isEmpty() }.count() Very similar. You may slightly prefer the syntax or dislike the "magical" auto-naming of the lambda parameter, I've seen it argued both ways. ---- https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/Stream.html#count https://docs.oracle.com/en/java/javase/21/docs/api/java.base...()
- kaba0 3y agoI think Scala is hands down the best collections/stream library, I especially like their _ shorthands: val num = words.count(!_.isEmpty())
- Marazan 3y agoI think that is a horrifying crime against man and nature. If I wanted to write PERL I would do so.
- kaba0 3y agoWhat is hard to understand about it? _ is universally used as a placeholder, it also usable in pattern matching. Scala can be used to make some perl-y <!! operators, but it is not like that at all, and makes for very readable, understandable code.
- SkiFire13 3y agoNote that Java also allows you to write that as words.filter(w -> !w.isEmpty()).count() And they just end up calling the same methods. The author probably used "expressive" and "efficient" to refer to the underlying APIs for the fact that you can compose operations without fully evaluating the whole stream until the terminal one.
- deleted 3y ago[deleted]
- dullcrisp 3y agoIf you look closely you’ll see that the given code is equivalent to your second example, though admittedly the Java syntax is a bit clumsy.
- another-dave 3y ago> evaluation begins only when a terminal operation is invoked. In this example, line (1) creates a stream, but does not evaluate it, line (2) sets up an intermediate filter operation but still does not evaluate the stream, and finally the terminal collect operation on line (3) evaluates the entire stream pipeline. It's quite clear, reading beyond just the code, that the original author is talking about _computational_ efficiency. It feels like you missed the point — that streams are only evaluated lazily as needed — in order to poke fun at the (toy) example. The Java code can (and in a real-world application would), be made more consise (e.g. via static imports) but the author is deliberately being more verbose to be more explicit in what's going on.
- mrkeen 3y ago> It feels like you missed the point — that streams are only evaluated lazily as needed — in order to poke fun at the (toy) example. GP may be closer to the point than you realise, even if unintentionally so. I mentioned Streams failing to be lazy in another comment, and predictably got down-votes rather than corrections. So anyway... https://bugs.java.com/bugdatabase/view_bug?bug_id=8079264 https://bugs.java.com/bugdatabase/view_bug?bug_id=8079264 Submitted: 2015-05-01 https://bugs.java.com/bugdatabase/view_bug?bug_id=8149614 https://bugs.java.com/bugdatabase/view_bug?bug_id=8149614 Submitted: 2016-02-10 https://bugs.java.com/bugdatabase/view_bug?bug_id=8155217 https://bugs.java.com/bugdatabase/view_bug?bug_id=8155217 Submitted: 2016-04-25 https://bugs.java.com/bugdatabase/view_bug?bug_id=8189234 https://bugs.java.com/bugdatabase/view_bug?bug_id=8189234 Submitted: 2017-10-11 https://bugs.java.com/bugdatabase/view_bug?bug_id=8196106 https://bugs.java.com/bugdatabase/view_bug?bug_id=8196106 Submitted: 2018-01-24 https://bugs.java.com/bugdatabase/view_bug?bug_id=8229983 https://bugs.java.com/bugdatabase/view_bug?bug_id=8229983 Submitted: 2019-08-21 https://bugs.java.com/bugdatabase/view_bug?bug_id=8267758 https://bugs.java.com/bugdatabase/view_bug?bug_id=8267758 Submitted: 2021-05-25 I wonder if 2024 will be the year of the lazy Stream.
- another-dave 3y agoI think the point that you are making — that even if Streams are meant to be efficient on paper, but reality is different because of bugs in implementation — is an interesting one that moves on the discussion! Surprised to hear you were downvoted for it. Personally, I don't think that's what the other comment was getting at though, as they were commenting on efficiency regards to syntax.
- oaiey 3y agoefficient compared to what? Previous Java: Yes. C++: Yes. C#: maybe not. Functional Programming Language X with 2 dozen language feature supporting it: Definitely not. But this is the Java Enhancement Process, and within this language AND the fact that this is specification/documentation example, this is efficient.
- PaulHoule 3y agoStreams is too complex for what it does and it doesn’t even parallelize well. Here is something that does roughly the same thing but I think is way better See https://github.com/paulhoule/pidove https://github.com/paulhoule/pidove https://central.sonatype.com/artifact/com.ontology2/pidove https://central.sonatype.com/artifact/com.ontology2/pidove
- nogridbag 3y agoThis is far less readable than Java streams to me. sum(filter(x->x%2==0,List.of(5,3,4,19,75,6))); Streams are formatted linearly similar to Clojure threading macros.
- PaulHoule 3y agoThe advantage of the static import DSL is that it is more composable: anyone else can define operators that interoperate with my operators without the complexity of the scheme linked above not to mention more generality. This goes not just for the low level operators like "filter" that you might want to supplement but the many more operators you can write that are implemented out of mine. I prototyped an API that works in the same direction as streams by having something like https://paulhoule.github.io/pidove/apidocs/com/ontology2/pidove/iterables/TidyIterable.html https://paulhoule.github.io/pidove/apidocs/com/ontology2/pid... in that it wraps all Iterables returned by my methods and has not just the teardown facility but also all the operators attached as instance methods which lets you write the chaining style you ask for that I know is in demand. If I was going to go any further on pidove it would have involved more use of code generation and this system https://github.com/paulhoule/ferocity https://github.com/paulhoule/ferocity which was supposed to be a code generator for writing code generators, and it could code generate stubs that would let you write expression trees in Java as S-expressions and build them up into methods and either compile the code to real Java source code or execute the methods by evaluating the expression tree in place. Like common LISP you can write syntactic macros in that that metalanguage because an Expression<Expression<X>> can be evaluated at compile time, one of quite a few concepts like "quoting" that I encountered in that spike. The idea was ferocity would get to the point where it synthesizes a more complete and perfect pidove.
- StevePerkins 3y agoIn fairness, you are omitting the list instantiation, and collecting only a count rather than an assignment of results. Apples to apples, your alternative algorithm would be implemented like this: words.stream().filter(w -> !w.isEmpty()).count() Which is hardly any longer, and arguably more readable intent with the English word "filter".
- benibela 3y agoMy favorite is XPath: let $numberOfWords := count($words[.]) or let $numberOfWords := $words[.] => count()