Code not evaluated in default argument value specification - ruby

(At least some kind of) Ruby code is accepted and evaluated within the default value specification of a method. In below, "foo" * 3 is evaluated:
def bar baz = "foo" * 3; baz end
bar # => "foofoofoo"
def bar baz: "foo" * 3; baz end
bar # => "foofoofoo"
However, when I try to evaluate a local variable/method under a certain scope in the default value description as follows, the local variable/method is evaluated under lexical scope:
MAIN = TOPLEVEL_BINDING.eval('self')
foo = 3
def bar baz = MAIN.instance_eval{foo}; end
bar # => undefined local variable or method `foo' for main:Object
def bar baz: MAIN.instance_eval{foo}; end
bar # => undefined local variable or method `foo' for main:Object
Why is foo above not evaluated within MAIN scope and is evaluated in lexical scope?
This seems there is some limitation to what Ruby expressions can be evaluated in the default value description. What exactly can be put there?

foo is local variable for main. Your attempt to access local variable from outside might be shorten to:
▶ MAIN = TOPLEVEL_BINDING.eval('self')
▶ foo = 3
▶ MAIN.foo
#⇒ NoMethodError: undefined method `foo' for main:Object
The analogue of this code in less tangled manner is:
class A
foo = 5
end
class B
def a_foo
A.new.foo
end
end
▶ B.new.a_foo
#⇒ NoMethodError: undefined method `foo' for #<A:0x00000002293bd0>
If you want to provide access from the universe to your local variable you are to implement getter:
def MAIN.foo ; 5 ; end
def bar baz = MAIN.instance_eval{foo}; baz; end
▶ bar
#⇒ 5
Hope it helps.

Related

Local variables for a class in ruby [duplicate]

This question already has answers here:
In ruby how to use class level local variable? (a ruby newbie's question)
(4 answers)
Closed 7 years ago.
I have noticed the following code is syntactically correct:
class Foo
bar = 3
end
Now, I know that instance variables are accessed by #, and class variables by ##, but I couldn't figure out where bar is stored in this case or how to access it.
How can I find bar's scope?
The body of a class in Ruby is just executable Ruby code. These are indeed local variables (no quotation needed) and follow the "regular" rules being local variables. You can access them in the body of the class. If you literally want the scope where bar is defined, you can use Kernel.binding:
class Foo
bar = 42
##scope = binding
def self.scope
##scope
end
end
Foo.scope.local_variables # => [:bar]
Foo.scope.local_variable_get(:bar) # => 42
A thing to note - using def changes the scope, therefore, they won't be visible inside methods defined using def.
It is accessible from the same class body.
class Foo
bar = 3
bar # => 3
end
It is lexically scoped, so it is accessible from within a block:
class Foo
bar = 3
pr = ->{p bar}
pr.call # => 3
end
but it is not accessible even in the same class once the class body has been closed:
class Foo
bar = 3
end
class Foo
bar # => error
end
nor can it be accessed from within a method definition:
class Foo
bar = 3
def baz; bar end
new.baz # => error
end
The bar variable will be accessible until you close the definition of the class. It will not be accessible inside the methods you define.
You can try to run the code in irb:
$ irb
irb(main):001:0> class Test
irb(main):002:1> bar = 1
irb(main):003:1> puts bar
irb(main):004:1> end
1
=> nil
irb(main):005:0> puts bar
NameError: undefined local variable or method `bar' for main:Object
from (irb):5
from /usr/bin/irb:11:in `<main>'
irb(main):006:0> class Test
irb(main):007:1> puts bar
irb(main):008:1> end
NameError: undefined local variable or method `bar' for Test:Class
from (irb):7:in `<class:Test>'
from (irb):6
from /usr/bin/irb:11:in `<main>'
irb(main):009:0>
Check for the availability in the instance methods:
irb(main):018:0> class Test
irb(main):019:1> bar = 1
irb(main):020:1> def test
irb(main):021:2> puts bar
irb(main):022:2> end
irb(main):023:1> end
=> :test
irb(main):024:0> a = Test.new
=> #<Test:0x00000000f447a0>
irb(main):025:0> a.test
NameError: undefined local variable or method `bar' for #<Test:0x00000000f447a0>
from (irb):21:in `test'
from (irb):25
from /usr/bin/irb:11:in `<main>'
Check for availability in the class methods:
irb(main):026:0> class Test
irb(main):027:1> bar = 1
irb(main):028:1> def self.test
irb(main):029:2> puts bar
irb(main):030:2> end
irb(main):031:1> end
=> :test
irb(main):032:0> Test.test
NameError: undefined local variable or method `bar' for Test:Class
from (irb):29:in `test'
from (irb):32
from /usr/bin/irb:11:in `<main>'
You could make it a constant and use it instance and class methods:
class Foo
Bar = 3
def local_bar(param = Bar)
param
end
end
p Foo.new.local_bar
#=> 3

Undefine class method and back to define it later

I need to disable a class method for some time, then enable it again. How could I do that? I know that I can remove a method:
class Foo
def Foo.bar
puts "bar"
end
end
Foo.bar # => bar
class <<Foo
remove_method :bar
end
Foo.bar # => undefined method `bar' for Foo:Class (NoMethodError)
Now I need Foo.bar again. How could I do that? I tried to save the method in a proc
m = Proc.new { Foo.bar }
and then define it again:
class Foo
define_method(:bar, &m)
end
but I get
NameError: undefined local variable or method 'm' for...
So I flattened the scope
P = Class.new(Foo) do
define_method(:bar, &m)
end
but I get undefined method if I run it
P.bar
Foo.bar
Is it possible to save a method, undefine it, and then define it back?
Instead of keeping a method body in a proc, you should keep methods as methods. Constantly keep it defined under some different name, and switch Foo.bar between alias of it or not.
class Foo
def Foo.temporal_bar
puts "bar"
end
end
Foo.singleton_class.class_eval{alias bar temporal_bar}
Foo.bar # => bar
Foo.singleton_class.class_eval{remove_method bar}
Foo.bar # => Undefined local variable or method `bar' for #<Class:Foo>
Foo.singleton_class.class_eval{alias bar temporal_bar}
Foo.bar # => bar
You can try out instance_eval:
(class << Foo; self; end).instance_eval do
define_method(:bar, &m)
end

Ruby variables' visibility

If I declare #var in Ruby, every object of that class would have its own #var.
But what if I miss #? I mean, I declare a variable called var2 without #. Do they share the variable or it is temporarily created?
When variable is declared without scope prefix (# - instance, ## - class or $ - global) then is declared for current scope, i.e:
class Foo
def boo
#boo ||= 'some value'
var ||= 40
puts "boo: ##boo var: #{var}"
end
def foo
var ||= 50
puts "boo: ##boo var: #{var}"
end
end
c = Foo.new
c.boo # => boo: some value var: 40
c.foo # => boo: some value var: 50
def foo
$var ||= 30
puts "$var: #$var"
end
foo # => $var: 30
puts "$var: #$var" # => $var: 30
%w[some words].each do |word|
lol = word # blocks introduce new scope
end
puts lol # => NameError: undefined local variable or method `lol'
for word in %w[some words]
lol = word # but for loop not
end
puts lol # => words
Without an # it's discarded when the method it's in is done running.
class Foo
def initialize
#bing = 123
zing = 456
end
def get_bing
#bing
end
def get_zing
zing
end
end
foo = Foo.new
foo.get_bing #=> 123
foo.get_zing #=> NameError: undefined local variable or method `zing' for #<Foo:0x10b535258 #bing=123>
This shows that an instance variable #bing is saved with that instance. It's value is accessible within any method on that instance.
But a local variable zing is not persisted (in most cases), and as soon as the method is done running any local variables are discarded, and no longer accessible. When get_zing is run, it looks for a local variable or method named zing and doesn't find one, because the zing from initialize is long gone.
It will become a local variable that belongs to the local lexical scope.
Ex.
class Foo
def Bar
#fooz = 1
end
def Barz
fooz = 2
end
def test
puts #fooz
end
end
f = Foo.new
f.Bar
f.test
f.Barz
f.test
Output:
1
1 #not 2
If you use a variable called var2 it is local and is only in scope in the construct where it is declared. That is:
If you declared it within a method, it will be local to that method.
If you try to declare within a class definition but outside any method, it will raise a NameError. For example:
class Foo
bar = 2
end
Foo.new
NameError: undefined local variable or method 'bar'

Confusion with class and instance variable creations

I was playing with the local,class variable and instance variable creation inside the class block as below. But I found something which I failed to explain myself. My confusion has been posted between the two codes below.
class Foo
def self.show
##X = 10 if true
p "hi",##X.object_id,x.object_id
end
end
#=> nil
Foo.show
#NameError: undefined local variable or method `x' for Foo:Class
# from (irb):4:in `show'
# from (irb):7
# from C:/Ruby193/bin/irb:12:in `<main>'
The above erros is expected. But in the below code I have assigned the class variable ##X to 10. But in the p statement I used instance variable #X.Why did the error not throw up like the above code ?
class Foo
def self.show
##X = 10 if true
p "hi",#X.object_id
end
end
#=> nil
Foo.show
"hi"
4
#=> ["hi", 4]
Because of everything is object and no explicit variable declaration is required in Ruby, you code
p #X.object_id
silently introduces an instance variable #X (#X.nil? == true). You can see this magic in irb:
~ irb
> p #x.object_id
# 8
# ⇒ 8

Difference between Module#const_set and Module#module_eval

Aren't they supposed to do the same thing? Why is this happening? At this point, I'm using the module_eval in my code out of necessity, but the const_set seems more readable. Regardless, I'd really like to understand why this happens.
here's the code:
class A
def foo
FOO
end
def self.foo
FOO
end
end
module B
class C < A
end
end
B.const_set(:FOO,'asdf')
>> B::C.foo
NameError: uninitialized constant A::FOO
from ./foo.rb:6:in `foo'
from (irb):1
>> B.module_eval {FOO='asdf'}
=> "asdf"
>> B::C.foo
=> "asdf"
Your module_eval isn't actually putting the constant in the module. Then, you're just accessing it from main:
module B;end
B.module_eval { FOO = 'asdf' }
>> FOO
=> "asdf"
You can fix that with self::FOO = 'asdf', then it's the same as B.const_set(:FOO,'asdf'). You can also do it more directly like this:
B::FOO = 'asdf'
The main problem with your code is that you can't access constants from other modules like that. If they're in an outer module, you need to specify the scope of the constant with the :: prefix:
def foo
B::FOO
end

Resources