I don't understand ruby local scope
There a couple of things going on here. First, variables declared inside the if
block have the same local scope as variables declared at the top level of the method, which is why bar
is available outside the if
. Second, you're getting that error because bob
is being referenced straight out of the blue. The Ruby interpreter has never seen it and never seen it initialized before. It has, however, seen bar
initialized before, inside the if statement. So when is gets to bar it knows it exists. Combine those two and that's your answer.
Ruby, understanding local variables scope in if block
In ruby, classes, modules, functions, blocks and procs all have their own scope, so local variables defined within them will not normally be accessible outside of them.
In ruby, logic statements such as if, while and for do not have their own scope, so variables defined in them persist in the class, module, function block, or proc where they're used.
This is a design choice and part of what makes ruby ruby! It may feel counter-intuitive because languages like c have separate scopes for if statements and some (but not all) interpreted languages mimic that.
local variables scope in anonymous function objects in Ruby
When you create an anonymous function (a lambda or Proc), you give it a block, which is the body of the function, like so:
-> { this_is_the_function_body }
Proc.new { this_is_the_function_body }
A block retains all the local variables as they existed in the scope in which the block was created:
def my_lambda
text = 'foo bar baz'
-> { "text is: #{text}" }
end
l = my_lambda
text #=> #<NameError: undefined local variable or method `text' for main:Object>
l.inspect #=> "#<Proc:0x007f9863865a80@(pry):3 (lambda)>"
l.call #=> "foo bar baz"
As we see above, the local variable text
is still available even though even the lambda object resides in, and is called in, a scope in which text
doesn’t exist. This is even true if a local variable with the same name exists in the calling scope:
text = 'something else'
l.call #=> "foo bar baz"
This is called a closure. No matter where you call the function, you still have access to the scope in which it was created.
This doesn’t just apply to local variables, though, but instead the whole scope, as we can see here:
class C
def f; -> { g }; end
def g; 'foo'; end
end
C.new.f.call #=> "foo"
Local variables in `class` definition scope versus `def` method scope
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.
Ruby's local variable scope convention?
if
returns value. It's cleaner to use this behaviour.
x = if condition
# several lines of calculations can be here
'true value'
else
# several lines of calculations can be here
'false value'
end
Or, in this concrete case it's better to use ternary operator. It does the same thing and is shorter.
x = condition ? 'true value' : 'false value'
Variable scope in Ruby
The difference is that the if
block isn't actually a separate scope like it is in other languages such as Java. Variables declared within the if
block have the same scope as the surrounding environment. Now, in your case, that if
block won't actually be executed, so you'd normally expect d
to be undefined (resulting in the same error you got in the second example). But ruby is a little "smrt" in that the interpreter will set up a variable with that label the moment it sees it, regardless whether it is actually executed, because it essentially doesn't know just yet whether that branch will indeed execute. This is explained in "The Ruby Programming Language" by David Flanagan and Yukihiro Matsumoto (can't copy paste the text, adding screenshot instead):
In the case of the .each
loop, that do...end
you've written in is actually a block, and it does have its own local scope. In other words, variables declared within a block are local to that block only.
However, blocks "inherit" the scope of the environment in which they're declared, so what you can do is declare flag
outside of the .each
iteration block, and then the block will be able to access it and set its value. Note that in the example you've given, that won't happen because you're attempting to iterate an empty array, but at the very least you won't receive an error any more.
Some additional reading:
- Block variable scope in ruby
- Understanding scopes in ruby
Ruby scope on main
Indeed it is a scoping issue. If you want array
to be accessible inside that method change it to @array
@array = [1,2,3]
def display_size()
puts "self from method: #{self}"
puts "Size from method: #{@array.size}"
end
puts "self from main: #{self}"
puts "Size from main: #{@array.size}"
display_size()
array
is a local variable and is only scoped locally.
@array
is an instance variable and is available everywhere within the same class.
If you want to avoid creating an instance variable, you will need to pass the array to your method instead.
array = [1,2,3]
def display_size(array)
puts "self from method: #{self}"
puts "Size from method: #{array.size}"
end
puts "self from main: #{self}"
puts "Size from main: #{array.size}"
display_size(array)
Ruby forgets local variables during a while loop?
I think this is because message is defined inside the loop. At the end of the loop iteration "message" goes out of scope. Defining "message" outside of the loop stops the variable from going out of scope at the end of each loop iteration. So I think you have the right answer.
You could output the value of message at the beginning of each loop iteration to test whether my suggestion is correct.
Related Topics
Find Out If Current Time Is Between Two Times
To_D to Always Return 2 Decimals Places in Ruby
How to Get the Current Absolute Url in Ruby on Rails
How to Reload the Current Page in Ruby on Rails
How to Update Ruby Gems from Behind a Proxy (Isa-Ntlm)
Difference Between Class Variables and Class Instance Variables
Ruby: String Comparison Issues
Cannot Load Such File - Zlib Even After Using Rvm Pkg Install Zlib
Is Ruby Pass by Reference or by Value
Why Do Ruby Setters Need "Self." Qualification Within the Class
Why Does Ruby 1.9.2 Remove "." from Load_Path, and What's the Alternative
How to Find Where a Method Is Defined At Runtime
Ruby Block and Unparenthesized Arguments
Understanding the Rails Authenticity Token
Why Is the Shovel Operator (≪≪) Preferred Over Plus-Equals (+=) When Building a String in Ruby