"Pointer" to an object? (i.e. reflect changes to original)

D

Doug Glidden

Hi,

I've already made a post similar to this on the Rails list, so I
apologize to anyone who subscribes to both for being repetitive. From
what I've read, it seems to be impossible to create "pointers" in Ruby,
such as the following pseudocode:

string = 'a string'
copy = *string
string = 'a different string' # both string and copy are changed

The only way I've found to accomplish this is the following:

string = 'a string'
copy = string
string.replace 'a different string' # both string and copy are changed

But this seems a very poor alternative, because it forces code operating
on the _original string_ to know that a _copy_ of the string exists!
(Heaven help us if string should be some arbitrary object instead of an
actual String!) This is clearly in violation of all sorts of good
programming principles. Consider the following example, which could be
a very realistic usage in a Ruby address book. (Note: Person is an
actual class I've implemented that is extensively self-modifying, so the
code below is entirely possible. Basically, if the @foo instance
variable does not exist, Person.foo= redirects to first create a new
@foo instance variable and accessors for it, add it to @field_list, and
assign it. Assume that Person class is a library being used here.)

# code snippet 1
p = Person.new # => #<Person:{id} @field_list=[:first_name,
:last_name,
# :middle_name, :location, :primary_email,
# :primary_phone_number]>
p.personal_email = '(e-mail address removed)'
p.primary_email = p.personal_email # I want p.primary_email to always
reflect
# the contents of p.personal_email
# p.inspect now produces #<Person:{id} @personal_email="(e-mail address removed)",
# @primary_email="(e-mail address removed)", @field_list=[:first_name, :last_name,
# :middle_name, :location, :primary_email, :primary_phone_number,
# :personal_email]>

# code snippet 2, perhaps called by some library, who knows where
p.personal_email = '(e-mail address removed)' # whoops, p.primary_email is no
longer the
# same as p.personal_email

Code snippet 2, being agnostic of the fact that we want to make a copy
of p.personal_email that will reflect any reassignment of it, has
unwittingly made it impossible for us to do so. Of course we could fix
that, nominally, by changing the implementation of Person.foo=:

class << Person
def foo=(new_val)
@foo.replace new_val
end
end

But changing libraries isn't a great idea, and it still violates the
principles of OO coding, because we've forced the library to reflect the
way we're using it. Worse yet, what happens when either @foo or new_val
is an object of a class other than String? Most likely a NameError, and
as far as I can tell, no way to get around it.

Now, the most elegant solution I can think of would require some pretty
low-level meddling with the core of Ruby, and that would be to add some
level support for pointers, but that seems to be frowned upon by
Rubyists.

The only other solution I can think of is to change the functionality of
Object.= to act like String.replace (not creating a new copy), requiring
coders to always use dup if they want to specifically specify creating a
new copy. I'm pretty sure we'd all agree that would be a terrible
solution.

I've read a lot of "I don't think there's a realistic situation where
that functionality would be needed" type of responses in past
discussions of this, but I think the example I've described is pretty
common and realistic. If there's a better, elegant way to allow this
sort of functionality, please let me know.

Thanks,
Doug
 
D

David A. Black

Hi --

Hi,

I've already made a post similar to this on the Rails list, so I
apologize to anyone who subscribes to both for being repetitive. From
what I've read, it seems to be impossible to create "pointers" in Ruby,
such as the following pseudocode:

string = 'a string'
copy = *string
string = 'a different string' # both string and copy are changed

Assignment means breaking the binding between the identifier and the
(reference to an) object, and reusing the identifier. So reusing
'string' as an identifier doesn't mean that any object has actually
changed.
The only way I've found to accomplish this is the following:

string = 'a string'
copy = string
string.replace 'a different string' # both string and copy are changed

But this seems a very poor alternative, because it forces code operating
on the _original string_ to know that a _copy_ of the string exists!
(Heaven help us if string should be some arbitrary object instead of an
actual String!) This is clearly in violation of all sorts of good
programming principles.

No it isn't. Ruby is designed along very sound lines; it just doesn't
happen to be the same language design as C and others. Variables are
bound to object references, and all references to a given object have
exactly the same relation to the object as all other references.

You have to know the difference between assignment and object
mutation, and use whichever you think is right in a given situation.
Consider the following example, which could be a very realistic
usage in a Ruby address book. (Note: Person is an actual class
I've implemented that is extensively self-modifying, so the code
below is entirely possible. Basically, if the @foo instance
variable does not exist, Person.foo= redirects to first create a new
@foo instance variable and accessors for it, add it to @field_list,
and assign it. Assume that Person class is a library being used
here.)

It sounds a lot like OpenStruct.
# code snippet 1
p = Person.new # => #<Person:{id} @field_list=[:first_name,
:last_name,
# :middle_name, :location, :primary_email,
# :primary_phone_number]>
p.personal_email = '(e-mail address removed)'
p.primary_email = p.personal_email # I want p.primary_email to always
reflect
# the contents of p.personal_email
# p.inspect now produces #<Person:{id} @personal_email="(e-mail address removed)",
# @primary_email="(e-mail address removed)", @field_list=[:first_name, :last_name,
# :middle_name, :location, :primary_email, :primary_phone_number,
# :personal_email]>

# code snippet 2, perhaps called by some library, who knows where
p.personal_email = '(e-mail address removed)' # whoops, p.primary_email is no
longer the
# same as p.personal_email
Code snippet 2, being agnostic of the fact that we want to make a copy
of p.personal_email that will reflect any reassignment of it, has
unwittingly made it impossible for us to do so. Of course we could fix
that, nominally, by changing the implementation of Person.foo=:

class << Person
def foo=(new_val)
@foo.replace new_val
end
end

But changing libraries isn't a great idea, and it still violates the
principles of OO coding, because we've forced the library to reflect the
way we're using it.

Worse yet, what happens when either @foo or new_val
is an object of a class other than String? Most likely a NameError, and
as far as I can tell, no way to get around it.

Now, the most elegant solution I can think of would require some pretty
low-level meddling with the core of Ruby, and that would be to add some
level support for pointers, but that seems to be frowned upon by
Rubyists.

The only other solution I can think of is to change the functionality of
Object.= to act like String.replace (not creating a new copy), requiring
coders to always use dup if they want to specifically specify creating a
new copy. I'm pretty sure we'd all agree that would be a terrible
solution.

I've read a lot of "I don't think there's a realistic situation where
that functionality would be needed" type of responses in past
discussions of this, but I think the example I've described is pretty
common and realistic. If there's a better, elegant way to allow this
sort of functionality, please let me know.

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.

It sounds like you're seeing a conflict between wanting a program to
work a certain way, and writing it to work that way. I don't think
there's a conflict. If you feel like you're tailoring a library too
much to a single use case, that's still better than tailoring Ruby to
a single use case :) More to the point, if you take a step back and
write something that handles this kind of use case, you can turn it
around so that it's beneficial and comfortable and doesn't feel like a
squeaky wheel.


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!
 
D

David A. Black

Hi --

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!
 
D

Doug Glidden

Calamitas wrote:
[snip]
Or closer to the pointer solution you want:

p.primary_email = lambda { p.personal_email }
p.personal_email = '(e-mail address removed)'
puts p.primary_email.call

The lambda expression corresponds to the address operator, the .call
corresponds to a pointer dereference.

Peter

Peter,

Thanks! I had just (literally within the last ten minutes) thought of
the possibility of using an enclosure of some sort to do the trick. I
was playing around with using a proc, you used a lambda, I think either
one will probably work with some tweaking. This is the "perfect"
solution, I think.

I'm a little curious how this will react if p.primary_email.call is
executed when p is not in scope, e.g. if something like the following
occurs, so I'm going to play around:

def do_something(person)
email = person.primary_email.call
# do more stuff...
end

p = Person.new
p.personal_email = '(e-mail address removed)'
p.primary_email = lambda {p.personal_email}
do_something p

Will the lambda be able to handle that situation? Like I said, I'm
going to play around with it.

Thanks again,
Doug
 
D

David A. Black

Hi --

Calamitas wrote:
[snip]
Or closer to the pointer solution you want:

p.primary_email = lambda { p.personal_email }
p.personal_email = '(e-mail address removed)'
puts p.primary_email.call

The lambda expression corresponds to the address operator, the .call
corresponds to a pointer dereference.

Peter

Peter,

Thanks! I had just (literally within the last ten minutes) thought of
the possibility of using an enclosure of some sort to do the trick. I
was playing around with using a proc, you used a lambda, I think either
one will probably work with some tweaking. This is the "perfect"
solution, I think.

I'm a little curious how this will react if p.primary_email.call is
executed when p is not in scope, e.g. if something like the following
occurs, so I'm going to play around:

def do_something(person)
email = person.primary_email.call
# do more stuff...
end

p = Person.new
p.personal_email = '(e-mail address removed)'
p.primary_email = lambda {p.personal_email}
do_something p

Will the lambda be able to handle that situation? Like I said, I'm
going to play around with it.

Yes; the lambda carries its local scope with it, so wherever it's
called, it will have access to the p that it started with.


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!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top