ruby block and returning something from block
When you pass in the block with &, you're converting it to a proc. The important point is that a proc and a lambda are different (lambda is actually a subclass of proc), specifically in how they deal with return.
So your refactored code is actually the equivalent of:
p = Proc.new { return 10;}
def lab(block)
puts 'before'
puts block.call
puts 'after'
end
lab p
which also generates a LocalJumpError.
Here's why: A proc's return returns from its lexical scope, but a lambda returns to its execution scope. So whereas the lambda returns to lab
, the proc passed into it returns to the outer scope in which it was declared. The local jump error means it has nowhere to go, because there's no enclosing function.
The Ruby Programming Language says it best:
Procs have block-like behavior and lambdas have method-like behavior
You just have to keep track of what you're using where. As others have suggested, all you need to do is drop the return
from your block, and things will work as intended.
Using return keyword in Ruby block
Herein lies one of the primary differences between a block/Proc and a lambda/Method (the other primary difference would be arity). If you don't want a call to return
to exit the method, that would infer that you expect that block should be self-contained in its flow control, and be treated as an encapsulated method.
This description is essentially what a lambda is - an anonymous method. However, a standard ruby block is essentially an anonymous Proc, and takes nothing away from the flow control of the method.
As mentioned in the comments, you may be able to use next
to escape the block without returning control away from the method. 'May' because next
may just continue to the next item that the method iterator is passing to the block.
Related to this, see http://yehudakatz.com/2012/01/10/javascript-needs-blocks/ and Differences between Proc and Lambda
How can I return something early from a block?
next
inside a block returns from the block. break
inside a block returns from the function that yielded to the block. For each
this means that break
exits the loop and next
jumps to the next iteration of the loop (thus the names). You can return values with next value
and break value
.
Return the value from Ruby block
Use call
.
block.call
if block
takes arguments, then give arguments:
block.call(whatever_arguments)
Return statements inside procs, lambdas, and blocks
As one answer in the linked question shows:
The
return
keyword always returns from the method or lambda in the current context. In blocks, it will return from the method in which the closure was defined. It cannot be made to return from the calling method or lambda.
Your first example was successful because you defined victor
in the same function you wanted to return from, so a return
was legal in that context. In your second example, victor
was defined in the top-level. The effect of that return
, then, would not be to return from batman_yield
(the calling method), but [if it were valid] to return from the top-level itself (where the Proc
was defined).
Clarification: while you can access the return value of a block (i.e. "The value of the last expression evaluated in the block is passed back to the method as the value of the yield" - as per your comment), you can't use the return
keyword, for the reason stated above. Example:
def batman_yield
value = yield
return value
"Iron man will win!"
end
victor = Proc.new { return "Batman will win!" }
victor2 = Proc.new { "Batman will win!" }
#batman_yield(&victor) === This code throws an error.
puts batman_yield(&victor2) # This code works fine.
Are `return` and `break` useless inside a Ruby block when used as a callback?
Actually it's kind of interesting...
When you use before_create in Rails 3, we take the block or lambda that you give us and convert it into a method. We then invoke the method with the current ActiveRecord object, for backwards compatibility with the old Rails approach.
As a result, the following is equivalent to your snippet:
class User < ActiveRecord::Base
validates_presence_of :login, :email
before_create do
self.name = login.capitalize if name.blank?
end
end
Because of this behavior, you can call return from the block, and it will behave the same as a return in a normal method (because it is a normal method).
In general, next
in a block "returns" from the block.
The specific behavior of normal blocks is:
- When you call
next
, you are skipping the rest of the block, and returning control to the method that invoked the block. - When you call
break
, you are skipping the rest of the block, and also immediately returning from the method that invoked the block.
You can see that behavior in normal iterators:
value = [1,2,3,4].each do |i|
next if i == 2
puts i
end
In this case, value
will be [1,2,3,4]
, the normal return value of the each
method, and the output will be:
1
3
4
In the case of break:
value = [1,2,3,4].each do |i|
break if i == 2
puts i
end
In this case, the value
will be nil
, since the break
also immediately returned from the each
method. You can force a return with a specific value by using break n
, which will make value
the same as n
. The output in the above case will be:
1
The important thing is that next
and break
do not just apply to iterators, although their semantics are designed to behave like their equivalents in C in the case of iterators.
Ruby: the yield inside of a block
The block is passed similarly to the argument of that function. This can be specified explicitly, like so:
class Test
def my_each(&block)
"abcdeabcabc".scan("a") do |x|
puts "!!! block"
yield x
# Could be replaced with: block.call(x)
end
end
end
Technically, it's exactly the same (puts
put in there for clarification), its presence is not checked the way it is usually done for arguments. Should you forget to give it a block, the function will halt on the first yield
it has to execute with exactly the same LocalJumpError
(at least, that's what I get on Rubinius). However, notice the "!!! block" in the console before it happens.
It works like that for a reason. You could check whether your function is given a block, if it is specified explicitly as above, using if block
, and then skip the yield
s. A good example of that is a content_tag
helper for Rails. Calls of this helper can be block-nested. A simplistic example:
content_tag :div do
content_tag :div
end
...to produce output like:
<div>
<div></div>
</div>
So, the block is executed "on top" (in terms of call stack) of your method. It is called each time a yield
happens as some sort of function call on a block. It's not accumulated anywhere to execute the block afterwards.
UPD:
The Enumerator
returned by many each
es is explicitly constructed by many iterators to save context of what should happen.
It could be implemented like this on my_each
:
class Test
def my_each(&block)
if block
"abcdeabcabc".scan("a") { |x| yield x }
else
Enumerator.new(self, :my_each)
end
end
end
Related Topics
Phonegap and Rails 3: How to Interact with a Rails 3 App
Ruby Syntactic Sugar: Dealing with Nils
How to Use Present? in Ruby Projects
Superclass Mismatch, Struct, Reloading and Spork
How to Declare a Rake Task That Depends on a Parameterized Task
Where Is the Best Place to Add Methods to the Integer Class in Rails
Ruby Imap "Changes" Since Last Check
How to Find Out Why a Gem Bundle Has Locked a Gem at a Specific Version
When to Use Self in Module's Methods
Ways to Define a Global Method in Ruby
Prawnto Displaying Tables That Don't Break When New Page
Access Slack Files from a Slack Bot
How to Use Ruby CSV Converters
Ruby Cannot Find SQLite3 Driver on Windows
Rails 3.1 Rspec Creating Test Case Validate Field for Model