A Rational number class in Ruby

As is my custom when learning a new language, I implement a basic custom Rational class in Ruby:

# implementation of a Rational number class in Ruby

module MyRational
    class Rational
        attr_reader :numerator, :denominator

        def initialize(num, denom = 1)
            if denom == 0
                raise "Denominator cannot be 0 for a rational number"
            end
            
            @numerator = num
            @denominator = denom

            normalize
        end

        
        # += etc all come for free when the basic operator is overloaded!
        def +(other)
            @numerator = @numerator * other.denominator + @denominator * other.numerator
            @denominator = @denominator * other.denominator

            normalize
            self
        end
        

        def -(other)
            @numerator = @numerator * other.denominator - @denominator * other.numerator
            @denominator = @denominator * other.denominator

            normalize
            self
        end

        
        def *(other)
            @numerator = @numerator * other.numerator
            @denominator = @denominator * other.denominator

            normalize
            self
        end

        
        def /(other)
            @numerator = @numerator * other.denominator
            @denominator = @denominator * other.numerator

            normalize
            self
        end


        def to_s
            if @denominator == 1
                @numerator.to_s
            else
                @numerator.to_s + "/" + @denominator.to_s
            end
        end

        
        # generics class methods
        def self.+(f, s)
            n = f.numerator * s.denominator + f.denominator * s.numerator
            d = f.denominator * s.denominator
            
            Rational.new(n, d)
        end

        def self.-(f, s)
            n = f.numerator * s.denominator - f.denominator * s.numerator
            d = f.denominator * s.denominator
            
            Rational.new(n, d)
        end


        def self.*(f, s)
            n = f.numerator * s.numerator
            d = f.denominator * s.denominator
            
            Rational.new(n, d)
        end


        def self./(f, s)
            n = f.numerator * s.denominator
            d = f.denominator * s.numerator
            
            Rational.new(n, d)
        end


        private

        def gcd(x, y)
            if y == 0
                x
            else
                gcd(y, x%y)
            end
        end

        
        def normalize()
            g = gcd(@numerator, @denominator)

            @numerator /= g
            @denominator /= g
        end
    end
end

Taking it for a test spin:

irb(main):003:0> load "my_rational.rb"
load "my_rational.rb"
=> true
irb(main):004:0> x = MyRational::Rational.new(1,2)
x = MyRational::Rational.new(1,2)
=> #<MyRational::Rational:0x007fd02a09bc88 @numerator=1, @denominator=2>
irb(main):005:0> y = MyRational::Rational.new(3,4)
y = MyRational::Rational.new(3,4)
=> #<MyRational::Rational:0x007fd02a0a21a0 @numerator=3, @denominator=4>
irb(main):006:0> x += y
x += y
=> #<MyRational::Rational:0x007fd02a09bc88 @numerator=5, @denominator=4>
irb(main):007:0> puts x
puts x
5/4
=> nil
irb(main):008:0> x -= y
x -= y
=> #<MyRational::Rational:0x007fd02a09bc88 @numerator=1, @denominator=2>
irb(main):009:0> puts x
puts x
1/2
=> nil
irb(main):010:0> x *= y
x *= y
=> #<MyRational::Rational:0x007fd02a09bc88 @numerator=3, @denominator=8>
irb(main):011:0> puts x
puts x
3/8
=> nil
irb(main):012:0> x /= y
x /= y
=> #<MyRational::Rational:0x007fd02a09bc88 @numerator=1, @denominator=2>
irb(main):013:0> puts x
puts x
1/2
=> nil
irb(main):014:0> MyRational::Rational.+(x, y)
MyRational::Rational.+(x, y)
=> #<MyRational::Rational:0x007fd02a0bad90 @numerator=5, @denominator=4>
irb(main):015:0> puts MyRational::Rational.+(x, y)
puts MyRational::Rational.+(x, y)
5/4
=> nil
irb(main):016:0> puts MyRational::Rational.-(x, y)
puts MyRational::Rational.-(x, y)
-1/4
=> nil
irb(main):017:0> puts MyRational::Rational.*(x, y)
puts MyRational::Rational.*(x, y)
3/8
=> nil
irb(main):018:0> puts MyRational::Rational./(x, y)
puts MyRational::Rational./(x, y)
2/3
=> nil

Of course, operations like number op rational, rational op number, where “op” is an operation will not work. These could be added by extending the number classes, etc, and also adding support in the class methods, but perhaps there is a better way that I will learn when I improve my Ruby knowledge! For now, the more I use Ruby, the more I like it!

A Rational number class in Ruby

Hoyte’s Line Number closure in a few choice languages

Douglas Hoyte, in his excellent (if a bit fanatical) book, “Let over Lambda” gives a simple and pithy example to demonstrate the concept of closures – little anonymous functions that can capture variables in the environment in which the closure was created.

The example is very simple – create a mini-logging facility by capturing a variable representing the current line number (initially set to 0), incrementing it for every invocation of the closure, and printing it out.

An implementation in Common Lisp might look so –

(defun get-line-logger ()
  (let ((line-num 0))
    #'(lambda (id)
        (incf line-num)
        (format t "[~a] Line number: ~d~%" id line-num))))


(defun logger-demo ()
  (let ((logger-1 (get-line-logger))
        (logger-2 (get-line-logger)))
    (flet ((f (id logger)
             (dotimes (i 5)
               (funcall logger id))
             (terpri)))
      (f "logger-1" logger-1)
      (f "logger-2" logger-2))))

Sample run:

CL-USER> (logger-demo)
[logger-1] Line number: 1
[logger-1] Line number: 2
[logger-1] Line number: 3
[logger-1] Line number: 4
[logger-1] Line number: 5

[logger-2] Line number: 1
[logger-2] Line number: 2
[logger-2] Line number: 3
[logger-2] Line number: 4
[logger-2] Line number: 5

NIL

As expected, we can not only capture local variables in the lexical environment during the time of the closure’s creation, but also modify them independently of any other instances of the closure. This is what makes is a proper closure. Also note that the capture is automatically done (whether we actually use the variables or not is irrelevant).

The Racket version is, unsurprisingly, almost identical not only in syntax, but also semantics:

#lang racket

(define (get-line-logger)
  (let ([line-no 0])
    (lambda (id)
      (set! line-no (+ 1 line-no))
      (fprintf (current-output-port)"[~a] Line number: ~a~%" id line-no))))

(define (logger-demo)
  (let ([logger-1 (get-line-logger)]
        [logger-2 (get-line-logger)])
    (letrec ([f (lambda (id logger)
                  (do
                      ((i 0 (+ i 1)))
                      ((= i 5))
                    (logger id))
                  (newline))])
      (f "logger1" logger-1)
      (f "logger2" logger-2))))

And the behaviour is exactly the same:

hoyte-closure.rkt´╗┐> (logger-demo)
[logger1] Line number: 1
[logger1] Line number: 2
[logger1] Line number: 3
[logger1] Line number: 4
[logger1] Line number: 5

[logger2] Line number: 1
[logger2] Line number: 2
[logger2] Line number: 3
[logger2] Line number: 4
[logger2] Line number: 5

Racket is, in some ways, more elegant than even Common Lisp. I especially love the part where lambdas don’t need any funcallS or applyS to make them run (unlike in Common Lisp). Still, pretty much a branch off the same family tree.

Moving on, let’s try the same in Java, shall we?

import java.util.function.Function;

public class HoyteClosure {
    private static Function<String, Void> getLineLogger() {
        int lineNum = 0;

        return (String id) -> { lineNum++; System.out.printf("[%s] Line number: %d\n",
                                                                 id, lineNum); return null; };
    }

    private static void invokeLogger(String id, Function<String, Void> logger) {
        for (int i = 0; i < 5; i++) {
            logger.apply(id);
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Function<String, Void> logger1 = getLineLogger();
        Function<String, Void> logger2 = getLineLogger();

        invokeLogger("logger1", logger1);
        invokeLogger("logger2", logger2);
    }
}

Okay, looks good. However, if we try to run it, we run into issues immediately:

Timmys-MacBook-Pro:Java8 z0ltan$ javac HoyteClosure.java 
Timmys-MacBook-Pro:Java8 z0ltan$ javac HoyteClosure.java 
HoyteClosure.java:7: error: local variables referenced from a lambda expression must be final or effectively final
        return (String id) -> { lineNum++; System.out.printf("[%s] Line number: %d\n",
                                ^
HoyteClosure.java:8: error: local variables referenced from a lambda expression must be final or effectively final
                                                                 id, lineNum); return null; };
                                                                     ^
2 errors

The problem is that Java really does not have real closures. The lambda support in Java 8 is just syntactic sugar for the good old anonymous class which can read local variables in the environment, but cannot modify them. So what can we do?

To make Java happy, we can create a new object for every logger instance i.e., use instance variables in lieu of local variables so that the modification of local variables is not an issue any more:

import java.util.function.Function;

public class HoyteClosureModified {
    static class Closure {
        private int lineNum;
        
        private  Function<String, Void> getLineLogger() {
            return (String id) -> { lineNum++; System.out.printf("[%s] Line number: %d\n",
                                                                 id, lineNum); return null; };
        }
    }

    private static void invokeLogger(String id, Function<String, Void> logger) {
        for (int i = 0; i < 5; i++) {
            logger.apply(id);
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Function<String, Void> logger1 = new Closure().getLineLogger();
        Function<String, Void> logger2 = new Closure().getLineLogger();

        invokeLogger("logger1", logger1);
        invokeLogger("logger2", logger2);
    }
}

Taking it for a test spin, we get:

Timmys-MacBook-Pro:Java8 z0ltan$ javac HoyteClosureModified.java
Timmys-MacBook-Pro:Java8 z0ltan$ java -cp . HoyteClosureModified
[logger1] Line number: 1
[logger1] Line number: 2
[logger1] Line number: 3
[logger1] Line number: 4
[logger1] Line number: 5

[logger2] Line number: 1
[logger2] Line number: 2
[logger2] Line number: 3
[logger2] Line number: 4
[logger2] Line number: 5

It’s not the same though, is it? The whole point of using a closure was so that we wouldn’t have to do this explicit management of state ourselves. As such, Java doesn’t really have full-blown closures, just a poor man’
s version of it. Better luck next time, Java.

Finally, the same using Ruby. As I have said before, Ruby feels remarkably like a Lisp despite the syntactic differences.

module HoyteClosure 
    class Demo
        def self.get_line_logger
            line_no = 0

            lambda do |id|
                line_no += 1
                puts "[" + id + "] Line number: " + line_no.to_s
            end
        end

        def self.logger_demo(id, logger)
            5.times {
                logger.call(id)
            }
            puts ""
        end

        def self.main
            logger1 = get_line_logger
            logger2 = get_line_logger

            logger_demo("logger1", logger1)
            logger_demo("logger2", logger2)
        end
    end
end

And the final test run:

irb(main):009:0> load "./hoyte_closure.rb"
load "./hoyte_closure.rb"
=> true
irb(main):010:0> HoyteClosure::Demo.main
HoyteClosure::Demo.main
[logger1] Line number: 1
[logger1] Line number: 2
[logger1] Line number: 3
[logger1] Line number: 4
[logger1] Line number: 5

[logger2] Line number: 1
[logger2] Line number: 2
[logger2] Line number: 3
[logger2] Line number: 4
[logger2] Line number: 5

=> nil

Short, non-idiomatic, and sweet!

Hoyte’s Line Number closure in a few choice languages