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

Reply via email to