# Natural Number Example (Version #2) # H. C. Cunningham # 13 September 2006 # This file defines a class hierarchy with base class Nat and subclasses # Zero, Succ, and Err. This cluster of classes defines a structural # representation for the "natural numbers" inspired by Peano's Postulates. # The implementation does not use any builtin integer type. We consider 0 as # being a natural number as is customary for many computer scientists. # Mathematically, we can define the set Nat (of natural numbers) as follows: # # x in Nat if and only if # (1) x = 0 -- zero # or (2) (Exists y : y in Nat :: x = Succ(y)) -- successor function # The design of the Nat hierarchy uses the Composite design pattern to # represent natural numbers. The "abstract" base class for the set is Nat. # Subclass Zero is a leaf case of the Composite pattern; it represents 0. # Subclass Succ is the composite case of the Composite pattern; it # represents the successor (i.e., add 1) to the single Nat value it # contains. Subclass Err, which represents errors, is a second leaf case of # the Composite pattern. # The designs and implementations of subclasses Zero and Err also reflect # the Singleton design pattern, which provides for exactly one instance of # each of those classes to be created. # In addition, subclass Err reflects the Null Object pattern, in that its # instances are null or inert objects that can be safely returned by # operations in error situations. (Of course, this is not a complete # solution for errors that arise in ordered comparison operations such as # is_less?.) # The Nat hierarchy essentially gives a unary representation for the # natural numbers; there is one Succ object in a linear structure for each # natural number value greater than 0. (This may be seen visually by # applying to_s to a Nat object.) # The implementation below redefines several Ruby methods from root class # Object: # # to_s is redefined to print the Nat structure in a parenthesized # notation, # eql? is redefined to compare for equal values # (assuming Singletons for Zero and Err) # == is redefined to be the same as eql? # clone is redefined to do deep copy of Nat structure # Note: The builtin definition of clone just creates copies for the # top-level object in a structure, not for any other objects referenced by # that object. That is, it does a shallow copy. # Caveats: This program is one of the author's first Ruby programs and was # constructed, in part, to enable him to learn the features the Ruby class # structure and how to adapt object-oriented concepts previously used in the # Java environment into Ruby. It may not, in all cases, reflect the best # Ruby programming style. Also, this class hierarchy does not implement all # the features that would be needed for a natural number abstract data type # in the Ruby environment. # Issues for future thought and work: # use of module Comparable mixins or other support for comparisons # connection of Nat to Integer or Numeric? # overriding of other default methods from Object # implement other arithmetic operators? ##### # Class Nat is the base class of our Natural numbers. It specifies the # operations that Nats must support. Additional operations should be # included in a fully functional implementation. # Because Ruby does not support abstract classes, we achieve a similar # result by causing an attempt to create objects of the class or call any of # the abstract methods to raise exception NotImplementedError. # Class Nat provides default implementations for some methods. Some of the # subclasses will need to override these to get the desired behaviors. For # example, clone is overridden in Succ to give the desired deep copy # functionality. class Nat def initialize; raise NotImplementedError; end # Deferred methods def add(x); raise NotImplementedError; end def sub(x); raise NotImplementedError; end def is_less?(x); raise NotImplementedError; end def to_i; raise NotImplementedError; end # Default concrete methods to redefine Object methods. # May be overridden when alternative functionality needed. def clone; self; end # by default, no copy def to_s; self.class.to_s; end # by default, return class name def ==(n); self.eql? n; end # by default, same as eql? # Class method to convert Fixnum to Nat def Nat.to_n(i) if i.instance_of? Fixnum if i > 0 Succ.new(to_n(i-1)) elsif i == 0 Zero.the_zero else Err.the_err end else Err.the_err end end end ##### # Subclass Zero represents the natural number 0. It is a leaf case of the # Composite design pattern. The class implements the Singleton design # pattern by (1) making method "new" private, (2) creating one object of the # class and storing its reference in class variable @@zero, and (3) # introducing a class method Zero.the_zero to enable clients of the class to # get the reference to the Singleton. class Zero < Nat # Disallow external calls of new, to implement Singleton. private_class_method :new @@zero = nil def initialize; end # Accessor for Singleton object Zero def Zero.the_zero @@zero = new if @@zero == nil @@zero end # Add Nat argument n: 0 + n = n; otherwise error def add(n) if n.kind_of? Nat n else Err.the_err end end # Subtract Nat argument n: 0 - n = 0 if n = 0; otherwise error def sub(n) if @@zero.eql? n # compare values; eql? overridden in subclasses @@zero else Err.the_err end end # Return Fixnum equivalent def to_i; 0; end # Determine whether receiver is smaller than Nat argument n; error if not # comparable def is_less?(n) if n.instance_of? Succ or Zero.the_zero.eql? n ! @@zero.eql? n # compare values; eql? overridden in subclasses else raise "Attempt to compare unordered objects" end end # Equal iff Nat argument n is the same object as Singleton Zero def eql?(n); @@zero.equal? n; end end ##### # Subclass Succ is the composite case of the Composite design pattern. It # has one attribute that is itself a Nat. An object of the class represents # the successor value (i.e., one greater) to the attribute. class Succ < Nat # Initialize reference to Nat for which this is successor; error if # argument n is not a Nat. def initialize(n) if n.instance_of? Succ or Zero.the_zero.eql? n @rest = n else raise "Attempt to create illegal Succ object" end end # Add Nat argument n to the receiver by recursively moving one layer of # Succ from the receiver to the argument on successive calls, stopping # when the receiver becomes 0. def add(n) if n.instance_of? Succ or Zero.the_zero.eql? n @rest.add(Succ.new(n)) else Err.the_err end end # Subtract Nat argument n from the receiver by recursively removing one # layer of Succ from both the receiver and the argument on successive # calls, stopping when one or other becomes zero. def sub(n) if n.instance_of? Succ @rest.sub(n.pred) elsif Zero.the_zero.eql? n self else Err.the_err end end # Redefine clone to do deep copy of Succ sequences (but not Zero or Err) def clone; Succ.new(@rest.clone); end # Return Fixnum equivalent def to_i; 1 + @rest.to_i; end # Convert Succ structure to String def to_s; self.class.to_s + "(" + @rest.to_s + ")"; end # Determine whether Nat argument n is smaller than the receiver by # recursively removing one layer of Succ from both the receiver and the # argument on successive calls, stopping when one or other becomes zero. def is_less?(n) if n.instance_of? Succ @rest.is_less? n.pred elsif Zero.the_zero.eql? n false else raise "Attempt to compare unordered objects" end end # Determine whether receiver and argument n have some number of nested Nats. def eql?(n) if n.instance_of? Succ @rest.eql? n.pred else false end end # Return predecessor of this Succ def pred; @rest; end end ##### # Subclass Err represents an error value. Like Zero, it is a leaf case of # the Composite design pattern that also implements the Singleton design # pattern. In addition, Nil implements the Null Object pattern in that it # (in most cases) represents an inert Nat object that can be returned in # most error situations. class Err < Nat # Disallow external calls of new, to implement Singleton. private_class_method :new @@err = nil def initialize; end # Accessor for Singleton object Zero def Err.the_err @@err = new if @@err == nil @@err end # If possible, do not do anything for any mutator operation def add(n); self; end def sub(n); self; end # There is no Fixnum equivalent, so return negative value. def to_i; -1; end def is_less?(n) raise "Attempt to compare unordered object Err" end # Comparing for equality is tricky because of possible Err nested inside # Succ structure. def eql?(n) if @@err.equal? n # compare for identity to Singleton Err object true elsif Zero.the_zero.eql? n false elsif n.instance_of? Succ self.eql? n.pred else false end end end ##### # Class method TestNat # This class does some testing of the Nat hierarchy methods. The testing is # not as complete as would be desirable. class TestNat # Call "TestNat.do_test" to do some tests of the classes defined in # this file. def TestNat.do_test # Bad data to use in tests bad = "BAD" puts "bad = \"#{bad}\"" big = 1000000000000 puts "big = #{big}" # Test restriction on instantiation of Nat objects begin print "Nat.new ==> "; puts Nat.new rescue NotImplementedError puts "Exception: " + $! end # Test Zero creation print "zero = Zero.the_zero ==> "; puts (zero = Zero.the_zero) print "zero.to_s ==> "; puts zero.to_s print "zero.inspect ==> "; puts zero.inspect # Test Err creation print "err = Err.the_err ==> "; puts (err = Err.the_err) print "err.to_s ==> "; puts err.to_s print "err.inspect ==> "; puts err.inspect # Test Succ creation print "three = Succ.new(Succ.new(Succ.new(zero))) ==> "; puts (three = Succ.new(Succ.new(Succ.new(zero)))) print "three.to_s ==> "; puts three puts "three.inspect ==> "; puts three.inspect # Test conversion from Fixnum to Nat print "Nat.to_n(0) ==> "; puts Nat.to_n(0) print "Nat.to_n(5) ==> "; puts Nat.to_n(5) print "Nat.to_n(-1) ==> "; puts Nat.to_n(-1) print "Nat.to_n(bad) ==> "; puts Nat.to_n(bad) print "Nat.to_n(big) ==> "; puts Nat.to_n(big) # Test Zero methods print "zero.add(zero) ==> "; puts zero.add(zero) print "zero.add(three) ==> "; puts zero.add(three) print "zero.add(err) ==> "; puts zero.add(err) print "zero.add(bad) ==> "; puts zero.add(bad) print "zero.sub(zero) ==> "; puts zero.sub(zero) print "zero.sub(three) ==> "; puts zero.sub(three) print "zero.sub(err) ==> "; puts zero.sub(err) print "zero.sub(bad) ==> "; puts zero.sub(bad) print "zero.eql? zero ==> "; puts (zero.eql? zero) print "zero.eql? three ==> "; puts (zero.eql? three) print "zero.eql? err ==> "; puts (zero.eql? err) print "zero.eql? bad ==> "; puts (zero.eql? bad) print "zero == zero ==> "; puts (zero == zero) print "zero == three ==> "; puts (zero == three) print "zero == err ==> "; puts (zero == err) print "zero == bad ==> "; puts (zero.eql? bad) print "zero.is_less? zero ==> "; puts (zero.is_less? zero) print "zero.is_less? three ==> "; puts (zero.is_less? three) begin print "zero.is_less? err ==> "; puts (zero.is_less? err) rescue puts "Exception: " + $! end begin print "zero.is_less? bad ==> "; puts (zero.is_less? bad) rescue puts "Exception: " + $! end print "zero.to_i ==> "; puts zero.to_i print "zero.clone ==> "; puts zero.clone.inspect # Test Succ methods print "three.add(zero) ==> "; puts three.add(zero) print "six = three.add(three) ==> "; puts (six = three.add(three)) print "six.to_s ==> "; puts six.to_s print "three.add(err) ==> "; puts three.add(err) print "three.add(bad) ==> "; puts three.add(bad) print "three.sub(zero) ==> "; puts three.sub(zero) print "three.sub(three) ==> "; puts three.sub(three) print "three.sub(six) ==> "; puts three.sub(six) print "three.sub(err) ==> "; puts three.sub(err) print "three.sub(bad) ==> "; puts three.sub(bad) print "three.eql? zero ==> "; puts (three.eql? zero) print "three.eql? three ==> "; puts (three.eql? three) print "three.eql? six ==> "; puts (three.eql? six) print "three.eql? err ==> "; puts (three.eql? err) print "three.eql? bad ==> "; puts (three.eql? bad) print "three == zero ==> "; puts (three == zero) print "three == three ==> "; puts (three == three) print "three == six ==> "; puts (three == six) print "three == err ==> "; puts (three == err) print "three == bad ==> "; puts (three == bad) print "three.is_less? zero ==> "; puts (three.is_less? zero) print "three.is_less? three ==> "; puts (three.is_less? three) print "three.is_less? six ==> "; puts (three.is_less? six) begin print "three.is_less? err ==> "; puts (three.is_less? err) rescue puts "Exception: " + $! end begin print "three.is_less? bad ==> "; puts (three.is_less? bad) rescue puts "Exception: " + $! end print "three.to_i ==> "; puts three.to_i print "three.pred ==> "; puts three.pred puts "three.clone ==> "; puts three.clone.inspect print "three.clone.equal? three ==> "; puts (three.clone.equal? three) # Test Err methods print "err.add(zero) ==> "; puts err.add(zero) print "err.add(three) ==> "; puts err.add(three) print "err.add(err) ==> "; puts err.add(err) print "err.add(bad) ==> "; puts err.add(bad) print "err.sub(zero) ==> "; puts err.sub(zero) print "err.sub(three) ==> "; puts err.sub(three) print "err.sub(err) ==> "; puts err.sub(err) print "err.sub(bad) ==> "; puts err.sub(bad) print "err.eql? zero ==> "; puts (err.eql? zero) print "err.eql? three ==> "; puts (err.eql? three) print "err.eql? err ==> "; puts (err.eql? err) print "err.eql? bad ==> "; puts (err.eql? bad) print "err == zero ==> "; puts (err == zero) print "err == three ==> "; puts (err == three) print "err == err ==> "; puts (err == err) print "err == bad ==> "; puts (err == bad) begin print "err.is_less? three ==> "; puts (err.is_less? three) rescue puts "Exception: " + $! end print "err.to_i ==> "; puts err.to_i print "err.clone ==> "; puts err.clone.inspect r = "esrever".reverse; s = "deksafiesiarpeerf"; puts "\"#{s}\".#{r}" end end