I Don't Understand Ruby Local Scope

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):
enter image description here

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



Leave a reply



Submit