SapphireSteel Software

 

  -  
     
     
     
  -  
     
     
     
  -  
     
     
     
  -  
     
     
     
  -  
     
     
     
     

 

  rss
RSS (SITE)
 
  rss
RSS (BLOG ONLY)
 
   
 
 
     

 

Section :: Programming
- Format For Printing...

Ruby The Smalltalk Way #2 - A Question of Style

by Huw Collingbourne
More on the basics of Smalltalk and Ruby
Wednesday 16 May 2007.
 

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).


Lesson Plan:
- Introduction - guide to free software and eBooks

Chapter One of the Smalltalk/V Tutorial. I’ll be covering this in three parts...
- 1) Fundamentals
- 2) A Question of Style (this article)
- 3) The World According To Objects


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:

(see: example6.rb)

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

All the code is supplied ‘ready to run’ for Dolphin Smalltalk. In most cases you may also run the code in other Smalltalk systems though compatibility is not guaranteed. To try out the code just select it and press CTRL+D or right-click and pick DisplayIt from the menu.


Ruby ‘translations’ of the Smalltalk code are also supplied in the archive. If you are using Ruby In Steel Developer (shown here) you can load all files in a single solution. Press CTRL+F5 to run the code. If you are using a different IDE you will need to load the source files one by one.

If Statements

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:

(See: example7.rb)

Smalltalk

a < b
  ifTrue: [^'a is less than b']
  ifFalse: [^'a is greater than or equal to b']

Ruby

if a < b then
  puts "a is less than b"
else
  puts "a is greater than or equal to b"
end

Iterative Statements

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:

Smalltalk

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:

(See: example8.rb)

Ruby

a = [1,2,3,4,5,6,7,8,9]
i = 0
sum = 0
while( i < 9 )
  sum += a[i]
  i += 1
end

Returning Function Results

In Smalltalk, the value returned by a method is indicated by preceding it with the ^ character:

^answer

In Ruby, the value returned may be indicated by preceding it with the keyword, return:

return answer

If there is no explicit return, Ruby returns the last expression evaluated:

def x
  &#8220;hello world&#8221;.upcase
end

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):

(See: example9.rb)

Smalltalk

a := Array new: 5

Ruby

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:

Smalltalk

| s f |
s := Prompter prompt: 'enter line'.
f := Bag new.
s do: [ :c | c isLetter ifTrue: [f add: c asLowercase] ].
^f

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:

(See: example10.rb)

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:


For an example of the density of the Smalltalk class hierarchy, try expanding the Dolphin Smalltalk Collection class in the class browser (CTRL+B). Here I’ve expanded just a few of its sub-branches to show some of its descendents. If you scroll down you can carry on expanding yet more branches to reveal a very large Collection family...

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:

(see: chars.rb)

"a"[0]
?a

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[0] >= 97 and self[0] <= 122)
 or  (self[0] >= 65 and self[0] <= 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’.

AddThis Social Bookmark Button


Forum

  • Ruby The Smalltalk Way #2 - A Question of Style
    25 September 2007, by manveru

    I am a full-time Ruby developer, but quite fond of Smalltalk as its older big brother from which we can learn a lot (please, don’t flame me for putting them in this relationship, i don’t mean you harm), what you are saying about the class-hierarchy is true, but to be honest, i very often feel lost when confronted with the massive amount of classes in Smalltalk that are included in the default images.

    It might just be my unfamiliarity (i wished i had someone who could show me in a live session how to use Smalltalk efficient. For now I will be checking the links you put in the previous article as soon as i find some time.)

    However, in ruby, you probably would take advantage of regular expressions:

    print "enter line: "
    a = gets.downcase.scan(/[[:alpha:]]/).sort

    Or maybe closer to the Smalltalk example:

    print "enter line: "
    line = gets
    a = []
    line.split('').each do |char|
     char.downcase!
     a << char if char.is_alpha?
    end
    a.sort!

    And yet another way

    print "enter line: "
    chars = gets.downcase.sort.split('').select{|char| char.is_alpha?}

    Another thing I should point out, is that the support for both the "a"[0] method behavior and ?a syntax will be dropped by ruby 1.9 because #[] is not coherent with the way other classes implement #[] and ? is no method but syntax, so "a"[0] will simply return the first character (as a String) and not its ASCII value (that will become String#ord).

    I’m hoping for a new chapter soon, thanks for your efforts,

    ^ manveru

    • Ruby The Smalltalk Way #2 - A Question of Style
      25 September 2007, by Huw Collingbourne

      Thanks for the comment. I know what you mean about getting lost in the Smalltalk hierarchy. This can be a particular problem when you are working with classes that are logically related in your own project but are remote from one another in the class hierarchy. Zipping up and down the hierarchy tree to move from one to the other is not the ideal way to program (imho). Suffice to say, we have some ideas which we are trying out for (a future version of) Ruby In Steel which may get you the best of both worlds... ;-)

      As for more in my Smalltalk/Ruby series... there will be some when I have the free time to write it. At the moment, all my time is being spent on developing the next iteration of Ruby In Steel.

      I’ll get back to the Smalltalk series later, though...

      best wishes

      Huw

  • Ruby The Smalltalk Way #2 - A Question of Style
    17 May 2007, by Ramon Leon

    You’re being a bit verbose with your Smalltalk.

    s := Prompter prompt: ’enter line’.
    f := Bag new.
    s do: [ :c | c isLetter ifTrue: [f add: c asLowercase] ].
    ^f

    should be written more cleanly as a single expression

    ^((Prompter prompt: ’enter line’) select:[:c | c isLetter] thenCollect:[:c | c asLowercase]) asBag

    putting an #ifTrue: inside a #do: is almost always wrong, #do: is too low level most of the time.

    • Ruby The Smalltalk Way #2 - A Question of Style
      17 May 2007, by Huw

      Thanks. To clarify, however, the Smalltalk samples are all taken verbatim from the Smalltalk/V Tutorial which I am using as the ’course text’ for this series and the example you quote is provided ’as is’ (page 21 in the PDF file of the tutorial) with just a minor alteration for Dolphin Smalltalk compatibility.

      best wishes

      Huw

    • Ruby The Smalltalk Way #2 - A Question of Style
      30 September 2008, by Jaroslaw Zabiello

      I can say the same about Ruby example. This is more Ruby-way:

      s.each {|c| a << c.downcase if c.isLetter }

      • Ruby The Smalltalk Way #2 - A Question of Style
        30 September 2008, by Huw Collingbourne

        Not sure why the modified ’if’ is more Ruby-like. All a matter of personal preference, I think.

        best wishes

        Huw

  • Ruby The Smalltalk Way # 2 - A Question of Style
    16 May 2007, by josh susser

    I’ve been following your series and think it’s cool. I’m an old-time Smalltalker, did VM work at Xerox and used Smalltalk/V on the Mac some too. As a relative Ruby newbie, it’s always interesting to see comparisons.

    Two things to add to your post... One is to point out that Smalltalk’s model of everything is an object and all operations are message sends is a bit illusory in places. One of those is control flow. The illusion is that you are sending ifTrue: to a boolean object, but in a traditional Smalltalk implementation, the compiler cheats and produces some special control flow bytecodes that avoid message sending overhead. It does that for if and while messages, so if you try to create a subclass of Boolean (like for fuzzy logic) and override ifTrue: your new method will never run. That’s how it’s done in a blue book implementation, but I don’t remember exactly how Smalltalk/V handles it.

    The other thing is that in Smalltalk the default return value for methods is self, so unless you explicitly return a value, the method returns self.

    By the way, at some point you should mention Smalltalk’s cascaded messages and the #yourself method, and compare to Ruby 1.9’s Object#tap and the K combinator. Heh.

    • Ruby The Smalltalk Way # 2 - A Question of Style
      16 May 2007, by Huw

      Thanks for the ideas ;-)

      Yes, I plan to look at a few Smalltalk and Ruby ’cheats’ later on. As I mentioned (briefly) in the article above, the implementation details can vary quite a bit from one Smalltalk to another (compare Smalltalk/V’s ’pure’ Smalltalk version of isLetter with Dolphin Smalltalk’s "and then some magic happens" version which goes off and does something in a DLL).

      On a related subject, I just happened to be reading Avi Bryant’s blog (creator of Seaside, the Smalltalk alternative to Rails), which addresses "the ill-effects of implementing basic classes in C rather than in Ruby". Some interesting comments in that blog entry, I think. I plan to save my discussion of the implementation matters of Ruby/Smalltalk for a later date (to be honest, I’m still getting together my thoughts on the subject). Anyway, in the meantime, here’s the link to Avi’s blog entry.

      best wishes

      Huw

 

© 2009 SapphireSteel Software. All rights reserved