Monday, September 25, 2006

Interface Implementation Syntax and Open Classes

Tom and I got together and brainstormed the interface implementation issue today. We think we've come up with a reasonably solid solution.

First some background.

As most of you will know, the current way to implement a Java interface with Ruby code is to extend it:

include_class "java.awt.event.ActionListener"

class MyListener < ActionListener
...
end

This works fine for many cases, and it's great for a simple single-interface implementation. However it breaks down if you want to extend either a Java or Ruby class at the same time or if you want to implement multiple interfaces.

A few weeks back, we on the JRuby dev list kicked around the idea of using mixin
inheritance to do interface implementation:

class MyListener
implement ActionListener
end

This has the advantage of allowing you to also extend a class and implement multiple interfaces, but there's a problem here. By the time we encounter MyListener, the class is already created and there's no opportunity to make such drastic changes as modifying the list of implemented interfaces.

In the case above, MyListener is already created as a pure Ruby class by the time we encounter the implement line...we can't then change it into a Ruby/Java proxy class. Even if we had a way to mark it ahead of time as a Java proxy, that proxy would have to be created already by the time we're in the class body. Ruby's unusual way of instantiating classes is to blame: all classes start out "blank" and the class body is basically eval'ed within that blank instance. With Java types, we do not have such flexibility.

So a new option comes into the debate today. It's not as clean, and it's not as Rubyish, but it should support Java typing and Java interface implementation very well:

include_class "java.util.AbstractList"
include_class "java.util.Map"

MapList = AbstractList.implement(Map)

class MyMapList < MapList
...
end

Or the shortcut version:

class MyMapList < AbstractList.implement(Map)

We will probably also continue to allow the single-inheritance shortcuts as well, since they're nice and clean:

class MyListener < ActionListener
end

...which is roughly synonymous with:

include_class("java.lang.Object") { |p, n| "J" + n }

class MyListener < JObject.implement(ActionListener)
end

The logic behind this approach (very similar to that being taken by RubyCLR and IronPython) is that a concrete class plus multiple interfaces as a whole represents a very rigid, specific type in the Java world. We do not have the flexibility to juggle the internals of those types after they're created, so having a very clear-cut way of specifying that combination of concrete + interfaces allows us to satisfy Java's typing requirements. We can then extend that with Ruby code, implement whatever we want, alter behavior, reopen classes, and so on. We're essentially creating a rigid top-level Java type with a "back door" for implementing its behavior with Ruby code under the covers.

We'll certainly want to try to coordinate with other projects addressing this same issue (Ruby.NET, RubyCLR, IronRuby, IronPython, Jython?), since we don't want multiple incompatible syntaxes for this stuff. You out there guys?

3 comments:

Anonymous said...

I think this one would work either:

class MyListener < JObject(ActionListener)
end

Anonymous said...

What about? :

class A < B/C/D...

end

or:

class A < B/C+D...

end

operator associativity will help here

mortench said...

I would like to strongly support your last statement on his blog about coordination with other projects on this issue and other language extension issues.... I am not talking about a formal ISO process here, but I would welcome if you guys can get an agreement with the stakeholders in Ruby.NET, RubyCLR, IronRuby.

Maybe even Matz have input here (especially to avoid future problems with Ruby 2.0).