Ruby

Ruby Crowned Kinglet

Ruby is a computer programming language with style and charm. Programming in Ruby is like being hugged. This chap on the left is a ruby-crowned kinglet, one of Ruby's trial mascots (the other being some anime bint). I think it's pretty cute, but I can't get over the desire to pluck and barbecue it. Y'see, in Australia we have this barbecue chicken take away joint called Red Rooster and their logo...

The following is an introduction to Ruby I wrote in third year university for my fellow students because we were learning Python. It's had a few updates since then.

Ruby Rant

Why do I think Ruby is neato?

Blocks

Ruby has a beautiful and unified way of dealing with closures, iterators, callbacks and a number of other miscellanous tasks. Smalltalk had it right. Unifying these things means I don't have to remember all the different ways to do these things.

To explain, in Ruby, if you include a piece of code surrounded by curly braces after a method, that piece of code will be passed to the method as a 'block' (of code). The method can call the block at any time, passing any number of parameters to it.

By identifying and formalising this mechanism in the language, a number of common tasks which can be performed using blocks suddenly have a uniform syntax. Not only that, by allowing the programmer to write custom methods which are able to use blocks, the programmer can refactor program flow into methods. I cannot express to you the geeky joy of putting a complex piece of behaviour into a convenient piece of code which is easily called upon later to perform that behaviour.

Iteration

Let me give you an example. The following code (deep breath) implements the textbook definitions of inorder, preorder and postorder traversal of trees of arbitrary order. Each method is a few lines of code, and clearly labels that code's behaviour with its name.

1: # Tree supporting various kinds of traversal.
2: class Tree
3:     def initialize(value, *children)
4:         @value, @children = value, children
5:     end
6: 
7:     # Inorder: C1, V, C2, C3, ..., Cn
8:     def traverse_inorder(&block)
9:         first = @children[0]
10:         rest = (@children[1..@children.size] or [])
11: 
12:         first.traverse_inorder(&block) if first
13:         yield @value
14:         for c in rest
15:             c.traverse_inorder(&block)
16:         end
17:     end
18: 
19:     # Preorder: V, C1, C2, ..., Cn
20:     def traverse_preorder(&block)
21:         yield @value
22:         for c in @children
23:             c.traverse_preorder(&block)
24:         end
25:     end
26: 
27:     # Postorder: C1, C2, ..., Cn, V
28:     def traverse_postorder(&block)
29:         for c in @children
30:             c.traverse_postorder(&block)
31:         end
32:         yield @value
33:     end
34: end
35: 
36: # Example with a binary tree:
37: #
38: #     4
39: #    / \
40: #   2   5
41: #  / \
42: # 1   3
43: 
44: tree = Tree.new(4, Tree.new(2, Tree.new(1), Tree.new(3)), Tree.new(5))
45: 
46: values = []
47: tree.traverse_inorder do |value|
48:     values << value
49: end
50: puts "Inorder  : " + values.join(", ")
51: 
52: values = []
53: tree.traverse_preorder do |value|
54:     values << value
55: end
56: puts "Preorder : " + values.join(", ")
57: 
58: values = []
59: tree.traverse_postorder do |value|
60:     values << value
61: end
62: puts "Postorder: " + values.join(", ")
63: 
64: # Program output
65: #
66: # Inorder  : 1, 2, 3, 4, 5
67: # Preorder : 4, 2, 1, 3, 5
68: # Postorder: 1, 3, 2, 5, 4

Download the above code

Pure OO / Unified Type Model

Some people don't see the benefit of this. It doesn't mean we have to be like Java with a zillion heavily documented classes for every tiny thing under the Sun (joke!) and objects flying everywhere because this OO stuff is a new paradigm so we'd better do things differently.

No. It just means I can forget the difference between types and classes and will never be forced to uglify my code with conversions between the two. It means I can use polymorphism without fear. It means I can clone objects without tricks and traps. It means I can always inherit from library classes. It means a lot of little things which, to me, add up to a whole lot of ease.

Simple Syntax

Python. Variables prefixed with 'self.' are member variables and you declare global ones with the 'global' keyword.

Ruby. Variables prefixed with '@' are member variables and ones prefixed with '$' are global. Everything else has local scope.

Python enthusiasts hate this, because 'self.' apparently makes it more obvious that the variable is a member. And it does... if you're used to Python.

I appreciate code readabilty as much as the next person, but it's hardly an effort to remember '@' means 'member' instead of remembering that 'self.' means 'member'. This also avoids the need to declare variables, whereas you still need to declare 'global' variables sometimes in Python.

Ruby's syntax for class inheritance is thus: class MyArray < Array Again, uses a symbol, but isn't difficult.

Data Hiding

I can make member methods or data private, so I can model OO design patterns if I want to. People are writing implemenations of design by contract in Ruby, for example. It's nice to know that nobody's going to come along and change your object's internal state without you knowing about it.

Customisability

At any time I can add/modify/delete methods from a class. Even a library class. If I don't like Array's include? method because I'm a Java programmer, I can change it to has. If I want to be able to pass an array of keys to a hash (dictionary) and receive an array of sorted values, I can add this method to Hash. I don't need to worry about where to put it.

1: # Add a method to library class 'Hash' for our own nefarious purposes.
2: class Hash
3:     # Pass an array of keys to retrieve an array of values.
4:     # Simpler method definiton supplied by Gavin Sinclair
5:     # (gsinclair@soyabean.com.au).
6:     def sorted_values(*keys)
7:         return indices(*keys).compact.sort
8:     end
9: end
10: 
11: myhash = {1=>'A', 2=>'B', 3=>'C'}
12: 
13: p myhash.sorted_values(3, 1, 2)
14: 
15: # Program output
16: #
17: # ["A", "B", "C"]

Download the above code

Pseudocode Look and Feel

I appreciate that Python tries to make it so that there is 'only one way to write a piece of code', but frankly the whole indentation issue leaves me always wondering where my statements belong. After two if statements and a loop have ended, I'm always confused. Maybe I'm just stupid. Ruby uses the keyword 'end' to end blocks and avoid all related confusion. This also has the benefit that if I don't like the way somebody has formatted a file, I can highlight the entire thing and tell my text editor to re-indent it all, without fear of breaking it.

Ruby's philosophy is that if you write a piece of Ruby code, it should run. What you think is right, is what Ruby takes as what should be right. That's why Ruby often looks like pseudocode. There aren't many conventions you have to pick up before your pseudocode starts looking like Ruby. *grin*

A lot of Ruby coders are amazed at how they can hack together something like they used to do in Perl, have it run first time, and yet be able to read and understand it the next day.

Operator overloading

Syntactic sugar, but it means I can make my own objects behave correctly when I add/multiply/subtract/divide/compare/cast or print them. Can improve code readability a great deal for some tasks.

1: class Vector < Array
2:     def initialize(*values)
3:         push(*values)
4:     end
5: 
6:     # Inner product.
7:     def *(other)
8:         if size == other.size
9:             pos = 0
10:             result = 0
11:             for v in self
12:                 result += v*other[pos] if other[pos]
13:                 pos += 1
14:             end
15:             return result
16:         else
17:             raise "Attempting to find inner product of \
18:                    two vectors of different dimensions"
19:         end
20:     end
21: 
22:     def inspect
23:         return "Vector:" + super
24:     end
25: 
26:     def to_s
27:         return "<" + join(", ") + ">"
28:     end
29: end
30: 
31: v1 = Vector.new(0, 1, 2)
32: v2 = Vector.new(4, 5, 6)
33: p v1
34: p v2
35: puts "%s.%s = %d" % [v1, v2, v1 * v2]
36: 
37: # Program output
38: #
39: # Vector:[0, 1, 2]
40: # Vector:[4, 5, 6]
41: # <0, 1, 2>.<4, 5, 6> = 17

Download the above code

Language Comparisons by Others

Here are some documents comparing the two languages:

http://www.mindview.net/Etc/FAQ.html#Ruby

A typically petty and biased opinion by Java/C++/Python nut Bruce Eckel. How disappointing. I can dispute every one of his vague arguments.

http://www.ruby-lang.org/en/compar.html

The opposite view. Ruby-biased view of Python. Lists the major differences in dot form and isn't as petty as Mr. Eckel, although still biased.

http://mail.python.org/pipermail/python-list/1999-October/014811.html

Discussion on the Python mailing list of Python's problems in light of Ruby.

http://www.ruby-lang.org/en/testimony.html

If you're not convinced... get convinced! Read these and be happy.

Ruby Downloads

Ruby Interpreter

The latest version of Ruby (for UNIX systems) can be downloaded from http://www.ruby-lang.org.

There's also a Windows version.

Ruby Applications and Libraries

The Ruby Application Archive is a big list of applications and libraries written with Ruby.

Emacs Mode

The file you'll need for syntax highlighting and indentation while editing Ruby code in Emacs/XEmacs is called ruby-mode.el.

To install it, put it somewhere convenient, we'll call that directory X. Now open up the file in Emacs/XEmacs and select 'Byte-compile This File' from the 'Emacs-Lisp' menu. Next edit your .emacs file (or .xemacs/init.el for newer versions of XEmacs) and insert the following at the end:

(autoload 'ruby-mode "ruby-mode" "Major mode for editing ruby scripts." t)
(setq auto-mode-alist (cons '("\\.rb$" . ruby-mode) auto-mode-alist))
(setq interpreter-mode-alist (append '(("ruby" . ruby-mode)) interpreter-mode-alist))
(setq load-path (append load-path '("X")))
				

Replace 'X' in the above code with the directory where you put ruby-mode.el. For example, if I put it in a subdirectory from my login directory called ".elisp", I would replace X with ~/.elisp. Newer versions of XEmacs likes to put these kind of user-added files in ~/.xemacs, so you might consider putting it there.

Setting Up Ruby on UNSW CSE Computers

Ruby isn't installed on the CSE computers by default, however it is installed on ~stulocal, which is exceptionally easy to set up.

If you use tcsh, firstly, you rock, and secondly, ignore the instructions on the web site and put this in your .login file and re-login:

source ~stulocal/bin/setup-env.csh

If you use bash, put this in your .profile file and re-login (yes, that's a period at the front):

. ~stulocal/bin/setup-env.sh

SSDI Lab Solutions in Ruby

I haven't included test cases for these labs, which is half of the marks. There is a simple reason for this: I don't have that much time on my hands. ;) There is a Ruby package called runit which is a unit testing framework, equivalent to Python's unittest module. It's used by a lot of Ruby packages for testing, and if you're really so enthusiastic, I encourage you to check out some examples.

lab1.rb lab2.rb
overload.rb
lab3.rb
overload.rb
lab4.rb lab5.rb (Labs after this are in C++)