Variable scope within a block in ruby - ruby

I'm struggling with variable scope within a block in ruby. I learnt that do..end that follows while, until, or for loops are not blocks because while, until, or for are not method calls. So they don't create their own inner scope. but this example confused me
loop do
b = 2
p b # => 2
break
end
p b
Here in this example b is initialized with the number 2 inside the do..end. The puts method is called with the variable b passed to it as an argument, and it through an NameError. saying that b is undefined local variable, but as i said before the
do..end that follows while, until, or for loops don't create their own inner scope

but as i said before the
do..end that follows while, until, or for loops don't create their own inner scope
Exactly. But there is no while, until, or for loop in your code.

Related

In Ruby, what does a top-level assignment do?

I have the following code at the top-level of a .rb file:
class Times
def initialize(n)
#n = n
end
def each()
(1..#n).each {yield}
end
end
three_times = Times.new(3)
def f()
Times.new(3).each {puts 'Test'}
end
f()
This works, and prints 'Test' three times, as expected. However, if I replace Times.new(3) in f with three_times, i.e. three_times.each {puts 'Test'}, I get an error:
`f': undefined local variable or method `three_times' for main:Object (NameError)
Why does this not work? Why is Times accessible from within f, but not three_times?
More generally, what exactly does an assignment at the top level (e.g. three_times = Times.new(3)) do?
Because
three_times is a local variable
local variables are only accessible inside of a specific scope
def in ruby creates a new scope
So when f is invoked, it does not see or have access to three_times
To access three_times change it to either a global variable $three_times or an instance variable #three_times
The reason that you are able to reference the class Times is that it is a constant and ruby goes through a separate process of lookup for constants.
Sidestepping issue with def
You could also access the local variable by using a block to define your method, which sidesteps the whole scope gate issue. I do this sometimes when writing rake tasks but rarely do it outside of scripts.
three_times = Times.new(3)
define_method :foo do
three_times.each { puts 'Tests'}
end
foo
Why does this not work? Why is Times accessible from within f, but not three_times?
Variables whose name starts with a lowercase letter are local variables. Local variables are local to the scope they are defined in (that's why they are called local variables.)
Variables whose name starts with an uppercase letter are constants. Constants are looked up first in the default constant scope, then lexically outwards, then dynamically upwards by inheritance.
More generally, what exactly does an assignment at the top level (e.g. three_times = Times.new(3)) do?
Nothing special. It does the same thing that an assignment anywhere else does. In this case, it:
Dereferences the variable (constant) Times, let's call this object o1.
Evaluates the literal integer expression 3, let's call the resulting object o2.
Sends the message new to o1, passing o2 as an argument. Let's call the answer to that message send o3.
Binds o3 to the local variable named three_times.
As you can see, there's nothing in there that is somehow specific to script scope or the top-level.
It's because it's looking for a local variable called "three_times". If you wish to make "three_times" to be "top-level" or "global", prepend the variable name with $ so that it's "$three_times".
Your code works for me, there is no errors. You can call f() sucecsfully

Ruby loop local variables and inmutability

I have the following code:
# Assuming each element in foo is an array.
foo.each do |bar|
zaz = bar.first
# Other code using zaz, but not modifying it.
end
Will zaz local variable be modified on each iteration inside this loop, making it mutable? I am not sure about the behavior of Ruby here.
It depends on the code before the loop, really.
If that is all the code, then zaz is a block-local variable, and a new zaz variable will be created every time the loop body is evaluated.
If, however, there is a zaz local variable in the surrounding scope, then zaz is a free variable in the block, and since block scopes nest in their surrounding scope, the existing zaz variable outside the block will be re-assigned over and over again, every time the block is evaluated.
You can ensure that zaz is always treated as a block-local variable and never looked up in the surrounding scope, by explicitly declaring it as a block-local variable in the block's parameter list:
foo.each do |bar; zaz|
zaz = bar.first
end
Note, however, that your code only makes sense IFF your code is impure and mutable:
You assign to zaz but never actually use it inside the block. So, the only way that this makes sense at all is if zaz is a local variable in the outer scope and you are assigning it. Although in that case, your entire loop is just equivalent to zaz = foo.last.first.
each evaluates the block only for its side-effects. Without side-effects, each makes no sense at all, so the fact that you are using each implies that you have side-effects.
Note that the term "immutable" without additional qualification usually refers to values. When talking about "immutable variables", we usually say "immutable variable" explicitly, to make clear that we are only talking about whether or not a variable can be re-bound, not about mutating object state. Or, one could just say "constant", which is the technical term for "immutable variable" … although that term already has a specific meaning in Ruby.
each loops often mutate the object. Each has to do something.
Because each doesn't return anything useful - it returns the array itself, It won't mutate the object if it sends every element somewhere, like to be printed on screen.
foo.each do |bar|
# do something with element like
# print it, change it, save it
end
Functional alterantive is map
foo.map { |bar| bar.something }
It returns new array which is original array processed in immutable way. Obviously you have to be careful to use immutable methods. This would not be immutable:
foo.map { |bar| bar.something! }
Here something! does something destructive to the element of array.
However I have never seen map used like that. Use each for something destructive.

Usage of for loop?

Most of the time when I see a for loop used in Ruby, the person who wrote it does not know Ruby well. Usually, it is much more readable when a for loop is replaced by an iterator taking a block such as each.
Is there any use-case where for cannot be easily rewritten by an iterator with a block, or there is an advantage in using for?
Is it true that for is faster than an iterator method because for is a keyword? What is the purpose of for?
I saw the for loop a lot in Rails books 6-8 years before. But is not preferred anymore.
There is a difference in the scope of the iterator variable. Take the following example:
numbers = [1, 2, 3]
numbers.each do |n|
# do nothing
end
begin
puts n
rescue Exception => e
puts e.message
end
for n in numbers do
# do nothing
end
puts "still: #{n}"
That would have this output:
# undefined local variable or method `n' for main:Object
# still: 3
The block syntax is generally preferred by Ruby community.
There is a small difference in variable scope while using for or each.
Variable declared within a for loop will be available outside the loop, where as those within an each block are local to the block and will not be available outside it.

how to use truly local variables in ruby proc/lambda

Beginner Ruby question. What is the simplest way to change this code, but leaving the block completely intact, that eliminates the side effect?
x = lambda { |v| x = 2 ; v}
x.call(3)
#=> 3
x
#=> 2
This is the simplest example I could contrive to illustrate my issue, so "remove the assignment" or "don't assign the Proc to x" is not what I'm looking for.
I want to set local variables in a Proc (or lambda) that can be assigned without affecting the original enclosing scope. I could dynamically create a class or module to wrap the block, but that seems overkill for such a basic thing.
Equivalent Python to what I'm trying to do:
def x(v):
x = 2 # this is a local variable, what a concept
return v
Sometimes it is the desired behavior:
total = 0
(1..10).each{|x| total += x}
puts total
But sometimes it's accidental and you don't want to mess with an outside variable which happens to have the same name. In that case, follow the list of parameters with a semicolon and a list of the block-local variables:
x = lambda{|v; x| x = 2; v}
p x.call(3) #3
p x #<Proc:0x83f7570#test1.rb:2 (lambda)>
The reason for this is that the lambda is bound to its defining scope (NOT its calling scope), and is a full closure, which, among other things, includes the local variable x. What you really want here is an unbound proc to pass around and call without any particular binding. This isn't something that Ruby does very easily at all, by design (it's possible with eval, but that's less of a block and more just of a string statement). Procs, lambdas, and blocks are all bound to their defining scope. Lexical scope is only established on classes, modules, and methods; not on blocks/procs/lambdas/anything else.
It's worth noting that Python doesn't even permit assignment in lambdas in the first place.

What does "shadowing" mean in Ruby?

If I do the following with warnings turned on under Ruby 1.9:
$VERBOSE = true
x = 42
5.times{|x| puts x}
I get
warning: shadowing outer local variable - x
Presumably it's to do with using x as a block parameter as well as a variable outside of the block, but what does "shadowing" mean?
Shadowing is when you have two different local variables with the same name. It is said that the variable defined in the inner scope "shadows" the one in the outer scope (because the outer variable is now no longer accessible as long as the inner variable is in scope, even though it would otherwise be in scope).
So in your case, you can't access the outer x variable in your block, because you have an inner variable with the same name.
Shadowing is more general term, it is applicable outside the Ruby world too. Shadowing means that the name you use in an outer scope - x = 42 is "shadowed" by local one, therefore makes in non accessible and confusing.

Resources