Wednesday, December 5, 2007

Groovy in Ruby: Implement Interface with a Map

Some of you may know I participate in the Groovy community as well. I'm hoping to start contributing some development time to the Groovy codebase, but for now I've mostly been monitoring their progress. One thing the Groovy team has more experience with is integrating with Java.

Now if you ask the Groovy team, they'll make some claim like "it's all Java objects" or "Groovy integrates seamlessly with Java" but neither of those are entirely true. Groovy does integrate extremely well with Java, but it's because of a number of features they've added over time to make it so...many of them not directly part of the Groovy language but features of their core libraries and portions of their runtime.

Since Ruby and Groovy seem to be the two most popular (or noisiest) non-Java JVM languages these days, I thought I'd start a series of posts showing how to add Groovy features missing from Ruby to JRuby. But there's a catch: I'll use only Ruby code to do this, and what I show will work on any unmodified JRuby release. That's the beauty of Ruby: the language is so flexible and fluid, you can implement many features from other languages without ever modifying the implementation.

First up, Groovy's ability to implement an interface from a Map.
1. impl = [
2. i: 10,
3. hasNext: { impl.i > 0 },
4. next: { impl.i-- },
5. ]
6. iter = impl as Iterator
7. while ( iter.hasNext() )
8. println iter.next()
Ok, this is Groovy code. The brackety thing assigned to 'impl' shows Groovy's literal Map syntax (a Hash to you Rubyists). Instead of providing literal strings for the keys, Groovy automatically turns whatever token is in the key position into a Java String. So 'i' becomes a String key referencing 10, 'hasNext' becomes a String key referencing a block of code that checks if impl.i is greater than zero, and so on.

The magic comes on line 6, where the newly-constructed Map is coerced into a java.util.Iterator implementation. The resulting object can then be passed to other code that expects Iterator, such as the while loop on lines 7 and 8, and the values from the Map will be used as the code for the implemented methods.

To be honest, I find this feature a bit weird. In JRuby, you can implement a given interface on any class, add methods to that class at will, and get most of this functionality without ever touching a Hash object. But it's pretty simple to implement this in JRuby:
1. module InvokableHash
2. def as(java_ifc)
3. java_ifc.impl {|name, *args| self[name].call(*args)}
4. end
5. end
Here we have one of Ruby's wonderful modules, which I appreciate more each day. This InvokableHash module provides only a single method 'as' which accepts a Java interface type and produces an implementation of that type that uses the contents of hash keys to implement the methods. That's really all there is to it. So by reopening the Hash class, we gain this functionality:
1. class Hash
2. include InvokableHash
3. end
And we're done! Let's see the fruits of our labor in action:
1. impl = {
2. :i => 10,
3. :hasNext => proc { impl[:i] > 0 },
4. :next => proc { impl[:i] -= 1 }
5. }
6. iter = impl.as java.util.Iterator
7. while (iter.hasNext)
8. puts iter.next
9. end
Our final Ruby code looks roughly like the Groovy code. On lines 1 through 5 we construct a literal Hash. Notice that instead of automatically turning identifier tokens into Strings, Ruby uses the exact object you specify for the key, and so here we use Ruby Symbols as our hash keys (they're roughly like interned Strings, and highly recommended for hash keys). On line 6, we coerce our Hash into an Iterator instance (and we could have imported Iterator above to avoid the long name). And then lines 7 through 9 use the new Iterator impl in exactly the same way as the Groovy code.

You've gotta love a language this flexible, especially with JRuby's magic Java integration features to back it up.

6 comments:

  1. "On line 6, we coerce our Hash into an Iterator instance (and we could have imported Iterator above to avoid the long name)."

    By imported, you meant included, right? :-)

    ReplyDelete
  2. "Now if you ask the Groovy team, they'll make some claim like "it's all Java objects" or "Groovy integrates seamlessly with Java" but neither of those are entirely true."

    Honestly Charles I don't know why you bother. It's a silly argument. To argue that Groovy is as far removed from Java as Ruby (or JRuby in this case) is, is only an opinion shared with other Ruby wannabees.

    You seem to be like those people (few though one would hope they to be) who use J++ on .NET. Why bother? Just use C# and be done with it! Move on!

    I don't understand this shoehorning of Ruby into Java. The languages are vastly different, even if at just a purely syntactic. Groovy on the other hand is vastly more similar. I barely break stride switching between the two. I can definitely not say the same about looking at your Ruby code in this article. (Not knocking Ruby since I read enough about how people love it. But it is very different to Java or Groovy).

    Just make the complete move to Ruby... on Ruby (i.e. not Java) and I'm sure you'll be a happier man for it.

    ReplyDelete
  3. Ola,

    "Finally, it's interesting to see this black-or-white approach. "If you wanna use Ruby, you damned well better keep away from Java. YOU CAN'T HAVE BOTH."... Or can I?"

    If I gave the impression I was jealously guarding the Java runtime from Rubyists, then I miscommunicated. By all means have at it. Personally it doesn't matter to me in the slightest. I have to say that for some reason I'm not as disgruntled about Java (the language) as some seem to express. However, certainly, there are very obvious and enjoyable benefits to the terseness of Groovy, so I embrace those benefits wholeheartedly. Which was my point. To me Groovy kinda slots in more naturally alongside Java, than Ruby does. I like the fact that I can easily include Java code in Groovy without even breaking stride. Since Groovy fairly closely follows Java in syntax it is a more intuitive DSL partner to Java for me.

    I must admit I get tired of hearing how much better Ruby is to Java, yet in my humble yet Ruby-ignorant point of view, I struggle to see these benefits when comparing some of the examples. My subjective opinion is that Groovy is generally more 'readable' than Ruby code. The syntax is a context switch for me away from Java.

    So perhaps what would be useful is for somebody like yourself or Charles to illustrate these significant differences with some practical comparisons demonstrating why Ruby is so much better than Groovy. I don't deny my Ruby ignorance, so it would be instructive to see practical examples rather than bold (but unsubstantiated) claims.

    ReplyDelete
  4. Whoops, I meant to say "how much better Ruby is to Groovy" in the post above, and "The Ruby syntax is a context switch away from Java for me."

    I blame these silly little textarea's that blogger provides for comments. Makes you lose track of your edits. :)

    ReplyDelete
  5. Well that's your choice I guess. But perhaps it proves my point. Ruby:Groovy - tomaytoe:tomahtoe.

    The differences between Ruby and Groovy (syntactically - not behind the covers) are according to you so negligible it really just comes down to taste.

    Which brings me back to my original point. As somebody who has found Groovy to be a really natural and intuitive complement to Java due to its syntactic and API similarities (I don't care what's going on in the compiler unless it noticeably affects performance) I still don't see the need to learn a completely new language that has no significant benefit to what Groovy already offers. I suppose I still just don't get the fascination with shoehorning Ruby into Java when it appears to me that the Ruby-only folks are perfectly happy without Java.

    The real problem is that I should have known better than to wade into this language back-and-forth stuff that just goes nowhere. So with my apologies, I would like to bow out of any further exchanges on this matter.

    ReplyDelete
  6. @darryl

    I think you are correct, a lot of Java people will feel more comfortable with Groovy. But others are looking for something new, but still need to live inside of the Java world. It is all about taste, I personally feel more comfortable and enjoy writing Ruby better than Groovy. But I prefer writing either over Java. Someone else might prefer to write Python. It isn't about which is better, it is about which allows the developer to be most effective and express his idea clearest. I believe each developer will have a different preference on which language they wish to use and that is fine. The great thing about what is happening with the JVM is that soon it will be possible for each developer to chose the language that best suites him/her, but still take advantage of the power that the JVM brings.

    ReplyDelete