Is a block converted to a Proc object before yield?

Hello!

As you know, in Ruby, a block is not an object for some reasons, one of
which is performance.

def f
yield
end

def g &blk
blk.call
end

In g, a block is explicitly converted to a Proc object.
What about in f?
Is a block still converted to a Proc object implicitly?

If it’s not converted, I understand the performance issue.
However, it’s always converted to a Proc object, I don’t understand why
blocks improve performance just because they are not objects.

Can somebody explain this, please?

Thanks.

Sam

Hi,

In message “Re: Is a block converted to a Proc object before yield?”
on Fri, 30 Jun 2006 02:57:32 +0900, Sam K. [email protected]
writes:

|Is a block still converted to a Proc object implicitly?

Not under the current implementation.

						matz.

Yukihiro M. wrote:

|Is a block still converted to a Proc object implicitly?

Not under the current implementation.

  					matz.

I’m pretty sure that this is a very official answer…:slight_smile:

Matz, I want to take this opportunity to thank you for making me a happy
programmer with Ruby.

Sam

Joel VanderWerf wrote:

Sam K. wrote:

blk.call
Can somebody explain this, please?
You’ve got the answer already, but it’s interesting to see how much of a
difference it makes: (YMMV, of course.)

Thank you for the benchmark which convinces me of blocks being more
efficient.

What a lovely community Ruby has!

Sam

Sam K. wrote:

blk.call
Can somebody explain this, please?
You’ve got the answer already, but it’s interesting to see how much of a
difference it makes: (YMMV, of course.)

require ‘benchmark’

def outer11(&bl)
inner1(&bl)
end

def outer12(&bl)
inner2(&bl)
end

def outer21
inner1 {yield}
end

def outer22
inner2 {yield}
end

def inner1(&bl)
bl.call
end

def inner2
yield
end

n = 100000

Benchmark.bmbm(10) do |rpt|
rpt.report(“outer11”) do
n.times {outer11{}}
end

rpt.report(“outer12”) do
n.times {outer12{}}
end

rpt.report(“outer21”) do
n.times {outer21{}}
end

rpt.report(“outer22”) do
n.times {outer22{}}
end
end

END

Output:

Rehearsal ---------------------------------------------
outer11 0.890000 0.010000 0.900000 ( 0.894500)
outer12 0.370000 0.000000 0.370000 ( 0.364880)
outer21 0.770000 0.000000 0.770000 ( 0.776638)
outer22 0.170000 0.000000 0.170000 ( 0.163393)
------------------------------------ total: 2.210000sec

            user     system      total        real

outer11 0.490000 0.000000 0.490000 ( 0.491969)
outer12 0.400000 0.000000 0.400000 ( 0.396264)
outer21 0.760000 0.000000 0.760000 ( 0.764508)
outer22 0.160000 0.000000 0.160000 ( 0.161982)

While to anybody else this code made things clear, for me it is still
a little bit confusing:

why outer12 is performing slower than outer22?

According to prev posts, I have understood that the usage of
block.call requires a conversion to a Proc and this is slower. But in
above case where is this conversion taking place? (or simply, why is
it slower?).

thanks (and sorry to be confused),

./alex

.w( the_mindstorm )p.

(http://themindstorms.blogspot.com)

Alexandru P. wrote:

While to anybody else this code made things clear, for me it is still
a little bit confusing:

why outer12 is performing slower than outer22?

According to prev posts, I have understood that the usage of
block.call requires a conversion to a Proc and this is slower. But in
above case where is this conversion taking place? (or simply, why is
it slower?).

def outer12(&bl)
inner2(&bl)
end

The conversion from block to a Proc object happens because of the &bl in
the above definition. When outer12 is actually called, the Proc is
instantiated and the variable bl is bound to it.

def outer22
inner2 {yield}
end

No & here, so no Proc is created.

Thanks Joel… it looks like my initial understanding was wrong. It is
not block.call the one that triggers block to Proc conversion, but in
fact passing blocks as parameters. Is this the correct understanding?

./alex

.w( the_mindstorm )p.

(http://themindstorms.blogspot.com)

Alexandru P. wrote:

Thanks Joel… it looks like my initial understanding was wrong. It is
not block.call the one that triggers block to Proc conversion, but in
fact passing blocks as parameters. Is this the correct understanding?

Right, it happens when the block is passed to a method that expects a
“&block” argument (or if the method explicitly calls Proc.new). If you
are executing “block.call”, then “block” is a variable and it, like any
variable, refers to an object.

It’s up to the callee, not the caller, to determine how to treat the
block: either yield to it or turn it into an object. The former has a
small performance advantage (and yield is an elegant notation). The
latter allows the Proc object to be saved away somewhere and called even
after the method has finished.

Joel VanderWerf wrote:

Sam K. wrote:

Hello!

As you know, in Ruby, a block is not an object for some reasons, one of
which is performance.

I though this had been largely optimized. Can’t ruby refrain from
instatiating the block as a proc until it is needed as such? If so then
simply calling .call on the block reference could just trigger yield.
That would give them nearly the same performance characteristics.

T.

On Jul 1, 2006, at 8:05 PM, Joel VanderWerf wrote:

Alexandru P. wrote:

Thanks Joel… it looks like my initial understanding was wrong.
It is
not block.call the one that triggers block to Proc conversion, but in
fact passing blocks as parameters. Is this the correct understanding?

Right, it happens when the block is passed to a method that expects
a “&block” argument (or if the method explicitly calls Proc.new).
If you are executing “block.call”, then “block” is a variable and
it, like any variable, refers to an object.

I like to compare these situations as ‘explicit block argument’ vs.
‘implicit block argument’. You could also think of it as named vs.
anonymous. In any case, as long as the block remains unnamed/
implicit there is no need to package it up into an explicit object
(i.e. instance of Proc). It is the objectification step that burns
up the extra cycles.

Gary W.

Hi –

On Mon, 3 Jul 2006, [email protected] wrote:

executing “block.call”, then “block” is a variable and it, like any
variable, refers to an object.

I like to compare these situations as ‘explicit block argument’ vs. ‘implicit
block argument’. You could also think of it as named vs. anonymous. In any
case, as long as the block remains unnamed/implicit there is no need to
package it up into an explicit object (i.e. instance of Proc). It is the
objectification step that burns up the extra cycles.

I see it (subtly) differently. The block itself isn’t an argument to
the method; it’s a syntactic construct, more analogous to the argument
list than to a particular argument. In that capacity, it’s always
explicit; that is, it’s written out as part of the method call (by
definition). It’s anonymous, but only in the sense that one could say
an argument list is anonymous; there’s no ArgumentList class for an
argument list to be a named instance of, and no Block class for blocks
to be named instances of, so anonymity is sort of a red herring.

From that perspective, the objectification is entirely layered on top
of the block; it doesn’t change the status of the block per se.

David


David A. Black ([email protected])
Ruby Power and Light, LLC (http://www.rubypowerandlight.com)

See what the readers are saying about “Ruby for Rails”!