The
RosettaPattern defines a standard way for
CLforJava and Java to interact. We stumbled on the pattern during the design of the
TypeSystem for
CLforJava. The original impetus was the need to implement the Common Lisp type system using the mechanisms available in Java. The Common Lisp type hierarchy is actually a somewhat "tangled" directed acyclic graph (DAG). The addition of the CLOS feature increases the complexity of the type system. Since Java specifically forbids multiple inheritence by classes, there were two choices:
- Build our own type mechanism using classes to define type connections
- Use the Java Interface object to mirror the CL type system since interfaces can be multiply inherited
Since option 1 would be a complex undertaking, we opted to examine option 2: modelling the CL
TypeSystem using combinations of Java interfaces.
Mapping to Java Interfaces
The first stage was nearly trivial. We defined a set of interfaces based on the subtype diagram in the
TypeSystem. To name the interfaces, we just took the Lisp type name and capitalized it (the Lisp type names are all upper case) - T, Atom, Symbol, etc. Each interface extended its parent(s) interface. This proved satisactory including the ability to prohibit further subclassing the Lisp types that cannot be subclassed:
Fixnum,
Null, and
Nil. These interfaces were made
final.
From Interface to Lisp Type
The second stage was to create a standard procedure for determining the Lisp type name from one of the Java interfaces. Since all of the Lisp type names are symbols defined in the
COMMON-LISP package, we could add a
public static final Symbol member that holds a reference to the appropriate symbol. In all interfaces, this member would be named
typeName, each interface hiding the definition in each superinterface. The static variable of course required initialization code, leading to the third stage.
Factory Pattern
In the third stage, we had to write code that created a new Symbol instance to initialize the
static final typeName member. The first concept was to use a public constructor in the
SymbolImpl class. But when we looked at the problem from the perspective of a Java programmer using the public API we would provide, we didn't want to be constrained to the specific implementation class. Our solution was to add an inner class (
Factory) with a public static method called
newInstance. This factory method for creating Symbols is therefore available in the public API, e.g.
Symbol.Factory.newInstance("FOO"). The original implementation called for each type interface to call the Symbol factory method to obtain its type Symbol. From there it was a small leap of imagination to provide Factory inner classes in each of the Lisp types that can instantiate an object of that type. For example, there is a
Symbol.Factory.newInstance() method, but there is no
Atom.Factory inner class since it's not possible to directly create an object of Lisp type
ATOM. The result is that we have a uniform way to determine the type of any
CLforJava object and the corresponding Lisp symbol for that type, and a uniform method for creating instances of these Lisp objects.
Once we had created the
Factory inner class for the
Package interface, we also added
static code to create and initialize the
public static final members that hold the 4 fundamental packages:
COMMON-LISP,
COMMON-LISP-USER,
SYSTEM, and
KEYWORD. This enabled us to directly use the Package
intern method to create all of the type symbols required. This also had the added value of separating creation of an uninterned symbol through the
Symbol interface and interning a symbol through the
Package interface.
Adding Method Signatures
The fourth stage was the one we expected - to add the interface methods appropriate to objects of that type. For example, an instance of the List (LIST) type has methods to get the CAR and CDR of each element.
Benefits
This pattern has many benefits beyond the specific needs it addressed.
- Java programmers have a consistent API for accessing and creating instances of all Lisp objects.
- A Java programmer can create a subclass of any Lisp type by extending the corresponding Java interface and following the RosettaPattern.
- The compiler can create these patterns as needed when it encounters DEFSTRUCT or DEFCLASS macros.
References
--
JerryBoetje - 12 Jul 2003