Forum: Ruby Proposed RCR: Object#replace

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
halostatue (Guest)
on 2005-11-16 19:54
(Received via mailing list)
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
mfp (Guest)
on 2005-11-16 20:06
(Received via mailing list)
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?
ara.t.howard (Guest)
on 2005-11-16 20:18
(Received via mailing list)
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
transfire (Guest)
on 2005-11-16 21:24
(Received via mailing list)
> 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.
dblack (Guest)
on 2005-11-16 21:34
(Received via mailing list)
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
ara.t.howard (Guest)
on 2005-11-16 21:55
(Received via mailing list)
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
halostatue (Guest)
on 2005-11-16 21:58
(Received via mailing list)
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
transfire (Guest)
on 2005-11-16 22:04
(Received via mailing list)
> 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.
rmagick (Guest)
on 2005-11-16 22:04
(Received via mailing list)
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>
transfire (Guest)
on 2005-11-16 22:13
(Received via mailing list)
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.
transfire (Guest)
on 2005-11-16 22:19
(Received via mailing list)
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.
ara.t.howard (Guest)
on 2005-11-16 22:25
(Received via mailing list)
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
eric.mahurin (Guest)
on 2005-11-16 23:50
(Received via mailing list)
>     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?
halostatue (Guest)
on 2005-11-17 00:02
(Received via mailing list)
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
eric.mahurin (Guest)
on 2005-11-17 01:14
(Received via mailing list)
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...
halostatue (Guest)
on 2005-11-17 01:44
(Received via mailing list)
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
ara.t.howard (Guest)
on 2005-11-17 02:17
(Received via mailing list)
On Thu, 17 Nov 2005, Austin Z. wrote:

<snip>

> 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
halostatue (Guest)
on 2005-11-17 03:30
(Received via mailing list)
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
dblack (Guest)
on 2005-11-17 04:18
(Received via mailing list)
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
halostatue (Guest)
on 2005-11-17 06:33
(Received via mailing list)
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
J. M. (Guest)
on 2005-11-18 05:38
halostatue wrote:
> 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. [snip]

What if the receiver of #replace has instance variables that don't exist
in the parameter?  Do you intend for them to go away?  If they don't,
you could break code that uses
    if @instancevar ...

Doing so would require getting both sets of instance variables etc...
halostatue (Guest)
on 2005-11-18 17:21
(Received via mailing list)
On 11/17/05, J. Merrill <removed_email_address@domain.invalid> wrote:
>>       unless other.class == self.class
>> This is, by the way, exactly what:
>     if @instancevar ...
>
> Doing so would require getting both sets of instance variables etc...

That's easier than one might think. I already do a lot of this work in
Transaction::Simple.

-austin
J. M. (Guest)
on 2005-11-18 17:41
halostatue wrote:
> On 11/17/05, J. Merrill <removed_email_address@domain.invalid> wrote:
>> Doing so would require getting both sets of instance variables etc...
>
> That's easier than one might think. I already do a lot of this work in
> Transaction::Simple.
>
> -austin

It's not difficult, but when you didn't include it in your "how this
should work" sample code you left it unclear as to whether you _want_
existing instance vars (that don't exist in the replacing-from object)
to be removed from the receiver.  Am I right that you _do_ want them to
be removed?
halostatue (Guest)
on 2005-11-18 17:55
(Received via mailing list)
On 11/18/05, J. Merrill <removed_email_address@domain.invalid> wrote:
> It's not difficult, but when you didn't include it in your "how this
> should work" sample code you left it unclear as to whether you _want_
> existing instance vars (that don't exist in the replacing-from object)
> to be removed from the receiver.  Am I right that you _do_ want them to
> be removed?

Yes.

-austin
This topic is locked and can not be replied to.