Ruby gives us a solution that is simple and efficient. We had developed this code in section for a binary search tree
class Tree
def initialize()
@overallRoot = nil
end
def insert(v)
@overallRoot = insert_helper(v, @overallRoot)
end
def print()
print_helper(@overallRoot)
end
private # beginning of private definitions
class Node
def initialize(data = nil, left = nil, right = nil)
@data = data
@left = left
@right = right
end
attr_reader :data, :left, :right
attr_writer :data, :left, :right
end
def insert_helper(v, root)
if root == nil
root = Node.new(v)
elsif v < root.data then
root.left = insert_helper(v, root.left)
else
root.right = insert_helper(v, root.right)
end
return root
end
def print_helper(root)
if root != nil then
print_helper root.left
puts root.data
print_helper root.right
end
end
end
We can define an iterator by implementing the each method that looks a lot like
the current print and print_helper methods. The big difference is that instead
of calling puts to print values, they will call yield to generate values:
class Tree
...
def each
inorder_helper(@overallRoot) {|n| yield n}
end
private
def inorder_helper(root)
if root then
inorder_helper(root.left) {|n| yield n}
yield root.data
inorder_helper(root.right) {|n| yield n}
end
end
...
end
Given this method, we can call it with a block. In fact, print can now be
redefined as a foreach loop:
def print()
for n in self
puts n
end
end
We loaded this new version into irb and tested it out. First we create a tree
and inserted 25 random values:
>> t = Tree.new
=> #<Tree:0xb7eb0a5c @overallRoot=nil>
>> 25.times{t.insert(rand(100))}
=> 25
We found that print still worked just fine:
>> t.print
2
2
15
16
19
23
23
32
38
42
43
47
51
61
64
68
70
73
77
79
80
83
88
90
96
=> nil
But now we could specify variants of print by using the inorder iterator, like
printing each number doubled:
>> t.each {|n| puts 2 * n}
4
4
30
32
38
46
46
64
76
84
86
94
102
122
128
136
140
146
154
158
160
166
176
180
192
=> nil
We were also able to use the iterator to find the sum of the numbers:
>> sum = 0
=> 0
>> t.each {|n| sum += n}
=> nil
>> sum
=> 1282
I pointed out that not only was this iterator fairly easy to define, it is also
highly efficient. We would describe it as lazy in the sense that
it doesn't compute a value until it needs it. For example, we reset the sum to
be 0 and wrote this variant that breaks out of the computation as soon as the
sum becomes greater than 100:
t.each do |n|
puts n
sum += n
break if sum > 100
end
When we ran it, it produced this output:
2
2
15
16
19
23
23
32
We found that it had set sum to 132 and then stopped. As we noted earlier, one
approach is to precompute the entire traversal before it begins. For a
computation like the one above that breaks out early, that would be very
expensive.Then I finished our discussion of OO in Ruby. I pointed out an interesting behavior. I redefined the each method in the Range class to return twice the normal value:
class Range2 < Range
def each
super {|n| yield 2 * n}
end
end
This produced the expected behavior that a foreach loop now produces doubled
values when you use a Range2 object:
>> x = Range2.new(1, 10)
=> 1..10
>> y = 1..10
=> 1..10
>> for n in x do puts n end
2
4
6
8
10
12
14
16
18
20
=> 1..10
>> for n in y do puts n end
1
2
3
4
5
6
7
8
9
10
=> 1..10
But we also noticed that now map behaves differently:
>> x.map
=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
>> y.map
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
as does the any? member function:
>> x.any? {|n| n % 2 == 1}
=> false
>> y.any? {|n| n % 2 == 1}
=> true
What's going on? Obviously the map and any? functions are calling each. This
is an important aspect of OO design that we didn't have time to discuss in
CSE143. In CSE143 we talked about how to design your code for a client who
will call your methods. We didn't talk about how to design your code for a
client who will extend your class through inheritance.I then talked about how this is implemented. How would the Range object's map function "know" to call the new version of "each"? We've spent so much time getting students to understand this notion in Java, that perhaps it seems obvious. This ability to override a method is what we refer to as polymorphism, dynamic dispatch, late binding, runtime binding, etc.
But what is the mechanism? In Java and Ruby the model is that each method has an additional unstated parameter. In Java we refer to it as "this". In Ruby we refer to it as "self". By knowing which object is calling a method, we can at runtime figure out which method to call. This extra parameter is often referred to as the "implicit parameter."
I then pointed out an odd detail of Ruby. Because parentheses are optional, it is possible to interpret a simple identifier as either a function call or a local variable. For example, given this code:
def f
18
end
def g
puts f + 3
end
The reference to f in g is interpreted as a call on the function f. So in the
interpreter we get:
>> g
21
=> nil
But consider this variation of g:
def g
puts f + 3
f = 7
puts f + 3
end
In this case, the first occurrence of f in g is interpreted as a function call,
but the second occurrence is interpreted as a local variable:
>> g
21
10
=> nil
The Ruby rule is that a local variable shadows a function.Then I briefly discussed Ruby blocks as closures. Blocks in Ruby provide the two key elements of a closure:
>> lambda {|n| 2 * n}
=> #<Proc:0xb76aad08@(irb):1>
This expression is constructing an object of type Proc, as we can see below:
>> x = lambda {|n| 2 * n}
=> #<Proc:0xb76a441c@(irb):2>
>> x.class
=> Proc
Once constructed, you can invoke the block using the "call" member function:
>> x.call 13
=> 26
>> x.call 25
=> 50
We then discussed what are known as mixins. This is one of the most
interesting features of Ruby.Before looking at Ruby mixins, I spent a few minutes discussing Java's inheritance model. I asked people what you get when class B extends class A in Java. The answer is that you get two different things:
C++ is an interesting contrast. C++ supports multiple inheritance. With multiple inheritance, you can get multiple code reuse relationships. But it turns out that multiple inheritance is rather messy. For example, Arthur Riel in his book Object-Oriented Design Heuristics includes as item 54:
54. If you have an example of multiple inheritance in your design, assume you have made a mistake and then prove otherwise.C++ also has a notion of private inheritance where you have code reuse but no subtype relationship.
Ruby offers something in between. It has single inheritance, just as Java does. Subtyping doesn't matter in Ruby because it uses duck typing (Ruby doesn't care what kind of duck you are as long as you can quack in an appropriate manner when asked to do so). So the only issue in Ruby is code resuse. We've seen that inheritance of classes is similar in Ruby to what we saw in Java. Mixins offer an alternative. You can define a mixin by define a module and including a set of methods. For example, I wrote the following mixin that defines two methods that allow sequences to be stuttered:
module Stutterable
def stutter
result = []
for n in self
result.push n
result.push n
end
result
end
def stutter_each
for n in self
yield n
yield n
end
end
end
You use the word "module" instead of "class". Once you have defined this
module, you can include it in classes by saying:
include Stutterable
It is almost as if the actual code from the module is included. For example,
we went into the interpreter and added this code to the Array class:
>> class Array
>> include Stutterable
>> end
=> Array
>> x = [1, 2, 3]
=> [1, 2, 3]
>> x.stutter
=> [1, 1, 2, 2, 3, 3]
>> x.stutter_each {|n| puts n}
1
1
2
2
3
3
=> [1, 2, 3]
and we added it to the Range class:
>> class Range
>> include Stutterable
>> end
=> Range
>> x = (1..5)
=> 1..5
>> x.stutter
=> [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
>> x.stutter_each {|n| puts n}
1
1
2
2
3
3
4
4
5
5
=> 1..5
I mentioned that the two most common mixins are Comparable and Enumerable. For
example, we modified the Point class to implement a method called <=>, which is
the Ruby equivalent of the java compareTo method. We had it find which point
is closer to the origin. To make this more efficient, we introduced a class
variable called @@origin. The double at-sign indicates that it's a class
variable versus an instance variable (i.e., one shared value for the entire
class, like a static field in Java):
class Point
include Comparable
def initialize (x = 0, y = 0)
@x = x
@y = y
end
attr_reader :x, :y
attr_writer :x, :y
def to_s
"(#{@x}, #{@y})"
end
def distance(other)
return Math.sqrt((x - other.x) ** 2 + (y - other.y) ** 2)
end
@@origin = Point.new
def <=> other
return distance(@@origin) - other.distance(@@origin)
end
end
What the mixin gets us is five extra methods built on top of the <=> method.
For example, now we can say:
>> p1 = Point.new(3, 5)
=> #<Point:0xb8052298 @y=5, @x=3>
>> p2 = Point.new(5, 3.1)
=> #<Point:0xb804e2b0 @y=3.1, @x=5>
>> p1 < p2
=> true
>> p1 <= p2
=> true
>> p1 > p2
=> false
>> p1 >= p2
=> false
So this is an example of code reuse without using the inheritance mechanism.
Instead, we have defined five methods in terms of another method. This is a
very convenient way to be able to build up new functionality.The other common Ruby mixin in Enumerable. It defines a series of methods built on top of the each method. Remember that we defined a MyRange class with an each method:
class MyRange
def initialize(first, last)
@first = first
@last = last
end
def each
i = @first
while i <= @last
yield i
i += 1
end
end
end
By including the Enumerable mixin, we get 21 new methods. I demonstrated it
this way in the interpreter:
>> x = MyRange.new(1, 10)
=> #<MyRange:0xb7f7ccec @last=10, @first=1>
>> m1 = x.methods
=> ["methods", "respond_to?", "dup", "instance_variables", "__id__", "eql?",
"object_id", "id", "singleton_methods", "send", "taint", "frozen?",
"instance_variable_get", "__send__", "instance_of?", "to_a", "type",
"protected_methods", "instance_eval", "display", "instance_variable_set",
"kind_of?", "extend", "to_s", "each", "class", "hash", "tainted?", "==",
"private_methods", "===", "nil?", "untaint", "is_a?", "inspect", "method",
"clone", "=~", "public_methods", "instance_variable_defined?", "equal?",
"freeze"]
>> class MyRange
>> include Enumerable
>> end
=> MyRange
>> m2 = x.methods
=> ["reject", "methods", "respond_to?", "dup", "instance_variables", "member?",
"__id__", "eql?", "object_id", "find", "each_with_index", "id",
"singleton_methods", "send", "collect", "all?", "entries", "taint", "include?",
"frozen?", "instance_variable_get", "__send__", "instance_of?", "detect",
"to_a", "zip", "type", "map", "protected_methods", "instance_eval", "any?",
"display", "sort", "min", "instance_variable_set", "kind_of?", "extend",
"find_all", "to_s", "each", "class", "hash", "tainted?", "==",
"private_methods", "inject", "===", "sort_by", "nil?", "untaint", "max",
"is_a?", "select", "inspect", "method", "clone", "=~", "partition",
"public_methods", "instance_variable_defined?", "grep", "equal?", "freeze"]
>> m1.length
=> 42
>> m2.length
=> 63
>> m2.length - m1.length
=> 21
And we can see the list of the actual methods by saying:
>> m2 - m1
=> ["reject", "member?", "find", "each_with_index", "collect", "all?",
"entries", "include?", "detect", "zip", "map", "any?", "sort", "min",
"find_all", "inject", "sort_by", "max", "select", "partition", "grep"]
So by defining a single method (each) and using the mixin, we can get 21 very
useful methods. This is certainly easier from a programming point of view and
it is easier to create consistency across different types of objects when they
all refer to the same mixin.Finally, we looked at a few more reflection features of Ruby. I pointed out that you can ask an object for one of its methods:
m = 3.method(:+) double = 2.method(:*)Once defined, we can use the function call to call the method:
>> m.call 5
=> 8
>> double.call 18
=> 36
You can also selectively send a message to an object:
>> 3.send(:+, 8)
=> 11
This may seem like an odd thing to do, because we could just as easily have
said "3 + 8". But this allows us to use an expression inside the call on
send. For example, we created a list of symbols and used it to call various
methods of the object 25:
>> x = [:+, :*, :-, :/, :**, :%, :==]
=> [:+, :*, :-, :/, :**, :%, :==]
>> 25.send(x[rand(x.length)], 3)
=> 28
>> 25.send(x[rand(x.length)], 3)
=> 15625
>> 25.send(x[rand(x.length)], 3)
=> 1
>> 25.send(x[rand(x.length)], 3)
=> false
>> 25.send(x[rand(x.length)], 3)
=> 22
I also pointed out that Ruby has an eval function:
>> eval("2 + 2")
=> 4
It is fairly powerful. For example, you can define local variables using
it:
>> z
NameError: undefined local variable or method `z' for main:Object
from (irb):55
>> eval("z = 13")
=> 13
>> z
=> 13
So you can construct a string with code to be executed and simply eval it.