9 ms·
Java 8: No more loops
- Beltiras 12y agoAll I could think: it's still sunk in too much architecture....
- pron 12y agoToo much architecture for what? For bankings? For medical devices? For the safety-critical avionics software? For high-frequency trading? For manufacturing control? For weapon-systems? For power-plant command and control? For government ERP running on mainframes? Because Java is used for all of these things. Yes, some bits might be too architected for your MVP web app that you're going to re-write in a year (and even that depends mostly on the libraries you're using; there are plenty of lean libraries for the web-startup crowd).
- obstinate 12y agoI really want to like functional programming, but the functional version of each of these seems less readable and more verbose.
- CmonDev 12y agoI guess this is why Microsoft diverged from the "standard" names like 'map', 'collect' and 'filter' in favour of more human-oriented SQL-like LINQ: 'select', 'where', 'toList'. I have to read the underscore.js documentation every time I am using it :(.
- api 12y agoMS did the right thing. A huge barrier to FP adoption is the unnecessarily obtuse terminology. In playing around with FP languages I often find myself having to re-look-up basic syntax because the terms are simply so obtuse that they won't "stick" in my head. I think it comes from FP's rooting in mathematics, and math is itself unnecessarily arcane.
- sswezey 12y agoI disagree. The benefit of having the standard terms is they are the same in every FP library you use. If you spend the time to learn and internalize them within one, it'll be the same in every language or library that implements them. I don't understand how map, filter, collect, reduce are obtuse or arcane.
- api 12y agoThose aren't the worst offenders. I'm referring to curry, cadr, lambda, monad, etc. But map is confusing: it's also a data structure. "Wait, does map() create a new key-value dictionary?" Filter and collect are okay. Reduce is pretty arcane but tolerable, but the actual operation feels intuitively closer to "categorize" or "group." My point about math was cultural. Math seems to revel in having its own peculiar and often even domain specific (even within mathematics!) terms and symbols for things.
- rosta 12y agoreduce (or fold) would be accumulate, certainly not categorize or group.
- Someone 12y ago1. Map isn't "also a data structure", the data structure is a way to implement a mapping function. There is no conceptual difference between code and data (for an example, see http://en.m.wikipedia.org/wiki/Cons#Not_technically_fundamental http://en.m.wikipedia.org/wiki/Cons#Not_technically_fundamen...) 2. cadr is relatively easy, if you know your assembly (http://en.m.wikipedia.org/wiki/Car_and_cdr#Etymology http://en.m.wikipedia.org/wiki/Car_and_cdr#Etymology) :-) And math has locally defined terms because one cannot give every concept a unique short meaningful memorable name. That is no different in computer science, where 'integer' can include negative numbers or not, may or may not wrap around, can be any number of bits or potentially unlimited, may include minus zero, etc.
- luddypants 12y ago
- KfZ 12y agoI'm not sure how those are supposed to be more human-oriented? I'll grant you "where" might be a slight improvement over "filter", but calling "map" "select"? I can't make sense of that even knowing what it's supposed to mean. If anything I'd assume it to be yet another synonym for filter.
- remon 12y agoThis was exactly what I was thinking when I read the code. The goal should not be to remove loops. As a language construct there is nothing wrong with loops itself. The goal should be to make more readable and maintainable code, preferably without increasing verbosity. The functional alternatives posted here are not actually code quality improvements and should not be presented as such.
- BFay 12y agoI welcome the functional features, but I have to agree that these specific examples are ugly compared to the more familiar alternatives. Maybe this will look more readable after getting used to it, but I'd much rather be looking at Clojure or Scala for now.
- bollockitis 12y agoOut of curiosity, could you share an example of how this compares to Clojure or Scala? I am not familiar with either.
- markc 12y agoHere's a quick version in Clojure. I'm sure it can be done cleaner than this.. (def articles [{:title "title1" :author "author1" :tags #{:Java :t2 :t3}} {:title "title2" :author "author1" :tags #{:Jvxa :t2 :t3}} {:title "title3" :author "author3" :tags #{:Java :t3}}]) ;; find the first article in the collection that has the tag “Java”. (first (filter #(contains? (:tags %) :Java) articles)) ;; ==> {:tags #{:t2 :Java :t3}, :title "title1", :author "author1"} ;;get all the elements that match instead of just the first (filter #(contains? (:tags %) :Java) articles) ;; ==> ({:tags #{:t2 :Java :t3}, :title "title1", :author "author1"} {:tags #{:Java :t3}, :title "title3", :author "author3"}) ;;group all the articles based on the author. (group-by :author articles) ;; cheating? ;; ==> {"author1" [{:tags #{:t2 :Java :t3}, :title "title1", :author "author1"} {:tags #{:Jvxa :t2 :t3}, :title "title2", :author "author1"}], "author3" [{:tags #{:Java :t3}, :title "title3", :author "author3"}]} ;;find all the different tags used in the collections (apply clojure.set/union (map :tags articles)) ;; ==> #{:Jvxa :t2 :Java :t3}
- mattnewton 12y agoI think the examples shown here don't really show how it can help- the real win for me is if you already have a function to filter over (like "article.isJava()" or something) it becomes much clearer. It also is eye opening to see or higher order functions like pluck or flatmap that build off these blocks. Edit- there is a good flatmap example there that I glossed over when first reading. It looks like Java even has serviceable syntax for passing around lambadas like that now too, that is cool.
- jimktrains2 12y agoI disagree. Perhaps I'm simply more use to seeing this style, but I feel it's more concise, more maintainable, and more easily built on than the loop-based version. Additionally, I find it to be more semantically clear as to what the expected behavior is, as opposed to reasoning about the loop, especially when filters become complex.
- TYPE_FASTER 12y agoI don't think the article should have framed it as "let's replace traditional looping constructs," but "let's apply a filter or query to a data set." That's how I've typically seen .NET LINQ written up, and it makes more sense to me.
- hibikir 12y agoThe extra verbosity comes from the way java 8 is trying to make functional programming more comfortable while doing as little as possible to the language to do it. When you add all the sugar to make lines more expressive, then you'd hear complaints like the ones Scala gets about having too many ways of doing the same thing. in scala, finding the first article looks like: def topJavaArticle(articles:List[Article]) = articles.find(_.tags.contains("Java")) which returns an Option[Article], so it handles the null check. Since List in scala already handles functional constructs directly, then the filter example is just as easy: def javaArticles(articles:List[Article]) = articles.filter(_.tags.contains("Java")) group by author? def byAuthor(articles:List[Article) = articles.groupBy(_.author) And yes, that's the entire method definition, signature included. We could have added the return types for documentation if we felt like it. You might still not like the style, but you can't say it's more verbose than the floor loop. Backwards compatibility and design philosophy makes sure that Java 8 doesn't go hard enough on the sugar. It's why I am not optimistic of Java's future. There's awesome features out there in newer languages, like proper pattern matching, that Java just won't be able to borrow from functional languages.
- thescrewdriver 12y agoIt's doesn't take long when working with functional code to never want to go back to loops.
- Figs 12y agoI had the opposite reaction after having to work with a number of complicated reductions; in a lot of cases, a short for-loop and accumulator variable would have made the code much easier to read...
- breckinloggins 12y agoI saw a quote recently from Rich Hickey: "Elegance and familiarity are orthogonal." I didn't fully understand this concept until I read your post and found myself disagreeing with you, thinking "what is obstinate talking about? OBVIOUSLY the streams way is way more readable and far less verbose". The thing is, I can't believe I'm thinking that because I was exactly in your place a few months ago. Since that time, however, I've become very familiar with functional styles and now the Java 8 streams way seems "almost, but not quite right" and the imperative iteration style seems "gratuitously complicated and philosophically wrong... I mean... look at all that special syntax! That mutation! The horror!" All of this is to say that I'm not quite sure either of us is more correct than the other, but familiarity does seem to cause a profound mental shift.
- obstinate 12y agoIt might be worth mentioning that I already am quite fimiliar with functional concepts and occasionally use them in my code. I find them more verbose in C++ and Java despite my familiarity with them.
- codygman 12y agoWhat about this Haskell example doing the same tasks? https://news.ycombinator.com/item?id=8878421 https://news.ycombinator.com/item?id=8878421
- wodude 12y agoI've been doing this in Java WebObjects for a decade tagList = articles.tags.@flatten.@unique.@sort
- edgyswingset 12y agoThe first example is actually a poor way to do it, IMO. Even if behind the scenes, these are implemented lazily (like C# LINQ), it may not be obvious what's going on to someone else who isn't familiar with the API. I'd opt for the for() loop each time when it's something like halting when you find what you're looking for.
- anon4 12y agoThe for loop is actually a poor way to do it, IMO. Even if behind the scenes, these are implemented via conditional gotos (like C), it may not be obvious what s going on to someone else who isn't familiar with the syntax. I'd opt for the if-goto loop each time when it's something like halting when you find what you're looking for. To wit: You can expect others to be familiar with basic features of the language and ecosystem, or prepared to learn them.
- deleted 12y ago[deleted]
- philippelh 12y agoIt's the price to pay for implementing generics via type erasure, not for getting functional features in a statically typed language. http://stackoverflow.com/a/24421331 http://stackoverflow.com/a/24421331
- eeperson 12y agoThis isn't necessarily due to erasure. This actually about boxing. Reified generics is one way to solve this. Another option is tagged pointers. That is how OCaml handles this[1]. [1] http://stackoverflow.com/questions/3773985/why-is-an-int-in-ocaml-only-31-bits http://stackoverflow.com/questions/3773985/why-is-an-int-in-... edit: clarity
- orbifold 12y agoNo its the price you pay, when you improperly implement generics and do not have polymorphic functions. In other words its the price you pay, if you ignore the developments in computer science in the last 10 years and invent a crippled terrible language in 1995 (roughly the same time OCaml came out) and push it onto the world with success, because you are a big corporation. At the point they introduced generics, there really was no way you should have gotten it wrong, but they did... Incidentally I think it's a failure of the Free Software movement, that they did not do much to actually move towards a modern statically typed language (or family of languages) that was not burdened by corporate policy. In Haskell the type of map is map :: Functor f => f a -> (a -> b) -> f b (actually for historical reasons its called fmap, but nevermind). Here f a in Java could be something like Stream<a>, that is something that contains things of type a.
- vram22 12y ago>actually for historical reasons its called fmap, but nevermind Heh, reading that reminded me of this blog post I wrote a while ago: fmap(), "inverse" of Python map() function: http://jugad2.blogspot.in/2012/10/fmap-inverse-of-python-map-function.html http://jugad2.blogspot.in/2012/10/fmap-inverse-of-python-map... And someone I know also commented on it and gave what is probably a better version, called compose.
- glifchits 12y agoConstructing a collection of things is just one case where a for loop is nice. Would be nice to see some more interesting examples of how we can use streams.
- bitL 12y ago...aaand the obfuscation of Java begins!
- aaronetz 12y agoIn the last example's "for" loop, a Set would probably be clearer and more efficient than a List for gathering distinct elements, at least for large data sets. I haven't tried the functional Java yet, but I wonder if using Collectors.toSet() and skipping the distinct() stage would be better?
- anton_gogolev 12y agoIn C#, this is significantly more elegant: public IList<String> getDistinctTags(IEnumerable<Article> articles) { return articles.SelectMany(a => a.Tags).Distinct().ToList(); } The entire LINQ "empire" (.NET 3.5) is built on top of IEnumerable<T> which was around since .NET 2.0. Streams seem to be very artificial; why not rely on Iterable<>? Oh, and no "yield" in Java.
- deleted 12y ago[deleted]
- djb_hackernews 12y ago"Significantly" is a bit dramatic when we are talking about the difference between a single extra method call. One of the benefits of the Java version is it is easier to understand if you don't have a Java background but do have an FP background. With your C# example you'd need to find the documentation to find out what SelectMany does (which is probably just a helper method that abstracts a map and flatMap call)
- TranquilMarmot 12y agoIf you're writing a few hundred queries in a large appliaction, then that single extra method call will start to get awfully tedious to write over and over and over again. And talk about hurting readability, too... stream()! stream() everywhere! I'd say it's pretty significant.
- iwwr 12y ago>One of the benefits of the Java version is it is easier to understand if you don't have a Java background but do have an FP background. Are there professional programmers with only FP and no imperative language experience?
- hueyp 12y agoSelectMany is flatMap. Could the java equivalent be? articles.stream().flatMap(article -> article.getTags().stream()) Or is the previous map required?
- carsongross 12y agoThis demonstrates a major problem with development of Java since the Collections work (which was fantastic): the libraries suffer from over-engineering and surface far too much implementation flavor in the API. Who cares about streams? Who cares about Optional? We just want to filter a list in a clear, terse manner. (Some people do care about streams and Optional, and I wish them well, but that's orthogonal to the question at hand.) Consider the examples given. Here they are implemented in Gosu: getFirstJavaArticle() : Article { return articles.firstWhere(\ article -> article.Tags.contains("Java")) } getAllJavaArticles() : List<Article> { return articles.where(\ article -> article.Tags.contains("Java")) } groupByAuthor() : Map<String, List<Article>> { return articles.partition( \ article -> article.Author ) } public getDistinctTags() : Set<String> { return articles.*Tags.toSet() } (I cheated a bit on the last one by just using a Set, but that's more appropriate and communicates the uniqueness of the elements in the collection to the API consumer.) Beyond the dot-star flatmap operator, there isn't anything very fancy going on: just closures being passed to methods, returning familiar classes that don't require additional transformation to pass on to the rest of the world. It's too bad, because this is certainly good enough. As Jack Nicholson said: What if this... is as good as it gets?
- skywhopper 12y agoThe examples feel very much like Ruby to me. In a good way. This sort of chaining of operations also feels very natural for someone thinking in terms of a chain of Unix commands piped together. However, having that explicit "stream()" signifier is a very Java-y thing to do and appears to ask the programmer to decide how best to compile the given line. I would expect the compiler should be doing that work for us.
- mythz 12y agoJava's one of the worst language examples of using FP collections I've seen. Even with hindsight I still find this to be uglier and unnecessarily more verbose than it needs to be. E.g. same Example in Dart: class Article { String title; String author; List<String> tags; Article(this.title, this.author, this.tags); } Article getFirstJavaArticle() => articles.firstWhere((x) => x.tags.contains("Java")); List<Article> getAllJavaArticles() => articles.where((x) => x.tags.contains("Java")); List<String> getDistinctTags() => articles.expand((x) => x.tags).toSet().toList(); Can even be shorter without the Optional typing, but it's more readable to be explicit to have them. Dart benefits from having Collection and Stream mixins so you always get a rich API on Dart's collections. If anyone's interested to comparing FP collections in different languages, I've ported C# 101 LINQ examples in: - Swift https://github.com/mythz/swift-linq-examples - Clojure https://github.com/mythz/clojure-linq-examples - Dart https://github.com/dartist/101LinqSamples
- gavinpc 12y ago> uglier and unnecessarily more verbose than it needs to be Nice autologism.
- kuschku 12y agoAnd your example doesn’t even tell what it’s doing. I’d have to look up what .expand, .where etc means, while Java just uses the standard FP names that every CompSci student knows.
- kasey_junk 12y agoWhere makes sense if you think of it from a SQL perspective which I think has more widespread use than the corresponding FP terms (ie lots of developers don't have a CompSci background, but few of them haven't used trivial SQL).
- markc 12y agoDart looks very elegant. You didn't include "group all the articles based on the author". Would that be equally clean in Dart? Here's the Clojure version: ;; given articles = [{:title "t1" :author "a1" :tags #{:t1 :t2}} .. etc. ] ;; These 4 Clojure one-liners replace all the J8 code examples in the article. (first (filter #(contains? (:tags %) :Java) articles)) (filter #(contains? (:tags %) :Java) articles) (group-by :author articles) (apply clojure.set/union (map :tags articles))
- Proleps 12y agowhy not use: public final class Article{ public final String title; public final String author; public final List<String> tags; public Article(String title, String author, List<String> tags) { this.title = title; this.author = author; this.tags = tags; } } Getters don't seem very useful on an immutable object.
- thescrewdriver 12y agoJava code tends to follow the JavaBeans naming convention even when not implementing beans. Scala version of your code: case class Article(title: String, author: String, tags: List[String])
- organsnyder 12y agoIf you ever want to change the implementation of Article, you'd break anyone that was using that part of your API. If you use getters, you can change your implementation without breaking the consumers of your API. For instance, let's say that you don't want to store the author's name as a string anymore, and want to store a reference to an Author object. If you have a getAuthor() method, you can change it from a simple getter to instead call author.getName(), preserving your public-facing API.
- thescrewdriver 12y agoThat's also one of the differences between Scala and Java. In Scala you have referential transparency, so you easily can switch between fields/properties and methods without changing the calling code.
- TheLoneWolfling 12y agoAnd this is one of the major reasons why Java is regarded as verbose. There's a simple solution (have the compiler transparently rewrite references to x.foo to x.getFoo / x.setFoo and transparently add getFoo/setFoo - the JVM inlines (trivial) getters and setters anyways) and yet Java, in the interests of "transparency", doesn't allow it. And their justification fails. Sure, currently if you read x.foo you know that no code is being executed - but you never see x.foo because everything has getters and setters. So all it does in practice is make things (even) more verbose.
- deleted 12y ago[deleted]
- hristov 12y agoSo Java is trying to slowly turn into Haskell.
- kasey_junk 12y agoMore like it is trying to catch up with C#.
- nsxwolf 12y agoI still find streams far more difficult to teach to a new programmer than loops.
- spullara 12y agoHere is another example, porting a spellchecker from Python to Java 8. https://github.com/jmoy/norvig-spell/blob/master/java/src/main/java/spellcheck/Main.java https://github.com/jmoy/norvig-spell/blob/master/java/src/ma...
- SeanDav 12y agoAny thoughts on performance differences between loops and streams? My gut says that loops, being a more primitive concept are likely to perform better in most situations. In addition I just find loops easier to reason about, but that is probably purely personal.
- kasey_junk 12y agoI have not looked at the java8 constructs surrounding this. This is largely implementation specific. For instance, the .net LINQ to object implementations are largely syntactic sugar around loops (that is they compile to the same thing). Similarly, for loops are frequently just syntactic sugar around while loops.
- azth 12y agoHave they improved the implementation recently? I have not benchmarked myself, but according to [1], they are slower than the equivalent loop-based code. [1] http://arxiv.org/pdf/1406.6631v2.pdf
- kasey_junk 12y agoThis paper rightly points out that there will be overhead of object creation and potentially virtual calls (though that will be very JIT dependent). That said, my cursory examination (and it was very cursory) of their benchmark for C# is that they are really benchmarking the time difference between the iterator of an IEnumerable (or maybe IList, don't have an ability to decompile it right now) and the highly optimized case of looping through an array (which is literally one of the most highly optimized tasks on modern commodity hardware). On one hand, it does prove their point that in certain very specialized cases (looping through an array with no abstraction atop it), you will have significant performance penalties in the generic iterator case. On the other, I'm not sure I would attribute this to LINQ. I'm reasonably certain (and in these cases the space "reasonably" represents could have a truck driven through it) if you were to write the same code as a foreach loop using the same iterator and generic collections you wouldn't see significant performance differences. I'm definitely confident in most "real world" uses, where you are already using generic collections and iterators, you should bias towards using the LINQ implementation (assuming you believe it is better code) until definitive performance testing proves otherwise. For instance, in the case of the sum of squares, that looks like classic loop unrolling optimizations not being applied which any indirection in the looping code can prevent. Further, they show that there already exist optimization libraries that can eliminate much of the overhead. I will say, I'm quite impressed by the java results on this benchmark. Also, I didn't write any tests to prove any of this, so could be wildly off the mark. Further, I've spent more time than i ever wanted either hand translating or writing macros to, translate high level collections code into while loops. But that was in an extremely performance sensitive environment.
- deleted 12y ago[deleted]
- codygman 12y agoHere's the Haskell equivalent for anyone curious (Beware of curry[0]!): data Article = Article { title :: String , author :: String , tags :: [String] } deriving (Show) articles = [ Article "Functional Java" "James Gosling" ["functional"] , Article "Practical java" "James Gosling" ["enterprise","architechture"] , Article "Imperative Haskell" "Simon P Jones" ["imperative", "purely imperative"] ] firstJavaArticle = headMay . filter (isInfixOf "Java" . title) allJavaArticles = filter (isInfixOf "Java" . title) groupArticlesByAuthor = groupBy ((==) `on` author) distinctArticleTags = nub . join . map tags Full working code example with code imports and type signatures: import Data.List (isInfixOf, groupBy, nub) import Safe (headMay) import Data.Function (on) import Control.Monad (join) data Article = Article { title :: String , author :: String , tags :: [String] } deriving (Show) articles :: [Article] articles = [ Article "Functional Java" "James Gosling" ["functional", "functional"] , Article "Practical java" "James Gosling" ["enterprise","architechture"] , Article "Imperative Haskell" "Simon P Jones" ["imperative", "purely imperative"] ] firstJavaArticle :: [Article] -> Maybe Article firstJavaArticle = headMay . filter (isInfixOf "Java" . title) -- implemented in terms using allJavaArticles (NOTE: This IS performant in Haskell and IIUC due to stream fusion will only iterate once. Did not verify though.) firstJavaArticle' :: [Article] -> Maybe Article firstJavaArticle' = headMay . allJavaArticles allJavaArticles :: [Article] -> [Article] allJavaArticles = filter (isInfixOf "Java" . title) groupArticlesByAuthor :: [Article] -> [[Article]] groupArticlesByAuthor = groupBy ((==) `on` author) distinctArticleTags :: [Article] -> [String] distinctArticleTags = nub . join . map tags main = undefined 0: http://en.wikipedia.org/wiki/Currying http://en.wikipedia.org/wiki/Currying http://tech.pro/tutorial/2011/functional-javascript-part-4-f.. http://tech.pro/tutorial/2011/functional-javascript-part-4-f.... https://www.haskell.org/haskellwiki/Currying https://www.haskell.org/haskellwiki/Currying