CSE341 Notes for Monday, 12/07/09

I began by pointing out several higher order functions that Ruby has. They are similar to what we have seen in ML and Scheme. There is a map function that expects a block specifying an operation to apply to each value in a structure:

        >> x = [1, 42, 7, 19, 8, 25, 12]
        => [1, 42, 7, 19, 8, 25, 12]
        >> x.map {|n| 2 * n}
        => [2, 84, 14, 38, 16, 50, 24]
There is a find function that expects a block that specifies a predicate:

        >> x.find {|n| n % 3 == 1}
        => 1
This version finds just the first occurrence. If you want to find them all, you can call find_all which is Ruby's version of filter:

        >> x.find_all {|n| n % 3 == 1}
        => [1, 7, 19, 25]
Ruby also has methods for determining whether every value satisfies a certain predicate and whether all values satisfy a certain predicate:

        >> x.any? {|n| n % 3 == 1}
        => true
        >> x.all? {|n| n % 3 == 1}
        => false
These are computatinal equivalents of the mathematical existential quantifier ("there exists") and universal quantifier ("for all").

Then I discussed the inject method. When you don't supply a parameter, it behaves like the reduce function in ML (collapsing a sequence of values into one value of the same type):

        >> [3, 5, 12].inject {|a, b| a + b}
        => 20
But you can also call it with a parameter, in which case it behaves like foldl:

        >> [3, 5, 12].inject("values:") {|a, b| a + " " + b.to_s}
        => "values: 3 5 12"
It's nice that Ruby has the inject function for other types as well like ranges:

        >> (1..20).inject("values:") {|a, b| a + " " + b.to_s}
        => "values: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"
Then I briefly discussed Ruby blocks as closures. Blocks in Ruby provide the two key elements of a closure:

They aren't true closures, though, because they can't be manipulated as first-class entities. We can't, for example, use a variable to store a block. But Ruby provides a mechanism for converting a block into an object using the "lambda" keyword:

        >> 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
I then spent some time discussing the object-oriented features of Ruby. I reminded people of the Point class from last week's section:

        class Point
          def initialize (x = 0, y = 0)
            @x = x
            @y = y
          end
        
          attr_reader :x, :y
          attr_writer :x, :y
        
          def to_s
            return "(#{@x}, #{@y})"
          end
        end
Fields like @x and @y are encapsulated, which basically makes them private. But Ruby has a different notion of "private" than Java does. In the case of fields, there isn't a convenient way to refer to the fields of another object, even an object of the same type. For example, we tried to write this distance function for the Point class:

        def distance(other)
          return Math.sqrt((@x - other.@x) ** 2 + (@y - other.@y) ** 2)
        end
Ruby rejected this as syntactically invalid. Although the fields are called @x and @y, we can't refer to other.@x and other.@y. We can only refer to fields of "self" in that manner. But we found that this version worked:

        def distance(other)
          return Math.sqrt((@x - other.x) ** 2 + (@y - other.y) ** 2)
        end
In this case, we are calling the "getter" function of the other Point object. So other.x and other.y are really function calls, as in other.x() and other.y(). Then I moved the attr_reader and attr_writer calls into a private section of the class:

        class Point
          ...
        
          private
          attr_reader :x, :y
          attr_writer :x, :y
        end
This broke the distance method. When we called it, we got an error message about trying to call a private method. In Java, private means private to the class. In Ruby, private means private to the object. In other words, the only way to call a private method is in the context of self calling it. Even other instances of the same class can't call a private method.

These are the two extremes. A public method can be called by anyone. A private method can only be called by self. There is a third option. We can declare a method to be protected, in which case it can be called by objects of the same class and objects whose type is a subclass of this type. The calls still have to appear in the class definition, so that clients of the class aren't able to call the method. So changing "private" to "protected" in the example above allowed us to have a functioning distance method without exposing the x and y getters and setters outside the class.

I then pointed out an interesting property of instance variables. I added this new method to the class that mentions an instance variable @z:

        def foo
          @z = 26
        end
When I then constructed a point, we saw that @z was not there:

        >> p = Point.new 3, 15
        => #<Point:0xb7b692e0 @y=15, @x=3>
        >> p.instance_variables
        => ["@y", "@x"]
But as soon as I called the foo method, the instance variable appears:

        >> p.foo
        => 26
        >> p
        => #<Point:0xb7b692e0 @z=26, @y=15, @x=3>
        >> p.instance_variables
        => ["@z", "@y", "@x"]
This is very different from Java. Java is statically typed, so before the program ever begins executing, we have to specify exactly what each object will look like. So when Java constructs an object, it makes all of the fields at the same time. But Ruby is far more dynamic. Instance variables are added to the object as they are encountered in executing methods of the class. The @x and @y instance variables are mentioned in the constructor, so they are allocated when the constructor is called. The @z instance variable is only mentioned in the foo method, so it is allocated only when we call foo.

I then showed a quick example of inheritance:

        class Point2 < Point
          def translate(dx, dy)
            self.x += dx
            self.y += dy
          end
        end
The notation "Point2 < Point" in the header indicates that Point2 extends (inherits from) Point. This subclass adds a translate method. By referring to "self.x" and "self.y", this code is calling the superclass getters and setters for x and y. This is the "right" way to go to preserve encapsulation. But Ruby would allow us to directly access the instance variables if we wanted to:

        class Point2 < Point
          def translate(dx, dy)
            @x += dx
            @y += dy
          end
        end
This wouldn't be allowed in Java where we tend to make instance variables private, but in Ruby the philosophy is that subclasses should have access to the superclass instance variables.

I pointed that the keyword "super" is used differently in Ruby than in Java. In Java we say things like "super.to_s" to refer to the superclass version of the to_s method. In Ruby, "super" refers to the superclass version of whatever method you are overriding. So if you are overriding "to_s", then "super" refers to the superclass version of the method. For example, we added this method to the Point class to have the to_s method add an exclamation point:

          def to_s
            super + "!"
          end
You can also override intialize while still calling its superclass version:

          def initialize(x, y, z)
            super(x, y)
            @z = z
          end
But then we lost our default values unless we add them back in:

          def initialize(x=0, y=0, z=0)
            super(x, y)
            @z = z
          end

Stuart Reges
Last modified: Fri Dec 11 10:00:19 PST 2009