patternrubyMinor
Ruby 1.9 interner
Viewed 0 times
internerrubystackoverflow
Problem
I'm currently implementing an interner in the style of Guava's
Of course, in Ruby, you can replace methods like
Example usage:
I realise that my implementation is less than perfect. What improvements can you recommend?
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
endExample 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) # => trueI 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
Being a member of the "lots of tiny methods" fan club, I'd consider this:
Now, on to bigger things. Interner makes a resonable assumption that
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
endNow, 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
endContext
StackExchange Code Review Q#36470, answer score: 2
Revisions (0)
No revisions yet.