sites
sites

Reputation: 21785

Block does not recognise variable at first time

y = lambda { z }
z = 9

y.call
=> NameError: undefined local variable or method `z' for main:Object

y = lambda { z }
y.call
=> 9

I thought the idea of having a block was defer the execution of the piece of code inside the block. Can you explain this?

Upvotes: 1

Views: 103

Answers (4)

Darshan Rivka Whittle
Darshan Rivka Whittle

Reputation: 34031

lambda creates what's known as a lexical closure, which means it captures a reference to the enclosing environment and can use variables that are in scope at the time the closure is created.

y = lambda { z } # Woops! There's no variable named `z`!

It doesn't matter if you create a variable named z after creating the closure; the idea of the closure is that it captures the environment at that particular moment.

In theory, you could hope for an error when you create that lambda, because it's possible to analyze it and determine that a variable will be dereferenced which has not been defined. Ruby doesn't work that way, however; it is only when code is being evaluated that you get an error for attempting to access an undefined variable.

When you create a second closure, a few lines later, that one does capture an environment in which z exists:

y = lambda { z } # This closure is broken and can't be fixed
z = 9
y = lambda { z } # This is a new closure, and `z` is accessible within it

I thought the idea of having a block was defer the execution of the piece of code inside the block.

Sort of, but it's not the same as simply copying and pasting the code from the lambda and evaluating it when you call it. evaling a string would do that:

y = "z"
z = 9
eval(y)
# => 9

Again, the idea of a closure is that it captures both the code and the environment.

Upvotes: 2

Wally Altman
Wally Altman

Reputation: 3545

When you define a lambda, it gets a closure, consisting of references to all the variables in scope at the time of definition. The lambda always has access to the current value of each variable it has "closed over", but newly defined variables aren't added to the closure retroactively. So when you do this:

x = 5
y = lambda { x + z }
z = 7

the lambda y has access to the value of x, but not z, because only x was in existence at the time you defined the lambda. But if you do this:

x = 5
z = 0
y = lambda { x + z }

z = 7
y.call
# => 12

then all will be well.

Upvotes: 2

Arup Rakshit
Arup Rakshit

Reputation: 118261

undefined local variable or method 'z' for main:Object comes when local variables are not created. local variables created by only assignment operation.

your first y = lambda { z } couldn't bind z as z is defined after this lambda definition.

the below works :

y = lambda { z }
y.call
=> 9

as you defined z = 9 before 2nd lambda definition.

Upvotes: 1

DarkDust
DarkDust

Reputation: 92336

In the first line, when the block is created, there simply is no z yet that could be "catched" by the block yet. When you write y = lambda { z } the second time, there is a z.

The block takes a kind of snapshot of the surrounding variables. And in this case, no variable named z was present the first time.

Upvotes: 2

Related Questions