Issue #6688 has been updated by prijutme4ty (Ilya Vorontsov).


trans (Thomas Sawyer) wrote:
> =begin
> I don't think #replace works on Enumerables, rather it works on Array and Hash.
> 
> It does seem a rather powerful notion to be able to replace an object with another wherever is may be referenced. Though @shudo may be right that it is too dangerous. With good design, a proper interface can handle the reassignment through the usual mechanisms, although admittedly it may entail many more compute cycles to do it.
> 
> On the other hand, if Smalltalk supports it then perhaps it's worth consideration. Always respect the Smalltalk :) But I agree with it, better name is #become.  
> 
> Also note that a less dangerous notion of a generic replace is simply to copy common instance variables.
> 
>   class X
>     def initialize(a)
>       @a = a
>     end
>   end
> 
>   class Y
>     def initialize(a,b)
>       @a, @b = a, b
>     end
>   end
> 
>   x = X.new(1)
>   y = Y.new(2,3)
> 
>   x.a  #=> 1
>   x.replace(y)
>   x.a  #=> 2
> 
> Although a more appropriate name for this is probably #instance_replace. This preserves object identity, but can only be used to "copy" an object of the same type --which, if you think about it, is what Array#replace and Hash#replace does too actually.
> =end

I've answered Shugo about dangers. I suppose that it's not more dangerous than bang-methods.

Also when one changes constants for stubbing a class it's almost the same dangerous but particulary useful.

Try to write Hash#with_indifferent_access! that gives an object ability to understand both symbols and strings without defining singleton methods (because you can't dump them).

What for me, good design is a design that gives me ability to write flexible and concise code. And nothing more. Implicit typing and duck-typing obliges us to write tests for all. But result worth spent efforts. You can use tests you've already wrote to test that everything works. And you don't need to write and test complex data structres in order to support multiple structres consistency (here you can make much more bugs).

----------------------------------------
Feature #6688: Object#replace
https://bugs.ruby-lang.org/issues/6688#change-27954

Author: prijutme4ty (Ilya Vorontsov)
Status: Open
Priority: Normal
Assignee: 
Category: 
Target version: 


I suggest that #replace works not only on Enumerables but on any Object. It can make use the same object in different places more consistent. It makes it possible to write
class Egg; end
class Hen; end
class HenHouse; attr_accessor :species; end
class Incubator; def incubate(egg) Hen.new; end

# Here it is!
class IncubatorWithReplace; 
  def incubate(egg) 
    egg.replace(Hen.new)
  end
end

e1,e2,e3 = Egg.new, Egg.new, Egg.new
h1, h2 = HenHouse.new, HenHouse.new

# One egg is shared between hen houses
h1.species = [e1, e2]
h2.species = [e1, e3]
p h1 # ==> <HenHouse @species = [#<Egg 1>,#<Egg 2>]
p h2 # ==> <HenHouse @species = [#<Egg 1>,#<Egg 3>]


 # First option. It's bad choise because it makes two "data structures" HenHouse inconsistent: 
 #   they have different object while must have the same
h1[0] = Incubator.new.incubate(h1[0])
p h1 # ==> <HenHouse @species = [#<Hen>,#<Egg 2>]
p h2 # ==> <HenHouse @species = [#<Egg 1>,#<Egg 3>]

 # Second option is ok - now both shared objects're changed.
IncubatorWithReplace.new.incubate(h1[0])
h1 # ==> <HenHouse @species = [#<Hen>,#<Egg 2>]
h2 # ==> <HenHouse @species = [#<Hen>,#<Egg 3>]  

 # Third option is bad - it wouldn't affect HenHouses at all
e1 = Incubator.new.incubate(e1)
p h1 # ==> <HenHouse @species = [#<Egg 1>,#<Egg 2>]
p h2 # ==> <HenHouse @species = [#<Egg 1>,#<Egg 3>]

 # while Fourth option is ok and works as second do
IncubatorWithReplace.new.incubate(e1) ## would affect both HenHouses
p h1 # ==> <HenHouse @species = [#<Hen>,#<Egg 2>]
p h2 # ==> <HenHouse @species = [#<Egg 1>,#<Egg 3>]


I can't imagine how it'd be realized, it looks like some dark magic with ObjectSpace needed to replace one object at a reference with another at the same reference. But I didn't found a solution.

About ret-value. I think it should be two forms:
Object#replace(obj, retain = false)
If retain is false #replace should return a reference to a new object (in fact the same reference as to old object but with other content)
If retain is true, old object should be moved at another place and new reference to it is returned, so:
e1 # ==> <Egg id:1>
e1.replace( Hen.new, true ) # ==> <Egg id:2>
e1 # ==> <Hen id:1>


-- 
http://bugs.ruby-lang.org/