From: "David A. Black" Date: 2008-09-09T01:36:46+09:00 Subject: Re: "Pointer" to an object? (i.e. reflect changes to origina Hi -- On Tue, 9 Sep 2008, Doug Glidden wrote: > David A. Black wrote: > [snip] >> You have to know the difference between assignment and object >> mutation, and use whichever you think is right in a given situation. > > Okay, but...well, I'll comment more on this in a minute. > > [snip] > >> The things that come to mind are aliasing methods, overriding methods, >> and possibly writing a modified version of OpenStruct that automates >> the twinning of attributes, or maybe attr_twins or something. It's >> miles from anything that suggests to me a need to re-engineer Ruby's >> object model. > > I don't think I'm suggesting re-engineering the object model, merely > adding the possibility of creating pointers. It could be (and probably > is) as simple as adding a Pointer class, but as far as I can tell that > would require delving into C, which is definitely not my forte. It's a radical change because it adds a completely new axis to the language, in which variables have a significance that's quite different from what they have now. Ruby variables don't know about each other; if you do: a = x b = x c = x x doesn't know how many references there are to it, and reusing any of the three identifiers has no implications for the others. If you have: a = x b = *a a = y # b is now y (aside from the fact that *a is already meaningful, but never mind that) you've now introduced the concept of a pointer to a variable, which is very different from a reference to an object. > The long and short is, I don't know how to do this. Maybe I'm being > dense, but I'm not catching what you have in mind with the list of > options above (aliasing methods, overriding methods, etc.). The most > elegant solution I've come up with is an ObjectWrapper class... > > class ObjectWrapper > attr_accessor :wrapped_object > > def initialize(object) > @wrapped_object = object > end > > def method_missing(method, *args) > @wrapped_object.send method, *args > end > > def respond_to?(method) > @wrapped_object.respond_to? method > end > end > > ...which still seems decidedly inelegant (not to mention extraordinarily > wasteful), as it requires wrapping every object that could ever be > pointed to with it. IMHO, an object shouldn't need to know that it's > being pointed to. In other words, the following is elegant and > least-surprising, but impossible to implement without dropping into C, > as far as I can tell: > > object = MyObject.new > copy = *object > ... > object = MyOtherObject.new # object and copy are both changed > > On the other hand, forcing the following is neither elegant nor > least-surprising: > > object = ObjectWrapper.new MyObject.new > copy = object > ... > # I might accidentally use object = MyOtherObject.new -- whoops! That > doesn't > # do what I expected at all, but its not a noticeable error, either. > object.wrapped_object = MyOtherObject.new # this does what I intended > > Furthermore, anywhere in the program that we want to access the MyObject > instance itself, we have to reference object.wrapped_object, which is > painful and far too easy to forget. > > If Object had a #replace method like String, Hash, Array, et al, that > would be an acceptable (albeit annoying) workaround, but that's not > available either. There's talk now and then of a "become" method, which would act kind of like what you're describing: a = something c = a a.become(another) # all something refs are now another refs but it might actually do too much. (There's an implementation of it in the 'evil.rb' library, I believe.) I would consider something like this, which achieves the kind of coupling you need in a fairly specific way (it doesn't affect all references, but affects the way the attributes relate): module Twinnable def attr_twin(a,b) (class << self;self;end).class_eval do attr_accessor b define_method(a) { send(b) } define_method("#{a}=") do |val| send("#{b}=", val) instance_variable_set("@#{a}", val) instance_variable_set("@#{b}", val) end end end end class Person end p = Person.new p.extend(Twinnable) p.attr_twin(:personal_email, :primary_email) p.personal_email = "abc" puts p.primary_email # abc p.personal_email = "def" puts p.primary_email # def Or you could generalize this into an OpenStruct-like class, and have Person descend from it. David -- Rails training from David A. Black and Ruby Power and Light: Intro to Ruby on Rails January 12-15 Fort Lauderdale, FL Advancing with Rails January 19-22 Fort Lauderdale, FL * * Co-taught with Patrick Ewing! See http://www.rubypal.com for details and updates!