[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 self 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)
Its 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 ...

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 $$