Bistro Support for Smalltalk Pool Dictionaries

The definition and use of pools is generally not a great idea from the standpoint of object-oriented design principles. It is usually better to assign ownership of data to some class (or metaclass). However, Smalltalk provides the pool mechanism for those situations where no clear ownership responsibility can be determined. Sometimes it is simply more convenient to collect a large number of constants into a pool for reference by several classes.

Smalltalk Pool Features

Smalltalk pools hold constant and variable values that are shared by several classes.

Design Goals

The following design goals guided the design of Bistro support for Smalltalk pools:

  1. Translate global and pool value references into static member references.
  2. Support round-trip conversions between Smalltalk and Bistro.
  3. Keep the translation model as simple as possible given the foregoing.

Translating pool value references into static member references will keep the runtime cost of pool value accesses reasonable.

Design Sketch

Bistro support for Smalltalk pools was implemented by establishing some language translation conventions. Pooled values are implemented as public static data members. Constant values are declared final. Pool value initializers are implemented as block evaluations. Class references within initializers are "softened" into strings. These conventions will make roundtrip conversions between SIF and Bistro easier.

Pool Definitions

Pool Data References

Global Data Definitions

Global Data References

Value Initializers

Packaging

Example

Consider the following SIF code fragment:


Global constant: 'GlobalConstant' !
GlobalConstant initializer ! 10 !

Pool named: 'SamplePool' !
SamplePool constant: 'SampleConstant' !
SamplePool initializerFor: 'SampleConstant' ! SmallInteger maximum !
SamplePool variable: 'SampleVariable' !
SamplePool initializerFor: 'SampleVariable' ! 1 + 1 !

Class named: 'SampleClass'
superclass: 'Object'
indexedInstanceVariables: #'none'
instanceVariableNames: ''
classVariableNames: ''
sharedPools: 'SamplePool'
classInstanceVariableNames: '' !
Annotation key: 'category' value: 'sample.package' !

SampleClass method !
sampleConstant
^SampleConstant !

SampleClass method !
sampleVariable
^SampleVariable !

SampleClass method !
sampleGlobal
^GlobalConstant !

When converted, the sample will produce the following Bistro code:


package: smalltalk.global;
nil subclass: GlobalConstant
class: [
public static final value := [ 10 ] value.
]

package: smalltalk.pool;
nil subclass: SamplePool
class: [
public static SampleVariable := [ 1 + 1 ] value.
public static final SampleConstant :=
[ 'smalltalk.magnitude.SmallInteger' resolvedToClass maximum ] value.
]

package: sample.package;
import: smalltalk.global.*;
import: smalltalk.pool.*;
Object subclass: SampleClass
metaclass: []
class: [
sampleConstant [
^SamplePool.SampleConstant
]
sampleVariable [
^SamplePool.SampleVariable
]
sampleGlobal [
^GlobalConstant.value
]
]

Concerns / Issues

  1. ANSI Smalltalk does not provide any approved standard for name spaces. Does it make sense to provide some mechanism for associating pools with packages other than the proposed ones, e.g., using a SIF annotation to identify the Java package name?
  2. Should the "null" package be used rather than the proposed ones to prevent the pools and globals from being sealed? Or, should developers simply be forewarned to not seal the proposed packages?

Discussions

1. On the one hand, I'd rather give developers control over the packaging of pools and globals. But, there is no standard mechanism for specifying packages in Smalltalk. For classes, I've decided to use the category annotation as the carrier of their package names (see the example above). I could do something similar for pools and globals, but I'm not sure about the extent to which Smalltalk tools support the categorization of pools and globals. Also, there should be some intelligent default in the absence of an explicit category for each pool and global. On the other hand, giving developers this option may make it more difficult to detect that a particular Bistro class represents a pool or a global. Although, I could probably establish some additional coding conventions that would make them easier to detect automatically.

2. Java provides a mechanism for sealing JAR files and packages prior to deployment. Package sealing has reference and security implications.

"A package sealed within a JAR specifies that all classes defined in that package must originate from the same JAR. Otherwise, a SecurityException is thrown."

If pools and globals all live in the proposed packages (smalltalk.pool and smalltalk.global), the package sealing mechanism will give deployment managers (perhaps unwittingly) the ability to break references to pools and globals that are intended to be shared between applications packaged in separate JAR files. Just sealing a JAR file without keeping the proposed packages open explicitly could cause such a breakage. So, I don't think this is just a concern over a pathological case.

I couldn't identify a way to automatically force the proposed packages to remain open. However, the "null" package can never be sealed (see the note at the very end of the extensions spec). So, it may be a better choice for the default pool and global package. I just usually avoid using the "null" package because I've grown used to the benefits of explicit packaging. Giving developers the option to specify the package in which a pool or global lives (via category annotation) will give them the freedom / responsibility to determine whether or not that package is sealed. Using the "null" package as the default will prevent unwitting inter-application reference breakages.

3. The Java Language Specification (JLS) indicates that static final values (constants) may be in-lined by the compiler in client classes.

"If a field is a compile-time constant, then deleting the keyword final or changing its value will not break compatibility with pre-existing binaries by causing them not to run, but they will not see any new value for the constant unless they are recompiled."
"The best way to avoid problems with "inconstant constants" in widely-distributed code is to declare as compile time constants only values which truly are unlikely ever to change. Many compile time constants in interfaces are small integer values replacing enumerated types, which the language does not support; these small values can be chosen arbitrarily, and should not need to be changed. Other than for true mathematical constants, we recommend that source code make very sparing use of class variables that are declared static and final."

Final Thoughts

Given the foregoing discussions, I'm definitely leaning toward using category annotations to specify packaging for classes, pools and globals, while using the "null" package as the default package for pools and globals. Please let me know your thoughts on these issues.


Java™ is a trademark of Sun Microsystems, Inc.

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