top | item 8873840

Java 8: No more loops

85 points| GarethX | 11 years ago |deadcoderising.com | reply

93 comments

order
[+] anton_gogolev|11 years ago|reply
In 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.

[+] djb_hackernews|11 years ago|reply
"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)

[+] jfager|11 years ago|reply
The main reason they build these on Stream rather than Iterable is b/c they wanted to include the `parallel()` method, which works via "spliterators" rather than plain old iterators.

In other words, in order to support a gimmick you can actually use in production in a maybe a handful of use cases, they complicated the api for the use cases you hit 99% of the time. Awesome.

[+] azurelogic|11 years ago|reply
Yeah, I read this and immediately thought it felt like a knockoff of LINQ.
[+] olavgg|11 years ago|reply
I've done this in Java for almost ten years now

articles.findAll{ it.tags.contains("Java") }

All I've done is adding groovy.jar

[+] obstinate|11 years ago|reply
I really want to like functional programming, but the functional version of each of these seems less readable and more verbose.
[+] hibikir|11 years ago|reply
The 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.

[+] remon|11 years ago|reply
This 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.
[+] CmonDev|11 years ago|reply
I 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 :(.

[+] jimktrains2|11 years ago|reply
I 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.
[+] breckinloggins|11 years ago|reply
I 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.

[+] mattnewton|11 years ago|reply
I 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.

[+] TYPE_FASTER|11 years ago|reply
I 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.
[+] thescrewdriver|11 years ago|reply
It's doesn't take long when working with functional code to never want to go back to loops.
[+] mythz|11 years ago|reply
Java'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
[+] markc|11 years ago|reply
Dart 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))
[+] kuschku|11 years ago|reply
And 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.

[+] gavinpc|11 years ago|reply
> uglier and unnecessarily more verbose than it needs to be

Nice autologism.

[+] edgyswingset|11 years ago|reply
The 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|11 years ago|reply
The 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.

[+] aaronetz|11 years ago|reply
In 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?
[+] SeanDav|11 years ago|reply
Any 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|11 years ago|reply
I 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.

[+] acaloiar|11 years ago|reply
It should be noted that one must assume a performance penalty when calling stream() on a Collection, as doing so creates a new Stream object. Within inner loops and for small Collections I recommend avoiding stream() altogether if performance is a concern. Furthermore, while tempting, Stream.parallel() should only be called when it is certain that the additional cost of a ForkJoinPool instance creation can be amortized over the duration of the lambda's runtime. With that said, I welcome Streams and other FP concepts to Java.
[+] skywhopper|11 years ago|reply
The 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.

[+] carsongross|11 years ago|reply
This 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?

[+] Proleps|11 years ago|reply
why 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.
[+] organsnyder|11 years ago|reply
If 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|11 years ago|reply
Java 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])
[+] V-2|11 years ago|reply
But you can still modify elements of a final List (thereby modifying the Article object).

Properly implemented getTags should create a copy of tags so that fiddling with the returned value doesn't affect the object.

[+] glifchits|11 years ago|reply
Constructing 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.
[+] unknown|11 years ago|reply

[deleted]

[+] orbifold|11 years ago|reply
No 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.

[+] ac_386|11 years ago|reply
"Static typing" is not quite where those come from – mapTo{Int,Long,Double} exist because of Java's primitive/object dichotomy. You can just write map and do the same calculation, but then your lambda will have type ? -> Long (vs. the http://docs.oracle.com/javase/8/docs/api/java/util/function/... , which provides ? -> long).

It's mostly the same semantics, but it costs an extra object for each element in your list. If the only thing you're going to do is sum those longs or serialize them over the wire or something, the extra 6 characters have a significant impact on run-time. There's lots of solutions in this space (e.g. Rust is a static language that uses a lot of "zero-cost" abstractions like tagged pointers that it can prove are safe specifically because of the static types), but Java's "make the programmer do it explicitly" isn't so bad for 1995.

[+] wodude|11 years ago|reply
I've been doing this in Java WebObjects for a decade

tagList = articles.tags.@flatten.@unique.@sort

[+] nsxwolf|11 years ago|reply
I still find streams far more difficult to teach to a new programmer than loops.
[+] Beltiras|11 years ago|reply
All I could think: it's still sunk in too much architecture....
[+] pron|11 years ago|reply
Too 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).

[+] hristov|11 years ago|reply
So Java is trying to slowly turn into Haskell.
[+] kasey_junk|11 years ago|reply
More like it is trying to catch up with C#.
[+] bitL|11 years ago|reply
...aaand the obfuscation of Java begins!
[+] codygman|11 years ago|reply
Here'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://tech.pro/tutorial/2011/functional-javascript-part-4-f....

https://www.haskell.org/haskellwiki/Currying