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 #=> #<Foo:0xb7a8a360 @x=2>
   b.replace(a)
   b #=> #<Foo:0xb7a8a360 @x=1>
   a.x = 3
   b #=> #<Foo:0xb7a8a360 @x=1>

(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 #=> #<Set: {1}>

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 #=> <OpenStruct foo=123>
   p.bar = 456
   q #=> <OpenStruct foo=123>

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 Fernndez 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 <daniel / brockman.se>