Are There Better Ways to Prevent 'Yield' When No Block Is Passed In

Are there better ways to prevent 'yield' when no block is passed in?

Yes! :)

def a_method(*params)
# ...
yield if block_given?
# ...
end

What if method has yield inside, but a block wasn't passed to it?

You can use Kernel#block_given? to determine if a block is passed and take the appropriate action.

class SomeClass < Array
def some_method
if block_given?
yield(self[i])
else
# not given
end
end
end

That means you can also have a default action if the block is not passed.

Ruby: How can I properly use `yield` to pass unnamed code block to Integer#times method?

For sake of completeness on this topic, I wanted to demonstrate another technique to call the original block:

def withProc
p = Proc.new
3.times(&p)
end
withProc { print "Go" }

When Proc.new is not given a block, it uses the block that was given to withProc instead. Now you can call p, and it will call the original block. You can also pass p to other methods like times either as a regular argument or as a block argument.

See https://medium.com/@amliving/proc-new-trick-c1df16185599 for more discussion

How to prevent problems with `return` from block when using Ruby `yield`

There is a built-in solution to detect whether a block contains a return statement.

You can use RubyVM::InstructionSequence.disasm to disassemble the block passed in by the user, then search it for throw 1, which represents a return statement.

Here's a sample implementation:

def safe_yield(&block)
if RubyVM::InstructionSequence.disasm(block) =~ /^\d+ throw +1$/
raise LocalJumpError
end

block.call
end

Here's how you might incorporate it into your library:

def library_method(&block)
safe_yield(&block)
puts "library_method succeeded"
rescue LocalJumpError
puts "library_method encountered illegal return but resumed execution"
end

And here's the user experience for a well-behaved and a misbehaving user:

def nice_user_method
library_method { 1 + 1 }
end

nice_user_method
# library_method succeeded

def naughty_user_method
library_method { return false if rand > 0.5 }
end

naughty_user_method
# library_method encountered illegal return but resumed execution

Commentary:

Using raise LocalJumpError/rescue LocalJumpError gets around the issues you encountered when using a blanket ensure.

I chose LocalJumpError because it seems relevant, and because (I think!) there is no possible Ruby code that would result in LocalJumpError being raised "naturally" in this context. If that turns out to be false, you can easily substitute your own new exception class.

Yield within Set to eliminate in an Array

In Ruby, when you are putting yield keyword inside any method(say #bar), you are explicitly telling #bar that, you will be using a block with the method #bar. So yield knows, inside the method block will be converted to a Proc object, and yield have to call that Proc object.

Example :

def bar
yield
end

p bar { "hello" } # "hello"
p bar # bar': no block given (yield) (LocalJumpError)

In the uniq_by method, we did not do anything to handle block argument. How is the passed argument handled by uniq_by method?

You did do, that is you put yield. Once you will put this yield, now method is very smart to know, what it supposed to so. In the line Messages.all.uniq_by { |h| h.body } you are passing a block { |h| h.body }, and inside the method definition of uniq_by, that block has been converted to a Proc object, and yield does Proc#call.

Proof:

def bar
p block_given? # true
yield
end

bar { "hello" } # "hello"

Better for understanding :

class Array
def uniq_by
seen = Set.new
select{ |x| seen.add?( yield( x ) ) }
end
end

is same as

class Array
def uniq_by
seen = Set.new
# Below you are telling uniq_by, you will be using a block with it
# by using `yield`.
select{ |x| var = yield(x); seen.add?(var) }
end
end

Read the doc of yield

Called from inside a method body, yields control to the code block (if any) supplied as part of the method call. If no code block has been supplied, calling yield raises an exception. yield can take an argument; any values thus yielded are bound to the block's parameters. The value of a call to yield is the value of the executed code block.



Related Topics



Leave a reply



Submit