[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Devious or what? : hacking super : should this work?

Jeremy Henty

7/15/2006 7:20:00 PM

I've discovered that an unadorned call to "super" need *not* call the
parent method with the same arguments!!! If you bind the entire
argument list to a variable you can modify the list before calling
"super". Check it out:

class Show
def initialize(*args)
puts args.inspect
end
end

Show.new(1,34,"asd")

class Show2 < Show
def initialize(*args)
puts args.shift
super
end
end

Show2.new(1,34,"asd")

===>

[1, 34, "asd"]
1
[34, "asd"]

So "args" is bound to the *actual* array of arguments, not just a
copy. Is this a bug or a feature? Might this behaviour change in
future?

Regards,

Jeremy Henty

4 Answers

dblack

7/15/2006 8:18:00 PM

0

Jeremy Henty

7/15/2006 9:14:00 PM

0

On 2006-07-15, dblack@wobblini.net <dblack@wobblini.net> wrote:
> On Sun, 16 Jul 2006, Jeremy Henty wrote:
>
>> If you bind the entire argument list to a variable you can modify
>> the list before calling "super". Check it out:
>
> ... super should be pretty transparent; for example, if an argument
> is for some reason going to be altered in place by the original
> method, you want that to still happen when you call the method that
> calls super.

True, but that's not what surprised me. Given "def foo(*args) ..." I
always expected the *members* of args to be the arguments of the
method call (and not copies of them). I did not expect args *itself*
to be part of the Ruby call stack. I assumed it was just a temporary
object created on the fly to give me access to the method arguments.
I thought that nothing else in Ruby had a reference to it, so I
expected that modifying args itself (rather than its members) would
have no effect outside of the current scope.

But I was wrong. It looks like args is the actual array of method
arguments in the Ruby call stack. Modifying args changes the call
stack, causing super to supply a different list of objects to the
parent method.

Regards,

Jeremy Henty

Dominik Bathon

7/16/2006 12:14:00 AM

0

On Sat, 15 Jul 2006 23:15:05 +0200, Jeremy Henty <jeremy@chaos.org.uk>
wrote:

> On 2006-07-15, dblack@wobblini.net <dblack@wobblini.net> wrote:
>> On Sun, 16 Jul 2006, Jeremy Henty wrote:
>>
>>> If you bind the entire argument list to a variable you can modify
>>> the list before calling "super". Check it out:
>>
>> ... super should be pretty transparent; for example, if an argument
>> is for some reason going to be altered in place by the original
>> method, you want that to still happen when you call the method that
>> calls super.
>
> True, but that's not what surprised me. Given "def foo(*args) ..." I
> always expected the *members* of args to be the arguments of the
> method call (and not copies of them). I did not expect args *itself*
> to be part of the Ruby call stack. I assumed it was just a temporary
> object created on the fly to give me access to the method arguments.

This definitely is the case for Ruby 1.8 and I can't reproduce your
example with 1.8.4, only with 1.9. I am not sure why this was changed.

In 1.8 the arguments are stored in a C array in the FRAME struct and when
splatting is used a new Ruby array is created every time:

$ cat super_splat.rb
class A
def foo(*a); p a, a.object_id; end
end

class B < A
def foo(*a); p a, a.object_id; a.shift; super; end
end

B.new.foo(1,2,3)

$ ruby super_splat.rb
[1, 2, 3]
-604430366
[1, 2, 3]
-604430956


And even in 1.9 it actually is a different Ruby array:

$ ruby19 super_splat.rb
[1, 2, 3]
-604528228
[2, 3]
-604528338


Dominik

Jeremy Henty

7/16/2006 10:50:00 AM

0

On 2006-07-16, Dominik Bathon <dbatml@gmx.de> wrote:
> On Sat, 15 Jul 2006 23:15:05 +0200, Jeremy Henty <jeremy@chaos.org.uk>
> wrote:
>
>> Given "def foo(*args) ..." I always expected the *members* of args
>> to be the arguments of the method call (and not copies of them). I
>> did not expect args *itself* to be part of the Ruby call stack. I
>> assumed it was just a temporary object created on the fly to give
>> me access to the method arguments.
>
> This definitely is the case for Ruby 1.8 and I can't reproduce your
> example with 1.8.4, only with 1.9.

I am running 1.8.5-preview1 . If I downgrade to 1.8.4 I get the
behaviour I first expected. So my expectations were not that
ridiculous, which is comforting. And I guess the answer to my
question "might this behaviour change" is clearly "yes"! (Which means
I have some code to rewrite, /me sighs.)

> In 1.8 the arguments are stored in a C array in the FRAME struct and
> when splatting is used a new Ruby array is created every time:
>
> $ cat super_splat.rb
> class A
> def foo(*a); p a, a.object_id; end
> end
>
> class B < A
> def foo(*a); p a, a.object_id; a.shift; super; end
> end
>
> B.new.foo(1,2,3)

Under 1.8.4 :
[1, 2, 3]
-605563672
[1, 2, 3]
-605563762

Under 1.8.5 :
[1, 2, 3]
-605295548
[2, 3]
-605295638

Is this an intentional change or a side-effect of something else?

> And even in 1.9 it actually is a different Ruby array:

Yes, the array is always a new VALUE (with a different
(RARRAY(self))->ptr). What's changed is the relation between the C
array of method arguments that is copied when we call super, and the C
array that is wrapped by args. It seems that in 1.8.4 the latter is a
copy of the former, so the call to super gets a copy of the array that
was originally passed to the current method, even if the contents of
args have subsequently changed. In 1.8.5 they look to be the same, so
the call to super gets a copy of the current contents of args.

Regards,

Jeremy Henty