Euclid's Algorithm in Java, Smalltalk and Ruby
Somebody recently told me that they thought computer science was regarded as “a bit of a joke” when they were at university — so what would be an example of quality work taught as part of undergraduate Computer Science?
If I had to pick a single body of work it’d be John Tucker’s “Theory of Programming Languages Notes” or TPL for short. For quite some time now TPL has been a major part of the Swansea CS curriculum.
Several new chapters have been added over the last ten years but the section on Object-Oriented Programming caught my eye and made me wonder what illustrative example I’d use to represent Euclid’s Algorithm written in an object-oriented style.
Euclid’s Algorithm in Java
In the TPL notes we’re given a first attempt at Euclid’s algorithm in Java. Unfortunately, it’s not generally possible to implement Euclid’s algorithm in an object-oriented way in Java because Java doesn’t model integers as objects nor does it perform arithmetic or logic using object-oriented method dispatch.
The best we can do in Java using a plain int or java.lang.Integer is to wrap a conventional imperative implementation of the kind we’d write in ANSI C in a method.
public static int gcd(int x, int y) {
while (y != 0) {
int r = x % y;
x = y;
y = r;
}
return x;
}
It’s a program in an object-oriented language but it contains no actual object-oriented programming:
- It uses zero objects: The integers and the booleans involved are not objects.
- All the logic, arithmetic and flow control is performed by imperative, non-object oriented means.
- No methods are ever called. The static “method” is just a function in the imperative sense rather than a definition of a method that can be polymorphically dispatched. Conceptually this static “method” is no different from a function in Pascal.
Another attempt at object-orientation in Java
Object-oriented programs involve objects computing by sending messages to other objects, updating the internal (hidden) states of those objects as those messages are executed.
As the TPL notes point out, we could try to create a class called GCDInteger and use it to define a gcd(…) function:
public class GCDInteger extends Integer {
public Integer gcd(Integer a) {
int y = a.intValue();
int x = this.intValue();
while (y != 0) {
int r = x % y;
x = y;
y = r;
}
return new Integer(x);
}
}
This code demonstrates the most important object-oriented concept: work is done by sending messages to objects. Here we see the definition of a binary message .gcd(i) on a GCDInteger rather than a merely a static method with two arguments.
Although this is at least a tiny bit object-oriented it’s not even possible because Integer is a final class so we can’t just add that subclass.
Practical problems with this approach:
- Almost all the other Java code in the world will be using
Integer. Every piece of code that needs to use the GCD-enabled version of Integer would need to be recompiled and would acquire a dependency on our definition ofGCDInteger. - We’d like to add our method into the definition of an integer class. Unfortunately, the class java.lang.Integer isn’t really a representation of an integer. As the javadoc describes this class as something that creates objects that “wrap” an integer value. This means it defines an “integer holder” rather than integers themselves. For example it doesn’t implement arithmetic operations. There is no right class into which to place our method unless we want to define our own version of an integer class.
- There is a growing maintenance burden:
- of changing Integer code to use
GCDIntegeror of writing adapter code. - of deploying and managing dependencies on definitions of new kinds of Integer
- of changing Integer code to use
Note: It is possible to “cheat” at this by implementing Euclid’s algorithm using BigInteger — an arbitrary magnitude integer implementation. The BigInteger class allows the creation of objects that represent arbitrary magnitude integers with methods that implement basic arithmetic. This of course will carry a performance penalty and means longer, more complicated code.
Object-Oriented implementation in Smalltalk and Ruby
In order to demonstrate integer computation in an object-oriented way we need to use a language that performs arithmetic and logic using object-oriented method dispatch.
For example in Smalltalk we can simply add a new method to the existing Integer class.
greatestCommonDivisor: anInteger
|x y |
x := self.
y := anInteger.
[(y=0) not] whileTrue: [ |r|
r := self rem: y.
x := y.
y := r.]
^ x.
In Smalltalk this is executed as: 42 greatestCommonDivisor:2
There are several curious things about computing with integers and booleans in Smalltalk:
- whileTrue is merely a method defined on the True and False objects. (On True it runs the block and sends whileTrue with the block to the resultant value. On the False object, whileTrue does nothing.)
- The blocks (surrounded by square brackets) are both instantiated into objects of class BlockClosure which when evaluated produce a boolean-valued object — either True or False.
- Integers really are objects and new methods can be defined on Integer.
- There are many Integer classes, including arbitrary magnitude implementations. Normal arithmetic operations such as addition and multiplication are defined on the integer-related classes so that where an overflow would have occurred, the method returns the correct answer as an instance of a class that can represent the next-largest representation. (Usually using an OOP technique called “double dispatch”.)
- A pleasant side-effect of this is that functions can be defined on an abstract superclass of the integer implementations and so long as the function uses the standard arithmetic operations defined for all integer implementations it will ‘just work’ on numbers from any of the integer implementations. The Java implementation only works on the built-in,
inttype.
- A pleasant side-effect of this is that functions can be defined on an abstract superclass of the integer implementations and so long as the function uses the standard arithmetic operations defined for all integer implementations it will ‘just work’ on numbers from any of the integer implementations. The Java implementation only works on the built-in,
In the more conventional syntax of Ruby we can define:
class Fixnum
def gcd(y)
x=self
while y!=0 do
r = x%y;
x = y;
y = r;
end
return x
end
end
Unlike Smalltalk, ruby implements the while loop in the conventional, imperative manner. The other point to note is that we’re not defining the Fixnum class above, just “re-opening” it to add a new method definition.
The keyword return is optional in ruby but has been included for clarity here. Where return is not present the method returns the value of the last expression computed.
Classless Object-Oriented Programming
The Self language dispensed altogether with the notion of classes. Self is an experimental simplification of Smalltalk-80. Other more common languages to use classless object-oriented programming include Javascript and Ruby. This style of object-oriented programming is often referred to as Prototype-based Programming
Ruby is an interesting example because it takes a hybrid approach to object-oriented representation. Classes exist, but individual method definitions can be attached to objects at runtime. This leads to an interesting strategy for implementing gcd:
- Make the algorithm work by recursion instead of using a while loop.
- Implement a special case gcd implementation on object
0which always returns the parameter. - Implement a main case of the gcd implementation on the
Fixnumclass calling recursively on the object referred to asyin the ruby code above.
What you should be able to do in ruby:
class Fixnum
def gcd(i)
r = i % self
return r.gcd(self)
end
end
def 0.gcd(i)
return i
end
- Unfortunately, you can’t do this in Ruby because
Fixnumis a special case where methods can’t be attached directly to objects. - Aside: Even if we could solve the problem of attaching methods to
Fixnumobjects this would not be an efficient, practical solution in either the original Ruby implementation (MRI) or JRuby as neither supports tail recursion optimisation.
Rewriting this code in Self is beyond the scope of this post…
Quick recap of broken Java language concepts causing trouble here
- The concept of a static method — because Java has no class methods. These methods should just be methods implemented on the class rather than the instance.
- The concept of a constructor — because Java has no class methods. We should be able to write e.g.
ComplexNumber.new(5,2)where we simply ask a class method to create the new object. No special, “magic form of method” required. - Anonymous inner classes — what you could do instead of Smalltalk-style BlockClosures…
- Although int can be auto-box carried into java.lang.Integer exposure to both forms continue to exist, and the java.lang.Integer concept is a value holder rather than an object with arithmetic behaviour.
- We cannot dynamically add new methods onto existing classes for security reasons (not so much for efficiency reasons.)
Summary
- Object-orientated programming doesn’t necessarily resemble the imperative programming found in Pascal or ANSI C.
- As the implementations are revised through this post to be more object-oriented they come to resemble more the functional and logic programming implementations of gcd in the TPL notes. Perhaps this shouldn’t be surprising as Alan Kay (the inventor of Smalltalk) cited Lisp as an influence in the design of Smalltalk.
- It is amazing how such a small, simple example algorithm can highlight so many deficiencies in the support for object-oriented programming in Java… and illustrate so well most of my daily frustrations with Java over the last ten years.
- The popular object-oriented languages rarely seem to implement boolean logic or arithmetic in a genuinely object-oriented way using method dispatch.
- The main emphasis in Java is on security rather than on a powerful, elegant object system.