ProgrammingBack to Tutorials Sapphire (Ruby In Steel) Tutorials Programming
Ruby The Smalltalk Way #2 - A Question of Style
Previously I discussed a few of the more obvious points of similarity and difference between Smalltalk and Ruby - and discussed how Smalltalk’s ‘message sending’ differs from Ruby’s ‘method-calling’. Using the Smalltalk/V Tutorial as a guide, part one of this series took us about half way through chapter one. In this article, I shall aim to complete all the ‘hands on coding’ section of Chapter One to provide a more complete overview of basic Smalltalk and Ruby syntax, before moving onto a more theoretical discussion of OOP in my next article.
This series aims to compare Smalltalk and Ruby. It aims to highlight the main points of similarity in the two languages’ approaches to Object Orientation, to point out the ways in which Ruby differs from Smalltalk and to consider whether it is possible (or, indeed, desirable) to program Ruby in a ‘Smalltalk style’. For a background to this series and an overview of the documentation of software which I’ll be using, refer to the Introduction. You will, at the very least, need the free eBook, The Smalltalk/V Tutorial, which can be downloaded from Stéphane Ducasse’s :: Free Online Books. Code samples are provided in the downloadable Zip archive (see Part One). You can load up chapter1.st into Dolphin Smalltalk and simply mark and evaluate (CTRL+D) blocks of code as you go along. If you are using Ruby In Steel Developer (or Trial) you can load all the Ruby and C# sample files as a single solution, chapter1.sln which also includes some C# samples. If you are using some other editor or IDE, you will need to load the Ruby (.rb) files one by one).
Introduction - guide to free software and eBooks
|Background reading is the second part of Chapter One of the Smalltalk/V manual - in the PDF download, that’s pages 18 to 21 (as shown in the bar at the bottom of the Acrobat Reader) , ending with the section named ‘The World According To Objects’.|
Subscripted Variable Access
In Smalltalk, if a is an array and i is an integer, you can evaluate the expression a at: i to retrieve the value stored at the array index i. Let’s say that value is 100 - in Smalltalk terms, 100 is an integer object (or some specific variety of integer such as SmallInteger). You can now pass to this integer object the message * 2 in order to multiply its value by two. This yields another integer object, 200, as a result. Now you can pass this new integer object (200) to the at:put: method (and so on). Here’s a complete example:
a := Array new: 26.
i := 2.
y := 100.
a at: i put: y.
a at: i + 1 put: (a at: i ) * 2.
The end result is that the array, a, contains the following:
#(nil 100 200 nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil)
This is more naturally written in Ruby using a Pascal-like syntax (compare with the Pascal code in the Smalltalk/V Tutorial):
a = Array.new( 26 )
i = 2
y = 100
a[i] = y
a[i+1] = a[i]*2
I mentioned Smalltalk’s lack of a conventional; if statement in the last part of this series. When you need to take conditional action in Smalltalk, you can evaluate an expression which returns a true or false object to which messages such as ifTrue:ifFalse: may be sent. The code to execute when either a true or a false value is returned is enclosed within a block delimited by square brackets:
a < b
ifTrue: [^'a is less than b']
ifFalse: [^'a is greater than or equal to b']
if a < b then
puts "a is less than b"
puts "a is greater than or equal to b"
Here are two examples showing one way of iterating through an array of 10 digits and adding them together. Note that Smalltalk arrays (usually) start at index 1, Ruby’s start at 0 so I’ve made adjustments to allow for that. As with if tests, when Smalltalk runs through a while loop, it evaluates an expression (here [i < 1]) which yields an object (an instance of the True or False class) and then sends a message to that object whileTrue:
a := #(1 2 3 4 5 6 7 8 9).
i := 1.
sum := 0.
[ i < 10]
whileTrue: [sum := sum + (a at: i).
i := i + 1].
Ruby, on the other hand, runs its while loop in the same way as procedural languages. Both if and while are keywords rather than methods - so Ruby once again sacrifices the strict ‘purity’ of the OOP message-sending paradigm in order to gain the benefit of familiarity for the vast majority of programmers who have moved to Ruby from some other language such as C, Java or Pascal:
a = [1,2,3,4,5,6,7,8,9]
i = 0
sum = 0
while( i < 9 )
sum += a[i]
i += 1
Returning Function Results
In Smalltalk, the value returned by a method is indicated by preceding it with the ^ character:
In Ruby, the value returned may be indicated by preceding it with the keyword, return:
If there is no explicit return, Ruby returns the last expression evaluated:
The above returns: “HELLO WORLD”.
Storage Allocation and De-Allocation
Just like Smalltalk, Ruby is a garbage-collecting language. That means that, while you may have to allocate memory for new objects, you don’t need to deallocate it later on. When objects are no longer referenced they are automatically marked for garbage collection. From time to time, Ruby looks for objects that are no longer needed and frees up their memory. As with Smalltalk, you allocate memory by calling the new method. To create an array of 5 empty ‘slots’ (each containing nil):
a := Array new: 5
a = Array.new(5)
A Complete Program
To round off this section, you might care to glance at the example in the Smalltalk/V Tutorial of a character-frequency counter. I have to say that I have never found this program either interesting or illuminating. The idea seems to be to show how verbose a procedural languages such as Pascal is and how succinct Smalltalk is by comparison. There are two problems with this, however. First, the example is unfairly weighted to take advantage of built-in features of the Smalltalk class library and, secondly, the ‘Smalltalk-style’ version of the code (page 11 in the printed book or 21 in the page bar of Acrobat) is not “the same program” as it is claimed to be in the text. The first (Pascal-style) program displays a count of each letter in a 26-slot array; the second (Smalltalk-style) program merely adds each letter to an unordered collection called a Bag.
For completeness, I’ve slightly adapted the Pascal-style version for use with Dolphin Smalltalk and you’ll find this in the Smalltalk sample file, chapter1.st. I don’t think that program is particularly relevant to this series, however, so let’s move on to the Smalltalk-style version. This at least has a few points of interest since it highlights a few of the differences between Smalltalk and Ruby, which I’ll talk about in mire detail later in thus series. Here is the code:
| s f |
s := Prompter prompt: 'enter line'.
f := Bag new.
s do: [ :c | c isLetter ifTrue: [f add: c asLowercase] ].
This prompts the user for a string, then creates a new Bag (collection) and iterates through each character in the string, adding its lowercase version to the Bag only if the character is alphabetic. So if the string were “Hello 123 World”, the Bag, f, would end up containing these characters (Smalltalk characters are prefixed by a $):
($o $o $e $d $r $h $l $l $l $w)
Here is an approximate Ruby equivalent:
print( "enter line: " )
s = gets().to_chars
a = Array.new
s.each do |c| if( c.isLetter ) then a << c.downcase end end
At first sight this looks pretty similar to the Smalltalk version. However, there are quite a few differences. First of all, Ruby doesn’t have a Bag class. In fact, Ruby has rather few collection classes - and for most straightforward lists, the Array class is used. Smalltalk, on the other hand, has a great variety of collection classes. Open up the Dolphin class browser (from the Tools menu). Now, in the top-left hand pane, scroll down to Collection and click the + signs to open up the branches. Here you will see a hierarchy of increasingly specialised collections:
The density of the Smalltalk class hierarchy is one of the differences that really leaps out at you when you switch between programming in Smalltalk and Ruby. The Ruby Array class does not have all the same behaviour as the special-purpose collections of Smalltalk. The Bag, for example, groups together the same characters as they are added whereas the Array adds them in sequence. So, after running my code, Smalltalk’s Bag contains this:
($o $o $e $d $r $h $l $l $l $w)
Whereas Ruby’s Array contains this:
["h", "e", "l", "l", "o", "w", "o", "r", "l", "d"]
I could, of course, write some extra code to mimic the Bag’s behaviour. Indeed, I can get pretty close just by calling the Array’s sort method, to return an array like this:
["d", "e", "h", "l", "l", "l", "o", "o", "r", "w"]
Nevertheless, this illustrates a characteristic difference between Ruby and Smalltalk. While you could create dense hierarchies of increasingly specialised classes in Ruby, this is not the norm and it is not the way the default Ruby class library is structured; in Smalltalk it is.
Another little difference worth pointing out is that Ruby has no Character class. When you wish to work with characters, you have two options - you can either work with strings with a length of 1 or you can work with Fixnum (integer) character codes.
In Ruby, you can represent a character by indexing a string at 0 or by prefixing a character literal with a question mark:
In fact, while both of the above may represent the character ‘a’, they are in fact Fixnum objects with the ASCII value of ‘a’, namely 97. To get around the problem in my example10.rb program, I’ve extended the String class itself by giving it a to_chars method which returns an array of characters (that is, one-letter strings), and an isLetter method which returns true if a string is one character in length and its ASCII value falls between 97 and 122 (‘a’..’z’) or between 65 and 90 (‘A’..’Z’). Otherwise it returns false.
Now, at first sight, it might seem that my implementation of isLetter is a bit of a hack (in the less positive sense of the word). Dolphin Smalltalk, I note, cheats a bit in its implementation of isLetter by calling out to an external library (presumably for reasons of efficiency). But when I checked on Smalltalk/V’s implementation of the isLetter method I was surprised to find that it is remarkably similar to my own Ruby version. In particular, we both just do the old trick of comparing ASCII values. Here’s the test from my Ruby code:
if (self >= 97 and self <= 122)
or (self >= 65 and self <= 90)
And here’s the test from Smalltalk/V’s isLetter method...
^((asciiInteger > 64 and: [asciiInteger < 91])
or: [asciiInteger > 96 and: [asciiInteger < 123]])
OK, I think that’s enough of a grounding in the basics of Smalltalk and Ruby. In the next article, I’ll be moving onto the theory of ‘The World According To Objects’.