How to Evaluate a Block Inside a Proc

Can I evaluate a block inside a Proc?

No you can't, because the Proc you've created is an independent yield - that is, it's a yield that has no block in its context. Although you can call procs with specified parameters and thereby pass the parameters into the proc, yield doesn't work based on specified parameters; it executes the block found within the proc's closure. And the proc's closure is predefined; it is not modified just because you call it later with a block.

So it's equivalent of just typing 'yield' straight into irb (not within any method definitions) which returns the LocalJumpError: no block given (yield) error.

evaluate a proc object in different context

You must pass the Proc as an argument so it's executed in that context.

Practice.instance_eval(&p)

Can I pass a block to a Proc?

Procs can't accept blocks as implicit arguments (the format you're trying). A proc can receive other proc objects as arguments, either explicitly, or using & arguments. Example:

a = Proc.new do |&block|
block.call
end

a.call() {puts "hi"}

yield is a bit of laguage level magic that only works in the context of a method.

Evaluate a block in a certain scope and pass an argument

Use instance_exec instead:

instance_exec(arg...) {|var...| block } → obj

Executes the given block within the context of the receiver (obj). In order to set the context, the variable self is set to obj while the code is executing, giving the code access to obj’s instance variables. Arguments are passed as block parameters.

So this will make it go:

"Foo".instance_exec(3, &block)

and give you the 'foo 3' that you desire.

The problem with this:

"Foo".instance_eval { block.call(3) }

is that self will be "Foo" inside { block.call(3) } but not inside block, block will retain whatever self was when block was defined; there's nothing in block.call(3) that forces a context so self doesn't change. For example, given this:

class C
def m
proc { |n| "#{downcase} #{n}" }
end
end
c = C.new
"Foo".instance_eval { c.m.call(3) }

When the proc is called, self will be c because that's what self was when the proc was defined (i.e. when m was called). What self is inside the instance_eval block doesn't have any effect on self inside the proc.

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.

using proc instead of block inside hash iterator

What your doing here in your attempt is wrapping yield with a Proc, but in reality you can replace yield with the Proc:

def each_word(&proc)
rank=0
keys = self.sort_by{|k,v| v}.reverse.to_h.keys
keys.each do |key, abs, rel|
if proc
proc.call(rank+=1, key,self[key], self.frequency(key))
end
end
end

What &proc does it take the block that is passed in (whether with do ... end or { ... } syntax) and converts it to a proc.

To check whether a block was given, you would simply use if proc (whereas if you were using yield, you'd use block_given?). You could alternatively use proc&.call which is the safe navigation operator.

By the way, you should probably change rank +=1 to rank + 1, there's no no point reassigning the variable's value here since it's not going to change the value in the hash anyway (numbers are immutable).

Is it possible to see the ruby code in a proc?

Take a look at the sourcify gem:

proc { x + y }.to_source
# >> "proc { (x + y) }"

How do I convert a Proc to a block in a Ruby C extension?

From the pickaxe, p. 871 (1.9 edition)

VALUE rb_iterate( VALUE (*method)(), VALUE args, VALUE (*block)(), VALUE arg2 )

Invokes method with argument args and block block. A yield from that
method will invoke block with the argument given to yield and a second
argument arg2.

So pass your Proc objects as arg2 and define a (*block)() function that just forwards the passed value to the Proc's #call method.

Something like

for (i = 0; i < numProcs; i++)
{
rb_iterate( forwarder, receiver, block, procs[i] );
}

/*...*/

VALUE forwarder(VALUE receiver)
{
// the block passed to #instance_eval will be the same block passed to forwarder
return rb_obj_instance_eval(0, NULL, receiver);
}
VALUE block(VALUE proc)
{
return rb_funcall(proc, rb_intern("call"), 0);
}

I haven't tested this code, but it's consistent with the caveats in this article.

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.



Related Topics



Leave a reply



Submit