Works in irb but not elsewhere

P

Patrick Li

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]"
 
P

Patrick Li

Oh and I get:

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
 
C

cardboard42

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}

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
 
A

Adam Shelly

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
 
P

Patrick Li

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?

Thanks again for the workaround.
-Patrick
 
P

Patrick Li

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

Thanks goes to cardboard42 for noticing that.
 
M

Mikael Høilund

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 =20
instance_eval is executed in the scope of the receiver, but yield =20
inside that block will cause the block given to the *caller* to =20
execute, not raise a LocalJumpError as one would expect (since there's =20=

no block to yield inside the object). That block is executed at its =20
original scope:
=3D> :eek:utside # o_O

I might be completely missing something here, but I would expect that =20=

last line to raise that darn LocalJumpError. This shows that the =20
=93current block=94 inside the instance_eval is indeed what's passed to =20=

the caller.

=85
no block returns the current block, cf. http://bit.ly/37NtlX=3D> true

This might be a bug.

--=20
# Mikael H=F8ilund
def method_missing(m, a=3D0) a +
m.to_s[/[a-z]+/].size * 2; end
p What is the meaning of life?
 
C

cardboard42

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.

Ken
 
C

Calamitas

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 rai= se
a LocalJumpError as one would expect (since there's no block to yield ins= ide
the object). That block is executed at its original scope:

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
 
R

Robert Klemme

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>


Kind regards

robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,480
Members
44,900
Latest member
Nell636132

Latest Threads

Top