Vita Rara: A Life Uncommon

Why I Chose JRuby Over Groovy


Categories: | |

After reading The power of JRuby and the discussion that ensued I was inspired to write about why I chose Ruby and particularly JRuby as my company's primary development platform.

I didn't write this post as a knock on Groovy. If it wasn't for Groovy I doubt I'd be working in Ruby today. For me Groovy was my "gate-way drug" into the dynamic language realm. My reason for this post is to explain why someone might want to use Ruby on the JVM over Groovy.

After working with Groovy for quite a while I started dabbling in Ruby. At first I really didn't get it. Then I watched a presentation by Dave Thomas and the lights went on. A world of possibilities opened up for me, and they were easily in reach.

So, why JRuby?

Ruby's Fantastic Support for Meta-programming

Meta-programming in Ruby is extremely natural. The separation between programming and meta-programming in Ruby is minimal to non-existant. It's just a natural progression.

I particularly like the way the meta-programming facilities of Ruby combined with executable class definitions and open classes make a declarative style of programming simple to implement. We are using these capabilities to simplify our code, eliminating boilerplate and repetition everywhere we can. It's very easy in Ruby.

When I started into meta-programming in Groovy (ver. 1.5 or so) it was cumbersome. A mixture of meta-class implementations. Then there is the fact that what meta-class backs a class isn't consistent. Sometimes MetaClassImpl, sometimes ExpandoMetaClass. (cf: GROOVY-2925)

The straw that broke the camel's back for me was the calling of static methods from class static blocks. Being able to do this would get one as close as you could to executable class definitions. Problem is it doesn't work. (cf. a related issues: GROOVY-2955) This arose when I was working on a DSL to do declarative validation of Struts 2 actions.

Ruby Class Definitions are Executable Code

This for me is the single largest difference between Groovy and Ruby and what won me over to the Ruby side.

Groovy by design uses the Java object model. A Groovy class is a Java class. Groovy classes are compiled. The definition of the class itself is static. This has great advantages if your primary desire is to inter-operate with Java code, but it makes other things harder.

Being able to use executable code in a class definition, as Ruby does, allows me to abstract away boiler plate code and patterns. Ultimately it can lead to full blown DSL like extensions to classes that save many key strokes and aid in comprehension. (Rails' ActiveRecord is a great example of this paradigm put to work. Quick tutorial on this methodology.)

I've also used executable class definitions to implement Java interfaces when the implementations have followed a convention and were repetitive. One array, an each loop, and voila implemented interface.

Groovy goes a long way toward bringing dynamic methods to Java and the JVM, but it lacks executable class definitions in the manner that Ruby has.

Use Object#method_missing to Implement a Java Interface

This is a great trick if you need it. I had an interface for a DAO. The finder's were named by convention. Very easy to convert into EJBQL, SQL, or what have you. In Groovy when your class implements a Java interface it must have all the methods present. In Ruby just include the Java interface and put the implementation in method_missing.

Classes are Open

This is a shared feature of both Ruby and Groovy. Personally I find the Ruby implementation more natural though. Groovy uses meta-classes to achieve its open classes, whereas classes in Ruby are just open. Add a method to String and it's on String, not String's meta-class.

Consistency

I find Ruby to be very consistent (leaving closures, lambda's and blocks aside, that one I'll give to Groovy). For me the API is tight and comprehensible. Once I worked with it for a while it just seemed natural.

Groovy has some consistency of its own. It's closures are far more consistent than Ruby's. But the library is Java. It's a great library, but overkill for what I do, and a lot to comprehend and keep in one's head.

Groovy has some inconsistencies though, and to my taste more than Ruby has. Most of these seem to stem from creating a dynamic language that transparently inter-operates with Java. An amazing accomplishment, but one that comes at a cost.

I Like the Syntax

This is a totally subjective thing, but I prefer Ruby's syntax and style. I find it very easy on the eyes. I find I can identify the begin and end of blocks more easily. Not really a reason to change from Groovy to Ruby, but I like it.

Porting from Groovy to Ruby isn't Hard

This was the clincher for me. Porting Groovy code to Ruby is quite simple. Many of the coding idioms are the same or similar. Outside of meta-programming, which for me tends to be isolated, it goes very quickly. So, the change from Groovy to Ruby hasn't been hard.

Scripting Java

JRuby also shares many other features with Groovy that make both of them compelling solutions for scripting Java:

  • You can extend a Java class in either one.
  • You can implement a Java interface in either one.
  • You have access to any Java library you'd like from either one.
  • Java can call methods on your JRuby or Groovy objects.
  • Both of them are easier to learn and use on a daily basis than Java.
  • Both of them are more concise than Java (Yay! My carpal tunnel thanks them both.)
  • I'm sure I'm forgetting some things.

Why did we drop Groovy?

When explaining Groovy to non-Groovy Java folks in the past I used to say, "Groovy is Java." Meaning that the syntax is very close, it uses the Java object model, and can transparently inter-operate with Java. Eventually that closeness to Java became Groovy's downfall for me.

By cleaving so closely to the Java model Groovy gains advantages in interoperation with Java, but suffers in its implementation of dynamic features. Those issues were large enough for us to make the jump.

In the end we chose JRuby as our main platform. Its consistency, and ground up design as a dynamic language unburdened by the Java platform make it a joy to work in. Scripting Java with it is great. Its meta-programming, executable class definitions and open classes are a winning combination.

So, if you're thinking about using a scripting language on the JVM give JRuby a try. You might just find it a compelling alternative to the other offerings.

Rev 1: Added "Scripting Java" heading.

Ruby Open classes

> Classes are Open

>This is a shared feature of both Ruby and Groovy. Personally
>I find the Ruby implementation more natural though. Groovy
>uses meta-classes to achieve its open classes, whereas
>classes in Ruby are just open. Add a method to String and
>it's on String, not String's meta-class.

This seems both very cool and dangerous. You may add a new method but if someone else add the same method with a behavior slightly different, what will happen?

Executable classes force inheritance

One other point about using executable class definitions and the pattern that ActiveRecord uses. In order to make executable classes useful you typically have to inherit from some super class that defines the behavoir

This forces an inheritance model on users of your API, thus not allowing the user to subclass any other class. Plus, there are many other problems with inheritance that have been covered many times across the blogosphere

Inheritance is not a problem

You are incorrect. Most methods inherited as in ActiveRecord can also be mixed in, as another commenter pointed out. This inserts a virtual class into the hierarchy that serves only to include those methods. It also does not interfere with the actual type hierarchy, since methods called in the class body are called against the class object, so they don't need to exist as instance methods.

There's many other ways to do this as well, but basically the idea that executable classes force you to inherit from a specific class is entirely false.

Not entirely correct

In Ruby, at least, you can do it via mix-in rather than inheritance - see the DataMapper ORM, for instance, which mixes in its functionality via "include DataMapper::Resource" rather than by extending anything.

If you want a new feature in

If you want a new feature in Groovy why not raise a JIRA? Adding support for implementing a Java interface with methodMissing (which Groovy already has) is perfectly possible and would be a nice addition.

Other than that yes it is true that Groovy's meta programming features are not as slick as Ruby, due to the trade-off with Java integration. However, I think it is a good trade-off, to me Groovy's support for optional typing and Java 5 features makes it applicable in a much wider set of scenarios than JRuby.

Additionally, Grails is a shining example of how you can achieve just as elegant APIs with Groovy. GORM is as concise as ActiveRecord even without executable class definitions.

Why add to Groovy when something else already works?

I think the point was that the features he wants already exist in Ruby, which is supported on the JVM through JRuby. Sure, they could be added to Groovy over time. They could also be added to just about any other language over time. But they work today in JRuby.

Totally Agreed

This is basically the reason that I haven't dug into development of Groovy. I'm really not a language designer/implementer. I've done some small work in Groovy. (Fixed a proxy issue.) When I did that work I found myself far outside my element.

My day to day work in software is in analysis and implementation of business domain models. That's where I need to spend my time. Time spent adding features to my implementation language are hard to fit into the schedule.

Additionally when I dug into how Groovy works it has to do an enormous amount of work to be compatible with Java and still be dynamic. All of that adds cognitive overhead. I learned a lot about languages just fixing the small issue I had, but it's time I'd really rather spend on being a better business software developer.

Mark

development

When you develop do you develop on a ruby server and then during deployment do it on a jvm or do you also run during development on a jvm?

development

I tend to use JRuby all the time. Although I have developed in MRI and deployed on JRuby.

Mark

IDE?

Very nice article, thanks!
What IDE do you use to write your JRuby code?

TextMate

I'm not an IDE guy. I currently use TextMate for most of my work. I find it more than sufficient for developing Ruby apps.

Mark

Thanks for the comparison

I didn't like Groovy as much as Ruby when I looked at it (JRuby was not yet mature but did exist), and it has evolved a lot in the intervening time, but I'm glad to see a substantive comparison of the two languages that supports my bias toward Ruby (JRuby if I needed to be on a JVM).

Al Chou

Very interesting!

Thank you mark for this article. This is very interesting to see the evolution... and be able to compare both (even if a few code snippets would have been cool).
Ben

cool article really

A very cool article thanks really!

Agreed.

Thanks for posting this. I've been developing Java for greater than 12 years and fell in love with Ruby. All my Java colleagues think that Groovy is a much easier language to learn when coming from a Java background. I don't think so. In fact, the main reason I chose Ruby was so I could GET AWAY from all of the curly braces and unneeded parenthesis. While doing some Groovy work in recent months, I've found myself missing Ruby and the elegance of the language. Groovy seems complex in areas where it doesn't really need to be. Good to hear that I'm not alone and that others out there who have worked in Groovy, might actually prefer JRUBY.

That is not exactly true about Groovy and Interfaces

Your comment "In Groovy when your class implements a Java interface it must have all the methods present". Is not exactly true there are more Groovy ways to implement interfaces rather than the Java way.

Groovy supports the 'as' keyword for implementing interfaces. Then you can choose which method you want to implement. See my post about it. implementing interfaces in groovy

Agreed.... but

Ok, so there is a means of implementing a Java interface without implementing the whole thing using the as keyword. I'd forgotten about this. It's been a while since I've moved over to Ruby full time.

With that acknowledgement it doesn't address my use case. As far as I know there is no way to declare a class definition that implements an interface without implementing the complete interface in the definition.

I personally look at the "as" method as a technique that is used inline in code, not as a declared before thing. (Unless you want to use a more prototype based style, and clone the said object you did the "as" on.)

I still give the point to Ruby, but then again I would. ;)

Mark

"With that acknowledgement

"With that acknowledgement it doesn't address my use case. As far as I know there is no way to declare a class definition that implements an interface without implementing the complete interface in the definition."

It depends what your real use case is. Groovy currently takes the stance that you use interfaces when you want static checking of your class definitions. This gives you the early feedback at compile time when your class definitions don't comply with the static specification (interface).

If you find creating such specifications too restrictive, you just don't use them and instead rely on Groovy's normal duck-typing/method missing functionality at the expense of now not getting the early (compile-time) feedback. If you need to interface with Java you can use "as" - allowing you to have duck-typing while in the Groovy world and flip to interface-oriented programming just at the boundaries of your code where you interface with Java.

Then if the "as" mechanism is too restrictive you can use the full blown ProxyGenerator which allows you to create instances that aggregate behavior from any number of closures, maps of closures, base classes, delegates and creates a proxy complying to as many interfaces as you require.

Paul.