Monday, December 17, 2012

Java 8 vs. Xtend

As you might know Java 8 will finally bring an important new feature to the Java programming language :
Lambda Expressions, a feature already supported by many other languages, such as Xtend

Xtend is a statically typed JVM language that translates to readable Java source code. It's very different to other JVM languages in the sense that aims to be a better Java by better supporting existing Java idioms and fully supporting the existing ecoystem. Xtend doesn't force you to rewrite your existing Java apps or drop your beloved and well-proven frameworks, but instead allows for using existing Java APIs in a much nicer way without any interoperability issues. In contrast to e.g. Scala or Clojure it is more like an extension to Java than a replacement.

Identifying and supporting existing Java idioms is not too hard given the huge existing code base but what about the future? Will Xtend also be a better match for the upcoming Java 8 libraries which were explicitly defined for the new Java 8 lambda syntax?

To find out I simply looked at Brian Goetz’s latest version of State of the Lambda : Libraries Edition and translated the contained Java code examples to Xtend.

In the following you see the code for Java as written by Brian and the translation to Xtend. Both are using the exact same Java 8 APIs and contain the same amount of static type information!

Example 1

Java 8:
shapes.forEach(s -> { s.setColor(RED); });
Xtend:
shapes.forEach[color = RED]

This first example already reveals most of the important differences.

First the general syntax for a lambda expression in Java is using an arrow (->), while Xtend uses squared brackets (like smalltalk). Also in Xtend a lambda expression doesn't need to be passed using braces. Although you could write shapes.forEach([color = RED]) you don't need to.

In Java 8 the body of a lambda is either a block or a single expression. So whenever you need to use a statement you have to add the curly braces and semicolons. In Xtend everything is an expression and the lambda itself is already a block expression.

In Xtend you can omit the declaration of a parameter name. In that case the parameter is called 'it' and is implicit (similarly to 'this'). If you want to give it a name you do it like this : shapes.forEach[ s| s.color = RED]

Unrelated to Lambdas but interesting in this case: Xtend allows to use assignments to call setter methods.

Example 2

Java 8:
shapes.stream()
      .filter(s -> s.getColor() == BLUE)
      .forEach(s -> { s.setColor(RED); });
Xtend:
shapes.stream
      .filter[color == BLUE]
      .forEach[color = RED]

Not too much new stuff in here. Xtend lets you omit empty parenthesis and you can call a getter using the property's name.

Example 3

Java 8:
List<Shape> blue = 
    shapes.stream()
          .filter(s -> s.getColor() == BLUE)
          .into(new ArrayList<>());
Xtend:
val blue = 
    shapes.stream
          .filter[color == BLUE]
          .into(new ArrayList)

Here we see type inference in action. While you can use the diamond operator in Java, you can leave it out in Xtend. Also the variable doesn't need to be explicitly typed as the type can be fully inferred from the right hand side.

Example 4

Java 8:
Set<Box> hasBlueShape = 
    shapes.stream()
          .filter(s -> s.getColor() == BLUE)
          .map(s -> s.getContainingBox())
          .into(new HashSet<>());
Xtend:
val hasBlueShape = 
    shapes.stream
          .filter[color == BLUE]
          .map[containingBox]
          .into(new HashSet)

Note how readable the lambdas get when you can leave out all the cryptic clutter. Let's compare a last example from a later section of the document:

Example 5

Java 8:
List<Album> sortedFavs =
    albums.stream()
          .filter(a -> a.tracks.anyMatch(t -> (t.rating >= 4)))
          .sorted(comparing(a -> a.name))
          .into(new ArrayList<>());
Xtend:
val sortedFavs =
    albums.stream
          .filter[tracks.anyMatch[rating >= 4]]
          .sorted(comparing[name])
          .into(new ArrayList)

Although the libraries as well as the examples have been written for Java 8, the Xtend code is still much less cluttered with symbols and therefore significantly more readable. Yet the type information is exactly the same! Xtend is just a bit smarter with type inference and is syntactically less rigid.

Besides that and the fact the Xtend compiles to readable Java 5 code, it has many other important features to offer.

20 comments:

  1. To me "[ s| s.color = RED]" does not look like a lambda. Naturally I would read it as "The list of s so that the color of s is red" because it reminds me of the set syntax but uses angled brackets which I link with lists.

    Also could you say more about the scoping rules? If I add a field named RED to my shapes, would that change the meaning of the first example?

    ReplyDelete
    Replies
    1. The scoping rules are mostly the same in Java and Xtend. E.g. if you have a field RED in Shape, you'd have to refer to the enum literal in a qualified way.

      Delete
    2. that lambda syntax comes from smalltalk and is found in ruby as well

      Delete
  2. s -> s.getContainingBox() can be written as Shape::getContainingBox

    a -> a.name would be unusual, you'd normally see a method getName(), in which case it can be written as Album::getName

    What happens in shapes.stream.filter[color == BLUE].forEach[color = RED] if Shape no longer contains a setShape? Will the compiler look for a variable named color in scope and assign RED to it repeatedly?

    ReplyDelete
    Replies
    1. if Shape no longer contains a setColor, I meant..

      Delete
    2. As far as I know you get the same behavior in Xtend as in Java if setColor is no longer defined on shape: a compiler error.
      I think that's ok, because the APIs broken. Any other behaviour might cause unexpected site effects IMO.

      Delete
    3. > s -> s.getContainingBox() can be written as Shape::getContainingBox

      which doesn't look like a big improvement

      > a -> a.name would be unusual, you'd normally see a method getName(), in which case it can be written as Album::getName

      Yes, but the Java examples are exact copies from what Brian Goetz wrote in his document.

      > What happens in shapes.stream.filter[color == BLUE].forEach[color = RED] if Shape no longer contains a setShape?
      > Will the compiler look for a variable named color in scope and assign RED to it repeatedly?

      local variables or fields always have precedence. So if they were in scope you'd have to use the setter method.

      Delete
    4. 'local variables or fields always have precedence' looks like it could easily cause some surprises. I'd suggest that the implicit 'it' is bad news along the lines of JavaScript's with construct, but I definitely like foo.name = bar being translated to foo.setName(bar).

      Delete
  3. Especially for the last example I still prefer the APIs from the Xtend SDK, which allow to be more concise than the newly introduced methods in Java8

    val sortedFavs =
    albums.filter[tracks.exists[rating >= 4]]
    .sortBy[name]

    ReplyDelete
    Replies
    1. Indeed, I left out mentioning Xtend's APIs and what code could like like which was designed for Xtend (instead of Java 8). It had to fit with the average attention span on the web ;-)

      Delete
  4. Oliver Libutzki12/18/12, 8:07 AM

    Are you going to compile Xtend to Java 8 compatible code sometime, maybe depending on the chosen jdk version? Or is the strategy to have only one compiler which generates Java 5 compatible code?

    Maybe using "native" lambda expressions is faster than instantiating anonymous classes...

    ReplyDelete
    Replies
    1. Yes, we absolutely want to have a compiler flag to compile Java 8 compatible code.

      Delete
    2. Curious about the word "want" here. This post was a year ago. Would you consider doing an updated post on the current state of xtend vs java 8? The examples you provided in this post were very helpful and illustrative. I wonder if anything has changed since last December?

      Delete
  5. Funny, I see Xtend as a Java replacement and Scala as an extension so just the other way around. Scala tacks on too much alien stuff IMHO.

    ReplyDelete
  6. Sounds like Xtend has no reason to exist anymore...

    ReplyDelete
  7. I agree Xtend code is much better to read and looks clean then standard Java code for lambda, Why not Java can use same syntax ?

    ReplyDelete
  8. I have used Xtend lambda feature which makes my code concise when compared to the same code written in java .
    lambda expression is cool feature which has it's roots from functional programming languages like lisp & other ML based languages and i was eagerly waiting for the feature in java from the past 4-5 years.
    I agree that Xtend's lambda expression's are cleaner when compared to the Java ones.
    what about the comparison of memory footprint and runtime of using Xtend's lambda againist Java lambda expressions? did anyone checked for the same?



    ReplyDelete
    Replies
    1. Oliver Libutzki12/19/12, 1:33 PM

      If Xtend was compiled to Java code the runtime performance, would be the same compared to "pure Java" (because the resulting bytecode is the same).

      Comparing Xtend Java5 compatible lamda support with Java 8 lamdas is an apple orange comparison as I'm pretty sure that the usage of native lamda expressions is faster than instantiating an anonymous class like Xtend Java5 compiler does.

      Anyway, how long will it take until Java8 is widley used? The majority of enterprise business applications still uses Java 5 or even Java 6, so Xtend's Java5 compiler will be inevitable for quite a long time.

      Delete
  9. These examples focus on a particular use of anonymous lambdas as expressions for collection operations (e.g., filter).

    There is a strong similarity with the shorthand syntax defined in QVT Operational for selection and collection operations (see 8.2.2.7 in http://www.omg.org/spec/QVT/1.1)

    Basically, QVTO's syntax for Xtend's ".filter[color == RED]" would be exactly the same!

    In fact, the similarity goes a bit further in that Xtend's "it" implicit variable is "self" in QVTO (this is because QVTO is an imperative extension of OCL).

    However, Xtend's syntax is more uniform than QVTO's.

    For example, Xtend's "shapes.forEach[ s| s.color = RED]" would be in QVTO:
    "shapes->forEach( s| s.color := RED)"

    In QVTO, ":=" is assignment, "=" is equality.

    In practice, this can be a source of subtle bugs because the two things are legal in QVTO:

    "shapes.forEach[ s| s.color = RED]" // in QVTO, this means: s.color.equals(RED)
    "shapes.forEach[ s| s.color := RED]" // in QVTO, this is an assignment

    This can be the cause of subtle bugs that can be difficult to find as you can imagine.

    Is there an equivalent to QVTO in Xtend?

    (for an overview of QVTO, see the introduction here: http://www.eclipse.org/projects/project.php?id=modeling.mmt.qvt-oml)

    - Nicolas.

    ReplyDelete
    Replies
    1. Hi Nicolas,

      in Xtend the '==' operator is used for equality. It maps to java's Object.equals(Object other) method.

      The most important difference to QVTO is, that Xtend is based on Java's type system with 'real' generics, while QVTO AFAIK has collections and corresponding higher-order functions baked into the language.

      Sven

      Delete