Thanks to everyone for your replies. I've been wanting to write a summary + further thoughts for a while, but have been a bit time-constrained.
The central tension in this issue is nicely captured by what Damian wrote contrasted with what Stefan wrote: Damian (>): > Doing nothing should result in the safe behaviour (i.e. full encapsulation). > You ought to have to explicitly request anything else. > > One of the *big* selling points of Perl 6 is the prospect of "OO done right". > Leaky encapsulation ain't that. Stefan (]): ] I am [fine with leaky encapsulation], personally. It's not a problem unique ] to Perl 6 and I have yet to see a solution (in any language) that fails ] to be worse than the original ] problem. I find both sides very reasonable, each in its own way. I'd *like* to see a solution materialize along the lines of what Damian recommends, but it's not going to happen by itself and without a concrete proposal for how it should work. If we just leave things be, what we'll end up with will be the status quo ante bellum, along the lines of what Stefan recommends. I'd be fine with finding some part-way solution that would make life easier and less monotonous for people who care about defensive copying. I'd even be fine with defensive copying semantics being a (non-core) module, but it would feel like defeat in some ways. However, having read the whole thread, various pages on the web (see references), and IRC discussions, I see a vaguely hopeful picture emerging. Not sure where it leads yet. Non-rw attributes, non-rw parameters, and non-rw return values all share the same mechanism. Fine. Makes sense. The Rakudo devs have introduced the concept of "decontainerization" for scalar values that need to be stripped of their surrounding scalar container when they are passed through any of these barriers. This makes the values effectively immutable, since there's no container to put a new value in. The concept doesn't seem to apply cleanly to container types, but the parallel is perhaps noteworthy. Non-rw-ness in this case should be "all the way", not one level down. If it's good, non-leaky encapsulation we care about, it *has* to be all the way. One problem here is not hard-coding any semantics to the usual Array and Hash types -- it has to work even for container types we haven't dreamed up yet. It has to work for objects. Likely there'll have to be some contract that authors of new container types will have to adhere to. Patrick Michaud said something (I think AFK) that seems essential to me: the non-rw-ness of these objects isn't a trait of *the object itself*, it's a trait of *the context in which the object is used*. (Note the similarity to what Ruud suggested.) Some actual code shows what this means: > my %hash = foo => { bar => 42 } > # works, shouldn't: > sub parameter-barrier(%h) { %h<foo><bar> = "OH NOES"; say %h } > parameter-barrier(%hash) ("foo" => {"bar" => "OH NOES"}).hash > # works, shouldn't: > sub return-value-barrier(%h is rw) { %h } > return-value-barrier(%hash)<foo><bar> = "OH NOES twice"; say %hash ("foo" => {"bar" => "OH NOES twice"}).hash > # works, should: > sub let-me-through(%h is rw) is rw { %h } > let-me-through(%hash)<foo><bar> = "42 again"; say %hash ("foo" => {"bar" => "42 again"}).hash > # works, shouldn't: > class NonLeaky { has %.h } > my $instance = NonLeaky.new(:h(%hash)) > $instance.h<foo><bar> = "OH NOES thrice"; say $instance.h ("foo" => {"bar" => "OH NOES thrice"}).hash > # works, should: > %hash<foo><bar> = 5; say %hash ("foo" => {"bar" => 5}).hash Note that there's only ever *one* hash of hashes here. No defensive copying. Just references to the same one. So you see, it's not so much the *object* having a write-me-don't-write-me flag on it -- that simply won't fly. It's more like were passing a reference around through various (routine or object) boundaries, and each of these boundaries has the right to deprive the reference of its writeability. And then, somehow, all indexings into that same reference are affected too. I know that the specifics of this aren't trivial -- and in fact, the issues one runs into with this are likely exactly what Stefan suggests will be "worse than the original problem" -- but at least this forms a seemingly consistent model of how containers should work in order not to be leaky. A model that we could use as a starting point for further discussion and maybe even implementation. So, my question is now: does this make sense? Objects don't have to be defensively copied, but when passing through non-rw barriers they (and all their indexing descendants) can be deprived of their writeability. Some references. It all comes down, in the case of OO, to the object being able to protect its invariants. Over at c2, some people question whether this is always necessary. <https://c2.com/cgi/wiki?ReturnNewObjectsFromAccessorMethods> <http://stackoverflow.com/questions/926404/java-is-clone-really-ever-used-what-about-defensive-copying-in-getters-setter> // Carl