Hi i'm creating a DSL, and would like to use the following functionality.It works fine in irb but when I run it from the
commandline. Does it look like my fault or a ruby bug?
class A
def method
puts "a"
end
def A.create
A.new.instance_eval { yield }
end
end
A.create{method}
PS: I'm on "ruby 1.8.6 (2007-09-24 patchlevel 111) [i386-mswin32]"
Temp.rb:11:in `method': wrong number of arguments (0 for 1)
(ArgumentError)
from Temp.rb:11
from Temp.rb:7:in `create'
from Temp.rb:7:in `instance_eval'
from Temp.rb:7:in `create'
from Temp.rb:11
When i run it from commandline.
Thanks a lot for your insight.
-Patrick
Maybe I'm missing something, but shouldn't that be:
class A
def method
puts "a"
end
def A.create &block
A.new.instance_eval &block
end
end
···
On Aug 8, 3:26 pm, Patrick Li <patrickli_2...@hotmail.com> wrote:
Hi i'm creating a DSL, and would like to use the following
functionality.It works fine in irb but when I run it from the
commandline. Does it look like my fault or a ruby bug?
class A
def method
puts "a"
end
def A.create
A.new.instance_eval { yield }
end
end
I think the issue is in the last line:
you are calling Object#method', which expects one parameter. You
ought to be using the String "method" to send to instance_eval.
-Adam
···
On 8/8/08, cardboard42@gmail.com <cardboard42@gmail.com> wrote:
On Aug 8, 3:26 pm, Patrick Li <patrickli_2...@hotmail.com> wrote:
> Hi i'm creating a DSL, and would like to use the following
> functionality.It works fine in irb but when I run it from the
> commandline. Does it look like my fault or a ruby bug?
>
> class A
> def method
> puts "a"
> end
> def A.create
> A.new.instance_eval { yield }
> end
> end
>
> A.create{method}
Hey that works. Thanks that's a suitable workaround for me. Though I see
nothing wrong with my version. I think it's probably a bug. Can someone
confirm for me?
I think there's a bug in how ruby treats block parameters.
ie. the following works
def myMethod &block
A.new.instance_eval &block
end
but this doesn't
def myMethod
A.new.instance_eval{yield}
end
For reasons I cannot quite understand at the moment, the block for instance_eval is executed in the scope of the receiver, but yield inside that block will cause the block given to the *caller* to execute, not raise a LocalJumpError as one would expect (since there's no block to yield inside the object). That block is executed at its original scope:
>> @foo = :outside
=> :outside
>> class Klass
>> def initialize
>> @foo = :inside
>> end
>> end
>> Klass.new.instance_eval { @foo }
=> :inside # As expected
>> def lol; Klass.new.instance_eval { yield } end
>> lol do puts @foo end
=> :outside # o.O
I might be completely missing something here, but I would expect that last line to raise that darn LocalJumpError. This shows that the “current block” inside the instance_eval is indeed what's passed to the caller.
…
>> def lol; Klass.new.instance_eval { Proc.new } end # Proc.new with no block returns the current block, cf. http://bit.ly/37NtlX
>> my_proc = proc { :test }
=> #<Proc:0x0004fb28@(irb):15>
>> lol &my_proc
=> #<Proc:0x0004fb28@(irb):15>
>> lol(&my_proc) == my_proc
=> true
This might be a bug.
···
On Aug 8, 2008, at 22:16, Patrick Li wrote:
--
# Mikael Høilund
def method_missing(m, a=0) a +
m.to_s[/[a-z]+/].size * 2; end
p What is the meaning of life?
I think what's happening is that yield is calling the block passed to
the caller of instance_eval so method is being called on self in that
block's closure. Since it was executed at the top level that would be
Object. Object.method takes 1 parameter, thus causing the error you're
seeing. The reason the code I posted works because it takes the block
passed to create and passes it directly to instance_eval as it's
block. This means that method will be called on self in instance_eval,
where it is set to the object instance_eval is called on, in this case
the result of A.new.
Sorry if my explanation is hard to understand, but I'm positive this
is not a bug in ruby. instance_eval { yield } simply has very
different semantics from instance_eval &block.
The only effect of instance_eval on the block it gets as parameter is
that self is changed. It influences only instance variables and method
calls without explicit receiver, nothing else. yield is tied to the
method it is in, not what self is at that point, so it is not affected
by instance_eval.
This should also answer the OP's question: since yield is unaffected,
it doesn't change self in the block it calls. So it's definitely not a
bug. You're calling #method on the toplevel object, which is an
instance of Object, and Object#method expects 1 parameter.
Peter
···
On Fri, Aug 8, 2008 at 10:36 PM, Mikael Høilund <mikael@hoilund.org> wrote:
On Aug 8, 2008, at 22:16, Patrick Li wrote:
Thanks for all your comments:
I think there's a bug in how ruby treats block parameters.
ie. the following works
def myMethod &block
A.new.instance_eval &block
end
but this doesn't
def myMethod
A.new.instance_eval{yield}
end
For reasons I cannot quite understand at the moment, the block for
instance_eval is executed in the scope of the receiver, but yield inside
that block will cause the block given to the *caller* to execute, not raise
a LocalJumpError as one would expect (since there's no block to yield inside
the object). That block is executed at its original scope:
I think what's happening is that yield is calling the block passed to
the caller of instance_eval so method is being called on self in that
block's closure. Since it was executed at the top level that would be
Object. Object.method takes 1 parameter, thus causing the error you're
seeing. The reason the code I posted works because it takes the block
passed to create and passes it directly to instance_eval as it's
block. This means that method will be called on self in instance_eval,
where it is set to the object instance_eval is called on, in this case
the result of A.new.
Sorry if my explanation is hard to understand, but I'm positive this
is not a bug in ruby. instance_eval { yield } simply has very
different semantics from instance_eval &block.
Absolutely correct, there is no bug - at least not in Ruby. You can also see it from this:
$ ruby <<XXX
> class A
> def method
> puts "a"
> end
> def A.create
> A.new.instance_eval { yield }
> end
> end
>
> A.create{ p self; method}
>
> XXX
main
-:10:in `method': wrong number of arguments (0 for 1) (ArgumentError)
from -:10
from -:6:in `create'
from -:6:in `instance_eval'
from -:6:in `create'
from -:10
Note the output of "p self": it's main. And this is the case because self is not changed for the block invoked via "yield".
Note also that this does not work in IRB as well:
$ irb
irb(main):001:0> class A
irb(main):002:1> def method
irb(main):003:2> puts "a"
irb(main):004:2> end
irb(main):005:1> def A.create
irb(main):006:2> A.new.instance_eval { yield }
irb(main):007:2> end
irb(main):008:1> end
=> nil
irb(main):009:0>
irb(main):010:0* A.create{ p self; method}
main
ArgumentError: wrong number of arguments (0 for 1)
from (irb):10:in `method'
from (irb):10
from (irb):6:in `create'
from (irb):6:in `instance_eval'
from (irb):6:in `create'
from (irb):10
irb(main):011:0>