开发者

The scope is confusing

开发者 https://www.devze.com 2023-03-12 23:16 出处:网络
I am confused with the scope of 开发者_运维技巧a variable inside a block. This works: f = \'new\'

I am confused with the scope of 开发者_运维技巧a variable inside a block. This works:

f = 'new'
[1,2,3].each do |n| puts f * n end
#=> new newnew newnewnew

But this doesn't:

[1,2,3].each do |n|
  a ||=[]
  a << n
end
a
#=>a does not exsit!

Why is this? And please put some resource on this topic for me.


What's confusing?

In the first snippet f is created and then the each block is executed, which can see things outside itself (called enclosing scope). So it can see f.

In the second snippet you create a inside the block, and so its scope is that block. Outside the block, a doesn't exist.

When you refer to a name (a, for instance) ruby will go from the current scope outwards, looking in all enclosing scopes for the name. If it finds it in one of the enclosing scopes, it uses the value that name is associated with. If not, it goes back to the most local scope and creates the name there. Subsequent name lookups will yield the value tied to that name.

When a block ends, the names that were in that scope are lost (the values aren't lost, just the names; values are lost when the garbage collector sees that no more names (or anything) refer to that value, and the gc collects the value to reuse its memory).


If visualization is your thing, I find it helpful to think of scopes as a staircase, and at the beginning of a program, you are standing on the top step1. Every time a block is entered, you step down one step. You can see everything on the current step, and everything on steps above the one you're on, but nothing on the steps below. When you refer to a variable name, you look around on the step you're on to find it. When you see it, you use that value. If you don't see it, you look to the next step above the one you're on. If you see it, you use that value. You do this over and over till you've looked at the very top step but don't see that name. If this happens, you create the name on the step you are standing on (and give it a value, if you're looking it up for an assignment). The next time you look for that name, you'll see it on the step you're standing on, and use it there.

When a block ends, you step up one stair step. Because you can't see any names on the steps below, all the names on the step you were previously on are lost.

If that helps you, think of it that way. If not, don't.

1 Actually you're on the second step because you're not in global scope, but to use names from global scope, you have to use a $ at the beginning of the name. So in the staircase example, if the name you are looking for has an $ at the beginning, you look directly at the top step. If not, you don't look that far. However, this is kind of wrong, since all the stairs in the program would share the same top step, which is weird to think about.


map works much better:

a = [1,2,3].map do |n|
  n
end

No need to declare a outside of the block.


It's simple, a variable defined inside a block is not visible outside (if this happened, we'd say the variable had leaked, and as the word suggests, this would be bad):

>> lambda { x = 1 }.call
=> 1
>> x
NameError: undefined local variable or method `x' for main:Object
0

精彩评论

暂无评论...
验证码 换一张
取 消