[prev] [up] [next]

Things you can do in Smalltalk

Introduction

Many things which are difficult or even impossible to solve in other programming languages are simple or trivial in a highly reflective system such as Smalltalk. Many of the patterns as collected and described by the gang of four are not needed or are much simpler in Lisp or in Smalltalk.
This document will show some of this kind - it is not about things you can do in smalltalk which you can also do in any other language, but about things which are very easily done in smalltalk but drive you mad in other languages.
Even if you are already a smalltalker, it may also be interesting to read and to get new ideas.

Iterating over Private Variables (i.e. Instance Variables)

In Paul Graham's paper "Being Popular", we find the following citation:

In Common Lisp I have often wanted to iterate through the fields of a struct-- to comb out references to a deleted object, for example, or find fields that are uninitialized. I know the structs are just vectors underneath. And yet I can't write a general purpose function that I can call on any struct. I can only access the fields by name, because that's what a struct is supposed to mean.

In smalltalk, you can access an object's slot via the "instvarAt:" message. The names of the slots are known to the class and can be aquired with "allInstanceVariableNames".

Thus, a debugging method to dump *ANY* object's contents could be:

    dump: someObject
	someObject class allInstanceVariableNames
	    doWithIndex:[:name :idx |
		Transcript
		    show:name;
		    show:' is ';
		    showCR:(someObject instVarAt:idx).
	    ]
of course, you can also write a block (aka-function) for this:
    dumper :=
	[:someObject |
	    someObject class allInstanceVariableNames
		doWithIndex:[:name :idx |
		    Transcript
			show:name;
			show:' is ';
			showCR:(someObject instVarAt:idx).
		]
	].
and iterate over a collection of objects to be dumped with:
    objectsDoBeDumped do:dumper
to dump the Transcript, try:
    dumper value:Transcript

Generating Code Dynamically

One of the nice features of an IDE being "really" integrated is that the compiler tools are still around at program execution time. This can be useful to create code on the fly - a useful feature both for intelligent programs which learn new tricks, and to provide some scripting facility to the user.

Dynamically generated Blocks

Lets start with a dynamic block (aka a closure or function), created from a string:
    |s b|

    s := '[:a :b | (a squared + b squared) sqrt ]'.
    b := (Block fromString:s).
    Transcript showCR:(b value:3 value:4)
It's a bit of a pity, that the internal represenation differs much more from the textual one as it does in Lisp-like languages. But fair anough for our needs...

You can (and should) analyze the code for the messages being sent, to make sure that no bad messages (i.e. only allowed ones) are introduced if the codestring originates from a user:

    |s b allMessages|

    s := '[:a :b | (a squared + b squared) sqrt ]'.
    b := (Block fromString:s).
    allMessages := b homeMethod literals.
    Transcript show:'block contains messages: '; showCR:allMessages.
for example, to verify that the user does not inject bad code into a scripting engine:
    |s b codeString allowedMessages|

    allowedMessages := #( + - * / sqrt squared sin cos value ).
    codeString := Dialog request:'Give an expression on a and b.
Use parenthesis as in (a*5) + (b sin):'.
    codeString notEmptyOrNil ifTrue:[
	s := '[:a :b | ',codeString,']'.
	b := (Block fromString:s).
	((b homeMethod literals)
	    contains:[:msg |
		(allowedMessages includes:msg) not
	    ])
	ifTrue:[
	    Transcript showCR:'Sorry - the block contains a bad message'.
	] ifFalse:[
	    Transcript show:'The value of "',codeString,'" for a=4,b=5 is '; showCR:(b value:4 value:5).
	].
    ].
Use this as a basis to write your own spread-sheet; if required, write your own parser which adds proper operator precedence, or use the built-in JavaScript parser...

Dynamically generated Methods

Of course, a class can also learn (and forget) new tricks. Many AI algorithms depend upon a system which can learn and enhance itself. Here, a new method is added to an existing class:
    Number
	compile:'cubed ^ self * self * self'
and can be used immediately as in:
    5 cubed
or with a floating point number, as in
    5.0 cubed
or, it can be forgotten:
    Number removeSelector:#cubed
(retry the above example after the removal, to see that integers really no longer know how to compute volumes...)

Dynamically generated Classes

Anonymous classes are not known to anyone, but implement some interface. Let us dynamically generate a class to represent people with first and lastName, and the create an instance of it (which is shown in an inspector). Notice, that you will not find the class in the browser, and that it will be garbage collected automatically when you release the reference to it (by closing the inspector):
    |cls|

    cls := Object
	      subclass:'anonymous'
	      instanceVariableNames:'firstName lastName'
	      classVariableNames:nil
	      poolDictionaries:nil
	      category:nil
	      inEnvironment:nil.
    cls compile:'firstName ^firstName'.
    cls compile:'firstName:s firstName := s'.
    cls compile:'lastName ^lastName'.
    cls compile:'lastName:s lastName := s'.

    ((cls new firstName:'hello') lastName:'world') inspect.
... more to be added here ...

Singletons

Because classes are objects where the protocol is defined by the metaclass, overriding the "new" method allows for all kinds of additional functionality. A singleton class is simply one, which remembers the very first instance it ever created and returns that again. The best place to remember that instance is a classInstance variable (called "theOneAndOnlyInstance" in the example below).
A corresponding instance creation method could be:
    new
	theOneAndOnlyInstance isNil ifTrue:[
	    theOneAndOnlyInstance := super new.
	].
	^ theOneAndOnlyInstance

Tracing, Counting, Limiting the Number of Instances

From the above, it should be obvious, how all of the above features are implemented by either redefining the instance creation method or adding extra instance creators.
The fact, that we can add our own additional instance creation methods (with a name other than the common "new") is often overseen by non-Smalltalkers:
    newCounted
	instanceCount := instanceCount + 1.
	^ self new

Factory is not needed

The fact that classes are first-class citizens and can therefore be passed around as argument or returned as return value, makes the factory pattern almost obsolete. The code to let an object dynamically decide for itself which class to instanciate its controller looks as trivial as:
    controller
	^ self controllerClass new
controllerclass can be as simple as returning the class reference:
    controllerClass
	^ VeryStrictController
or do some fancy decision making:
    controllerClass
	(Time now hour between:18 and:20) ifTrue:[
	    ^ FriendlyControllerForHappyHour
	].
	^ super controllerClass
(where VeryStrictController and FriendlyControllerForHappyHour would be the classes to instanciate) The advantage of being a fully dynamically typed language ensures that this code even works unchanged in ten years, when fifty new classes and subclasses for controlling have been added in various parts of a bigger system.

Powerful Exception Handling

The exception handling system in Smalltalk is much more powerful than anything available in C, C++, Java or C#. The one feature which is missing in all of them is called "proceedable exceptions". This means that an exception handler is enabled to perform some operation (or none, if it likes) and let the program continue execution at the point after the raise.
Some have argued "why would one want to proceed after a raise" - but that's the typical "I am a hammer - everything must be a nail" attitude - or in other words: "if all I can use raising for is to signal non-proceedable hard error situations, why would I want to proceed after an exception ?".

A more intelligent aproach is to see an exception as "something unexpected happened - can anyone help ?". or even: "something unexpected happened - is anyone interested ?".
If you look at exceptions from this perspective, it makes sense to send notifications, warnings, progress-information etc. all using the exception mechanism. For example, in Smalltalk/X, all info- and progress notifications are performed by raising a Notification.
Even more convenient is the situation if some object deep down in the calling hierarchy needs additional information, to handle an unnormal situation. For example, a compiler might need to know if it is ok to compile code with ugly or old style code in it. If the compiler is executed in the interactive IDE, and a user has originated the compile operation, it is convenient to open a little dialog window and ask the user. However, if the compilation is within a batch operation, and 3000 files are to be compiled, you better not aske the user for every method. In Smalltalk/X, a so called Query is used - this is a kind of exception which - if unhandled, proceeds with a default value, but can be handled and return a value as requested from the user. In all languages without proceedable exceptions, you would have to pass such information down either via arguments along every called function, or by setting global or other static flags, which will later make it hard to use the compiler in a multithreaded operation.

Quining - A program which generates its own source

|a| a := '[:a | Transcript show:''|a| a := '';showCR:a storeString,''.'';showCR:''(Block readFrom:a) value:a'' ]'.
(Block readFrom:a) value:a


Copyright © 2007 eXept Software AG, all rights reserved

<info@exept.de>

Doc $Revision: 1.4 $ last modified $Date: 2009/10/10 07:14:00 $