Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How proc is executed when passed to `instance_exec`

The question is inspired by this one.

Proc::new has an option to be called without a block inside a method:

Proc::new may be called without a block only within a method with an attached block, in which case that block is converted to the Proc object.

When the proc/lambda instance is passed as a code block, the new instance of Proc is being created:

Proc.singleton_class.prepend(Module.new do
  def new(*args, &cb)
    puts "PROC #{[block_given?, cb, *args].inspect}"
    super
  end
end)

Proc.prepend(Module.new do
  def initialize(*args, &cb)
    puts "INIT #{[block_given?, cb, *args].inspect}"
    super
  end
  def call(*args, &cb)
    puts "CALL #{[block_given?, cb, *args].inspect}"
    super
  end
end)

λ = ->(*args) { }
[1].each &λ
#⇒ [1]

As one might see, neither the call to Proc::new happened, nor Proc#initialize and/or Proc#call were called.

The question is: how ruby creates and executes a block wrapper under the hood?


NB Don’t test the code above in pry/irb console: they known to have glitches with pure execution of this, basically because they patch procs.

like image 715
Aleksei Matiushkin Avatar asked Nov 22 '16 10:11

Aleksei Matiushkin


1 Answers

There has been some discussion of this behavior on the Ruby Issue Tracker, see Feature #10499: Eliminate implicit magic in Proc.new and Kernel#proc.

This is an implementation artifact of YARV: YARV pushes a block on the global VM stack, and Proc::new simply creates a Proc from the topmost block on the stack. So, if you happen to call Proc.new from within a method which was called with a block, it will happily grab whatever block is on top of the stack, without ever checking where it came from. Somehow, somewhere, in the mist of time, this (let's call it) "accidental artifact" (I'd actually rather call it a bug) became a documented feature. A feature that the developers of JRuby (and presumably Rubinius, Opal, MagLev, etc.) would rather get rid of.

Since most other implementations work completely differently, this behavior which comes "for free" on YARV, makes both blocks and Proc::new pontetially more expensive on other implementations and prohibits possible optimizations (which doesn't hurt on YARV, because YARV doesn't optimize).

like image 123
Jörg W Mittag Avatar answered Oct 21 '22 06:10

Jörg W Mittag