[hand with pencil]
Stuff For Sale
2004 Summer Tour
About
Blog
Class Stuff
Email Me
Events
Gallery
Home
In The Press
Newsletter
Services
Smalltalk
Veggie Van Gogh

Credits
© 2002,
Bytesmiths

[this is simply a banner and menu bar]

Please patronize sponsors of this page!

Bytesmiths no longer is involved in software consulting. Maintenance of this web site is currently subsidised by unrelated business activities. Please pass the word to other interested folks, so I can continue to host this page!
Your site could be listed here, for as little as $12 per month! Go to Bytesmiths Press for details.

This site has been selected by PC Webopaedia as one of the best on this topic!
This site has been awarded a Links2Go Key Resource Award in the Smalltalk category!

Originally published in The Smalltalk Report, January 1996.

A Case for Open Develpment Environments

by Jan Steinman

WARNING: This column contains inflammatory material. It is likely to raise the blood pressure of Smalltalk vendors. If you are such an inflamed person, and you don't care much about what your customers want or need, press "n" now (and start reading Chapter 11!)

As Smalltalk finds wider use and new vendors appear, the trend is clear: Smalltalk developers need to be protected from themselves. Those pesky developers are so demanding -- they want to change everything, and then have their changes supported, too! (Exit cynic mode -- we realize open environment technical support is no easy task.)

One way out of this mess is well specified interfaces. If Smalltalk vendors had the technical facilities to draw a line in the sand and say "pass this, and you're on your own," users and vendors could work together to determine the appropriate cost for different levels of support. But the easy way out is to simply remove access to the source code, without even specifying an SPI (S ystem P rogramming I nterface).

Everybody is talking about "API" these days, but they are forgetting that not everybody is an "application programmer." The beauty of Smalltalk is that it works both as a systems language and an applications language. With persistent rumors that the new regime at ParcPlace-Digitalk is considering "protecting developers from themselves," we've decided to publish some of our favorite Smalltalk system programming examples. Most of these examples will only work with VisualWorks today. Tomorrow, they may not work with VisualWorks either.

Compiler Macros

New users of Smalltalk often react with a sense of wonder when they discover that all control constructs are actually implemented in the language. One of those new users was a project leader at one of our clients.

"You know what I really miss about C," he said, "is the 'question-colon' operator." Upon closer questioning, we discovered that what he really wanted was a quick and simple way of dealing with uninitialized variables.

"Okay, let's implement it!" we replied. "Hmmm... it needs to be simple... don't want a bunch of parentheses everywhere... sounds like a binary message to me."

Object

    ? block
        "If I am not nil, answer myself, otherwise answer the value of
    <block>."

UndefinedObject

    ? block
        "If I am not nil, answer myself, otherwise answer the value of
    <block>."
 
        ^block value

This allows you to easily protect against unwanted nils, therefore making your system more robust. For example, you might have a method that prompts the user for a String, but you want a reasonable default:

    Dialog request: message
        ? ['Type something, will ya? We''re paying for this stuff!']

This avoids having to use a conditional assignment to a temporary variable, and is also quite easy to read. Also, it doesn't involve any "systems" programming, yet. On the other hand, a message send is involved, which costs a bit more time than ifTrue:ifFalse: does.

"But why should it cost more?" our client asked. It didn't take more than a few minutes of rummaging around the compiler to come up with the answer: it needn't cost more. Add the following method to MessageNode , keeping in mind the warnings we gave about base modifications in the July issue. (We put this and similar extensions in a separate ENVY application called CompilationBytesmiths .)

MessageNode

    transformIfNil
        "If the receiver is nil, evaluate the argument. MacroSelectors
    associate this action with the selector #?."
 
        ^((arguments first isBlockWithNumArgs: 0)
            and: [receiver hasEffect not]) ifTrue:  
               [receiver := self class new
                   receiver: receiver
                   selector: #==
                   arguments: (Array with: (LiteralNode new value: nil)).  
               self
                   makeIfTrue: arguments first
                   ifFalse: (BlockNode new body: receiver receiver)]

and evaluate:

    (MessageNode classPool at: #MacroSelectors)
        at: #? put: #transformIfNil

(Using ENVY, we put this expression, and other similar ones below, in CompilationBytesmiths class>>loaded , and also added a removing method to get rid of these new "macro selectors" when CompilationBytesmiths is removed.)

Now when ? appears in your code with a simple receiver, the compiler "in-lines" it into a test for nil and a conditional branch, the same way it deals with ifTrue: and other "fake" messages -- no message sends involved. Some simple timings show it to be about 30% faster without the message sends.

This is kind of cute, but the few microseconds it saves is hardly going to make or break a project. However, this basic mechanism can be exploited to strip your code of debug statements and assertions.

Assertions are like bran cereal -- everybody agrees it's good for you, but nobody really likes the taste. Smalltalk assertions typically steal cycles from you even beyond the development phase where they're needed. What assertion writers really want is the C pre-processor, so that when you hit "#ifdef DEBUG" on your final compile, the assertion code simply goes away. Well, we've got access to the VisualWorks compiler, so let's do it!

First, establish a predicate for all manner of development-only code. We define an ENVY application called TestingBytesmiths that holds our test management framework; its presence is an ideal development predicate:

Object

    isInDevelopment
        "Is this a development image?
    Since this consumes run-time, avoid using this in performance
    critical code."
 
        ^Smalltalk includesKey: #TestingBytesmiths

As the comment indicates, a dictionary look-up is a rather heavy price to pay to find out you don't want to print a Transcript message in a production environment! To further encapsulate this, we also have a conditional action:

Object

    ifInDevelopment: block
        "If this image is in a 'development' state (whatever that
    means), do <block>, otherwise do nothing. In either case,
    answer self.
        Since this consumes run-time in either case, avoid using this
    in performance critical code."
 
        self isInDevelopment ifTrue: [block value]

In fact, the greater encapsulation of the conditional action is much preferred, as you'll see shortly -- the predicate method isInDevelopment should be considered private.

Remember the ? example? It pays back the effort it took to understand it when used as a pattern for stripping out development-specific code:

MessageNode

    transformIfInDevelopment
        "If the system is not in development, remove this message. If
    the system is in development, insert the argument block's
    statements. MacroSelectors associate this action with the selector
    #ifInDevelopment:."
    "self halt.
    self ifInDevelopment:
        [Transcript cr; show: 'Yup, I''m in development.']. 
    27 = 27"
 
        ^((self respondsTo: #isInDevelopment) and: [self isInDevelopment])
            ifTrue: [receiver := arguments first body]
            ifFalse: [receiver := SequenceNode new statements: #()]

Let the compiler know you've defined a new macro. If you're using ENVY, remember to put this in your loaded method, and to remove your macro selector in your removing method.

    (MessageNode classPool at: #MacroSelectors)
         at: #ifInDevelopment
         put: #transformIfInDevelopment

Now if you do the comment expression in the above method, and step through the halt, you will see the following decompiled code in a "development" image:

    self halt.
    Transcript cr; show: 'Yup, I''m in development.'.
    27 = 27

and if you temporarily re-define Object >>isInDevelopment to answer false, the decompiled code will look like:

    self halt.
    27 = 27

all without a single "#ifdef"! We also "in-line" other conditional development-time messages using:

MessageNode

    transformRuntimeNoOp
        "If the system is not in development remove this message. If
    the system is in development, generate the message. MacroSelectors
    associate this action with the selectors #debug, #debug:, and
    #halt."
 
        ^((self respondsTo: #isInDevelopment) and: [self isInDevelopment])
            ifFalse: [receiver := SequenceNode new statements: #()]

This works because there is no "ifTrue:" part, so it returns nil when not in development, which tells the sender to generate the original message send, instead of generating an in-line bytecode sequence.

We can hear some of the Smalltalk vendors who hide their compiler mumbling something in the background like "we can provide 'hooks' to do things like that." Great -- we're happy they can perfectly anticipate all the potential uses one might make of the compiler! But what about their compiler bugs? (Naw, they don't have compiler bugs!)

Compiler Bug Fixes

Some of these are controversial, and some may well be a minority opinion, but our point is that without compiler source code, we would not even have a choice about dealing with, shall we say, "undesired compiler behavior."

Most Smalltalk dialects have block temporary variables, and using block temporary variables whenever possible can have an important performance benefit. For simple cases, we've measured a 100% speed penalty when using method temporaries instead of block temporaries.

Yet the "helpful" VisualWorks compiler always places undeclared temporaries in the method context. This often causes what could be a "clean" block to be a "copying" block instead. But we have the source -- let's fix it! In InteractiveCompilerErrorHandler >>declareTemp:from: change:

    endTemps := codeStream homeStream topNode body sourcePosition first.

to:

    endTemps := codeStream topNode body sourcePosition first.

That's right, simply remove "homeStream."

Now if you let the compiler declare your temps for you, it will put them where they usually belong, in the innermost scope. (If the variable is used after the block, it will complain that you've re-declared it, thus clueing you that you really need it in the outer scope, and perhaps clueing you that you should consider re-designing the method so that it won't have a full block!)

The only problem we've discovered with this "bugfix" is that the formatting is a little weird -- try further modifying the above method with (first and last lines are from the original method):

    ifFalse:    "No temp declarations yet; have to insert whole line"
         ["Added/modified by Bytesmiths, on 7 October 1995: figure out
           how many tabs to insert."
         tabs := 1.
         [(editor text at: endTemps - tabs) == Character tab] whileTrue:
             [tabs := tabs + 1].
             replacement := '| ', name, ' |',
                 (tabs = 1
                     ifTrue: [' ']
                     ifFalse: [(String new: tabs withAll: Character tab)
                         at: 1 put: Character cr; yourself])
         "*****end addition/modification*****"].
         editor selectAt: endTemps.

which makes things pretty again. We desperately needed this for a block editor we were building for a hypertext system -- temps automatically declared outside the block were simply unacceptable in this case.

A less controversial change for anyone who has needed the compiler in a "headless" environment is its insistence on interacting with someone when syntax errors are detected. We mentioned in our September column that we found it necessary to implement silentEvaluate: , which always raises an exception when evaluation fails for any reason, rather than bringing up a syntax error dialog. This "bugfix" would not have been possible without the compiler source code.

Comment Pulling

Access to the Smalltalk parser simplifies many tasks, particularly for tool builders. For example, our SmallDoc system (partially described in June and September) pulls method comments out of the source code and pastes them as styled Text into the little-used ENVY comment field, where they are easily accessed for a variety of documentation purposes:

CompiledMethod

    setSpecificationFromSource: source
        "Set my comment user field to the comment contained in <source>,
    my source code."
 
        | comments comment args charSet |
        comments := Compiler preferredParserClass new
            parseMethodComment: source
            setPattern: [:x | ].
        comments size > 0 ifFalse: [^self].
        comment := TextStream on: (String new: 100).      
        args := (Compiler preferredParserClass new
            parseArgsAndTemps: source
            notifying: nil) readStream.
        self selector numArgs = 0         
            ifTrue: [comment emphasis: #bold; nextPutAll: selector]
            ifFalse:  
                [self selector keywords do: [:kw |
                    comment    
                        emphasis: #bold; nextPutAll: kw; space;
                        emphasis: #italic; nextPutAll: args next; space]].
        comment emphasis: nil.
        args := args contents copyFrom: 1 to: self selector numArgs.
        charSet := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' asSet.
        comments do: [:cmt | | cmtStream |
            cmtStream := cmt readStream.
            comment cr; tab.
            [cmtStream atEnd] whileFalse: [ | pair |
                pair := cmtStream nextWordAndNonWordDefinedBy: charSet.
                (args includes: pair first) ifFalse:
                    [comment nextPutAll: pair first; nextPutAll: pair last]

This method is conditionally sent from ClassDescription >>insert:withSource:classified:ifNewAddTo: so that every "accept" updates the ENVY comment field.

Metrics

Interest in measurement is rapidly increasing, yet no standard solution exists. The Smalltalk vendors will probably give developers some sort of metrics capability someday, but will it be right, and will those early adopters who have implemented their own metrics be willing to give them up? Will emerging third-party products be rewarded for their risk by being stranded without sufficient SPIs?

We've implemented some code quality metrics for our clients that rely on "deep" access to compiler and parser classes. At the OOPSLA '95 Smalltalk Testing Workshop we hosted, John Brant presented a code quality tool he is working on at UIUC that goes far beyond what we have done. He expects to make it widely available when completed, but if the system-level classes he exploits become "protected," such a thing might not be possible. Emerging coverage tools that do bytecode manipulation might also be threatened.

Conclusion

As Smalltalk enters mainstream MIS shops, Smalltalk vendors claim there is a need to "protect developers from themselves." They propose to do this by removing source code and by supplying "hooks" to anticipated "APIs." This flies in the face of conventional wisdom about re-use. Re-use often involves modifications and extensions; it usually is discovered, harvested, re-engineered, sought-out, but it is rarely anticipated.

We feel this argument is a thinly disguised way of reducing support costs, which is better addressed by clearly defining SPIs in such a way that both system and application programmers know when they've overstepped their limits (and their support contracts!)

The power of Smalltalk comes from many different aspects, and trying to be more like Visual Basic, PowerBuilder, or Hot Java by reducing access to "dangerous" (i.e., "hard to support") system features in the name of making things "safe" for application programmers is likely to have a detrimental effect.

If you want to do some of the things in this column, but your Smalltalk dialect doesn't have the proper "hooks," let your vendor know you need the full source in order to discover what you need to re-use. If your Smalltalk vendor currently gives you all the source you need, call them and thank them, then tell them you expect the situation will not change if they don't want you running off to Hot Java!


Go to the previous column in the series, or the next column in the series.

160 Sharp Road, Salt Spring Island, British Columbia, V8K 2P6, Canada