Proposed RCR: Object#replace


#1

On the Sydney list, Daniel B. has been asking about a generic
Object#replace call to work similarly to Hash#replace, String#replace,
and Array#replace. I see no reason that this could not be implemented
for all objects. At a simplistic level, this is:

class Object
  def replace(other)
    other.instance_variables.each do |name|
      instance_variable_set(name, other.instance_variable_get(name))
    end
  end
end

It doesn’t necessarily work on C-based objects which may have “hidden”
state (as the Hash, String, and Array objects do), but that can be
implemented in a C-callback.

I have other thoughts about this, including how one might consider
replacing a graph so that Transaction::Simple doesn’t have object
growth on large object graphs, but that’s barely formed in my head
right now, and I thought that a discussion on the pros and cons of
having something like this – so that I can replace an object with the
contents of another object without losing the first object’s
#object_id.

-austin


#2

On Thu, Nov 17, 2005 at 02:51:47AM +0900, Austin Z. wrote:

  end
end

What about singleton methods?
Will Sydney relax the rebinding rules?


#3

On Thu, 17 Nov 2005, Austin Z. wrote:

 end

end

class SpecialFile < ::File
end

a = SpecialFile::new ‘4.txt’, ‘w’
b = SpecialFile::new ‘2.txt’, ‘w’

b.replace a

a.close

b << 42 # exception!

i see where you are coming from, but wouldn’t this more accurately be
called
something like Object#refer since all instance vars will then be shared?

regards.

-a


#4

i see where you are coming from, but wouldn’t this more accurately be called
something like Object#refer since all instance vars will then be shared?

class Object
def replace(other)
other.instance_variables.each do |name|
o = other.instance_variable_get(name)
if o.respond_to? :clone
instance_variable_set(name, o.clone)
end
end
end
end

T.


#5

On Thu, 17 Nov 2005, Trans wrote:

   end
 end

end

sure - dangerous though, resources can leak and not everything supports
clone.

i like it mind you.

-a


#6

Hi –

On Thu, 17 Nov 2005, Trans wrote:

   end
 end

end

How does instance variable duplication qualify as the general meaning
of “replace”? It’s not even part of the existing replace methods.

David


#7

How does instance variable duplication qualify as the general meaning
of “replace”? It’s not even part of the existing replace methods.

Yep. You’re right. Forget it. So I guess that answers Ara’s question
too.

T.


#8

On 11/16/05, Mauricio Fernández removed_email_address@domain.invalid wrote:

On Thu, Nov 17, 2005 at 02:51:47AM +0900, Austin Z. wrote:

On the Sydney list, Daniel B. has been asking about a generic
Object#replace call to work similarly to Hash#replace,
String#replace, and Array#replace. I see no reason that this could
not be implemented for all objects. At a simplistic level, this is:

class Object
  def replace(other)
    other.instance_variables.each do |name|
      instance_variable_set(name, other.instance_variable_get(name))
    end
  end
end

What about singleton methods?
Will Sydney relax the rebinding rules?

Why?

a = Hash.new { |h, k| raise “I am default proc for a” }
b = Hash.new { |h, k| raise “I am default proc for b” }

class << b
def x; puts “I am b.x”; end
end

b[:x] = “foo”
a.replace b
puts b[:x].object_id
puts a[:x].object_id
a.x # => NoMethodError
a[:y] # => RuntimeError: …default proc for b

Singleton methods aren’t transferred with Hash#replace now. Why should
they be with Object#replace?

On 11/16/05, Ara.T.Howard removed_email_address@domain.invalid wrote:
[…]

b << 42 # exception!

i see where you are coming from, but wouldn’t this more accurately be called
something like Object#refer since all instance vars will then be shared?

It is sharing, but it’s not just a reference. Typically, when I call
#replace, I “forget” about the item that I used to replace the original
value. However, I would suggest that File (well IO, actually) have
something equivalent to the following:

class IO
def replace; end
remove_method :replace
end

The blank define is done because Module.remove_method does not allow you
to remove a method from a class that has never had it defined in the
first place.

On 11/16/05, David A. Black removed_email_address@domain.invalid wrote:

On Thu, 17 Nov 2005, Trans wrote:
[…] (in response to Ara)

How does instance variable duplication qualify as the general meaning
of “replace”? It’s not even part of the existing replace methods.

It doesn’t and it explicitly breaks what I wanted – keeping the same
#object_id. I’ll freely admit that #replace is dangerous, but under
controlled circumstances, it can be better and more efficient (at least
memory-wise) than other methods.

-austin


#9

Ara.T.Howard wrote:

sure - dangerous though, resources can leak and not everything supports clone.

i like it mind you.

Though as David pointed out I don’t think the cloning qualifies it for
#replace actually. I have a Facet method called #assign_from. Currently
it does no duplication, but you make me think that maybe it should. And
it can be made more robust to check for #clone, then #dup and if all
else fails maybe then go ahead and reference directly.

But what resources could leak?

T.


#10

removed_email_address@domain.invalid wrote:

TypeError: can’t clone Fixnum
from (irb):4:in `clone’
from (irb):4
irb(main):005:0>

Oh, that’s nice :-/ Sheesh, can’t even trust your Ducks around here.


#11

Hmmm…but not everything that responds to :clone can be cloned.

C:>irb
irb(main):001:0> x = 1
=> 1
irb(main):002:0> x.class
=> Fixnum
irb(main):003:0> x.respond_to? :clone
=> true
irb(main):004:0> x.clone
TypeError: can’t clone Fixnum
from (irb):4:in `clone’
from (irb):4
irb(main):005:0>


#12

On Thu, 17 Nov 2005, Trans wrote:

it can be made more robust to check for #clone, then #dup and if all
else fails maybe then go ahead and reference directly.

But what resources could leak?

take an open file handle, memory map, or database connection. you’ll be
relying on the gc to close it. that may or may not work in all cases.

eg.

loop{ HasFileHandle::new.replace HasFileHandle::new }

not a show stopper - just something to consider. i don’t really see
what you
could be expected to do about it - just throwing it out there…

-a


#13
class Object
  def replace(other)
    other.instance_variables.each do |name|
      instance_variable_set(name, other.instance_variable_get(name))
    end
  end
end

I don’t like this a whole lot. It breaks encapsulation big time. If
other is a completely different object, this could be putting this
object into a strange state.

I’m not sure if there is a good general way to do this #replace with
breaking encapsulation. Maybe using something in Marshal may help.
Or what’s the big deal about just having each class make its own
#replace?


#14

On 11/16/05, Eric M. removed_email_address@domain.invalid wrote:

object into a strange state.
Obviously, I wasn’t clear. I said “At a simplistic level, this is:” …
This means that this is essentially what the operation would do, but
not necessarily the full scope of what would be needed. A more “safe”
operation would be:

class Object
def replace(other)
unless other.class == self.class
raise TypeError,
“cannot convert #{self.class} into #{other.class}”
end
other.instance_variables.each do |name|
instance_variable_set(name,
other.instance_variable_get(name))
end
end
end

This is, by the way, exactly what:

a = {}
b = []
b.replace a

does. Yes, I think it should be that restrictive – exact match only.
This isn’t #become, it’s #replace. This isn’t typecasting, it’s
replacing the internals of one object with those of another object OF
THE EXACT SAME KIND. (Granted, singleton objects tend to confuse things,
but this is closer to the behaviour of #dup than of #clone.) The
behaviour for this is relatively well-established by the #replace
implementations on the internal objects Array, Hash, and String.

I’m not sure if there is a good general way to do this #replace with
breaking encapsulation. Maybe using something in Marshal may help. Or
what’s the big deal about just having each class make its own
#replace?

  1. Marshal is extremely inefficient and it creates new objects, which is
    precisely the opposite of the desired behaviour from this (keep the
    same object ID).

  2. There are certain concerns (such as Transaction::Simple) which are
    better served by a common #replace and an optional custom #replace as
    necessary.

There are other things that I want to do, but this is, I think,
relatively non-controversial when it’s all said and done.

-austin


#15

On 11/16/05, Austin Z. removed_email_address@domain.invalid wrote:

    end

replacing the internals of one object with those of another object OF
THE EXACT SAME KIND. (Granted, singleton objects tend to confuse things,
but this is closer to the behaviour of #dup than of #clone.) The
behaviour for this is relatively well-established by the #replace
implementations on the internal objects Array, Hash, and String.

Definitely safer. But, like you said objects with their own meta
class do confuse things.

But, it still doesn’t seem very encapsulated because it is using
instance_variable_get on other.

If you have an object with instance variables/attributes @x/x/x= and
@y/y/y=, here would be a more encapsulated #replace:

def replace(other)
@x = other.x
@y = other.y
end

other then could be anything as long as it responds to x and y. Of
course Array#replace, Hash#replace, etc aren’t duck-typed like that,
but it would be nice…


#16

On 11/16/05, Eric M. removed_email_address@domain.invalid wrote:

things, but this is closer to the behaviour of #dup than of #clone.)
The behaviour for this is relatively well-established by the #replace
implementations on the internal objects Array, Hash, and String.
Definitely safer. But, like you said objects with their own meta
class do confuse things.

Not really; I addressed that in my first omnibus response.

a = {}
class << a
def x; end
end
a.x
b = {}
b.x # NoMethodError
b.replace a
b.x # NoMethodError
a.replace b
a.x

I am suggesting that we implement the same sort of behaviour that we see
in Hash#replace.

But, it still doesn’t seem very encapsulated because it is using
instance_variable_get on other.

It’s the best way to do it. It’s not perfect, actually, because it’s
only getting instance variables that are currently in self. It would
need to possibly be a bit more robust (pure pseudocode here):

class Object
def replace(other)
# check to make sure that we claim to be the same thing
# copy all instance variables from other
# delete instance variables in self that aren’t in other
# return self
end
end

Honestly, for the purposes that I need this, I have to have things
that don’t have pure correspondence to attribute accessors – and we
don’t really care whether or not we go through accessors in the least,
because we’re pulling state from the other object.

I do a lot of this already (including the instance variable deletion) in
Transaction::Simple. What I can’t do – and this is a larger problem
than just Object#replace, but this helps me get a little closer – is
deal with graphs properly after they’re marshaled.

Right now, I look for an explicit response to #replace and use that if
it’s available. I’d like to be able to just use #replace and know that
it’s safe.

-austin


#17

On Thu, 17 Nov 2005, Austin Z. wrote:

does. Yes, I think it should be that restrictive – exact match only. This
isn’t #become, it’s #replace. This isn’t typecasting, it’s replacing the
internals of one object with those of another object OF THE EXACT SAME KIND.
(Granted, singleton objects tend to confuse things, but this is closer to
the behaviour of #dup than of #clone.) The behaviour for this is relatively
well-established by the #replace implementations on the internal objects
Array, Hash, and String.

i like the idea. why not make it more generic and have semantics for a

class Object
def reinitialize other = self

end
end

paradigm/hook. a copy ctor sort of thing.

??

-a


#18

On 11/16/05, Ara.T.Howard removed_email_address@domain.invalid wrote:

class Object
def reinitialize other = self

end
end

paradigm/hook. a copy ctor sort of thing.

It’s not really a reinitialization. It’s almost #become – but not
quite. As I said, look at Transaction::Simple for the use case that I
have.

-austin


#19

Hi –

On Thu, 17 Nov 2005, Austin Z. wrote:

On 11/16/05, Eric M. removed_email_address@domain.invalid wrote:

On 11/16/05, Austin Z. removed_email_address@domain.invalid wrote:

This is, by the way, exactly what:
a = {}
b = []
b.replace a

Did you mean b = {} ?

class Object
def replace(other)
# check to make sure that we claim to be the same thing
# copy all instance variables from other
# delete instance variables in self that aren’t in other
# return self
end
end

I’m still totally not understanding what copying instance variables
has to do with Hash/String/Array#replace operations.

a = {}
b = {}
a.instance_eval { @x = 1 }
b.replace(a)
b.instance_eval { p @x } # nil

Other than the big umbrella category of “having to do with state”,
what’s the connection?

David


#20

On 11/16/05, David A. Black removed_email_address@domain.invalid wrote:

On Thu, 17 Nov 2005, Austin Z. wrote:

On 11/16/05, Eric M. removed_email_address@domain.invalid wrote:

On 11/16/05, Austin Z. removed_email_address@domain.invalid wrote:

This is, by the way, exactly what:
a = {}
b = []
b.replace a
Did you mean b = {} ?

No. It was intentionally to throw an exception, just as my (more robust)
sample implementation did.

[…]

end
Other than the big umbrella category of “having to do with state”,
what’s the connection?

It’s mostly the “having to do with state” umbrella. Consider:

a = { :x => “a” }
b = {}
b.replace a
b[:x].object_id == a[:x].object_id # true

I could certainly implement a generic #replace to be added with
Transaction::Simple; I would probably implement it to be similar to what
I have posted here. I do, however, think that there is a legitimate use
case for this being in the core and implemented either in C or in the
RubyC dialect of Ruby.

-austin