4

It's possible to call a lambda from another lambda:

first   = -> { 'Ok' }
second  = -> { first.call }
puts second.call
# => 'Ok'

but when the order is reversed:

first  = -> { second.call }
second = -> { 'Ok' }
puts first.call

the code fails with a NameError:

lambda_order.rb:1:in `block in <main>': undefined local variable or method `second' for main:Object (NameError)
Did you mean?  send
    from lambda_order.rb:3:in `<main>'

even though :second seems to be a local variable inside the scope of first:

first  = -> { local_variables }
second = -> { 'Ok' }
p first.call
# => [:first, :second]

I only use lambdas for golfing purposes so I'm not sure what's going on with the scope. Replacing second by a method or a constant lambda fixes the NameError. It seems related to this question but in my case, both lambdas are defined in main.

Could you please explain?

Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
  • 1
    This seems to explain it: "Proc bodies have a lexical scope like functions in Lisp and JavaScript, meaning that when Ruby encounters a free variable inside a proc body, its value is resolved within the context the proc was defined. This is what makes closures possible." ([Source](http://www.joshstaiger.org/archives/2006/12/of_closures_met_1.html)). – Cary Swoveland Sep 30 '18 at 21:53

2 Answers2

4

When a lambda/proc is created its content isn't executed, however on creation the lambda/proc itself is bound to the set of local variables currently in scope.

In your failing example

first  = -> { second.call }
second = -> { 'Ok' }
puts first.call

Since second is not defined before the creation of the first lambda the variable is not passed as part of the Binding for first.

However, simply defining second before the creation of first resolves this issue.

second = nil
first  = -> { second.call }
second = -> { 'Ok' }
puts first.call
# OK
#=> nil

The Proc::new documentation says the following:

Creates a new Proc object, bound to the current context. Proc::new may be called without a block only within a method with an attached block, in which case that block is converted to the Proc object.

Combine the above with the Binding documentation. Keeping in mind that the binding is bound to lambda/proc on creation (not when called):

Objects of class Binding encapsulate the execution context at some particular place in the code and retain this context for future use. The variables, methods, value of self, and possibly an iterator block that can be accessed in this context are all retained. Binding objects can be created using Kernel#binding, and are made available to the callback of Kernel#set_trace_func.

I hope this clears things up a little.

3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
4
first  = -> { defined? second }
second = -> { 'Ok' }
p first.call

results nil => The variable "second" is not defined in the lambda "first".

first  = -> { binding.receiver }
second = -> { 'Ok' }
p first.call

results main => This means that it uses the current binding of main and thus the variable "second" is defined only in the binding.

first  = -> { binding.local_variable_get(:second).call }
second = -> { 'Ok' }
p first.call

results "Ok". That's why the code also prints "Ok" when I ask for the content of the variable "second" of the binding.

Summary: The variable "second" is not defined in the lambda "first". The variable "second" is only defined in the binding. Therefore, the output of "local_variables" also returns "second" because the information is retrieved from the binding.

I also learned something myself. I hope I could help you!

Marek Küthe
  • 90
  • 1
  • 8
  • 1
    Excellent, thanks. It seems to be the case. I guess the question then becomes how does `defined?` check if a local variable has been set. Also, is it possible to list the local variables which are defined inside a lambda? – Eric Duminil Oct 01 '18 at 07:39
  • @EricDuminil [defined?](http://ruby-doc.org/docs/keywords/1.9/Object.html#method-i-defined-3F) is a keyword, it checks if an object is defined and if so, it returns the type of the object. In this case, "second" is not defined in lambda, so nil returns. defined? recognizes a defined variable because it is initialized. To output all variable names there are "local_variables", but in this case we would have the problemm described above. I can not imagine how one can output all defined variables in a lambda. – Marek Küthe Oct 01 '18 at 10:06
  • 1
    Thanks. I just noticed that `local_variables lists local variables but it lists them before they are defined` : https://stackoverflow.com/a/21800871/6419007 – Eric Duminil Oct 01 '18 at 10:16