The Bistro Programming Language

Bistro provides a method syntax resembling Smalltalk, but generates Java class files.


March 01, 2004
URL:http://www.drdobbs.com/web-development/the-bistro-programming-language/184405578

A Smalltalk/Java Hybrid

During the 1970s, Smalltalk pioneered several advanced computing technologies—the mouse, high-resolution bitmapped graphics displays, graphical user interfaces with overlapping windows, object-oriented programming, and virtual machines with automatic garbage collection—that have become prevalent in the computing industry. Smalltalk also influenced the development of platforms such as Java. For instance, the Java class libraries have grown to embrace several features offered by commercial Smalltalk libraries, including mature Collection classes and the introduction of the Swing libraries. Java has grown massive, providing a vast selection of reusable classes and frameworks.

Over the past decade, Java has been adopted throughout the computer, communications, and consumer-electronics industries. Some vendors have even gone so far as to integrate Java into their operating systems. The pervasive availability of Java—especially the Java VM—has motivated many software developers to port their favorite programming languages to the Java platform; see, for instance, Robert Tolksdorf's "Programming Languages for the Java Virtual Machine."

Of course, every programming language attempts to give easy expression to both conceptual and computational models. But as with all engineered artifacts, trade-offs balance various forces and emphasize some over others. Consequently, each programming language has its strengths and weaknesses, and a fitness for solving certain kinds of problems. But while some programming languages may ultimately be computationally equivalent, they often exhibit differences—sometimes vast differences—in the ease with which they express concepts and computations. Such differences then also impact the reusability of models and solutions developed with those languages.

There are several motivations for model reuse, the primary one being economic—it's often cheaper to reuse existing solutions. However, solutions developed in one programming language may not translate well into another programming language, or the investments needed to accomplish such translation may be prohibitive. Sometimes, the differences in expressiveness are so vast that the costs of solution conversion and maintenance may exceed the expenses incurred during their original development. So the differences between programming languages are often warranted.

While there are many similarities in the overall design approach to Smalltalk and Java, it remains that language expressiveness, readability, and agility are Smalltalk's greatest advantages. While these advantages may be lost on programmers responsible for lower level system and infrastructure facilities, it fulfills many needs of business application programmers, especially those responsible for modeling business enterprises.

Java's syntax was largely derived from C++, which was itself derived from C, and it retains many of the expressive limitations and biases of C/C++. This kind of language syntax lends itself to expressing mathematical formulas and long procedural methods. While it's still possible to use Java to write C-style code, it's much harder to do that with Smalltalk. You really have to work at it, and in contrast, it becomes much more obvious that so much procedural code detracts from the overall quality of an otherwise object-oriented design.

Smalltalk fosters a particular design mindset. It offers a set of language usage and design idioms, conventions, and metaphors that have material consequences on software designs. Well-crafted object-oriented designs are easier to express, evolve, and maintain in Smalltalk. While it is possible to create mathematically oriented designs with it, Smalltalk also supports method signature designs that resemble natural language expressions. This feature remains one of Smalltalk's primary strengths, and offers many advantages over most other algorithmic programming languages. Objects developed with Smalltalk generally adhere to a common style of simplicity. Methods are generally smaller and more readable. Less internal documentation is usually required. Long procedural methods, like those often found in C code, are seldom found in Smalltalk. Classes are also generally smaller. Responsibilities are generally more evenly distributed throughout a set of collaborating classes.

Some of the common design patterns discussed in the software patterns literature were motivated by the capabilities and idioms of Smalltalk, for example, the Iterator pattern, which resembles Smalltalk Collection processing idioms; Factory Method and Abstract Factory patterns, which resemble the responsibilities of Smalltalk Metaclasses; Observer and Model-View-Controller patterns, pioneered in the Smalltalk human interface classes; and Flyweight pattern, of which Smalltalk Characters and Symbols are examples. (See Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma et al., Addison-Wesley, 1995.)

Other commonly used software development techniques and technologies were also pioneered with Smalltalk. For example, the JUnit test library was derived from the earlier SUnit library, and the tenets and practices of Extreme Programming were originally pioneered in the context of software development with Smalltalk, especially because of the agility offered by the Smalltalk language and integrated development tools.

Bistro: Smalltalk Over Java

These issues of reuse and expressiveness led me to develop the Bistro programming language. The method syntax for Smalltalk supports designs that resemble natural language expressions. So Bistro provides a natural method syntax that closely resembles that of Smalltalk. However, the Bistro compiler translates all methods into Java and generates Java class files. So, Bistro provides close interoperability with Java, thereby supporting reuse of all the existing Java class libraries. The primary goals influencing the design of Bistro were:

While Smalltalk has a superior method syntax for modeling concepts, Java offers several features that Smalltalk lacks. Bistro integrates the best features from both languages, especially taking strength from the one where the other is weak. Table 1 lists the features found in Bistro and where the feature originated, whether from Smalltalk or Java.

Table 1: Bistro includes the best features from both Smalltalk and Java.

Language Model

Traditionally, Smalltalk systems were built in the context of an object memory image. Smalltalk class definitions were loaded from source files 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. Java is file based, both for its source code (.java) and executable binaries (.class). Java also supports the deployment of class libraries as files in archival form (as .zip, .jar, and so on). To leverage these Java features, 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 class files.

Smalltalk methods and blocks are similar. Each method and block contains a series of statements. The primary difference is that blocks are delimited with square brackets [ ], while methods are not. To support a declarative language model and normalize the syntax, Bistro uses square brackets as scope delimiters throughout. Thus, the declaration of Bistro classes, types, methods, and blocks are delimited with square brackets (except for primitive methods).

The overall source-code structure of each Bistro class resembles that 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. Listing One provides a general template for Bistro class definitions, while Listing Two provides more specific templates for several kinds of class and type definitions supported by Bistro.

Listing One
"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 methods ..."
]
class: [
	"... instance variables ..."
	"... instance methods ..."
]

Listing Two
"A root class definition"
nil subclass: RootClass
metaclass: [ "..." ]
class: [ "..." ]

"A derived (sub)class definition"
Superclass subclass: Subclass
metaclass: [ "..." ]
class: [ "..." ]

"A root type definition"
nil subtype: RootType
metatype: [ "..." ]
type: [ "..." ]

"A derived (sub)type definition"
Supertype subtype: Subtype
metatype: [ "..." ]
type: [ "..." ]

"A derived (sub)class that implements a type"
Superclass subclass: Subclass
implements: Type "..."
metaclass: [ "..." ]
class: [ "..." ]

Root classes and types 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.

Method and Blocks

There are two Smalltalk features—blocks and keyword method signatures—that tend to distinguish it from most other programming languages. Smalltalk blocks provide a unifying mechanism for statement execution. Both blocks and methods use the same underlying statement execution mechanism. Smalltalk keyword messages support method signature designs that resemble and model natural language expressions. Because these features are central to the expressiveness of Smalltalk, Bistro also offers these features. Bistro blocks and natural methods closely resemble Smalltalk, as do Bistro keyword method signatures. However, Bistro also provides some method signature extensions and decorations that let it integrate relatively seamlessly with Java.

Bistro supports four kinds of methods: natural, primitive, abstract, and native. All four kinds of methods are declared with method signatures that resemble Smalltalk. However, Bistro method signatures may include modifiers from Java and may also include result and argument types. Bistro natural method bodies resemble Smalltalk. Bistro primitive method bodies are Java code. Bistro abstract methods and native methods have empty bodies. Abstract and native methods merely define method signatures without implementations, and they are translated into the equivalent Java abstract and native methods. Listing Three illustrates these method variations using keyword method signatures.

Listing Three
"A sample natural method"
doItNaturally: argument [
	"... Bistro statements ..."
]
"A sample primitive method"
doItPrimitively: argument {
	/* ... Java statements ... */
}
"A sample abstract method"
abstract doItAbstractly: argument []

"A sample native method"
native doItNatively: argument []

Blocks are used for control flow and collection processing, as well as other features. Bistro supports the same block usage idioms offered by Smalltalk. Listing Four provides templates that illustrate how blocks are used for both flow-control and collection processing.

Listing Four
"Control Flow Alternatives"
booleanExpression
	ifTrue: [ "..." ]
	ifFalse: [ "..." ].
"Loops and Iteration"
start to: end do: [ :index | "..." ].
[ booleanExpression ] whileTrue: [ "..." ].

"Collection Processing"
aCollection do: [ :element | "..." ].
result := aCollection collect: [ :element | "..." ].
result := aCollection select: [ :element | "..." ].
result := aCollection reject: [ :element | "..." ].
result := aCollection detect: [ :element | "..." ].

Namespaces

Originally, Smalltalk provided a single, flat namespace for classes—and Smalltalk developers suffered. Smalltalk needed a mechanism for defining distinct namespaces to separate and group classes. The absence of namespaces permits class naming conflicts, especially when integrating large class libraries from third-party vendors. While some namespace models have been proposed for Smalltalk (see my article "Class Naming and Privacy in Smalltalk" The Smalltalk Report, November 1996 and "Name Space in Smalltalk/V for Win32," by Wayne Beaton, The Smalltalk Report, September 1994) and several proprietary mechanisms are available in the commercial Smalltalk environments, none has yet been widely adopted by Smalltalk vendors. Nor has the ANSI X3J20 committee included any namespace model in the Smalltalk standard (X3J20 Workgroup. ANSI Smalltalk. ANSI/NCITS 319-1998. ANSI, May 1998).

Luckily, the Java language model supports the namespace concept with packages. Packages organize Java classes into logical partitions that serve as namespaces for classes. This helps system designers prevent potential class naming conflicts. Java class files are physically organized into package directories in a filesystem. There is a direct correspondence between these logical and physical organizations.

Because Bistro code is translated into Java class files, Bistro reuses the Java package mechanism and the advantages it provides for separating, organizing, and grouping classes. All classes defined in a package are immediately visible to each other. The public classes from other packages can be made visible by importing them. As in Java, an import establishes visibility to an individual class or all the public classes contained in a package. Listing One includes examples of both a package declaration and import statement. 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 Listing One makes OrderedCollection visible, it may also be referred to by its fully qualified name smalltalk.collection.OrderedCollection.

Classes and Metaclasses

The Bistro compiler translates Bistro classes into Java classes. Bistro class member variables and methods become Java class variables and methods. However, Smalltalk has full metaclasses, while Java does not. So 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 as it does in Smalltalk. This provides inheritance and polymorphism for metaclasses such as those 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. Instead, metaclasses must provide default implementations for methods that would otherwise be declared abstract.

Figure 1 shows the full inheritance hierarchy for some of the essential behavior classes. 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 1: Classes and metaclasses.

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; that is, they serve as the root of an inheritance hierarchy derived from a Java base class. Finally, a Bistro class derived from a Java class may also be declared without a metaclass. In this case, the generated Java class will simply be another Java class, without a generated metaclass. Listing Five (HelloWorld) provides an example of a simple console application that has no metaclass defined.

Listing Five
"HelloWorld.bist"
package: smalltalk.example;
import: smalltalk.stream.Transcript;

"Demonstrates Bistro syntax and Java integration."
Object subclass: HelloWorld class: [

	"Supports program launch from the console."
	static (void) main: args (java.lang.String[]) [
		HelloWorld basicNew printHello.
	]
	"Prints hello on the console."
	printHello [
		Transcript printLine: 'Hello World!'.
	]
]

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 you the ability to coordinate programming and metaprogramming.

Each Bistro type and metatype is translated into a Java interface definition. Each metatype is defined as a nested public static interface of its corresponding type, 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, no corresponding metatype exists. In this case, the generated metaclass does not implement a metatype.

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, or private. While each class, method, and variable may be declared with one of these 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. 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. A synchronized method excludes concurrent usage by different threads.

Conclusion

In December of 2000, the Bistro project reached completion of its initial goals. In April of 2002, with the advent of support for identity collections in J2SE 1.4, the Bistro class library was improved to support IdentityDictionary and IdentitySet from the ANSI Smalltalk Standard. The essential portions of the SUnit library have been ported to Bistro. Listing Six is one of the smaller classes from that library. The next phases of the Bistro project include: adding support for pools, adding more unit tests (especially ANSI conformance tests), and improving conversions between Smalltalk and Bistro.

Listing Six
"TestSuite.bist"
package: smalltalk.test;
import: smalltalk.collection.OrderedCollection;

"A TestSuite is a Composite of test cases.
It runs a collection of test cases."

Object subclass: TestSuite
metaclass:
[
	"Returns a new TestSuite."
	new [
		^super new initialize
	]
]
class:
[
"accessing tests"

	"Contains the tests for the suite."
	tests (OrderedCollection).

	"Returns the test cases in the suite."
	tests [
		^tests
	]
	"Establishes the test cases of the suite."
	tests: anOrderedCollection [
		tests := anOrderedCollection.
	]
	"Adds (aTestCase) to the suite."
	addTest: aTestCase [
		self tests add: aTestCase.
	]
"initializing"
	"Initializes the receiver."
	initialize [
		self tests: OrderedCollection new.
	]
"running tests"
	"Runs the test cases in the suite."
	run [
		result := TestResult new.
		self run: result.
		^result
	]
	"Runs the test cases in the suite, 
	recording their results in (aTestResult)."
	run: aTestResult [
		self tests do: [ :each | 
			each run: aTestResult ].
	]
]

At a minimum, Bistro requires the capabilities of the Java 2 Standard Edition (J2SE), especially anonymous inner classes (which are used for blocks), reflection (which is used for dynamic methods resolution and dispatch), and the java.util and java.io packages (which are used to implement the standard Collections and Streams).


Nik has been designing and developing object-oriented software systems since 1987. He can be contacted at [email protected].


Bistro.zip is a file containing the source code that accompanies this article.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.