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, rimary_email,
# rimary_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, rimary_email, rimary_phone_number,
# ersonal_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
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, rimary_email,
# rimary_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, rimary_email, rimary_phone_number,
# ersonal_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