Local variables in class definitions / scope - ruby

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?

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).

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.

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.

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

Related

Ruby: Retrieve local variable from class

I was reading up on metaprogramming and came across this exercise:
http://ruby-metaprogramming.rubylearning.com/html/Exercise_1.html
The question is:
Given this class definition:
class A
def initialize
#a = 11
##a = 22
a = 33
end
#a = 1
##a = 2
a = 3
end
Retrieve all the variables from outside the class, with output like so:
1
2
3
11
22
33
Now, it's pretty straightforward to get the instance and class variables dynamically, even the local variable inside the constructor (it's the return value of the initialize method). But I'm stumped as to how to get the local variables a=3.
As far as I know, this is not possible because the local variables cease to exist after the class definition is first read.
The only roundabout way that I have made this work is to set a variable to the "return" value (for lack of a better term) of when the class is declared, like so:
val = class A
a = 3
end
puts val # => 3
Is this the only way?
The question seems to boil down to this: given the following:
class A
attr_reader :instance_var
def initialize
#instance_var = (#instance_var ||= 0) + 1
instance_local_var = 33
puts "instance_local_variables = #{ local_variables }"
instance_local_var = 33
end
class_local_var = 3
puts "class_local_variables = #{ local_variables }"
class_local_var = 3
end
# class_local_variables = [:class_local_var]
#=> 3
can one determine the values of instance_local_var and class_local_var?
Determine the value of class_local_var
The answer to this question is clearly "no" because class_local_var no longer exists (has been marked for garbage collection) after end is executed1:
A.send(:local_variables)
#=> []
Determine the value of instance_local_var
a = A.new
# instance_local_variables = [:instance_local_var]
#=> #<A:0x007ff3ea8dbb80 #instance_var=1>
Note that #instance_var #=> 1.
A.new does not return the value of instance_local_var, but because that variable is assigned a value in the last line of initialize, that value can be obtained by executing initialize once again.2
instance_local_var = a.send(:initialize)
#=> 33
There is a problem, however:
a.instance_var
#=> 2
Executing initialize a second time has caused an unwanted side effect. My definition of initialize is artificial, but it highlights the fact that many undesirable side effects could occur by executing initialize a second time.
Now let's obtain a new instance.
b = A.new
# instance_local_variables = [:instance_local_var]
#=> #<A:0x007fee0996e7c8 #instance_var=1>
Again, #instance_var=1. One possible workaround to the side-effects of calling initialize twice for a given instance is to subclass A and use super.
class B < A
attr_reader :b
def initialize
#b = super
end
end
B.new.b
#=> 33
a.instance_var
#=> 1
There is no guarantee that undesirable side-effects can be avoided with this approach (e.g., initialize for any instance may perform a database operation that should occur only once), but it appears to leave the initial instance a unaffected. This is of course all hypothetical.
1. send must be used because A.private_methods.include?(:local_variables) #=> true
2. A.new.send(:initialize) is required because iniitialize is private.
Your question is unclear. In the title, you write "local variables", but in the example, you mention only instance- and class-variables.
As for the instance variables, you can use Object#instance_variables to get a list of known instance variables at this point. Note however that instance variables are created dynamically, not at the time of class declaration. For example, given the class
class AA;
def initialize; #x=1; end;
def f; #y=1; end;
end
the expression
AA.new.instance_variables
would return [:#x] - the :#y is missing, because it doesn't exist yet.
You don't have a way to automatically (i.e. without modifying the class) retrieve local variables. As mudasobwa explained in his answer, you would have to explictly pass back a binding.
One might get an access to all the local variables of the class by returning it’s binding from the class definition (see Binding#local_variables for details):
a = class A
v1 = 3.14
v2 = 42
binding
end
a.local_variable_get(:v1)
#⇒ 3.14
a.local_variable_get(:v2)
#⇒ 42
But the main question is why would you want to do that? The local variable is meant to remain local. This is how it is supposed to behave. Also, one is unable to modify the local variable in the original binding (what is returned is a read-only copy of an original binding.)

Why do I get an undefined local variable or method error when using a constant, but not when using a method?

How come if I have a class like:
class Thing
def number
10
end
end
And I inherit from it like this:
class OtherThing < Thing
CONSTANT = number / 2
end
I get undefined local variable or method 'number' when I try to instantiate the class, but if I do this:
class OtherThing < Thing
def method_instead_of_constant
number / 2
end
end
It works?
EDIT
I'm not necessarily looking for a hack to make this work, but an understanding as to why it doesn't. mudasobwa's answer below helped the most; constants are assigned at the class level, not on instances.
You need a class method to achieve the functionality you are looking for:
class Thing
# ⇓⇓⇓⇓ HERE
def self.number
10
end
end
class OtherThing < Thing
CONSTANT = number / 2
end
CONSTANT assignment is happening on the class level, hence it has an access to class methods of Thing, but not to the instance methods of it.
On the other hand, you might instantiate Thing and then call the instance method on it:
class Thing
def number
10
end
end
class OtherThing < Thing
# ⇓⇓⇓⇓⇓⇓⇓⇓⇓ HERE
CONSTANT = Thing.new.number / 2
end
Because number is an instance method on Thing. The scope of the class definition of OtherThing is an instance of Class, which means it isn't an instance of Thing or an instance of OtherThing at definition.
That said, you shouldn't be defining constants by executing methods. You could presumably have a calculated class variable that you call freeze on to prevent editing post-startup, but even that's not a very common pattern.
Because of scope.
Methods calls are evaluated based on the dynamic scope and distinguish between class and instance scope. Constants are resolved in lexical scope and do not distinguish between class and instance scope like methods do.
class A
# expressions are evaluated in scope of class A
def m
# expressions are evaluated in scope of an instance of A
return 42
end
end
Class and instance are not the same.
A
a = A.new
A.class # => Class
a.class # => A
A.respond_to?(:m) # => false
a.respond_to?(:m) # => true
A.m # => raises NoMethodError
a.m # => 42
Hence in the class body you cannot call instance methods.
Constants however are looked up using lexical scope, that is the surrounding context of classes and modules in the source file.
module M
# can access global constants and those defined in M
class A
# can access global constants and those defined in M or A
def m
# can access global constants and those defined in M or A
end
end
end
You can inspect the current constant lookup path with Module.nesting
I'm not necessarily looking for a hack to make this work, but and [sic]
understanding as to why it doesn't.
Here's the error message:
undefined local variable or method 'number'
When the Ruby interpreter sees number it looks for a local variable or a method named number. In the context of a method, the interpreter reads number as self.number. So this line:
CONSTANT = number / 2
is actually considered as:
CONSTANT = self.number / 2
So what's self?
Well that depends on where you've defined it (explicitly or implicitly). Within a class block, self is the class itself i.e. OtherThing. Since OtherThing nor any of its ancestors has a class-method number defined, nor does there exist a variable number, Ruby throws the error message.
self defined within an instance method definition is the current object. But this is awfully abstract without examples and some more theory. Other relevant topics are Singleton-Classes and Inheritance. If you like books, then I recommend The Well-Grounded Rubyist 2nd Ed, Chapter 5 by David A. Black. On second thoughts, read/study the whole book.

Local variables in `class` definition scope versus `def` method scope

Here, I create a local variable in class scope:
class MyClass
x = 1
puts x
end
It prints 1 even if I don't create any instances of MyClass.
I want to use x in some method:
class MyClass
x = 1
def method
puts x
end
end
m = MyClass.new
m.method
And I can't. Why? I get that class definition creates a scope, but why is it not accessible in the method? Isn't scope of the method inside the scope of the class?
I can imagine that this is related to creation of a class. Since any class is an object of Class, maybe the scope of MyClass is the scope of some Class method, and the way of coupling methods of MyClass to that instance makes their scope completely different.
It also seems to me that I can't just create a scope with {} (like in C) or something like do..end. Am I correct?
Scope of a method is not inside the class. Each method has its own entirely new scope.
New scopes are created whenever you use the class, module, and def keywords. Using brackets, as in C, does not create a new scope, and in fact you cannot arbitrarily group lines of code using brackets. The brackets (or do...end) around a Ruby block create a block-level scope, where variables previously created in the surrounding scope are available, but variables created within the block scope do not escape into the surrounding scope afterward.
Instance methods share the scope of their instance variables with other instances methods. An instance variable defined in the scope of a class definition is available in class-level singleton methods, but not in instance methods of the class.
Illustration:
class Foo
x = 1 # available only here
#y = 2 # class-wide value
def self.class_x
#x # never set; nil value
end
def self.class_y
#y # class-wide value
end
def initialize(z)
x = 3 # available only here
#z = z # value for this instance only
end
def instance_x
#x # never set; nil
end
def instance_y
#y # never set; nil
end
def instance_z
#z # value for this instance only
end
end
Foo.class_x # => nil
Foo.class_y # => 2
Foo.new(0).instance_x # => nil
Foo.new(0).instance_y # => nil
foo3 = Foo.new(3)
foo4 = Foo.new(4)
foo3.instance_z # => 3
foo4.instance_z # => 4
You can access class-level instance variables from within instances using the class-level getter. Continuing the example above:
class Foo
def get_class_y
self.class.class_y
end
end
foo = Foo.new(0)
foo.get_class_y # => 2
There exists in Ruby the notion of a "class variable," which uses the ## sigil. In practice, there is almost never a reasonable use case for this language construct. Typically the goal can be better achieved using a class-level instance variable, as shown here.
Here, I create a local variable in class scope:
class MyClass
x = 1
puts x
end
It prints 1 even if I don't create any instances of MyClass.
Correct. The class definition body is executed when it is read. It's just code like any other code, there is nothing special about class definition bodies.
Ask yourself: how would methods like attr_reader/attr_writer/attr_accessor, alias_method, public/protected/private work otherwise? Heck, how would def work otherwise if it didn't get executed when the class is defined? (After all, def is just an expression like any other expression!)
That's why you can do stuff like this:
class FileReader
if operating_system == :windows
def blah; end
else
def blubb; end
end
end
I want to use x in some method:
class MyClass
x = 1
def method
puts x
end
end
m = MyClass.new
m.method
And I can't. Why? I get that class definition creates a scope, but why is it not accessible in the method? Isn't scope of the method inside the scope of the class?
No, it is not. There are 4 scopes in Ruby: script scope, module/class definition scope, method definition scope, and block/lambda scope. Only blocks/lambdas nest, all the others create new scopes.
I can imagine that this is related to creation of a class. Since any class is an object of Class, maybe the scope of MyClass is the scope of some Class method, and the way of coupling methods of MyClass to that instance makes their scope completely different.
Honestly, I don't fully understand what you are saying, but no, class definition scope is not method definition scope, class definition scope is class definition scope, and method definition scope is method definition scope.
It also seems to me that I can't just create a scope with {} (like in C) or something like do..end. Am I correct?
Like I said above: there are 4 scopes in Ruby. There is nothing like block scope in C. (The Ruby concept of "block" is something completely different than the C concept of "block.") The closest thing you can get is a JavaScript-inspired immediately-invoked lambda-literal, something like this:
foo = 1
-> {
bar = 2
foo + bar
}.()
# => 3
bar
# NameError
In general, that is not necessary in Ruby. In well-factored code, methods will be so small, that keeping track of local variables and their scopes and lifetimes is really not a big deal.
So just creating a class without any instances will lead to something
actually executing in runtime (even allocating may be)? That is very
not like C++. –
Check out this code:
Dog = Class.new do
attr_accessor :name
def initialize(name)
#name = name
end
end
If you execute that code, there won't be any output, but something still happened. For instance, a global variable named Dog was created, and it has a value. Here's the proof:
Dog = Class.new do
attr_accessor :name
def initialize(name)
#name = name
end
end
dog = Dog.new("Ralph")
puts dog.name
--output:--
Ralph
The assignment to the Dog constant above is equivalent to writing:
class Dog
...
...
end
And, in fact, ruby steps through each line inside the class definition and executes each line--unless the line of code is inside a def. The def is created but the code inside a def doesn't execute until the def is called.
A very common line you will see inside a class definition is:
attr_accessor :name
...which can be rewritten as:
attr_accessor(:name)
...which makes it obvious that it's a method call. Ruby executes that line--and calls the method--when you run a file containing the class definition. The attr_accessor method then dynamically creates and inserts a getter and a setter method into the class. At runtime. Yeah, this ain't C++ land anymore--welcome to NeverNever Land.
I get that class definition creates a scope, but why is it not
accessible in the method?
Because that is the way Matz decided things should be: a def creates a new scope, blocking visibility of variables outside the def. However, there are ways to open up the scope gates, so to speak: blocks can see the variables defined in the surrounding scope. Check out define_method():
class MyClass
x = 1
define_method(:do_stuff) do
puts x
end
end
m = MyClass.new
m.do_stuff
--output:--
1
The block is everything between do...end. In ruby, a block is a closure, which means that when a block is created, it captures the variables in the surrounding scope, and carries those variables with it until the the block is executed. A block is like an anonymous function, which gets passed to a method as an argument.
Note that if you use the Class.new trick, you can open two scope gates:
x = 1
MyClass = Class.new do
define_method(:do_stuff) do
puts x
end
end
m = MyClass.new
m.do_stuff
--output:--
1
Generally, ruby allows a programmer to do whatever they want, rules be damned.

what is local scope exactly?

To be honest, I still confused about the instance variable and local variable, not sure which should be used.
only one condition I know about local variable that can't be used is:
class MyClass
def initialize
local_var = 1
#instance_var = 1
end
def show_local_var
local_var
end
def show_instance_var
#instance_var
end
end
apparently, MyClass.new.show_instance_var works while MyClass.new_show_local_var not
the other thing about the two kind of variables is that the block seems share the same local scope, so the local variable can be referenced:
local_var = 1
3.times do
puts local_var
end
There are all I know about the distinctions, is there any other available? please let me know
if there is any articles about this, that would be so helpful for me,
A local variable is used "right here right now" and can't be accessed from anywhere else.
class MyClass
def foo
local_var = 2
#I use this to do some sort of calculation
#instance_var = local_var + 34
local_var = 5 * #instance_var
puts local_var
end
# From here, local_var is unaccessible.
end
Once you're out of scope (foo's end is passed) local_var is no more and can't be referred to.
The instance variable is available to the whole class at all times.
class MyClass
def initialize
#instance_var = 0
end
def foo
local_var = 2
#I use this to do some sort of calculation
#instance_var = local_var + 34
end
def some_operation
if #instance_var == 36
#instance_var = 3
else
#instance_var = 1
end
end
end
So when you call m = MyClass.new and later on m.some_operation, it's working with the same #instance_var .
And while we're at it, there are also Class variables (defined ##class_var) that are accessible from any instance of the class.
I don't have an article in particular to provide you, but some googling about ruby variable scope and about each type of variable independently should provide you with all the information you need!
The second example you describe is called a Closure. Paul puts it quite nicely there:
A closure is a block of code which meets three criteria:
It can be passed around as a value and
executed on demand by anyone who has that value, at which time
it can refer to variables from the context in which it was created (i.e. it is closed with respect to variable access, in the mathematical sense of the word "closed").
For a nice, quick introduction about the available scopes in Ruby you may refer to the Ruby Programming wikibook.
There is
Local Scope
Global Scope
Instance Scope
Class Scope
The "Default Scope", as it is sometimes referred to when executing code with no scope modifiers surrounding it, as in
#iv = "Who do I belong to?"
is the scope of the 'main' object.
Local scope is limited to the location in which the variable is declared, i.e. a function, or a for loop, but cannot be accessed from outside that location. However, if you nest other constructs within the function, for loop, etc. then the inner constructs can access the local variable. Instance variables are scoped to the instance of the class.
Article on ruby variable scope
Article on scope in general

metaprogramming access local variables

class Foo
def initialize
bar = 10
end
fiz = 5
end
Is there a possibility to get these local values (outside the class) ?
The local variable in initialize would be lost.
You are able to get the value fiz outside of the class, but only upon defining that class, and recording the return of the definition of the class.
return_of_class_definition = (class A ; fiz = 5 ; end) would assign the value of fiz to the variable.
You can also use binding but of course, this means changing the class, which may not be allowed for the exercise.
class A
bin = 15
$binding = binding
end
p eval 'bin', $binding
No. Once a local variable goes out of scope (for bar that is when the initialize method has run - for fiz when the end of the class definition has been reached), it's gone. No trace left.
While a local variable is still in scope you can see it (well, its name) with local_variables and get and set its value with eval (though that's definitely not recommended for sanity reasons), but once it's out of scope, that's it. No way to get it back.
In ruby we have something we could call scope gates - places when a program written in ruby leaves the previous scope. Those gates are: class, module and method (def keyword). In other words after class, module of def keyword in the code you're immediately entering into a new scope.
In ruby nested visibility doesn't happen and as soon as you create a new scope, the previous binding will be replaced with a new set of bindings.
For example if you define following class:
x = 1
class MyClass
# you can't access to x from here
def foo
# ...from here too
y = 1
local_variables
end
end
local_variables method call will return [:y]. It means that we don't have an access to the x variable. You can workaround this issue using ruby's technique called Flat Scopes. Basically instead defining a class using class keyword you can define it using Class.new and pass a block to this call. Obviously a block can take any local variables from the scope where it was defined since it's a closure!
Our previous example could be rewritten to something like like that:
x = 1
Foo = Class.new do
define_method :foo do
i_can_do_something_with(x)
y = 1
local_variables
end
end
In this case local_variables will return [:x, :y].

Resources