Copyright 2000,2001,2002 Nikolas S. Boyd. Permission is granted to copy this document provided this copyright statement is retained in all copies.


Nik Boyd

Smalltalk over Java™: An Introduction to Bistro

Bistro is a new programming language that integrates the best features of Smalltalk and Java™. Bistro is a variation of Smalltalk that runs on top of any Java™ virtual machine that conforms to Sun's Java™ specifications with reflection and inner classes. Bistro represents a further evolution of Smalltalk, preserving much of its simplicity, expressiveness and readability. Apart from a few minor extensions and differences in punctuation, the natural method syntax of Bistro is almost identical to that of Smalltalk. So, Smalltalk software developers will find most of Bistro familiar. However, Bistro also incorporates several language features from Java™, so that their integration appears relatively seamless. Table 1 summarizes the important features of Bistro, and the remainder of the paper elaborates upon each of these features in detail.

Table 1. Language Features
Language Model Bistro has a declarative language model.
Name Spaces Bistro classes can include a package and imports like those found in Java™.
Classes Bistro classes have a hybrid structure based on both Smalltalk and Java™.
Metaclasses Bistro supports the definition of metaclasses like those found in Smalltalk.
Types Bistro supports the definition and use of first-class interfaces (as types).
Metatypes Just as each Bistro class has a corresponding metaclass, each type has a metatype.
Access Controls Bistro supports Java™ access controls, including public, protected, private.
Decorations Bistro also supports: abstract, final, synchronized, native, static.
In-line Variables Bistro supports in-line variable declaration and initialization.
Type Specifications Bistro variable and argument type specifications are optional.
Natural Methods Bistro natural methods closely resemble those of Smalltalk.
Primitive Methods Bistro supports the implementation of primitive methods in Java™.
Interoperability Bistro method names are translated into compatible Java™ method names.
Comments Bistro quoted comments are translated into Java™ comment blocks.
Blocks Bistro blocks are implemented using Java™ inner classes.
Adapters Bistro uses a special message idiom for defining anonymous inner classes.
Threads Bistro blocks support the fork protocol for spawning Java™ threads.
Exceptions Bistro supports both standard Smalltalk exception handling and Java™ exception handling.
Migration Bistro includes a utility for converting Smalltalk class sources to Bistro.

LANGUAGE MODEL

Traditionally, Smalltalk systems were built in the context of an object memory image. Smalltalk class definitions loaded from a source file were interpreted in the context of the image. While an image-based development environment contributes significantly to the agility of Smalltalk's integrated suite of tools, it introduces some difficulties for code, component and system configuration management in large development projects.

A declarative, file based programming model makes it much easier to use existing commercial version control and configuration management tools. The Java™ language is file based, both for its source code (.java) and its executable binaries (.class). The Java™ platform also supports the deployment of class libraries as files in archival form (as .zip, .jar, etc.). In order to leverage these features of the Java™ platform, Bistro utilizes a declarative language model for its source code files (.bist). The current Bistro compiler performs source-to-source translation, from Bistro to Java™. Then, the Bistro compiler uses a standard Java™ compiler to compile the intermediate Java™ source code into Java™ class files.

The overall source code structure of each Bistro class resembles that found in Java™, including the location of classes with respect to their packages. However, the specific syntax used to declare a Bistro class resembles that found in Smalltalk. The following code example provides a general template for Bistro class definitions.

"Identify the package for the class."
package: smalltalk.example;

"Make all the Collections visible."
import: smalltalk.collection.*;

"Define a new class and metaclass."
Superclass subclass: Subclass
metaclass: [ "..." ]
class: [ "..." ]

The above example shows a template for only one of the several possible kinds of Bistro class and type definitions. Table 2 provides a more complete set of examples of the possible Bistro class and type definition templates.

Table 2. Definition Templates
Class Definitions Type Definitions Type Implementations
nil
subclass: RootClass
metaclass: [ "..." ]
class: [ "..." ]
nil
subtype: RootType
metatype: [ "..." ]
type: [ "..." ]
nil
subclass: RootClass
implements: Type "..."
metaclass: [ "..." ]
class: [ "..." ]
     
Superclass
subclass: Subclass
metaclass: [ "..." ]
class: [ "..." ]
Supertype
subtype: Subtype
metatype: [ "..." ]
type: [ "..." ]
Superclass
subclass: Subclass
implements: Type "..."
metaclass: [ "..." ]
class: [ "..." ]

Notice that root classes and interfaces are derived from nil. Root types have no equivalent Java™ supertype, but root classes are derived from java.lang.Object and root metaclasses are derived from smalltalk.behavior.Class. Thus, the smalltalk.behavior.Object class is derived from java.lang.Object and its metaclass is derived from smalltalk.behavior.Class. See the discussion of metaclasses below for more details.

Name Spaces

The lack of a standard name space mechanism is a deficiency from which Smalltalk has suffered historically. The absence of name spaces permits the occurrence of class naming conflicts, especially when integrating large class libraries from independent software development organizations, e.g., third party vendors. While some name space models have been proposed for Smalltalk1, 2, and several proprietary mechanisms are available in the commercial Smalltalk environments, none has yet been widely adopted by Smalltalk vendors, and the ANSI X3J20 committee did not include any name space model in the Smalltalk standard3.

Luckily, the Java™ language model supports the name space concept with packages. Packages organize Java™ classes into logical partitions that serve as name spaces for classes. This helps system designers prevent potential class naming conflicts. Java™ class files are physically organized into package directories in a file system. There is a direct correspondence between these logical and physical organizations. Figure 1 depicts these relationships. Because Bistro code is translated into Java™ class files, Bistro reuses the Java™ package mechanism and the advantages it provides.

packages contain classes

Figure 1. Physical vs. Logical Packaging

All classes defined in a package are immediately visible to each other. Public classes from other packages can be made visible by importing them. An import establishes visibility to an individual class or all the public classes contained in a package. The following code fragment provides an example of a Bistro package declaration and an import.

package: smalltalk.example;
import: smalltalk.collection.*;

As with Java™, any class outside the scope of the current package may be imported, or a class may be qualified by the name of the package in which it was defined. So, while the import in the above code fragment makes OrderedCollection visible, it may also be fully qualified as smalltalk.collection.OrderedCollection.

Classes and Metaclasses

Bistro classes are translated into Java™ classes. Bistro class member variables and methods become Java™ class variables and methods. However, Smalltalk has first-class metaclasses, while Java™ does not. Bistro implements its metaclasses by splitting each class definition into two parts - one Java™ class for the Bistro class, and one for the Bistro metaclass. Putting the Bistro metaclass methods and variables into another Java™ class allows the metaclass hierarchy to parallel the class hierarchy. This provides inheritance and polymorphism for metaclasses like that found in Smalltalk.

Each Bistro metaclass is implemented as a nested public static class. The metaclass name is always mClass. Each Bistro class has a public static member ($class) that refers to the sole instance of its singleton metaclass. However, because metaclasses are singletons, they do not support the definition of abstract methods. The following list shows the parallels between a few selected classes and their metaclasses.

smalltalk.behavior.Object
smalltalk.behavior.Object.mClass

smalltalk.collection.OrderedCollection
smalltalk.collection.OrderedCollection.mClass

smalltalk.geometry.Point
smalltalk.geometry.Point.mClass

Figure 2 shows the full inheritance hierarchy for some of the essential behavior classes. Note each class has a static link to its metaclass ($class). However, these links must be resolved (by instantiation) after the inheritance links have been established (during compilation). Making each metaclass a public static class enables this. Finally, the metaclass ($class) links of all the metaclasses refer a singleton public static instance of the class Metaclass. Figure 2 depicts these relationships for selected behavior classes. All root classes, those derived from nil, have inheritance and metaclass structures like that of smalltalk.behavior.Object. A Bistro class can also be derived from a Java™ class. In this case, the inheritance and metaclass structures of the generated classes also look like that of smalltalk.behavior.Object, i.e., they serve as the root of an inheritance hierarchy derived from a Java™ base class.

class and metaclass hierarchies

Figure 2. Classes and Metaclasses

Types and Metatypes

Support for object interfaces is one of the more powerful and innovative features of Java™. Interfaces provide a language mechanism for defining polymorphic behavior independent of inheritance. Bistro supports the definition and use of interfaces as types. However, because Smalltalk supports first-class metaclasses and full metaprogramming, Bistro takes a further step by coupling each type with a metatype. This gives software designers the ability to coordinate programming and metaprogramming.

Each Bistro type and metatype is translated into a Java™ interface definition. Each metatype interface is defined as a nested public static interface of its corresponding type interface, similar to the relationship between a class and its metaclass. As with Java™ interfaces, Bistro supports type inheritance. As with classes and metaclasses, the metatype inheritance structures parallel the type inheritance structures. When a Bistro class implements a type, the metaclass also implements the metatype. But, when a Bistro class implements a Java™ interface, a metatype does not exist. So, the Bistro metaclass does not implement a metatype in this case.

Access Controls and Decorations

Access controls play an important role in defining contracts in object-oriented designs. Like Java™, Bistro provides access controls on classes, types (interfaces), methods and variables. Bistro supports the Java™ reserved words that specify access control, including public, protected, private. While each class, method and variable may be declared with one of these three access controls, Bistro uses the common Smalltalk conventions for default access when no explicit control has been supplied. By default, Bistro classes and types are public, methods are public, and variables are protected. Also, Bistro metaclasses and metatypes are always public. All access controls are enforced at runtime by the Java™ VM.

Like Java™, Bistro supports the use of several reserved words in variable and method signatures, including those that control subclass derivation, and thread synchronization. The following code fragments illustrate these features.

"Define a static constant."
protected static final Zero := 0.
"Define a private synchronized method."
private synchronized syncMethod: argument [ "..." ]
"Define a protected final method."
protected final finalMethod: argument [ "..." ]
"Define a public abstract method."
abstract abstractMethod []
"Define a public native method."
native nativeMethod: argument []
"Define a public static main method."
static (void) main: arguments (String[]) [ "..." ]

Notice that both abstract and native method declarations must have empty blocks because their implementations cannot be specified. An abstract method must be implemented in the subclasses of the class in which it is defined. A final method cannot be implemented (overridden) in subclasses. A native method must be implemented using a Java™ Native Interface (JNI). A static method is not polymorphic like the methods defined in a metaclass. However, a static method can be used to define the main entry point for a console application.

VARIABLES

Many areas of programming language design have competing forces and trade-offs. Variable declaration is one such area. The design of Bistro attempts to achieve a balance between the features and limitations of both Smalltalk and Java™. Smalltalk has a special syntax for declaring all variables before the statements of a method or a block scope. Java™ allows variables to be declared and initialized in-line in the body of a scope. The later feature is preferable because it makes code maintenance easier. Java™ requires that every variable declaration includes a type specification, while Smalltalk has no type specifications. Both of these options are important. Some of these choices have implications for other design choices, such as whether a variable from an outer scope can be overridden by an inner scope. Table 3 compares these features and limitations with respect to variable declaration, and indicates what Bistro supports.

Table 3. Variable Declarations
Smalltalk Bistro Java™
Local variables must be declared in a special section. yes no no
Variables must be declared before usage within a scope yes yes yes
Variables declared in an outer scope may be used in an inner scope. yes yes yes
Inner scopes may override variables from outer scopes. no no yes
Variable declarations support initialization. no yes yes
Variable type specifications are . . . unsupported optional required

Bistro variable and argument type specifications are optional. Making type specifications optional imposes the need to forbid variable redeclaration. To simplify the language, Bistro eliminates the need to declare local variables in a separate section at the beginning of each block. As with Java™, variables can be declared anywhere in a scope, so long as they are declared before they are used, typically with an initial value. The Bistro compiler optimizes messages sent to typed variables, including sends to self and super. For untyped variables, the compiler automatically generates dynamic message sends using an appropriate perform: message.

Structured Member Names

Smalltalk has no explicit access controls, and Smalltalk does not provide any mechanism for directly accessing data members. All data values are accessed by sending messages to objects. On the other hand, Java™ does have explicit access controls and allows access to public data members. Since Bistro is hosted on the Java™ VM, it's natural for Bistro to support the Java™ mechanism for accessing data members with certain kinds of structured names. constants and accessing instance variables using structured names. The following code fragment illustrates how Bistro supports access to both instance variables and public static data members. Note that the standard output stream named out is a static member of the System class.

"Using structured names to access data members"
System.out println: self.dataMember.

METHODS

The syntax for Smalltalk methods and blocks are very similar. Both contain a series of statements. The primary difference is that blocks are delimited with square brackets [ ], while Smalltalk methods are not. In order to support a declarative language model and normalize the syntax, Bistro uses square brackets as scope delimiters. Thus, the declaration of Bistro classes, types, methods, and blocks are all delimited with square brackets.

In Bistro, natural methods are similar to those of Smalltalk. However, Bistro also supports primitive methods written with Java™. Both kinds of methods use signatures similar to those found in Smalltalk. The following code fragments illustrate these distinctions.

"A sample natural method"
naturalMethod: argument [ "..." ]
"A sample primitive method"
primitiveMethod: argument { /* ... */ }

The Bistro compiler translates quoted class and method comments into Java™ class and method comments. So, the javadoc utility can be used to generate HTML documentation from the resulting Java™ source files.

Interoperability

To achieve seamless integration between Bistro and Java™, their methods must be interoperable. Bistro code must be able to invoke methods in existing Java™ classes, and Java™ code must be able to invoke methods in Bistro classes. To achieve method interoperability, the Bistro compiler renames methods using some conventions. Special consideration must be given to both binary operators and keyword selectors.

Bistro supports the standard binary operators provided by Smalltalk. These are renamed using the conventions shown in Table 4. The Bistro compiler includes a prefix ($) in the translated method name to prevent confusion with existing Java™ protocols. Table 4 also shows the renaming conventions for Smalltalk keyword selectors. The colon in each keyword is replaced by an underscore. The final colon (if any) is dropped. Bistro also supports the use of colons as argument separators, serving as anonymous keywords like those found in block signatures. These are dropped during translation, which allows Bistro to support the definition and invocation of Java™ method signatures that take more than one argument (see the ternary: example in Table 4).

Table 4. Method Renaming Conventions
Smalltalk Java™
receiver & arg
receiver | arg
receiver @ arg
receiver , arg
receiver.$and( arg )
receiver.$or( arg )
receiver.$at( arg )
receiver.$append( arg )
receiver + arg
receiver - arg
receiver * arg
receiver % arg
receiver / arg
receiver // arg
receiver \ arg
receiver \\ arg
receiver.$plus( arg )
receiver.$minus( arg )
receiver.$times( arg )
receiver.$modulus( arg )
receiver.$dividedBy( arg )
receiver.$idiv( arg )
receiver.$into( arg )
receiver.$imod( arg )
receiver = arg
receiver ~= arg
receiver == arg
receiver ~~ arg
receiver < arg
receiver <= arg
receiver > arg
receiver >= arg
receiver.$equal( arg )
receiver.$notEqual( arg )
receiver.$is( arg )
receiver.$isnt( arg )
receiver.$lessThan( arg )
receiver.$lessEqual( arg )
receiver.$moreThan( arg )
receiver.$moreEqual( arg )
receiver unary
receiver binary: arg
receiver ternary: arg1 : arg2
receiver keyword: arg1 keyword: arg2
receiver.unary( )
receiver.binary( arg )
receiver.ternary( arg1, arg2 )
receiver.keyword_keyword( arg1, arg2 )

Dynamic Method Resolution

The absence of explicit type information in Smalltalk contributes to its simplicity and its agility during software development. Software prototyping is much easier without explicit type specifications. Specifying types pervasively throughout a software system during development requires much more time. Also, the type information simply makes the resolution of method implementations safer and more efficient.

The Java™ reflection facility provides information about object types at runtime. So, Bistro uses reflection to determine the type of an object and then resolve method invocations at runtime (late binding). The Bistro compiler takes advantage of the standard Smalltalk perform: messages to implement dynamic method resolution when needed. An unimplemented method discovered at runtime produces a MessageNotUnderstood exception.

The Bistro compiler attempts to resolve the type of each message receiver. For message primaries such as constants, variables and arguments, the compiler resolves an inferred type (for constants) or a declared type (for variables and arguments). For nested message expressions, the compiler attempts to resolve the type of each nested message result. Where such type resolution is possible, the compiler determines whether the receiver implements the message selector using the Java™ reflection facility. If the compiler can locate an appropriate method at compile-time, the compiler generates a direct method invocation against the receiver rather than a dynamic method resolution using one of the perform: selectors. Whenever the compiler can generate a direct method invocation, it also remembers the result type of the invoked method. This gives it the opportunity to resolve a further method invoked on the message result in the case of nested message expressions.

Of course, the most significant issue associated with dynamic method resolution is performance. Experimental measurements indicate that reflective method invocations are about 100 times slower than normal method invocations. This is significant, but not necessarily prohibitive if the overall percentage of dynamic method invocations can be kept relatively small.

All of these contributions make the exploration of dynamic method resolution worthwhile. Also, given that Sun has made the Java™ VM available through a community source license, it may even be possible to customize the VM to accelerate method lookup for dynamically typed languages like Bistro and Smalltalk.

Bistro provides a spectrum of optimization options. Smalltalk developers will feel at home with familiar idioms and classes. Then, as application interfaces harden and classes mature, type annotations may be added to classes in order to improve the performance of method resolution when and where needed. Performance critical methods may even be recoded directly in primitive Java™ methods. Finally, if 100% purity of the Java™ code can be sacrificed, a Java™ Native Interface (JNI) can be used to create platform specific optimizations written in C or C++.

Type Erasure and Wrapped Methods

When the Bistro compiler cannot locate an appropriate method at compile-time, the compiler generates an indirect method invocation against the receiver using one of the perform: selectors. Then, the Bistro method caches use the Java™ reflection facility to resolve the method implementation at runtime. The Java™ reflection API includes the types of the actual arguments when resolving a method implementation. So, except for certain special messages, the method caches assume that the most general kind of argument types will be appropriate, i.e., they use the Bistro Object class. This type erasure mechanism is also used with block implementations (see below).

Because the dynamic resolution mechanism depends on type erasure, when a Bistro method is declared with explicit argument types, the method implementation will not be located at runtime by a dynamic message send, i.e., by a perform: message. For this reason, Bistro supports the declaration of wrapped methods. The wrapped annotation on a method instructs the compiler to generate a wrapper method automatically if the method requires one, i.e., if any of the method arguments are explicitly typed. The wrapper method generated by the compiler has the same name and number of arguments as the wrapped method, but the argument types are generalized (erased) to Object. The wrapper method invokes the wrapped method by downcasting the incoming arguments to the types declared for the wrapped method.

BLOCKS

Blocks are a very powerful part of the Smalltalk language. They are so important, Bistro not only retains the block concepts of Smalltalk, but also extends them in thoughtful ways to improve integration with Java™. Blocks are so flexible, Bistro uses them for a wide variety of language features, including decision structures, collection iteration, exception handling, multithreading, and event handling. Table 5 lists the various block idioms supported by Bistro.

Decision Structures

There are no reserved words for decision structures in Bistro like there are in languages such as Java™ and C++. Instead, decision structures use message idioms that combine Boolean expressions with Blocks. Table 5 lists some of the most commonly used decision idioms as they are expressed in Bistro. Notice that the decision idioms do not include a switch or a case statement. There are a number of ways to mimic such a control structure in Bistro. However, it is generally discouraged in favor of object-oriented designs that make use of classes and polymorphism to distinguish and handle the separate cases.

Many of the decision structures identified in Table 5 can be optimized during translation to Java™. Often, they can be translated directly into equivalent Java™ decision structures. Similar optimizations are often performed by commercial Smalltalk compilers. However, under certain circumstances, these control structures and other custom blocks are best implemented using Java™ inner classes.

Table 5. Bistro Block Idioms
Decisions booleanExpression or: [ booleanExpression ]
booleanExpression and: [ booleanExpression ]
"Alternatives"
booleanExpression ifTrue: [ "..." ].
booleanExpression ifTrue: [ "..." ] ifFalse: [ "..." ].
booleanExpression ifFalse: [ "..." ].
booleanExpression ifFalse: [ "..." ] ifTrue: [ "..." ].
"Loops"
[ booleanExpression ] whileTrue: [ "..." ].
[ booleanExpression ] whileFalse: [ "..." ].
[ "..." booleanExpression ] whileTrue.
[ "..." booleanExpression ] whileFalse.
   
Iterators "Indexed"
start to: end do: [ :index | "..." ].
start to: end by: increment do: [ :index | "..." ].
"Collections"
aCollection do: [ :element | "..." ].
aCollection collect: [ :element | "..." ].
aCollection select: [ :element | "..." ].
aCollection reject: [ :element | "..." ].
aCollection detect: [ :element | "..." ].
aCollection detect: [ :element | "..." ] ifNone: [ "..." ].
aCollection
inject: initialValue
into: [ :aValue :element | "..." ].
Evaluations [ :a :b | "..." ] value: x value: y.
[ :a | "..." ] value: x.
[ "..." ] value.
   
Exceptions [ "..." ] ifCurtailed: [ "... catch any exception ..." ].
[ "..." ] ensure: [ "... evaluated finally ..." ].
[ "..." ] "maps to try {} catch {} finally {}"
catch: [ :exception (ExceptionClass) | "..." ]
"... additional catch blocks ..."
ensure: [ "... evaluated finally ..." ].
Threads "creating threads"
[ "..." ] fork.
[ "..." ] forkAt: aPriority.
"synchronizing threads"
anObject acquireMonitorDuring: [ "..." ].
anObject notifyWaitingThread.
anObject notifyAllWaitingThreads.
anObject waitForChangeIfInterrupted: [ "..." ].
anObject
waitForChange: millisecondDuration
ifInterrupted: [ "..." ].
Adapters SomeInterface asNew: [ "..." ]

Implementing Blocks with Inner Classes

Java™ inner classes make duplicating the semantics of Smalltalk blocks rather easy. Blocks can be implemented by translation into appropriate anonymous inner classes. By defining a few abstract base classes, Bistro provides the foundations for deriving these anonymous inner classes. Some Smalltalk implementations support an arbitrary number of block arguments. To keep the translation mapping simple, Bistro currently limits support for blocks to those that take 0, 1, or 2 arguments. Table 6 lists the abstract base classes for blocks included in Bistro and an example of the kind of block each supports.

Table 6. Bistro Block Base Classes
ZeroArgumentBlock [ "..." ] value
OneArgumentBlock [ :a | "..." ] value: x
TwoArgumentBlock [ :a :b | "..." ] value: x value: y

The following method provides a simple example of a block commonly used for sorting the elements in a SortedCollection. The Bistro compiler translates the block returned by this method into an instance of an anonymous Java™ inner class derived from TwoArgumentBlock.

"Returns a block used to sort the elements of a collection.
A pair of arguments is compared to determine whether the
first argument is less than or equal to the second argument."
sortBlock
[
^[ :a :b | a <= b ]
]

Bistro supports optional typing, including method and block results and arguments. When translating typed block arguments to Java™, the Bistro compiler uses type erasure to generate a generalized wrapper method in the inner class, as well as a typed method that contains the block code. The following example revisits the sortBlock method, this time with a typed block result and typed block arguments.

"Returns a block used to sort the elements of a String collection.
A pair of Strings is compared to determine whether the first String
argument is less than or equal to the second String argument."
sortBlock
[
^[ (Boolean) :a (String) :b (String) | a <= b ]
]

Local Block Variables

Bistro local block variables can be conveniently mapped (when needed) to instance variables in Java™ inner classes. The following method includes a block with a local variable. The local block variable uses italic to help identify it.

"Returns the non-nil elements from (anArray)."
extantElementsFrom: anArray
[
result := OrderedCollection new.
1 to: anArray size do: [ :index |
"-->" element := anArray at: index.
element isNil ifFalse: [ result add: element ]
].
^result asArray
]

Method Returns from Blocks

Like Smalltalk, Bistro supports the ability to return method results directly from inside nested blocks using a message expression that begins with a caret (^). Method returns exit all enclosing block scopes, including the enclosing method scope. The following search: method provides an example of this feature. If the method finds element in the searchTargets, it returns the element as the result of the method. Note that in this case, the method result is returned by an exit from a nested block scope.

"Returns the first element of (aCollection)
contained in the (searchTargets)."
search: aCollection for: searchTargets
[
aCollection do: [ :element |
(searchTargets includes: element)
ifTrue: [ ^element ] "<-- method exit"
].
^nil
]

When the Bistro compiler cannot optimize a block inside a method and uses an inner class to implement it, special consideration must be given to the method returns. Bistro implements such method returns using the Java™ exception facility. Bistro uses an instance of MethodResult to carry a result value across the scopes of the intervening inner classes. MethodResult is a subclass of RuntimeException. So, it does not impact the method signatures of the inner classes used to implement the block scopes. Bistro wraps the method result value in an instance of MethodResult, throws the MethodResult from the inner block scope and catches the MethodResult once it reaches the enclosing method scope. Then, the MethodResult is caught and unwrapped to return the result value.

Adapters

As of release JDK 1.1, the Java™ event model changed. The new event model uses interfaces and inner classes to provide a more flexible mechanism for handling events. In order to integrate easily with the new event model, the Bistro compiler recognizes a special message idiom to support the definition of event handlers as anonymous inner classes. The following code fragment provides an example of how this special idiom (asNew:) can be used to define an ActionListener that handles a button click.

"Attach an action listener to the closeButton."
closeButton addActionListener:
(
"The listener interface is implemented by a handler instance."
java.awt.event.ActionListener asNew:
[
"Define the event handler for the closeButton click event."
(void) actionPerformed: event (java.awt.event.ActionEvent)
[ "... handle the click event from the closeButton ..."
]
]
).

Threads

While not defined in the ANSI standard, Bistro supports the conventional block fork idiom for spawning threads. However, Bistro implements these threads using primitive Java™ threads. In Bistro, block fork expressions return instances of ZeroArgumentBlock. Once a new thread has been created, you can access the primitive thread by sending the message primitiveThread to the block instance.

aBlock (ZeroArgumentBlock) := [ "..." ] fork.
primitiveThread (Thread) := aBlock primitiveThread.

Blocks that have not been forked return primitive null in response to primitiveThread.

Thread Synchronization

Java™ supports thread synchronization on methods and within methods. Bistro supports the declaration of synchronized methods and also supports object synchronization within methods. The base class, smalltalk.behavior.Object, provides the following instance method that acquires an instance monitor. Thus, any Bistro method may synchronize threads on an object by using a statement similar to the following one.

anObject acquireMonitorDuring: [ "...critical section..." ].

Bistro methods can also wait on an object monitor using the following idioms.

"Wait until notified or interrupted."
anObject waitForChangeIfInterrupted: [ "..." ].
"Wait until notified, interrupted, or a millisecondDuration expires."
anObject waitForChange: millisecondDuration ifInterrupted: [ "..." ].

After a thread has been suspended using one these wait idioms, another thread can awaken the sleeping thread using one of the following idioms.

"Awaken a single waiting thread."
anObject awakenWaitingThread.
"Awaken all waiting threads."
anObject awakenAllWaitingThreads.

Exceptions

While some superficial similarities exist between Smalltalk and Java™ exception handling mechanisms, there are also some fundamental differences. So, deciding whether and how to integrate these mechanisms presented one of the more challenging problems for the design of Bistro. Table 7 compares the salient aspects of the Smalltalk and Java™ exception handling mechanisms.

Table 7. Smalltalk and Java™ Exceptions
Smalltalk models exceptions as instances of exception classes (Exception and its subclasses).
Java™ models exceptions as instances of exception classes (Throwable and its subclasses).
   
Smalltalk has no special syntax for dealing with exceptions, only standard message idioms.
Java™ has syntax for dealing with exceptions: try { } catch { } finally { }, throw and throws.
   
Smalltalk provides fine grained control over exception handling, including the ability to decide whether and when stack frames are unwound, and the ability to resume execution at the point of origin (where the exception was raised).
Java™ exception handling is strictly stack oriented - once unwound, the stack frames between the exception origin (throw) and the handler (catch) are unavailable.
   
Smalltalk exceptions never impact method signatures.
Java™ exceptions thrown within a method must be declared in a throws clause in the method signature, except for those exceptions whose classes are derived from java.lang.RuntimeException.

Finally, Bistro supports the declaration of exceptions thrown by methods and blocks. Both method and block signatures can include a throws: clause. If a Bistro method signature includes a throws: clause, the compiler translates it directly into the equivalent Java™ method signature - i.e., the Java™ method will also include a throws clause with the same list of exception classes. However, block exceptions require special handling by the compiler.

The signatures of the value methods in the block classes (e.g., ZeroArgumentBlock) do not include exception declarations. For this reason, the Bistro compiler automatically generates wrapper methods for blocks that are declared to throw exceptions. Each generated wrapper method catches any exception thrown by a block and rethrows it as an UnhandledJavaException. Because UnhandledJavaException is derived from RuntimeException, the Java™ compiler will not complain about the exception erasure. Exception erasure is a special kind of type erasure that makes the Java™ exceptions transparent. The following table provides examples of the syntax used to declare method and block exceptions.

Methods with Exceptions Description
unary ; throws: java.lang.Throwable
[ "..." ]
This unary method can throw any exception.
binary: argument ; throws: java.lang.Throwable
[ "..." ]
This binary method can throw any exception.
keyword: a1 : a2 ; throws: IOException
[ "..." ]
This keyword method can throw an IOException.
   
Blocks with Exceptions Description
[ throws: java.lang.Throwable | "..." ] This block can throw any exception. However, if one is thrown from within the block, it will be caught and rethrown as an UnhandledJavaException.
[ :argument ; throws: IOException | "..." ] This block can throw an IOException. However, if thrown from within the block, it will be caught and rethrown as an UnhandledJavaException.

MIGRATION SUPPORT

Bistro provides a utility for migrating classes from Smalltalk to Bistro. The Bistro tool set allows software models built with Smalltalk to be translated into Bistro, compiled into Java™ code, and then deployed on and executed by a Java™ VM. Bistro only supports the translation of model classes because of the extensive problems associated with mapping Smalltalk user interface classes to those supplied by the Java™ platform. For these reasons, Bistro focuses on server-side Java™ deployment. While it is possible to develop client-side user interfaces (apps and applets) with Bistro, these will need to be developed largely from scratch with the existing Java™ AWT or Swing classes. See the discussion on adapters for details regarding how Bistro supports the Swing event model.

STATUS AND AVAILABILITY

Bistro currently consists of the following resources:

The Bistro grammar is stable and feature complete. The Bistro class library includes many of the behaviors needed to support Smalltalk execution in a Java™ VM, including the classes and metaclasses shown in Figure 2. The entire Bistro project has been published as Open Source at the following web address:

http://bistro.sourceforge.net/

CONCLUSION

While Java™ has overshadowed Smalltalk in many ways, the Smalltalk market has continued to grow along with the Java™ market (though not nearly as quickly). It is ironic that most of the technologies found in Java™ were pioneered by Smalltalk. But now, Java™ and its widely available VM clearly offer opportunities for the further growth of Smalltalk. Bistro provides software developers with a means to reuse the Java™ platform for Smalltalk development. Because Bistro supports the deployment of Smalltalk components on the Java™ platform, it provides a means for Smalltalk to expand into the Java™ markets. Thus, Bistro offers the Smalltalk community the opportunity to integrate and grow along with the Java™ community. Surely, they will both be well served by the interchange of ideas and code.

BIBLIOGRAPHY

  1. Nik Boyd. Class Naming and Privacy in Smalltalk The Smalltalk Report 6(3). SIGS Publications, November, 1996.
  2. Wayne Beaton. Name Space in Smalltalk/V for Win32. The Smalltalk Report 4(1). SIGS Publications, September, 1994.
  3. X3J20 Workgroup. ANSI Smalltalk. ANSI/NCITS 319-1998. ANSI, May, 1998.

TRADEMARKS

Java™ is a trademark of Sun Microsystems, Inc.