Read-Only Array Access

G

Gavin Kistner

While I understand the (very valid) reasons that my RCR [see:
http://rcrchive.net/rcr/RCR/RCR201] got shot down, an underlying problem
still remains for me.

I want to provide access to a full collection of elements (returned as
an array, or iterated through an arbitrary .each{} block), but not let
the objects be modified.

Is this possible?

For example, assume that a House has Furniture and ArtObjects inside it.
I need to be able to modify those collections independently; at any time
give external objects access to read the full collection; and (the hard
part) prevent the objects in the collections from being changed.


class House
attr_reader:)furniture,:jewelry,:furnitureValue,:jewelryValue)
def initialize
@furniture,@jewelry=[],[]
@furnitureValue,@jewelryValue=0,0
end
def addFurniture (*newPieces)
@furniture.push(*newPieces)
newPieces.each{ |p| @furnitureValue+=p.value }
end
def addJewelry (*newPieces)
@jewelry.push(*newPieces)
newPieces.each{ |p| @jewelryValue+=p.value }
end
end
class Obj
attr_accessor:)name,:value)
def initialize(name,value)
@name,@value=name,value
end
end

myHouse = House.new
myHouse.addFurniture( Obj.new('Chair',100), Obj.new('Table',200) )
myHouse.furniture.each{ |p| puts p.name } #fine
myHouse.furniture.each{ |p| p.value*=2 }
#uh-oh! myHouse.furnitureValue is now out of sync



The problem cannot be solved by freezing @furniture, since I want to be
able to modify it at any time. The problem could be sort of solved by...

def furniture
return @furniture.dup
end

....except that (a) this provides 'silent' protection (the code can
modify the values, it just doesn't do what was expected) and (b) the
returned array is no longer synced with the original:

foo = myHouse.furniture
myHouse.addFurniture( Obj.new('Credenza',1000) )
# foo has two items, the house really has three.



So...how do I solve this? The problem is that a 'read-only' copy of an
array doesn't prevent code from getting a read-only reference to an
element in that array, and then modifying it directly.


[And as an aside...I realize that the particular problem could be solved
if I either disallowed .value= or recalculated the furnitureValue by
iterating the array each time it was asked for. Or I could have the
furniture pieces know what house they are in, and any time they are
changed have them tell the house that they were modified and any
dependent information needs to be recalculated. While all are solutions
to the above, I can sense that I'm going to be repeatedly bumping up
against this problem...the desire to keep a collection of elements who
should be able to be read, but not directly modified.]
 
R

Robert Klemme

Gavin Kistner said:
While I understand the (very valid) reasons that my RCR [see:
http://rcrchive.net/rcr/RCR/RCR201] got shot down, an underlying problem
still remains for me.

I want to provide access to a full collection of elements (returned as
an array, or iterated through an arbitrary .each{} block), but not let
the objects be modified.

Is this possible?
[snip]

The problem cannot be solved by freezing @furniture, since I want to be
able to modify it at any time. The problem could be sort of solved by...

def furniture
return @furniture.dup
end

...except that (a) this provides 'silent' protection (the code can
modify the values, it just doesn't do what was expected) and (b) the
returned array is no longer synced with the original:

foo = myHouse.furniture
myHouse.addFurniture( Obj.new('Credenza',1000) )
# foo has two items, the house really has three.



So...how do I solve this? The problem is that a 'read-only' copy of an
array doesn't prevent code from getting a read-only reference to an
element in that array, and then modifying it directly.


[And as an aside...I realize that the particular problem could be solved
if I either disallowed .value= or recalculated the furnitureValue by
iterating the array each time it was asked for. Or I could have the
furniture pieces know what house they are in, and any time they are
changed have them tell the house that they were modified and any
dependent information needs to be recalculated. While all are solutions
to the above, I can sense that I'm going to be repeatedly bumping up
against this problem...the desire to keep a collection of elements who
should be able to be read, but not directly modified.]

That ain't going to be solved easily: What you really want is to mark a
*graph* of instances to be unmodifiable, i.e., if any instance in your
collection returns some member, that in turn must not be modified, too
etc.

You could try to use Delegator and add a state variable "modifyable" which
is evaluated by all modifying methods. The hard part here is to
automatically know which methods are modifying methods...

If you've got enough mem you can clone the whole object graph starting
with the collection with the typical Marshal idiom "Marshal.load(
Marshal.dump( collection ) )". But if the collection is big, this will
burn a lot of mem and might take some time.

If you follow discussions in C++ ng's about "const" and think a while
about the matter than you'll discover a lot of annoying things that happen
once you start to try to differentiate const and mutable instances. For
example, consider lazy intialization: an instances state might look the
same from the outside (hence it could be regarded as const), while the
real state does change in certain situations. Now, is this instance const
or not? etc.

I'd say, don't invest too much time into this and rather document which
parts of the application are allowed to do modifications and which are
not.

Regards

robert
 
A

Ara.T.Howard

Date: Thu, 29 Jan 2004 17:11:40 GMT
From: Gavin Kistner <[email protected]>
Newsgroups: comp.lang.ruby
Subject: Read-Only Array Access

While I understand the (very valid) reasons that my RCR [see:
http://rcrchive.net/rcr/RCR/RCR201] got shot down, an underlying problem
still remains for me.

I want to provide access to a full collection of elements (returned as
an array, or iterated through an arbitrary .each{} block), but not let
the objects be modified.

Is this possible?

For example, assume that a House has Furniture and ArtObjects inside it.
I need to be able to modify those collections independently; at any time
give external objects access to read the full collection; and (the hard
part) prevent the objects in the collections from being changed.


class House
attr_reader:)furniture,:jewelry,:furnitureValue,:jewelryValue)
def initialize
@furniture,@jewelry=[],[]
@furnitureValue,@jewelryValue=0,0
end
def addFurniture (*newPieces)
@furniture.push(*newPieces)
newPieces.each{ |p| @furnitureValue+=p.value }
end
def addJewelry (*newPieces)
@jewelry.push(*newPieces)
newPieces.each{ |p| @jewelryValue+=p.value }
end
end
class Obj
attr_accessor:)name,:value)
def initialize(name,value)
@name,@value=name,value
end
end

myHouse = House.new
myHouse.addFurniture( Obj.new('Chair',100), Obj.new('Table',200) )
myHouse.furniture.each{ |p| puts p.name } #fine
myHouse.furniture.each{ |p| p.value*=2 }
#uh-oh! myHouse.furnitureValue is now out of sync



The problem cannot be solved by freezing @furniture, since I want to be
able to modify it at any time. The problem could be sort of solved by...

def furniture
return @furniture.dup
end

...except that (a) this provides 'silent' protection (the code can
modify the values, it just doesn't do what was expected) and (b) the
returned array is no longer synced with the original:

foo = myHouse.furniture
myHouse.addFurniture( Obj.new('Credenza',1000) )
# foo has two items, the house really has three.



So...how do I solve this? The problem is that a 'read-only' copy of an
array doesn't prevent code from getting a read-only reference to an
element in that array, and then modifying it directly.


[And as an aside...I realize that the particular problem could be solved
if I either disallowed .value= or recalculated the furnitureValue by
iterating the array each time it was asked for. Or I could have the
furniture pieces know what house they are in, and any time they are
changed have them tell the house that they were modified and any
dependent information needs to be recalculated. While all are solutions
to the above, I can sense that I'm going to be repeatedly bumping up
against this problem...the desire to keep a collection of elements who
should be able to be read, but not directly modified.]

first - why not freeze the objects in the array? or simply give an access
method which does something like

class House
def each_furniture_peice
@furniture.each{|f| f = f.dup; f.freeze; yield f}
end
end


and make this the _only_ method (and perhaps a few other) of getting at the
furniture.


or you could use interfaces:



class FurniturePrivate
def intialize peice; @peice = peice; end
def method_missing(meth, *args, &block); peice.send meth, *args, &block; end
end

class FurniturePublic
def intialize peice; @peice = peice; end

def exposed_method(*args); @peice.exposed_method *args; end
end

class House
private
class Furniture
...
end

def initialize
@furniture = [ Furniture.new, Furniture.new ]
@public_furniture = @furniture.map{|f| FurniturePublic.new f}
@private_furniture = @furniture.map{|f| FurniturePrivate.new f}
end

def _furniture
@private_furniture
end

public
def furniture
@public_furniture
end
end

there a million ways (all better probably too) to do the above - even
generically.

the basic idea is that you either need to limit access somehow or give two
entirely different access methods (interfaces)

other approaches could include a 'lock and key' approach whereby a peice
refused to modify itself unless 'unlocked' by a key know only to the house...


2 cts.

-a
--

ATTN: please update your address books with address below!

===============================================================================
| EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
| PHONE :: 303.497.6469
| ADDRESS :: E/GC2 325 Broadway, Boulder, CO 80305-3328
| STP :: http://www.ngdc.noaa.gov/stp/
| NGDC :: http://www.ngdc.noaa.gov/
| NESDIS :: http://www.nesdis.noaa.gov/
| NOAA :: http://www.noaa.gov/
| US DOC :: http://www.commerce.gov/
|
| The difference between art and science is that science is what we
| understand well enough to explain to a computer.
| Art is everything else.
| -- Donald Knuth, "Discover"
|
| /bin/sh -c 'for l in ruby perl;do $l -e "print \"\x3a\x2d\x29\x0a\"";done'
===============================================================================
 

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,796
Messages
2,569,645
Members
45,371
Latest member
TroyHursey

Latest Threads

Top