What is the purpose of setting Ruby block local variables when blocks have their own scope already?
Asked Answered
G

5

9

Learning about Ruby blocks here. What is the point of having block local variable in this example: enter image description here

When you can just do the below instead? The x in the block is already going to have its own scope, which is different than the x that is outside the block. enter image description here

Grefe answered 15/1, 2017 at 1:43 Comment(0)
S
13

Block scopes nest inside their lexically enclosing scope:

foo = :outerfoo
bar = :outerbar

1.times do |;bar|
  foo = :innerfoo
  bar = :innerbar
  baz = :innerbaz
end

foo #=> :innerfoo
bar #=> :outerbar
baz # NameError

You need a way to tell Ruby: "I don't want this variable from the outer scope, I want a fresh one." That's what block local variables do.

Slovak answered 15/1, 2017 at 2:2 Comment(9)
how is the 2nd example not already getting a fresh variable when it prints 0 thru 4 instead of 10 thru 14?Grefe
I don't know what you mean. There are no nested variables in the second example. x is a parameter of the block, not a variable.Cerebrovascular
so adding ; is the only way for the block scope to get variables from the outer scope?Grefe
No, the semicolon makes it so that the scopes will be separate. If x isn't mentioned between the pipes at all, then x will refer to the variable in the outer scope (if it exists).Tourcoing
why does your baz get error? It wasn't declared in the outer scope, but it was declared in the inner scope so should it be equal to :innerbaz just like how foo was set to :innerfoo inside block? Is there something in Ruby that allows reassignment inside blocks but not creation of variables?Grefe
@stackjlei: baz is a local variable. Local variables are local to the scope they are defined in, that's why they are called "local variables". baz is defined inside the block, it is local to the block.Cerebrovascular
Is there anyway to reference the value of the outer variable while using this? Like bar = outer_bar?Teutonism
I don't understand, in the original example, whether x and y depend on outer or inner, they have to be reassigned in the block, and the result makes no differencePunchboard
@JoshuaPinter if you want to reference the outer bar, just name the inner variable with a different name so they don't overlap.Sanatorium
T
4

The point they're trying to make is that a block local variable (or a block parameter) will be completely separate from the variable outside of the block even if they have the same name, but if you just refer to x within the block without it being a block local variable or a block parameter, you're referring to the same x that exists outside the block.

They have this example right above the one you cite:

            x = 10
            5.times do |y|
              x = y
              puts "x inside the block: #{x}"
            end

            puts "x outside the block: #{x}"

Output:

            x inside the block: 0
            x inside the block: 1
            x inside the block: 2
            x inside the block: 3
            x inside the block: 4
            x outside the block: 4

Note that this is only the case if you refer to x before the block. If you removed the x = 10 line at the beginning, then the x in the block would be completely local to the block and that last line would error out (unless you had a method named x in the same object, in which case it would call that method).

Tourcoing answered 15/1, 2017 at 2:6 Comment(4)
sorry my 2nd example before was wrong, please take a look again at it. the x inside the block doesn't refer to the outside x, but a new one side. so why would you need to use the ; specifically in the first example to declare a local variable when it's already local without the ;?Grefe
In your second example, x is a block parameter, which also has a separate scope. But if it isn't either a block parameter or a block local variable (notice that it's just y between the pipes in the example I copied from the page), then x inside the block does refer to the outer scope. I'll edit to note that it's true of both block parameters and block local variables.Tourcoing
Why "x outside" is also 4, I thought it should be 10Punchboard
@TPR, its because in that example, x is a block variable and is being passed directly to the method. It has nothing to do with anything outside the block.Cuttie
C
1

I feel like the examples given in the original post leave a lot to be desired and largely serve to further confuse matters because the results of |y; x| and |y, x| are going to be identical. So the obvious question becomes this:

Why would we ever need to use a semicolon to create a block local variable if simply creating a regular block variable does the same thing?

I think the answer is that you would want to use ; instead of , anytime you want to BOTH protect the outer variable from manipulation AND declare an inner variable that will NOT be passed to the method.

Consider for example what happens with multiple assignment or hash when working with the map method:

x = 10
array = [[1], [:a]]
hash = {:b => 2}   

array.map {|y| x = y}  #=>  [[1], [:a]]
x  #=>  [:a]  
array.map {|y, x| x = y}  #=>  [1, :a]
x  #=>  10 
array.map {|y; x| x = y}  #=>  [[1], [:a]]
x  #=>  10

hash.map {|y| x = y}  #=>  [[:b, 2]]
x  #=>  [:b, 2]
hash.map {|y, x| x = y}  #=>  [:b]
x  #=>  10
hash.map {|y; x| x = y}  #=>  [[:b, 2]]
x  #=>  10
Cuttie answered 1/2, 2022 at 18:37 Comment(3)
Sorry I can't understand the output of each hashPunchboard
@TPR, explaining #Hash.map logic really wasn’t my intent. For that you should probably read the documentation or post an appropriate question. The point I was trying to make was that contrary to some of the suppositions, there ARE differences between regular block variables and explicitly declared block local variables depending on the object, method, and desired output. You don’t really even have to understand what the code is doing to see the point I was trying to make.Cuttie
why "array.map {|y| x = y}" made x to [:a]Punchboard
D
0

The examples above are teaching you about block scopes and how inner scopes can mute outer scopes (your x variable). The point is that you have to be aware of the differences.

Dixiedixieland answered 15/1, 2017 at 1:47 Comment(1)
it seems like a block will always mute outer scope looking at example 2. that's why i'm asking what is the point of example 1Grefe
S
0

Here's a real-life use case from a rails routes.rb file.

->(; version_str, content_length_str) do
  version_str = File.read(Rails.root.join("VERSION")).strip.freeze
  content_length_str = version_str.bytesize.to_s.freeze

  get :version, to: proc { [200, { "Content-Length" => content_length_str }, [version_str]] }
end.call

On Rails boot, the lambda is called to install the :version route by reading the file. The file is only read once per rails instance (to avoid extra IO).

As the app evolves, routes.rb may grow very large, and another developer (or even my future self) may accidentally assign another variable with the same name (e.g. content_length_str) in the enclosing scope.

Without the block local variables, the block above will overwrite the values of the enclosing scope, possibly wreaking all kinds of havoc.

So this ruby feature lets you assign variables for temporary calculations without worrying about mutating anything outside.

Of course, there are ways to make this even safer, like performing the calculation in a method instead, but sometimes the logic is simple and you don't want the extra indirection of a method call.

Sanatorium answered 2/8, 2024 at 18:25 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.