HiveBrain v1.2.0
Get Started
← Back to all entries
patternrubyMinor

Ruby 1.9 interner

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
internerrubystackoverflow

Problem

I'm currently implementing an interner in the style of Guava's Interner, where:

  • an intern pool is maintained for an immutable class



  • whenever a new instance of that class is created, it's checked to see if it's equal to an existing instance in the intern pool



  • if such an existing instance is found, it's returned, otherwise the new instance is added to the pool and returned



Of course, in Ruby, you can replace methods like new and [], so my implementation does just that:

module Interner
  def self.extended(obj)
    intern_pool = Hash.new do |hash, key|
      # Ideally, this should be a deep freeze, but it's not supported
      # (see http://bugs.ruby-lang.org/issues/show/2509).
      if key.respond_to?(:clone)
        key = key.clone
        key.freeze if key.respond_to?(:freeze)
      end
      hash[key] = key
    end
    [:new, :[]].each do |name|
      if obj.respond_to?(name)
        obj.singleton_class.module_exec(obj.method(name)) do |orig|
          define_method name do |*args, &block|
            intern_pool[orig[*args, &block]]
          end
        end
      end
    end
  end
end


Example usage:

Foo = Struct.new :foo, :bar do
  extend Interner
end

a = Foo[1, 2]
b = Foo[3, 4]
c = Foo.new(1, 2)
d = Foo.new(3, 4)
a.equal?(c)  # => true
b.equal?(d)  # => true


I realise that my implementation is less than perfect. What improvements can you recommend?

Solution

I can't see much I'd change. It's a pretty neat concept, well done. I wouldn't change much. First, the minor things.

I would consider changing the name of the argument to Interner.extended from obj to klass.

Being a member of the "lots of tiny methods" fan club, I'd consider this:

def self.extended(klass)
    intern_pool = make_intern_pool
    ...
  end

  def self.make_intern_pool
    Hash.new do |hash, key|
      key = freeze_key(key)
      hash[key] = key
    end
  end

  def self.freeze_key(key)
    # Ideally, this should be a deep freeze, but it's not supported
    # (see http://bugs.ruby-lang.org/issues/show/2509).
    if key.respond_to?(:clone)
      key = key.clone
      key.freeze if key.respond_to?(:freeze)
    end
    key
  end


Now, on to bigger things. Interner makes a resonable assumption that new is a factory method. However, its assumption that [] is a factory method is less reasonable. That's certainly the case for Struct, and perhaps for many other classes, but not necessarily always. I would prefer to see a way to make explicit the name(s) of the factory methods, so that I have control over which methods are wrapped by Interner. There should still be an easy, default way to use Interner that wraps new, but perhaps there could be another way to use it that lets you specify which methods to wrap.

Code Snippets

def self.extended(klass)
    intern_pool = make_intern_pool
    ...
  end

  def self.make_intern_pool
    Hash.new do |hash, key|
      key = freeze_key(key)
      hash[key] = key
    end
  end

  def self.freeze_key(key)
    # Ideally, this should be a deep freeze, but it's not supported
    # (see http://bugs.ruby-lang.org/issues/show/2509).
    if key.respond_to?(:clone)
      key = key.clone
      key.freeze if key.respond_to?(:freeze)
    end
    key
  end

Context

StackExchange Code Review Q#36470, answer score: 2

Revisions (0)

No revisions yet.