From: Daniel Brockman Date: 2005-08-28T23:35:32+09:00 Subject: Re: Reopening with a bang: cannot assign to self Hi David, > Array#replace seems to be like collect!, it's a nicer way > around the specific problem for Array only. Well, *some* > classes only. Hash, String and Array all seem to have > #replace. But Fixnum, Bignum, Float, and File (to name > the few I just tested right now) do not. Fixnum could not possibly have it, because fixnum values are stored directly in the pointers. That is, if you do this, a = 123 b = a c = a then there will be three fixnums, one in each pointer. This is also true for symbols, true, false, and nil. > I'm asking to see if there's a general way to do it in > any class. [...] How would you implement #replace in an > arbitrary class that didn't already support it, such as > Float or StringIO? The state of a normal Ruby object depends entirely on its instance variables. Hence, normal objects can be replaced quite easily, by simply replacing their instance variables. class Object def remove_instance_variables for ivar in instance_variables do remove_instance_variable(ivar) end end def merge_instance_variables(other) for ivar in other.instance_variables do value = other.instance_variable_get(ivar) instance_variable_set(ivar, value) end end def replace(other) copy = other.clone remove_instance_variables merge_instance_variables(copy) return self end end class Foo attr_accessor :x def initialize(x) @x = x end end a = Foo.new(1) ; b = Foo.new(2) b #=> # b.replace(a) b #=> # a.x = 3 b #=> # (Of course, strange things is likely to happen if you try to use this method to change the type of an object.) There is one subtlety: neither Array#replace, Hash#replace, String#replace nor Set#replace cause the two objects to become shallow aliases of each other: a = [1] ; b = [] ; b.replace(a) ; a << 2 ; b #=> [] a = {1=>2} ; b = {} ; b.replace(a) ; a[3] = 4 ; b #=> {1=>2} a = "x" ; b = "" ; b.replace(a) ; a << "y" ; b #=> "x" require "set" a = Set[1] ; b = Set[] ; b.replace(a) a << 2 ; b #=> # That's why I defined Object#replace to clone the argument before snarfing its instance variables: require "ostruct" p = OpenStruct.new q = OpenStruct.new p.foo = 123 q.replace(p) q #=> p.bar = 456 q #=> However, none of the classes you mentioned (Float and StringIO) are "normal" in the sense required for this implementation of Object#replace to work: require "stringio" p = StringIO.new("foo") q = StringIO.new("bar") p.read(1) #=> "f" p.replace(q) # Though all p's instance variables have been replaced by # those of a copy of q, the old state of p remains: p.read(1) #=> "o" # This happens because StringIO objects do not use instance # variables to store their data: p.instance_variables #=> [] q.instance_variables #=> [] # The same is true for Float objects: a = 1.0 ; b = 2.0 a.instance_variables #=> [] b.instance_variables #=> [] b.replace(a) #=> 2.0 So how would one implement Float#replace? You can't do it without manually modifying the piece of memory in which the floating-point value is stored. This happens to be eight bytes at an offset of eight bytes into the Float object, assuming that longs and pointers are 32 bit wide. The standard #replace methods do not copy singleton methods nor instance variables, so Float#replace should reasonably not have to do this either. (In fact, floats cannot even have singleton methods. They *can* have instance variables, however --- these are stored in a global table keyed by the object's memory address instead of in the object itself.) Florian Groß and Julio Fernández Pradier have implemented a method called Object#become, which uses DL::PtrData to "simply" overwrite one object with (a copy of) another. See evil.rb, which can be found at the following URL: http://rubyforge.org/cgi-bin/viewcvs.cgi/evil/lib/evil.rb?rev=1.40&cvsroot=evil I hope this helps, -- Daniel Brockman