Monkey patching I18n.t in Rails

During the Ruby Hangout on Nov 7th, I suggested that it would be cool if instead of writing t 'path.to.my.string', one could instead write t.path.to.my.string. Josh Szmajda made the mistake of agreeing, and so I decided this would be a good challenge for me to learn some more Ruby tricks.

First of all, I poked into I18n and made a good discovery – the translate method is not particularly useful without passing parameters. This meant that I could use the case where there’s an empty list of parameters to return a Funky Object(TM) that would do something cool with method_missing. Also, I decided that since :t is simply an alias for :translate, I could choose to write my own t that would defer to translate when passed args and do my own thing when it wasn’t. Basically, I could write something like:

require 'i18n'

module I18n
  class << self
    def t(*args)
      if (args.empty?)
        # Do something cool here!
      else
        translate(*args)
      end
    end
  end
end

I did some quick testing and discovered that did indeed work. Now on to the tricky part. Here’s the first version:

require 'i18n'

module I18n
  class << self
    def t(*args)
      if (args.empty?)
        translator = Object.new
        translator.instance_variable_set(:@path, '')
        def translator.method_missing(method_id, *args)
          raise StandardError, "No args please!" unless args.empty?
          @path = "#{@path}.#{method_id.to_s}"
          answer = I18n.translate(@path)
          answer.respond_to?(:keys) ? self : answer
        end
        translator
      else
        translate(*args)
      end
    end
  end
end

This creates a new Object object, then sets the instance variable @path on it to an empty string, and then sets up method_missing on that object to handle arbitrary methods. It looks them up using I18n.translate in whatever path is currently active. If the returned thing behaves like a Hash, then we presume we’re going to need to do this again, and so we just return self so the next method call can follow along. On the other hand, if we gotten to something that doesn’t look like a Hash, then we just return that and we’re done.

The only problem with this approach is that there are a whole bunch of methods defined in Object that can’t be used as keys. For instance, if you have the key inspect in your locale file, you’re out of luck getting to it with this notation.

So I decided to try to swap in BasicObject for Object. This took me a little longer to figure out since I still needed a way to call instance_variable_set. I finally came up with the following. It may not be the best way to handle this, but it’s what I stumbled upon.

module I18n
  class << self
    def t(*args)
      if (args.empty?)
        translator = BasicObject.new
        def translator.__instance_variable_set(sym, obj)
          ::Object.instance_variable_set(sym, obj)
        end
        translator.__instance_variable_set(:@path, '')
        def translator.method_missing(method_id, *args)
          raise StandardError, "No args please!" unless args.empty?
          @path = "#{@path}.#{method_id.to_s}"
          answer = I18n.translate(@path)
          answer.respond_to?(:keys) ? self : answer
        end
        translator
      else
        translate(*args)
      end
    end
  end
end

I add another singleton that can call ::Object.instance_variable_set for me. I have no idea what this syntax is actually doing – I stumbled upon it by looking at the documentation on BasicObject in the Pickaxe Book. But it works! When I use I18n.t.hash_name in irb I get translation missing: en.hash_name.inspect – note that .inspect on the end that results because it tries to look up the inspect message that irb sends the result!

I’m not at all sure whether this whole exercise was a good idea from a production code standpoint, but it was fun!

Advertisements

Some Explorations in Functional Programming using Ruby

I’m taking a course in Ruby on Rails, and one of the suggested projects is to write a function that will return the class hierarchy for an object.  This is a very simple problem, but it got me to thinking.  The obvious solution is to use a loop.  But Ruby has a whole set of collection-based idioms using block syntax that seem to inherit from Smalltalk.  I wondered if I might be able to put those to use.

So I started thinking about this whole class of problems.  I wanted a way to express an enumerator where f(n) depends solely on the values of a fixed number of recent values.  For instance, in the case of returning the class hierarchy for an object, f(n) depends solely on f(n-1), whereas in the case of generating the Fibonacci sequence, f(n) depends upon f(n-1) and f(n-2).  One approach to these sort of problems is to use recursion, although in the case of the Fibonacci sequence, recursion can result in performance issues that can be resolved through the use of Memoization.  I wanted to encode the recursive relationship in a non-recursive solution.

So I came up with:

def enumerator_from_generator_simple(*history, &block)
	Enumerator.new do |yielder|
		history.each { |x| yielder.yield x }
		loop do
			history = history[1, history.length-1] + [block.call(*history)]
			yielder.yield history[-1]
		end
	end
end

This takes two things – a list of “seed” values for the history and a block. The block will be passed the history and is expected to generate the next value in the sequence. The number of history values to pass to the block is determined by the number of seed values passed initially. With this method in hand, we can then create an enumerator for the Fibonacci sequence and then extract the first 10 values (be very careful not to try to extract all the values – this is the enumerator for an infinite list):

foo = enumerator_from_generator_simple(1, 1) do
	|*history|
	history[0]+history[1]
end

p foo.first(10)

We can also then write the class hierarchy enumerator:

def get_superclasses(object)
	superclass_enum = enumerator_from_generator_simple(object.class) do |old_class|
		new_class = old_class.superclass
		raise StopIteration if new_class == nil
		new_class
	end
	superclass_enum.to_a
end

p get_superclasses(5)

Note than in this case, I defined the method by having it create a new enumerator using the class of the passed object as the history. I use raise StopIteration to halt the enumeration once we reach a class whose superclass is nil. Voila! No looping, just an expression of the recursive relationship.

My attention then turned to two different areas. The first was adding some performance improvements to the code. These may be a little excessive, but it struck me that the code listed above is a bit inefficient when dealing with some of the most common cases, such as a history of one element. So I wrote the following, which has hand-tweaked code for the most common cases (no elements, one element, and two elements):

def enumerator_from_generator(*history, &block)
	if history.length == 0 then
		Enumerator.new do |yielder|
			loop do
				yielder.yield block.call
			end
		end

	elsif history.length == 1 then
		history = history[0]
		Enumerator.new do |yielder|
			yielder.yield history
			loop do
				history = block.call(history)
				yielder.yield history
			end
		end

	elsif history.length == 2 then
		history_0 = history[0]
		history_1 = history[1]
		Enumerator.new do |yielder|
			yielder.yield history_0
			yielder.yield history_1
			loop do
				history_0, history_1 = history_1, block.call(history_0, history_1)
				yielder.yield history_1
			end
		end

	else
		Enumerator.new do |yielder|
			history.each { |x| yielder.yield x }
			loop do
				history = history[1, history.length-1] + [block.call(*history)]
				yielder.yield history[-1]
			end
		end

	end
end

With that in hand, I turned to some simple testing. As part of testing, I realized that the Fibonacci sequence is actually a special case of a set of Fibonacci sequences. We’ll call the “traditional” Fibonacci sequence Fib2, where Fib2(n) = Fib2(n-1) + Fib2(n-2). The next one in the set of sequences would be Fib3 = Fib3(n-1) + Fib3(n-2) + Fib3(n-3). And so forth and so on. If one uses history.inject(:+) in place of the explicit addition, it becomes simple to express a whole range of these. Finally, I start generating the enumerator using brace block syntax so I can cram everything on one line.

p ( enumerator_from_generator(1, 1) { |*history| history.inject(:+) } ).first(20)
p ( enumerator_from_generator(1, 1, 1) { |*history| history.inject(:+) } ).first(20)
p ( enumerator_from_generator(1, 1, 1, 1) { |*history| history.inject(:+) } ).first(20)
p ( enumerator_from_generator(1, 1, 1, 1, 1) { |*history| history.inject(:+) } ).first(20)

I just increase the number of elements passed initially and, voila, Fibx!

Then I got to thinking that all of this addition was a little excessive. In the case of two or three elements, it’s not bad. But what if I was computing Fib1000? The vast majority of the addition in each step was done in the previous step. I realized that Fibx(n) = 2 * Fibx(n-1) – Fibx(n-1-x). That is to say, each time the very oldest history element drops off and we add a new Fibx(n-1). But since Fibx(n-1) is equal to the whole sum without the changes, we can just double it and subtract the oldest element. Of course, then we need to keep track of x+1 elements in our history and we need to seed the list with an initial sum, but the following enumerates Fib5 just as well as the last example above:

p ( enumerator_from_generator(1, 1, 1, 1, 1, 5) { |*history| history[-1] * 2 - history[0] } ).first(20)

My final realization was that Fibx(n)/Fibx(n-1) has an asymptotic limit – specifically, if the limit as n goes to infinity of Fibx(n)/Fibx(n-1) is r, then r = 2 – 1 / r^x, which can be recast as r^(x+1) – 2 r^x + 1 = 0. For instance, in the case of Fib2, that is r^3 – 2 r^2 + 1 = 0. There are functions for computing cubic roots, but I’m lazy, so I’ll just take advantage of enumerator_from_generator to write a numeric solver!

p ( enumerator_from_generator(2.0) { |history| 2.0 - 1.0 / (history**2) } ).first(20)

Hey, that converges pretty quickly on the Golden Mean! Which means we’re getting the right answer, since it is well documented that the ratio of consecutive values in the Fibonacci sequence converges on the Golden Mean.

But what if I want to enumerate reasonable converged values for Fibx?

def fibx_ratio(x, iter)
	( ( enumerator_from_generator(2.0) { |history| 2.0 - 1.0 / (history**x) } ).first(iter) )[-1]
end

1.upto(10) { |x| p fibx_ratio(x, 100) }

And, as we expect, the ratio for Fibx approaches 2 as x approaches infinity. Which is to say that while Fib2 approximates n^1.618, and Fib3 approximates n^1.839, by the time we get to Fib9 and Fib10, there isn’t much difference between the two functions.

And that, I think, is probably the most I’m going to wring out of this specific suggested project!