This blog posting is primarily of interest to programmers who have worked with both Smalltalk and Java.
Introduction
I was an avid user of Smalltalk from the moment I first saw it in 1987. I liked the way everything was an object, and applying methods and extending classes was very easy. Since 1995, I have been working primarily with Java, and, though I appreciate the frameworks and web-services capabilities, I miss the simplicity and immediate evaluation capability. Why not have both?
Smalltalk was spectacular in no short part to the simplicity of the looping capability. If you want to know if the number 100 is in an array, you could perform:
anArray do: [ x : x = 100 ifTrue: [^true ]]
I have created most of a Smalltalk-inspired programming environment entirely written in Java, which operates on Java objects and constructed Smalltalk classes, called, "Just".
The #do: method is made implicit in that if you apply it to an array, it will find hardened code that uses the Array class. This is extended to String, byte[], integer[], long[], and any "L"-class. The methods at: and at:Put: would not make any adjustments from Smalltalk numbering to Java numbering.
Any method that ends with (with: )+ and is directed to the Java class preceding the first colon. There are a few ambiguities of calling specific Java methods where they vary by parameter type, and not parameter number, but these are relatively few, and allows you to access them by implementing only the #
Methods #isNil , #isTrue: , #isFalse: , #isTrue:isFalse: , and so on are implicitly grabbed by the system. As a variation, I support any combination of #isTrue:isFalse:always: to allow for code with multiple layers of common code.
#handle:do: and the exception handlers are more complex in that Java exceptions can be thrown, or Smalltalk-defined exceptions.
#hash is redirected to Object.hashCode() for Java objects.
#basicNew:, #basicNew:with:, #basicNewWithAll: , etc, are directed to Java constructors (for Java-only classes), or directed to JustObject.basicNew(), JustObject.basicNewWithAll() for classes that descend from JustObject.
Constructing instances also employs a few implicit methods, namely, "new", "new:", "new:with:", "new:with:with: ", "newWithAll:".
Variations from Standard Smalltalk
Through the versions, I have simplified the engine to work most naturally with the Java language. For example, Arrays are all 0-indexed (like Java) rather than 1-indexed (Smalltalk).
"dot"-notation, such as "java.lang.Long" represents a single global variable (regardless of capitalization). Usually, this is a Java class, but it need not be. This is to allow Java classnames to appear without quotation marks, primarily. If the statement separator "." is entered without whitespace on either side, and the token on both side is a viable Java identifier, then it will be interpreted as a global variable symbol.
The "import" directive in Java is very important to the class definitions. You cannot perform wildcard imports like: "import java.io.*", but you can perform statements such as:
this import: java.io.File
Since the block context is part of the execution, import: implicitly picks it up and creates a class map within the context as the unqualified instances are resolved.
file := File new: 'output.txt'.
For Just-based packages, I have considered allowing the wildcard form since the hierarchy of the Just objects is very visible. On the Java definition side, it seemed much more difficult.
The global variable Smalltalk is still supported as the root for the global space.
The Smalltalk collections are not mapped. Instead, use the Java collections. Example:
java.util.HashMap new.
Comments are supported only in the form /* ... */. Smalltalk syntax in the form "this is a comment" is compiled into a string.
The terms this and self are identical.
the terms nil and null are identical, as is the Java value null.
The class Just.lang.Symbol is a wrapper class on a Java String that has been interned using String.intern().
Operators
The operator ":=" is special in that it operates within the context of a block.
The unary and binary operators like "==" become "operatorX61X61" for implementation. If the target class does not support a method, it is directed to just.lang.JustObject.operatorX61X61() which works through what the result should be for the primative types.
Method Returns
An exception is added "just.lang.JustReturn", because the return, "^", operator is implemented as a Java "throw()". Method bodies are not transparent to returns, however, block bodies are.
Method Definitions
Methods can be added using Smalltalk syntax to any class whether Java-based or Smalltalk-based.
Crossing Language Barriers
Getting from Just into Java is very easy. Variables can hold Java values or Just values (which are a subclassed Java value). Java objects can be constructed with constructors and then stored as variables. There is a method called "#asJavaObject" which downcasts to the purest form of the Java object, removing the Just wrappers. There is a Java corrolary "asJavaObject()" on subclassed entities from Java class just.lang.JustObject.
Getting from Java to Just is also simple. asJustObject is defined on all objects on the Just side. On the Java side, just.lang.JustObject.asJustObject() and just.lang.JustObject.asJavaObject() are defined.
From the Java side, a runtime context needs to be made, or use the default. Executing a statement is performed by invoking JustRuntime.evaluate(target, method, args[]);
Possible Extensions
I haven't discussed file-in / file-out at all, nor the saving of images. As far as I got in the project was to allow file-in to read the contents of a file and compile it as methods. For each method in the system, the file-in format was essentially:
class methods at: #key put: [ block ] .
With extensions, this means an image could be saved in a database as well.
Compiling into Java is a goal. To that end, a Java-compiled evaluation block would be created for the methods. The class would represent the version level (by timestamp), and within it, there would be approximately 300 buckets to direct method signatures to implemented as methods. Using a modulo reduction on the hash value of the method, the approprate method signature would be found. In this case, assume the hash is 203, then the method called is justDispatch203(...).
Within justDispatch203(...), a Java if-else tree would dispatch by number of parameters, with an else resulting in super.justDispatch203(...). Within each of the recognized collections of attribute counts, there would be an if-else tree on a modulo hash-code (different than the 300 earlier) for the method. If the hash were not present, then it would fall out into super.justDispatch203(...), however it we did match at this point, then we'd verify the method names are, in fact, the same, and that the calling class is a subclass of the method this is implemented for, and then execute the Java code implementation of the compiled form.
This would change the form of implementing the operators like "==" (as stated before, just.lang.JustObject.operatorX61X61() ) to being an implementation in the highest-level method dispatcher.
It would also allow the extension of syntax of the form #{ java code snippet } into the vocabulary of the Just system. In an uncompiled form, performing do: #{ } would raise an exception. In the compiled form, it would call the implemented code. In the compiled form, it would allow most of the underpinnings of the system to be written within the system.
Edit 1.0 - 12/01/2008
Edit 1.1 - 07/02/2009