Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I pass a block to a Proc?

Tags:

ruby

I'm wondering if it's possible to pass a block to a Proc. Simply passing a block to Proc.call doesn't work:

foo = Proc.new {
  yield
}

foo.call {
  puts "test"
}

Results in:

LocalJumpError: no block given (yield)

The same happens with lambdas. However this does work with method objects:

class Foo
  def bar
    yield
  end
end

bar = Foo.new.method :bar

bar.call { puts "Success!" }

Results in:

Success!

The odd thing is that it still works after converting the method object into a proc:

bar.to_proc.call { puts "Success!" }

Results in:

Success!

So how come the Proc that was made from a block doesn't accept blocks, but the Proc that was originally a method does? Is it possible to create Procs from blocks that accepts blocks?

like image 557
Hubro Avatar asked Oct 01 '13 23:10

Hubro


2 Answers

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.

like image 70
Linuxios Avatar answered Nov 15 '22 22:11

Linuxios


The above answer is not 100% correct therefore can't be accepted answer. Especially the part;

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.

This is wrong. Procs and lambdas can call yield in their bodies. The fact to keep in mind is, Proc/lambda bodies have a lexical scope! Which means, if there is a block while defining the Proc/lambda, yield would successfully execute, like so;

def foo
  my_proc = Proc.new { yield }
  my_proc.call
end

foo { puts "Hello world!" } # would print "Hello world!"

As you can see, yield worked! Because there was block while defining the Proc.

One can say, the Proc is unfolded into method which has block while calling therefore yield worked. This is also wrong and can be disproved easily with the following snippet;

def foo
  @my_proc ||= Proc.new { yield }
  @my_proc.call
end

foo { puts "Hello again!" } # would print "Hello world!"
foo # would print "Hello world!"

As you can again see, it's about having block while defining the Proc.

If you want to have better understanding of whats being lexically scoped mean, let's have a look at the following example.

class Foo
  def self.hello_proc
    Proc.new { puts name }
  end

  def self.name
    "Alice"
  end
end

class Bar
  def self.put_name
    Foo.hello_proc.call
  end

  def self.name
    "Bob"
  end
end

Bar.put_name # would print "Alice"

You can copy and paste above code to an irb session to see what is the output. The reason it puts "Alice" is, the name was "Alice" while the Proc's being defined.

like image 26
Foo Bar Zoo Avatar answered Nov 15 '22 21:11

Foo Bar Zoo