7

I was under the impression that class definitions in Ruby can be reopened:

class C
  def x
    puts 'x'
  end
end

class C
  def y
    puts 'y'
  end
end

This works as expected and y is added to the original class definition.

I'm confused as to why the following code doesn't work as expected:

class D
  x = 12
end

class D
  puts x
end

This will result in a NameError exception. Why is there a new local scope started when a class is reopened? This seems a little bit counterintuitive. Is there any way to continue the previous local scope when a class is extended?

Matty
  • 33,203
  • 13
  • 65
  • 93
  • This is a good question on its own, but is there a reason you want to do this instead of storing an instance variable on the class object itself (i.e. replace `x` with `@x` above)? – Phrogz May 04 '12 at 16:36
  • @Phrogz No, there's no reason I'd want to do this in production code. I'm asking this to scratch an intellectual itch rather to solve a real-world problem. – Matty May 04 '12 at 16:55

4 Answers4

8

Local variables are not associated with an object, they are associated with a scope.

Local variables are lexically scoped. What you are trying to do is no more valid than:

def foo
  x = :hack if false  # Ensure that x is a local variable
  p x if $set         # Won't be called the first time through
  $set = x = 42       # Set the local variable and a global flag
  p :done
end

foo                   #=> :done 

foo                   #=> nil (x does not have the value from before)
                      #=> done

In the above it's the same method, on the same object, that's being executed both times. The self is unchanged. However, the local variables are cleared out between invocations.

Re-opening the class is like invoking a method again: you are in the same self scope, but you are starting a new local context. When you close the class D block with end, your local variables are discarded (unless they were closed over).

Phrogz
  • 296,393
  • 112
  • 651
  • 745
6

In ruby, local variables accessible only in scope that they are defined. However, the class keywords cause an entirely new scope.

class D
  # One scope
  x = 12
end

class D
  # Another scope
  puts x
end

So, you can't get access to local variable that defined in first class section, because when you leave first scope, local variable inside it is destroyed and memory is freed by garbage collection.

This is also true for the def, for example.

Flexoid
  • 4,155
  • 21
  • 20
0

The easy solution would be to make x a class instance variable. Of course, this doesn't answer your scope question. I believe the reason x is not in the scope (see Detecting the Scope of a Ruby Variable) of the re-opened class is because its scope was never that of class D in the first place. The scope of x ended when class D closed because x is neither an instance variable nor a class variable.

I hope that helps.

Community
  • 1
  • 1
0not
  • 190
  • 6
  • Not true. Try `x=42; class Foo; x=17; p x; end; p x` and notice that you see `17` and then `42`.Opening a class does start a new local context. – Phrogz May 04 '12 at 16:48
0

A part of the reason of why this behavior makes sense lies with metaprogramming capabilities; you could be using some temporary variables to store data that you could use to name a new constant, or to reference a method name:

class A
  names, values = get_some_names_and_values()

  names.each_with_index do |name, idx|
    const_set name, value[idx]
  end
end

Or maybe, you need to get the eigenclass...

class B
  eigenclass = class << self; self; end
  eigenclass.class_eval do
    ...
  end
end

It make sense to have a new scope every time you reopen a class, because in Ruby you often write code inside a class definiton that is actual code to be executed and opening a class is just a way to get the right value for self. You don't want the content of the class to be polluted by these variables, otherwhise you'd use a class instance variable:

class C
    @answer = 42
end

class C
  p @answer
end
Alberto Moriconi
  • 1,645
  • 10
  • 17