CS 214: Programming Languages
Spring 2009

Home|Syllabus|Schedule
<<|>>

Open Classes
Ruby, Iteration 4

Set Up

Create a new folder ruby/factorial. Create a file factorial.rb and factorial_test.rb. Use the template below for factorial_test.rb.

factorial_test.rb

require 'test/unit'
require 'factorial'

class FactorialTest < Test::Unit::TestCase
end

Run Ruby tests like so:

unix-%  ruby factorial_test.rb

Testing Factorial

The name of test methods in Ruby must begin with test_. Inside, use assert_equal to make assertions:

assert_equal   1, 0.factorial
assert_equal 720, 6.factorial

Write a test_factorial in FactorialTest (the class). Put in the assertions above and one for 100!. Run the tests, and red bar.

Open Classes

The computations above may seem odd to you. Ruby is one of a few languages that allows you to apply methods to seemingly primitive data like an int. This is what makes Ruby (like Smalltalk) a pure object-oriented language—everything is an object and can have a method applied to it. (In C++ you'd have to write factorial as a stand-alone function; in Java, it'd have to be a static (class) method.)

However, the red bar is due to the fact that factorial isn't defined. So I can I expect you to ever complete this assignment? (Surprise! You're going to re-implement Ruby!)

Ruby has open classes. Just define a new method for a class by reopening it. The syntax for reopening a class is exactly the same as defining it for the first time:

class Integer
  def factorial
    1
  end
end

Add this definition to factorial.rb. Run, and red bar.

The red bar should complain about the second assertion.

First you need a base case: 0! = 1. Testing for zero is pretty easy: zero?, but what do you apply it to? In other languages, you pass in the integer explicitly to the factorial function. But now factorial is being applied (i.e., sent as a message) to an implicit integer instance.

The name of this instance is self, so you can write self.zero? to see if the Integer instance is zero.

Add an if to factorial so that the 0! and 6! cases pass (by hardcoding the number for the else case). Red bar.

It's 100! that should be complaining.

Actually, self.zero? can be written just zero?. Operators require an explicit self.

The recursive case is a recursive call and two arithmetic operators. You need to refer to the current instance again with the arithmetic operators, and self works in these situations too.

Fix factorial. Green bar.

Sub- and Superclasses

Truth be told, 6 is not really an Integer. It's really a Fixnum. Integer is the superclass of Fixnum. By reopening Integer, you end up defining factorial for both Fixnums and Bignums. This is why you can compute 100!.

However, what if you wanted to compute the factorial of other numeric types, like floating-point and complex numbers (Float and Complex, respectively)? Put factorial is a higher superclass: Numeric!

It is worth asking yourself if this is really a good idea. Floating-point numbers that aren't whole numbers and complex number with an non-zero imaginary component will not trigger the factorial base case!

Things for You to Do

The new methods you need to write are defined in the Wikipedia's factorial entry.

  1. Write tests and code for Integer#double_factorial.
  2. Write tests and code for Integer#quadruple_factorial.
  3. Write tests and code for Integer#super_factorial.