My error or bug in "attr_reader" ?

H

Hexren

Hi guys.

The code posted below actually modifies the array in the object.
Am I doing something wrong here ?
If not, this should imho be included in the doc. It seems
counterintuitive to me ;)


Regards
Oliver

----------------------------------
class AttrReaderErr
attr_reader :entries
def initialize()
@entries = [0,1,2]
end
end

showcase = AttrReaderErr.new

puts showcase.entries
#should this really work ?
showcase.entries[1]="foo"
#it does, but looks wrong
puts showcase.entries
----------------------------------
 
S

Stefano Crocco

Hi guys.

The code posted below actually modifies the array in the object.
Am I doing something wrong here ?
If not, this should imho be included in the doc. It seems
counterintuitive to me ;)


Regards
Oliver

----------------------------------
class AttrReaderErr
attr_reader :entries
def initialize()
@entries = [0,1,2]
end
end

showcase = AttrReaderErr.new

puts showcase.entries
#should this really work ?
showcase.entries[1]="foo"
#it does, but looks wrong
puts showcase.entries
----------------------------------

It works correctly, once you understand exactly what's going on. attr_reader
creates a method which returns the object stored in the instance variable
@entries. If it were written by hand, it would be something like:

def entries
@entries
end

Writing entries[1]="foo", you're calling the []= method of the object returned
by entries, that is, the array stored in @entries. Since the []= method
modifies the receiver, the content of @entries is modified. If you don't want
this to happen, you need to create the entries method by hand and return a
copy of the array:

def entries
@entries.dup
end

I hope this helps

Stefano
 
H

Hexren

Stefano said:
Hi guys.

The code posted below actually modifies the array in the object.
Am I doing something wrong here ?
If not, this should imho be included in the doc. It seems
counterintuitive to me ;)


Regards
Oliver

----------------------------------
class AttrReaderErr
attr_reader :entries
def initialize()
@entries = [0,1,2]
end
end

showcase = AttrReaderErr.new

puts showcase.entries
#should this really work ?
showcase.entries[1]="foo"
#it does, but looks wrong
puts showcase.entries
----------------------------------

It works correctly, once you understand exactly what's going on. attr_reader
creates a method which returns the object stored in the instance variable
@entries. If it were written by hand, it would be something like:

def entries
@entries
end

Writing entries[1]="foo", you're calling the []= method of the object returned
by entries, that is, the array stored in @entries. Since the []= method
modifies the receiver, the content of @entries is modified. If you don't want
this to happen, you need to create the entries method by hand and return a
copy of the array:

def entries
@entries.dup
end

I hope this helps

Stefano


Yup, what you say makes sense, thanks :)

But I maintain, that the doc is kind of confusing. The fingerprint:
"attr(symbol, writable=false) => nil"

Very strongly implies (at least to me) that the default is, for the
instance variable to be not writable. I am going to go, cry wolf on the
doc mailing list :)

Oliver
 
J

J. Cooper

Well, it's true; the instance variable isn't writable. Doing

showcase.entries = [3,2,1,7]

will throw an error, because entries is read-only. (There is no entries=
method or attr_writer :entries). There is a difference between a
read-only attribute that is a collection, and an attribute that is a
read-only collection. In the former, you can't up and assign a different
collection to the attribute, but you can certainly modify the contents
of the collection itself. In the latter, the collection itself
(independent of the fact that it's an attribute of a class) isn't
modifiable.

Here is how to do the latter:

class AttrReaderErr
attr_reader :entries
def initialize()
@entries = [0,1,2].freeze # This makes the collection unmodifiable
end
end

showcase = AttrReaderErr.new
showcase.entries[1] = "foo" # TypeError: can't modify frozen array
showcase.entries = ["foo"] # NoMethodError: undefined method
`entries='...
 
R

Rick DeNatale

Stefano said:
Hi guys.

The code posted below actually modifies the array in the object.
Am I doing something wrong here ?
If not, this should imho be included in the doc. It seems
counterintuitive to me ;)


Regards
Oliver

----------------------------------
class AttrReaderErr
attr_reader :entries
def initialize()
@entries = [0,1,2]
end
end

showcase = AttrReaderErr.new

puts showcase.entries
#should this really work ?
showcase.entries[1]="foo"
#it does, but looks wrong
puts showcase.entries

It works correctly, once you understand exactly what's going on.
attr_reader creates a method which returns the object stored in the instance
variable @entries. If it were written by hand, it would be something like:
def entries
@entries
end

Writing entries[1]="foo", you're calling the []= method of the object
returned by entries, that is, the array stored in @entries. Since the []=
method modifies the receiver, the content of @entries is modified. If you
don't want this to happen, you need to create the entries method by hand and
return a copy of the array:
def entries
@entries.dup
end

I hope this helps

Stefano


Yup, what you say makes sense, thanks :)

But I maintain, that the doc is kind of confusing. The fingerprint:
"attr(symbol, writable=false) => nil"

Very strongly implies (at least to me) that the default is, for the
instance variable to be not writable. I am going to go, cry wolf on the doc
mailing list :)

This is a typical newbie confusion over instance variable vs. the
objects they reference.

http://talklikeaduck.denhaven2.com/articles/2006/09/13/on-variables-values-and-objects
See in particular the section on "Mutability, and Aliasing"
 
D

David A. Black

Hi --

Stefano said:
Hi guys.

The code posted below actually modifies the array in the object.
Am I doing something wrong here ?
If not, this should imho be included in the doc. It seems
counterintuitive to me ;)


Regards
Oliver

----------------------------------
class AttrReaderErr
attr_reader :entries
def initialize()
@entries = [0,1,2]
end
end

showcase = AttrReaderErr.new

puts showcase.entries
#should this really work ?
showcase.entries[1]="foo"
#it does, but looks wrong
puts showcase.entries
----------------------------------

It works correctly, once you understand exactly what's going on.
attr_reader creates a method which returns the object stored in the
instance variable @entries. If it were written by hand, it would be
something like:

def entries
@entries
end

Writing entries[1]="foo", you're calling the []= method of the object
returned by entries, that is, the array stored in @entries. Since the []=
method modifies the receiver, the content of @entries is modified. If you
don't want this to happen, you need to create the entries method by hand
and return a copy of the array:

def entries
@entries.dup
end

I hope this helps

Stefano


Yup, what you say makes sense, thanks :)

But I maintain, that the doc is kind of confusing. The fingerprint:
"attr(symbol, writable=false) => nil"

Very strongly implies (at least to me) that the default is, for the instance
variable to be not writable. I am going to go, cry wolf on the doc mailing
list :)

A writable attribute is different from a mutable object. You can't set
the attribute to a new object, but the read operation does expose the
object behind the attribute -- so if that object is mutable, you can
mutate it.


David

--
Rails training from David A. Black and Ruby Power and Light:
ADVANCING WITH RAILS April 14-17 New York City
INTRO TO RAILS June 9-12 Berlin
ADVANCING WITH RAILS June 16-19 Berlin
See http://www.rubypal.com for details and updates!
 
H

Hexren

David said:
Hi --

Stefano said:
On Wednesday 09 April 2008, Hexren wrote:
Hi guys.

The code posted below actually modifies the array in the object.
Am I doing something wrong here ?
If not, this should imho be included in the doc. It seems
counterintuitive to me ;)


Regards
Oliver

----------------------------------
class AttrReaderErr
attr_reader :entries
def initialize()
@entries = [0,1,2]
end
end

showcase = AttrReaderErr.new

puts showcase.entries
#should this really work ?
showcase.entries[1]="foo"
#it does, but looks wrong
puts showcase.entries
----------------------------------

It works correctly, once you understand exactly what's going on.
attr_reader creates a method which returns the object stored in the
instance variable @entries. If it were written by hand, it would be
something like:

def entries
@entries
end

Writing entries[1]="foo", you're calling the []= method of the object
returned by entries, that is, the array stored in @entries. Since the
[]= method modifies the receiver, the content of @entries is
modified. If you don't want this to happen, you need to create the
entries method by hand and return a copy of the array:

def entries
@entries.dup
end

I hope this helps

Stefano


Yup, what you say makes sense, thanks :)

But I maintain, that the doc is kind of confusing. The fingerprint:
"attr(symbol, writable=false) => nil"

Very strongly implies (at least to me) that the default is, for the
instance variable to be not writable. I am going to go, cry wolf on
the doc mailing list :)

A writable attribute is different from a mutable object. You can't set
the attribute to a new object, but the read operation does expose the
object behind the attribute -- so if that object is mutable, you can
mutate it.

And that doesnt sound strange to you ?
I see why the stuff is writable in a technical sense.

However that is not changing my opinion: Something called "attr_reader
:foo" should not allow somebody to write to "private @foo".

And if it does make something writable it should say so in the api doc
and not hope that everybody catches that fact by glancing at the example
in the doc.
Which btw: is for the other case "writable=true".


Oliver
 
H

Hexren

Hexren said:
David said:
Hi --

Stefano Crocco wrote:
On Wednesday 09 April 2008, Hexren wrote:
Hi guys.

The code posted below actually modifies the array in the object.
Am I doing something wrong here ?
If not, this should imho be included in the doc. It seems
counterintuitive to me ;)


Regards
Oliver

----------------------------------
class AttrReaderErr
attr_reader :entries
def initialize()
@entries = [0,1,2]
end
end

showcase = AttrReaderErr.new

puts showcase.entries
#should this really work ?
showcase.entries[1]="foo"
#it does, but looks wrong
puts showcase.entries
----------------------------------

It works correctly, once you understand exactly what's going on.
attr_reader creates a method which returns the object stored in the
instance variable @entries. If it were written by hand, it would be
something like:

def entries
@entries
end

Writing entries[1]="foo", you're calling the []= method of the
object returned by entries, that is, the array stored in @entries.
Since the []= method modifies the receiver, the content of @entries
is modified. If you don't want this to happen, you need to create
the entries method by hand and return a copy of the array:

def entries
@entries.dup
end

I hope this helps

Stefano



Yup, what you say makes sense, thanks :)

But I maintain, that the doc is kind of confusing. The fingerprint:
"attr(symbol, writable=false) => nil"

Very strongly implies (at least to me) that the default is, for the
instance variable to be not writable. I am going to go, cry wolf on
the doc mailing list :)

A writable attribute is different from a mutable object. You can't set
the attribute to a new object, but the read operation does expose the
object behind the attribute -- so if that object is mutable, you can
mutate it.

And that doesnt sound strange to you ?
I see why the stuff is writable in a technical sense.

However that is not changing my opinion: Something called "attr_reader
:foo" should not allow somebody to write to "private @foo".

And if it does make something writable it should say so in the api doc
and not hope that everybody catches that fact by glancing at the example
in the doc.
Which btw: is for the other case "writable=true".


Oliver


That somehow sounds harsher than I intended. No offense meant, its only
my opinion.

Oliver
 
J

Julian Leviston

[Note: parts of this message were removed to make it a legal post.]

No,

It's not strange in any way.

attr_reader simply gives you what java would call a "getter" method. A
means to access the variable. It says nothing about the status or
changeableness of the variable itself. It's about the attribute. Try
as you might, you will not be able to change the variable that is
assigned to a attr_reader specified attribute of an object.

For instance.

class Blah
def initialize(someval = "hi")
@yeah = someval
end
end

b = Blah.new

now, without opening up the class again, try to set the value of yeah
inside this instantiated Blah object (b).

You can't, becuase there is no "yeah=" method defined (manually, or by
attr_writer)

The object itself cannot be changed.

Now, if we open up the class again, and give it a reader method

class Blah
def yeah
@yeah
end
end

Now, try as we might again, we STILL cannot modify the object that it
points to.

HOWEVER, we CAN modify the object itself. This is implicit. Ruby is
VERY flexible.

For instance, if we wanted to we could do the following:

b.yeah[0] = 'P'

then accessing b.yeah will yield 'Pi'

but if we query the object, it's still the same object, it's just been
modified. Note, though, that it is still the same object!

So, sending the "[]" message to yeah is not actually modifying any of
b's data. It's modifying some of b's ENCAPSULATED data, but not any of
b's direct data.

This is a good thing. It goes hand in hand with dynamic typing (IMHO).

Ruby creates a world where it dares to give programmers the no-
illusions awareness that they have the freedom and power to do as they
please, and assumes they don't want to have any barriers to that
freedom and power.

It turns out that programmers never do stupid things simply because
they can, in much the same way that drivers of cars rarely crash cars
on purpose.

Rules come out of fear.

I point you towards the Tao te Ching, one of the east's oldest texts,
stanza 38.

38. Degeneration
The man of superior character is not (conscious of his) character.
Hence he has character.
The man of inferior character (is intent on) not losing character.
Hence he is devoid of character.
The man of superior character never acts,
Nor ever (does so) with an ulterior motive.
The man of inferior character acts,
And (does so) with an ulterior motive.
The man of superior kindness acts,
But (does so) without an ulterior motive.
The man of superior justice acts,
And (does so) with an ulterior motive.
(But when) the man of superior li acts and finds no response,
He rolls up his sleeves to force it on others.

Therefore:
After Tao is lost, then (arises the doctrine of) humanity.
After humanity is lost, then (arises the doctrine of) justice.
After justice is lost, then (arises the doctrine of) li.
Now li is the thinning out of loyalty and honesty of heart.
And the beginning of chaos.
The prophets are the flowering of Tao
And the origin of folly.
Therefore the noble man dwells in the heavy (base),
And not in the thinning (end).
He dwells in the fruit,
And not in the flowering (expression).
Therefore he rejects the one and accepts the other.

In particular, after tao is lost, arises humanity, after humanity is
lost, arises justice.

Justice is rules. Now, do we need rules to bind us, or are we
enlightened enough to follow a path where no rules are necessary,
because we follow a way where our inner sense of naturalness and
beauty guides us on our way?

Julian.



Learn Ruby on Rails! Check out the FREE VIDS (for a limited time)
VIDEO #3 out NOW!
http://sensei.zenunit.com/
 
S

Stefan Lang

2008/4/10 said:
David A. Black wrote: [...]
A writable attribute is different from a mutable object. You can't set
the attribute to a new object, but the read operation does expose the
object behind the attribute -- so if that object is mutable, you can
mutate it.

And that doesnt sound strange to you ?
I see why the stuff is writable in a technical sense.

However that is not changing my opinion: Something called "attr_reader
:foo" should not allow somebody to write to "private @foo".

And it doesn't.
And if it does make something writable it should say so in the api doc and
not hope that everybody catches that fact by glancing at the example in the
doc.

It doesn't make anything writable. The array was already
mutable before it was assigned to @entries.

This is how most OO languages behave, of those I know:
Java, C#, Smalltalk, Common Lisp, Python, OCaml, D,
Objective C and Groovy.

Stefan
 
D

David A. Black

Hi --

And that doesnt sound strange to you ?

No, not at all. That's the way objects work. The question of what you
do to an object is completely separate from the question of whether a
given method returns that object.
I see why the stuff is writable in a technical sense.

However that is not changing my opinion: Something called "attr_reader :foo"
should not allow somebody to write to "private @foo".

The phrase "write to 'private @foo'" is a bit sprawling :) You're
bundling two very different things -- the existence of a foo= method,
and the mutability of the object bound to @foo -- into one word
("writable"), and it really doesn't carry both of those connotations.
And if it does make something writable it should say so in the api doc and
not hope that everybody catches that fact by glancing at the example in the
doc.

It's not something you have to catch; the behavior is absolutely in
conformity and compliance with the most basic rules of how Ruby
objects work.
Which btw: is for the other case "writable=true".

If the docs said that attr_reader created a "writable" attribute, that
would be an error. It creates a readable attribute only. It's in the
nature of Ruby objects that once you've got hold of them, by whatever
means, you can send messages to them. The object doesn't "know" that
your access to it was through a method wrapped around an instance
variable to which it was bound.

There's definitely a vulnerability in this -- meaning, it pays to be
careful about which objects you expose, and how. (Paul Brannan first
pointed this out to me, as we were walking to a restaurant in Seattle
during RubyConf 2002, and apparently it made an impression :) But the
read/write terminology is not about mutable objects. attr_reader
doesn't mean that the object is immutable, and attr_writer doesn't
mean that it isn't.


David

--
Rails training from David A. Black and Ruby Power and Light:
ADVANCING WITH RAILS April 14-17 New York City
INTRO TO RAILS June 9-12 Berlin
ADVANCING WITH RAILS June 16-19 Berlin
See http://www.rubypal.com for details and updates!
 
S

Simon Krahnke

* Julian Leviston said:
For instance.

class Blah
def initialize(someval = "hi")
@yeah = someval
end
end

b = Blah.new

now, without opening up the class again, try to set the value of yeah
inside this instantiated Blah object (b).

Well, I just learned that the initialize method is private. But there is
#send. :)

mfg, simon .... l
 
C

Charles Calvert

The code posted below actually modifies the array in the object.
Am I doing something wrong here ?
If not, this should imho be included in the doc. It seems
counterintuitive to me ;)
[snip]

----------------------------------
class AttrReaderErr
attr_reader :entries
def initialize()
@entries = [0,1,2]
end
end

showcase = AttrReaderErr.new

puts showcase.entries
#should this really work ?
showcase.entries[1]="foo"
#it does, but looks wrong
puts showcase.entries
----------------------------------

This is a common misunderstanding of references. I don't know ruby that
well, but if I understand it correctly, it handles references similarly to
many other modern, pointerless OO languages, such as Java and C#.

When you declare @entries, you're declaring a reference to an instance of
an array. @entries isn't an array, it's a pointer to an instance of the
array class on the heap. A reference is just a pointer with slightly
different access semantics. Under the hood, you have a 32 bit value
(assuming a 32 bit platform) that is the heap address of the instance of
the class. When you write "@entries[0]" what you're really doing is this:

1. Get the heap address of the object from @entries
2. Using that address, find the beginning of the instance on the heap.
3. Read through the instance's structure looking for the address of the
method to call (in this case the indexer - or is it enumerator in ruby?).
4. Call the method of the instance, passing 0 as the argument.
5. Receive the result.

The accessor created by the statement "attr_reader :entries" doesn't
return an array, it returns the reference stored in @entries. The
reference itself can't be changed (it will always reference the same
instance), but you can call methods of the instance and they can change
the instance's state (including internal data).

The key is to understand that the value stored in @entries is not the
actual array, so the fact that the accessor is read-only doesn't affect
the array itself, only the reference to it.

Weak analogy: Your friend Bob moves into an apartment in an area that has
the zip code (postal code) of 10001. You write his address down in your
address book in ink because Bob plans to live in that apartment forever.
Then the Post Office decides to reallocate zip codes and his is changed to
10002. Your record of his address (a reference) hasn't changed, but the
actual address has.

If you're not familiar with pointers, spend some time studying a language
that has them, such as C. This can be a hard concept to grasp, but once
you do it makes understanding what's going on under the hood a lot easier,
even in languages that don't expose pointers to the programmer in their
raw form.
 

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,756
Messages
2,569,540
Members
45,024
Latest member
ARDU_PROgrammER

Latest Threads

Top