2

When I pass a lambda to instance_eval as the block, it seems to pass an extra argument:

lamb = -> { puts 'hi' }
proc = Proc.new { puts 'hi' }
instance_eval(&lamb)
# >> ArgumentError: wrong number of arguments (given 1, expected 0)
#    from (irb):5:in `block in irb_binding'
#    from (irb):7:in `instance_eval'
instance_eval(&proc)
# => hi
instance_exec(&lamb)
# => hi

Why is this the case? Note that this question is NOT about why lambda throws an error. That is well understood. The question is about WHY instance_eval sends self of the receiver as a parameter. It is not needed, and confusing. And AFAIK not documented.

This helps, but doesn't explain WHY ruby would do it this way. The whole point of instance_eval is to set self to the receiver; why confuse things by also passing self to the proc?

sawa
  • 165,429
  • 45
  • 277
  • 381
Mitch VanDuyn
  • 2,838
  • 1
  • 22
  • 29
  • [Same question](https://stackoverflow.com/questions/12648157/instance-evals-block-arguments-documented-purpose), though not really answered in my opinion – Marcin Kołodziej Oct 03 '18 at 03:06
  • It is not just for `instance_eval`. Other methods like `tap` (and `Thread.new`) work in the same way. – sawa Oct 04 '18 at 05:31

1 Answers1

3

From the docs

For procs created using lambda or ->() an error is generated if the wrong number of parameters are passed to a Proc with multiple parameters. For procs created using Proc.new or Kernel.proc, extra parameters are silently discarded.

In your case both lamb and proc called with one parameter

From the docs of instance_eval

When instance_eval is given a block, obj is also passed in as the block's only argument

instance_eval is method of BasicObject class and can be called within instance. So given block will have access for private methods for example.

class Test
  def call
    secret_number + 100
  end
  private
  def secret_number
    42
  end
end

test = Test.new
show_secret = -> (obj) { puts secret_number }

test.instance_eval(&show_secret) # print 42

Without instance self of current context will be passed as an argument. I think instance_eval was designed more for calling it within objects.

From the docs of instance_eval

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 and private methods.

Fabio
  • 31,528
  • 4
  • 33
  • 72
  • 1
    yes I understand that... The question is why is there this extra parameter being passed from instance_eval... I can find it documented nowhere. – Mitch VanDuyn Oct 03 '18 at 02:42
  • @MitchVanDuyn, it is in documentation of [instance_eval](https://ruby-doc.org/core-2.5.1/BasicObject.html#method-i-instance_eval) – Fabio Oct 03 '18 at 02:43
  • 1
    Thanks... Answer accepted. Still is most curious why ruby would do this. Seeing as I have access to self inside the block, why add this complication. – Mitch VanDuyn Oct 03 '18 at 02:46
  • @MitchVanDuyn, because `instance_eval` was intended for use within instances, check updated answer. – Fabio Oct 03 '18 at 03:09
  • I'm not 100% satisfied but its seems this is a comprimise so that things like `each(&:puts)` will work. Its just unfortunate that instance_eval does not ask if the proc is a lambda (and if it is DONT add the extra param) – Mitch VanDuyn Oct 04 '18 at 09:42
  • just posted a detailed answer here on my theory of why here: https://stackoverflow.com/questions/12648157/instance-evals-block-arguments-documented-purpose/52643962#52643962 – Mitch VanDuyn Oct 04 '18 at 10:03