Question

Ruby and StackOverflow newb here working my way through Ruby and ran into my first major roadblock. I'm having a really hard time wrapping my head around Procs and Lambdas. Here is the code I'm working with.

def procBuilder(message)
  Proc.new{ puts message; return}
end

def test
  puts "entering method"
  p = procBuilder("entering proc")
  p.call
  puts "exit method"
end

test

By design, this is to throw a LocalJumpError, but I don't rightly understand why. If I had to guess what this did, I would guess it would initially print "entering proc" upon p = procBuilder("entering proc") running then throw an error on p.call as there is no string being passed by p.call, but clearly I'm missing something critical that is occurring between those 2 lines. I also don't completely understand why this works with a lambda rather than a proc but I imagine understanding the error will resolve that issue as well.

Thanks in advance for the clarification

Was it helpful?

Solution

Here's an answer I gave to a related question. It talks a bit about lambda vs proc and LocalJumpErrors.

In a proc, return is a special piece of syntax that returns from the lexical scope of the proc, not the proc itself. So it's trying to return out of procBuilder, which has already exited.

There are a couple ways to fix this:

  1. Don't use return at all. Ruby will return control to proc's caller all on its own.
  2. Change proc to lambda, which behaves the way you expect. Lambdas act like methods; procs act like blocks.

As for the error you're expecting, you shouldn't get that. procBuilder returns a proc that encloses the message variable. You don't need any arguments to the proc itself.

Edit: answering your additional question. The proc is a closure. It has "captured" the message variable (a local variable in procBuilder), which was in scope when the proc was created. The proc now can wander through your program with the message variable hidden inside of it, ready to be printed when you call it. The only trouble is the return statement, which has the additional requirement that it the lexical scope still be "live".

The reason for all this is that this behavior is really helpful in blocks. In this case, it's not helpful at all, so you should just use a lambda, where return means something less insane.

A really great tutorial on closures in ruby: http://innig.net/software/ruby/closures-in-ruby.rb

OTHER TIPS

An important difference between a proc and a method or lambda is the way in which they handle the return statement. If a method is defined inside another method, the return statement in the inner method exits only from the inner method itself, then the outer method continues executing. The same goes for defining a lambda within a lambda, a lambda inside a method or a method within a lambda. However, when a proc is defined within a method, the return statement will exit from the proc as well as the outer (enclosing) method. Example:

def meditate
    puts "Adjusting posture…"
    p = Proc.new { puts "Ringing bell…"; return }
    p.call
    puts "Sitting still…"  # This is not executed
end

meditate

Output:
Adjusting posture…
Ringing bell…

Notice how the last line of the method was not executed because the return statement within the proc has exited from both the proc and the enclosing method.

If we define a proc without an enclosing (outer) method and use a return statement, it will throw a LocalJumpError.

p = Proc.new { puts "Ringing bell…"; return }
p.call
Output:
Ringing bell…
LocalJumpError: unexpected return

This happens because when a return statement is reached within a proc, instead of returning from the context where it was called, it returns from the scope on which it (the proc) was defined. In the following example, a LocalJumpError happens because the proc is trying to return from the top-level environment, where it was defined.

def meditate p
    puts "Adjusting posture…"
    p.call
    puts "Sitting still…"  # This is not executed
end

p = Proc.new { puts "Ringing bell…"; return }

meditate p
Output:
Adjusting posture…
Ringing bell…
LocalJumpError: unexpected return

Usually, it's not a good idea to use a return statement within a proc. Procs are usually passed around between methods and if the method on which the proc was defined has already returned, it will throw an exception. In the example below we could just remove the return statement. However, there are cases we actually need to return something. In the latter, it's probably best to use a lambda instead of a proc. We will see later that lambdas handle return statements in a different way, more like methods.

Below is another scenario involving return statement:

def zafu_factory
    # This method will return the following proc implicitly
    Proc.new { puts "Round black zafu"; return }
end

def meditate
    puts "Adjusting posture…"
    p = zafu_factory
    p.call
    puts "Sitting still…"  # This is not executed
end

meditate
Output:
Adjusting posture…
Round black zafu
LocalJumpError: unexpected return

What just happened? The zafu_factory method created and implicitly returned a proc. Then, the proc was called by the meditate method and when the return statement within the proc was reached, it tried to return from the context on which it was defined (the zafu_factory method). However, zafu_factory already returned the proc and a method can only return once each time it's called. In other words, an exception was thrown because the zafu_factory method had already returned when the proc was called and tried to return a second time.

See more at this blog post about Procs and Lambdas: Closures in Ruby

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top