In the same vein as the last post, I thought it might be
valuable to go over the way Smalltalkers normally construct new
execution at runtime. One way, as I covered in my last post, is to
compile code from a string. All by itself, that gives you a fair
bit of power - you can compile anything, including new classes. You
can add new instance variables to existing classes, and have all
the subclasses get recompiled for you - at runtime. When I do live
updates of this server, I'm often doing just that.
So a few examples - say I wanted to add a new instance variable
to an existing class. I could do that a couple of ways. I could
recompile the class definition. Here's my class in the browser
before that step:
Now, I execute the code below:
Compiler evaluate: 'Smalltalk defineClass: #Foo
superclass: #{Core.Object}
indexedType: #none
private: false
instanceVariableNames: ''var1 var2 ''
classInstanceVariableNames: ''''
imports: ''''
category: ''test'''
The double quotes are to tell the compiler that the things
wrapped that way are strings, not the end of the string being
defined. Executing that yields:
You can also tell the class to add an instance variable, rather
than recompiling the class:
Foo addInstVarName: 'var2'.
In Smalltalk, all your objects - including the classes - are
live at runtime. They aren't dead text lying in the version control
system, and they can all be manipulated. You can insert new methods
into a class in much the same way:
Foo compile: 'initialize
var1 := 1.
var2 := 2'.
Which adds the #initialize method to class Foo. You can inject
new code into a class at runtime this way - so if you have a code
generator for some reason (database interfacing, whatever) - you
could have the code dynamically generated and added all at
once.
You can also dynamically create message sends that didn't exist
at deployment time via #perform. Say you have a variety of API
methods, all similarly named, but their use depends on a base
selection (in the post tool I'm using, that's how code fires, based
on whether the tool is set for my API, the MetaWebLog API, the MT
API, or the Blogger API.
So how does that happen? Here's an example. I have the following
message send:
^self performForAPI: 'GetAllItems'
And that code looks like:
performForAPI: messageString
^self performForAPI: messageString with: #()
performForAPI: messageString with: arguments
| prefix msg |
prefix := self perform: self apiToUse.
msg := (prefix, messageString) asSymbol.
^msg isKeyword
ifTrue: [self perform: msg withArguments: arguments]
ifFalse: [self perform: msg]
The message is created by appending strings, turning that into a
symbol, and then firing it. This is not a completely great idea in
all circumstances (it makes it hard to trace senders, for instance)
- and I wouldn't write the post tool that way were I to do it now.
It's a nice capability, and very valuable when you need it - but
you really need to be sure you need it.
In any case, that shows some of the more dynamic features of Smalltalk.IMHO, they are far less baroque than what C# is adding, because Smalltalk was created with this kind of thing in mind. C# wasn't, and it shows.