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 Fixnum
s and
Bignum
s. 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.
- Write tests and code for
Integer#double_factorial
. - Write tests and code for
Integer#quadruple_factorial
. - Write tests and code for
Integer#super_factorial
.