class Range2 < Range def each super {|n| yield 2 * n} end endThis 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..10But 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} => trueWhat'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 pointed out that you can explore this yourself in Ruby by displaying the value of self at various points in program execution:
>> puts self main => nil >> 3.times {puts self} main main main => 3In the interpreter, we are making definitions for a special object known as main. I asked why self is listed as "main" in the block for 3.times. Why isn't it 3? The answer is that to form a proper closure, Ruby has to capture the value of self at the point where the block is defined. For example, we might define a method in that outer context that we want to call in the block:
>> def f(n) >> 2 * n >> end => nil >> 3.times {|n| puts f(n)} 0 2 4 => 3To have access to the methods defined in the context surrounding the block, we have to keep track of what "self" was bound to at that point in execution.
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 endThe reference to f in g is interpreted as a call on the function f. So in the interpreter we get:
>> g 21 => nilBut consider this variation of g:
def g puts f + 3 f = 7 puts f + 3 endIn 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 => nilThe Ruby rule is that a local variable shadows a function.
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 of 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 endYou use the word "module" instead of "class". Once you have define this module, you can include it in classes by saying:
include StutterableIt 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..5I 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 endWhat 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 => falseSo 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 endBy 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 => 21And 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 => 36You can also selectively send a message to an object:
>> 3.send(:+, 8) => 11This 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)], 5) => 20 >> 25.send(x[rand(x.length)], 5) => 30 >> 25.send(x[rand(x.length)], 5) => 9765625 >> 25.send(x[rand(x.length)], 5) => 5 >> 25.send(x[rand(x.length)], 5) => 9765625 >> 25.send(x[rand(x.length)], 5) => 125I also pointed out that Ruby has an eval function:
>> eval("2 + 2") => 4It 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 => 13So you can construct a string with code to be executed and simply eval it.