Search code examples
rubyinstance-variablesmonkeypatching

Why is my instance_variable nil? (2 lines of code)


I'm trying to create a hash which stores an auto-increment number for a non-existent key. I'm aware there are other, less brittle, ways to do this; my question is : why does my instance variable fail so miserably?

h = Hash.new{|h,k| h[k] = (@max_value += 1)}
h.instance_variable_set(:@max_value, 0) # zero ! Not nil! Argh...

puts h[:a]  # expecting 1; getting NoMethodError undefined method '+' for nil:NilClass
puts h[:b]  # expecting 2
puts h[:a]  # expecting 1

Solution

  • You're not doing what you think you're doing.

    When you call Hash.new, you're referencing @max_value as it exists right now in the current scope. The current scope is the top level, it's not defined there, so you get nil.

    You then set an instance variable on the instance that happens to be called @max_value as well, but it's not the same thing.

    You probably want something like...well, actually, I can't imagine a situation where this mechanism is a good solution to anything, but it's what you asked for so lets run with it.

    h = Hash.new{|h,k| h[k] = (h.instance_variable_set(:@max_value,    
                                   h.instance_variable_get(:@max_value) + 1))}
    
    h.instance_variable_set :@max_value, 0
    
    puts h[1]  #=> 1
    puts h[10] #=> 2
    

    Note that I'm explicitly getting/setting the instance variable associated with `h in all cases. More verbose, but what you need.