Copyright 2000,2001,2002 Nikolas S. Boyd. Permission is granted to copy this document provided this copyright statement is retained in all copies.
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 | ||||||||||||||||||||||||||||||||||||||
|
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 | |||||||||||||||
|
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.
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.
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.
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.
Figure 2. Classes and Metaclasses
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 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.
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.
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.
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.
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.
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 | ||||||||||||||
|
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++.
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 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.
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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 | ||||||
|
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 ]
]
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
]
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.
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 ..."
]
]
).
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.
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.
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 | ||||||||||||||||||||||
|
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. |
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.
Bistro currently consists of the following resources:
The Smalltalk conversion utility translates Smalltalk classes and method sources into Bistro source files. The utility supports several commercial and open source versions of Smalltalk, including Visual Smalltalk, VisualWorks, Dolphin, and Squeak. The conversion utility has an open framework that makes ports to other Smalltalk environments relatively straightforward.
The Bistro compiler translates Bistro source files into Java source files and then invokes a standard Java compiler to create Java class files. The grammar and parser for the Bistro compiler were developed using Java and the ANTLR parser generator (www.antlr.org).
The Bistro class library duplicates many of the essential Smalltalk classes, but they have been implemented in Bistro and translated to Java, so that they may run on the Java VM. The class library protocols adhere to the ANSI Smalltalk standard as much as possible.
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/
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.
Java is a trademark of Sun Microsystems, Inc.