For years a tiny code style issue has bothered me in Ruby. I find myself frequently building up a set of variables and then building a Hash where the keys all match the variables. It is a small nit-pick, and never wastes much time, but it has always annoyed me. The wasted duplication to have {:some_key => some_key}
for a long list of variables, when the key and the variable are named the same. Convention over configuration and all that. Ruby is a beautiful and flexible language. Why can’t we make it simpler and cleaner to create a Hash based of some locally defined variables. It turns out more complicated than one would think, because of trying to bind to the current variable scope.
In the end what I wanted was a way to create a hash that would looks something like this, which I like to call a SelfReferringHash
calculation = 7
word = "a word"
data = "more data"
{:calculation <=, :word <=, :data <=}
#in this new fake syntax I imaged '<=' to mean points to self
#or perhaps being able to call a method on a array
[:calculation, :word, :data].bind_into_hash
#or perhaps something a bit more normal
Hash.build_from_vars([:calculation, :word, :data])
#all methods would have the same result of a hash like below
{:calculation => 7, :word => "a word", :data => "more data"}
###Inspiring a solution
I mentioned this annoyance years ago to Ara Howard, and he whipped up a example very quickly, but it didn’t feel quite right. Ara proposed a few iterations with various syntax’s :result.to_h{}
or [:a, :b, :c].to_h{}
but the syntax was distracting. So I kind of forgot about the issue and forgot about the issue for awhile.
I then recently came across this post Playing Around with Ruby Hashes. Where Alex wants to add various features such as setting a arbitrarily deep value on the Hash without all the intermediate checks and creation of empty hashes
hash[player][:stats][category][statistic] = value
#without littering my code with a bunch of lines like
hash[player] ||= {:stats => {}}
hash[player][:stats][category] ||= {}
The post build some fun features onto Ruby’s Hash and the post inspired me to play around with building a cleaner solution.
###Attempting a solution
While one of Ara’s symbol to hash solutions was pretty close to what I wanted, it Monkey patched the Array and and Symbol I wanted to avoid that. I decided that I should make a new object and subclass Hash opposed to tacking my code onto any existing objects in the system.
class SuperHash < Hash
def initialize(*args, &context)
if args && args.first.is_a?(Array) && context
super()
hash = SuperHash.from_array(args.first, &context)
hash.each_pair{|key, value| self[key]= value}
elsif args && args.first.is_a?(Array) && context.nil?
raise "requires context block for binding"
else
super()
end
end
def self.from_array(array, &context)
raise "requires context block for binding" if context.nil?
array.flatten.inject(SuperHash.new){|h, val| h.update({val => (eval(val.to_s, context))}) }
end
end
calculation = 7
word = "a word"
data = "more data"
my_hash = SuperHash.from_array([:calculation, :word, :data]){}
puts my_hash.class
puts my_hash.inspect
my_hash = SuperHash.new([:calculation, :word, :data]){}
puts my_hash.class
puts my_hash.inspect
begin
my_hash = SuperHash.new([:calculation, :word, :data])
puts my_hash.class
puts my_hash.inspect
rescue => err
puts err
end
my_hash = SuperHash.new()
puts my_hash.class
puts my_hash.inspect
Running that code results in the following output, with the SuperHash.new([:calculation, :word, :data]){}
ending up being the ‘cleanest’ way to invoke the code.
SuperHash
{:data=>"more data", :calculation=>7, :word=>"a word"}
SuperHash
{:data=>"more data", :calculation=>7, :word=>"a word"}
requires context block for binding
SuperHash
{}
In the end, this might be interesting if I had some other things to patch onto the Hash object. Unfortunately it seems like this still isn’t really that useful and it is a bit to awkward. I could never avoid the primary issue I had with Ara’s examples of having to awkwardly pass the context. All solutions seem to require you to pass a empty block to be able to bind to the context and access the variables at that time. Having a initializer or a class method which requires {}
just seems to awkward to be useable. So after all this time I am still left feeling, unsatisfied. While this is a very simple problem to understand and Ruby is a very flexible language. The issues of binding to a context and variable scoping makes building a more beautiful and usable version of a self referring Hash out of my reach.
If anyone has suggestions or ideas on how to build something that is a bit cleaner or closer to my goal and doesn’t resort to passing a empty block, I would love to see any other solutions. Perhaps, I am missing some more interesting way to bend Ruby to fit my mind.