Alan Lovejoy pointed
me
at a page that compares Java and Ruby, and suggested that I
provide Smalltalk examples. The Smalltalk is going to be a lot like
the Ruby code - both are dynamic languages with what people now
call "duck typing".
The page sets up a simple example with a pair of classes - Word
and Definition:
You can go to the linked site to see the Java and Ruby classes -
here are the class definitions in Smalltalk:
Smalltalk.Words defineClass: #Word
superclass: #{Core.Object}
indexedType: #none
private: false
instanceVariableNames: 'spelling partOfSpeech definitions synonyms
'
classInstanceVariableNames: ''
imports: ''
category: 'Words'
Smalltalk.Words defineClass: #Definition
superclass: #{Core.Object}
indexedType: #none
private: false
instanceVariableNames: 'word definition exampleSentence '
classInstanceVariableNames: ''
imports: ''
category: 'Words'
In Ruby, it looks like you can define getters/setters in the
class definition. In Cincom Smalltalk, the environment does the
same thing for you, offering to define getters/setters (and a
default initializer) as part of creating the class. So in step one,
we get a small bit further than Ruby, due to the tools we have. As
to the methods, let's start with the initiazers - I had to modify
the default initializer so that it matched the code in the example
page - first
Definition, then
Word:
initialize
"Initialize a newly created instance. This method must answer the
receiver."
word := nil.
definition := nil.
exampleSentence := Set new.
initialize
"Initialize a newly created instance. This method must answer the
receiver."
spelling := nil.
partOfSpeech := nil.
definitions := Set new.
synonyms := Set new.
So on to the meat - the tools mostly created what I needed, and
I just had to modify the defaults. The class definitions have a lot
of things we don't need to worry about right now - I created them
via the class creation wizard, seen below:
That wizard removes a lot of the typing drudgery of class
creation - mind you, old hands (like me) can still do it the "old
fashioned way" in the browser. Let's define the #addDefinition: and
#addSynonym: methods now - they auto-check for duplicates by being
Set objects:
addDefinition: definitionString
self definitions add: definitionString.
addSynonym: synonymString
self synonyms add: synonymString.
Now creating a new instance is easy - we create a class side
convenience method for creating new words::
newWord: wordString partOfSpeech: partOfSpeechString
^self new
spelling: wordString;
partOfSpeech: partOfSpeechString.
Some explanation - the #new method creates the new object and the #spelling: setter is immediately sent to that. The semi-colon is a cascade, which allows us to send the #partOfSpeech: setter to the same object as the first message went to. Note the name of the method: #newWord:partOfSpeec:. It's self describing. Instead of #new, we can create our own class creation protocol that is self explanatory. Now to use it:
"Create a Word"
word := Word newWord: 'ebullient' partOfSpeech: 'adjective'.
word addDefinition: 'Overflowing with Enthusiasm'.
word addDefinition: 'Boiling up or over'.
Notice how clear that is - and how much clearer it is than the Java example on that page (IMHO, it's clearer than Ruby, simply because of the keyword message). Keyword messages make it possible to make your code very intention revealing - which is a good thing. This is part of why Smalltalkers tend to send you to the source instead of to some doc - because the source (if done decently) is documentation. Yes, you can write bad Smalltalk, and I've done plenty of that myself :)
If you look at the explanation of collection protocol on that page, everything he says about Ruby's collections applies to Smalltalk. We can add things into the collections without fear of them blowing up. In fact, if we make sure that the things we add are polymorphically (i.e., message signature) identical to each other, it doesn't matter what they are at all. I take great advantage of that in BottomFeeder.
Moving past the example, the linked page goes into Ruby iteration. We have the same power in Smalltalk - we can iterate over any kind of collection like this:
someCollection do: [:each | each doSomethingHere].
The [ ] syntax is for a closure - the first place you'll find them in Smalltalk is in the logical operations (ifTrue/ifFalse, etc). They provide a lot more power though, and the fact that they are used to define the common operations (doWhile, et. al.) is telling - those operations are not reserved word syntax in Smalltalk - they are messages. Which means that you can define your own versions. I've seen plenty of people add #do: to class Object so that any object can be treated like a collection, for instance.
The entire discussion of duck typing applies to Smalltalk as well. Rather than formal interfaces, Smalltalkers worry about polymorphic equivalence. You can implement a matching protocol between any kind of objects that make sense, and then use them together. A common example used in intro classes is a FinancialInstrument - without regard to inheritance, just define a financial protocol amongst all the objects, and then use it - for instance, a #currentValue method might determine what an instrument is worth right now - and be very different for various objects that share little else. Even so, they can all be placed in a collection, and current worth calculated like this:
instruments inject: 0 into: [:subTotal :next | subTotal + next currentValue].
The comment for #inject:into:, since it may not be clear to non-Smalltalkers:
Accumulate a running value associated with evaluating the argument,
binaryBlock, with the current value and the receiver as block arguments.
The initial value is the value of the argument, thisValue.
That pretty much covers what the IBM page went into. The main difference you're going to see in Smalltalk is that it's not file-oriented. You work in an image, and you create code in a browser. You can save packages out into loadable modules though (we call them parcels here at Cincom) - and this server is fired up exactly that way. I have a basic image which starts, grabs a set of parcels, loads them, configures itself as a set of post-load actions, and then goes. Interested? Grab the Cincom Smalltalk non-commercial here.