All,
Something that tops my current list of Perl 6 features to desire, or
design flaws to deal with, concerns protecting private attributes of
our objects from being mutated outside of our control due to their
being assigned to from arguments to our routines that are mutable.
I want to raise the issue here for discussion since I see it as a
very real issue that affects many people, such that either our
objects are vulnerable, or we have to do manual copying in our
routines to save ourselves which results in great inefficiencies and
more verbose code.
Typically, the declared types of my routine parameters are immutable
types, because I want the guarantee that values I receive as
arguments through them won't change values later on.
For example, I may request a Seq or a Mapping rather than an Array or
Hash, because I want to avoid the Perl 5 hassle of needing to clone
such arguments to keep my version safe from action at a distance.
However, given that actual behaviour and/or best practice involves
accepting an argument if it .does() the parameter type, then a
parameter declared as a Seq will accept an Array, because Array .does
Seq. So despite requesting an immutable type, I can get a mutable
type, so I have the aforementioned problem.
It actually gets a lot worse, because in Perl 6 any built-in
immutable types, including Int and Str and Bool can be done by other,
mutable types, so even if you declare a Str parameter, what you get
could mutate on you from a distance, so there's more to deal with
than in Perl 5.
Now, I had a brief #perl6 discussion about this with Larry, which can
be seen at
http://colabti.de/irclogger/irclogger_log/perl6?date=2007-05-17,Thu&sel=172#l288
, and the meat of which is also quoted below this email (it's not too
long).
Larry had some ideas for dealing with the problem, but this is a
matter that should be more widely discussed, particularly among
implementers and such.
A general thought is that a parameter could be marked so that any
argument passed through it is effectively snapshot (which is a no-op
if the type is already immutable, or it is likely lazy/COW if it is
mutable) so further changes to the external version do indeed not
affect our internal copy.
Such as this could solve the problem in the general case.
(However, I should mention in postscript that there may be a
complicating factor which concerns immutable objects which are also
lazy to an extent, eg that may internally cache derived values, such
as their .WHICH, when the derived is first asked for rather than at
construction time, though this doesn't affect their actual value,
which stays immutable. We wouldn't want to lose that ability.)
Um, yes, so thank you all who assist in solving this problem.
-- Darren Duncan
---------------
[ 11:42pm ] dduncan : given that afaik it is best practice when
declaring parameter types or testing argument types that .does() is
used, so that valid arguments for the parameter are ones that .does()
the declared parameter type ...
[ 11:42pm ] dduncan : and given that we often have mutable types
saying they do immutable types, eg Array does Seq ...
[ 11:43pm ] dduncan : I get the impression that if I declare a
parameter of type Seq, I could validly be handed an array?
[ 11:44pm ] TimToady : I suppose so. 'Course, all parameters are
considered readonly by default anyway...
[ 11:44pm ] dduncan : but the reason I asked for a Seq is that I want
the value I am given to be immutable, so eg if I then assign it to
one of my private attributes, then subsequent modification of a
passed array argument won't affect my attribute
[ 11:45pm ] dduncan : afaik, the read-only thing just prevents me
inside my routine from changing it, but doesn't prevent the above
scenario
[ 11:45pm ] dduncan : or that is, read-only prevents me from
assigning to the parameter
[ 11:45pm ] TimToady : yeah, maybe we need an "is snap" or some such
for a readonly snapshot
[ 11:45pm ] TimToady : or COW or something
[ 11:46pm ] dduncan : and so then if the original is immutable, then
the process doesn't make a copy, and if the argument is mutable, then
it does make a snapshot
[ 11:46pm ] TimToady : have the same problem if we allow people to
use a class as a role
[ 11:46pm ] TimToady : we have to take a snapshot of the current
state of the class and make an immutable role from it
[ 11:47pm ] TimToady : if objects have some kind of versioning
builtin, then it could just refer to the particular state in time
[ 11:47pm ] dduncan : still, if we can get that kind of protection,
then it would be very useful to me, as I won't have to clone the
argument manually in case it was mutable, to protect my internals
[ 11:47pm ] TimToady : might work with STM somehow
[ 11:47pm ] dduncan : perhaps, though I thought STM was more for
short-term atomicity
[ 11:48pm ] dduncan : still, thanks for anything you can do to design
a fix for this matter, as I see it as a very real issue that would
affect many peole
[ 11:48pm ] TimToady : well, it's about proving lack of contradiction
in some update order, and it just feels like "not updated" is one
variety of assertion about the update.
[ 11:49pm ] dduncan : is it worth my bringing up this matter on p6l,
or is my just telling you now enough?
[ 11:49pm ] TimToady : but maybe it's more of a COW issue underneath,
so you only have to take a snapshot if someone has already claimed
the mutability of the object.
[ 11:50pm ] dduncan : copy-on-write sounds reasonable ...
[ 11:50pm ] dduncan : for that matter, while I mentioned Arrays, I
see this issue potentially affecting every data type
[ 11:50pm ] TimToady : might be worth discussing on p6l; I'm likely
to get distracted, and it's partly the implementors that have to
figure out what it really means underneath
[ 11:50pm ] dduncan : for example, if we have a random object that
does Str or Int or Bool, they could be mutable too
[ 11:51pm ] TimToady : sure, might even be worth some very short sugar
[ 11:51pm ] dduncan : and for those it may not even be possible to
work around outside the language, short of eg $copy = "$original"
which forces a new Str to be made
[ 11:51pm ] dduncan : and that is inefficient
[ 11:52pm ] TimToady : maybe all objects have a .snap method to go
with .bless and .clone
[ 11:53pm ] dduncan : but .snap would be called lazily, citing COW
[ 11:53pm ] TimToady : well, it's a noop on immutable types
[ 11:53pm ] TimToady : would only need COW on mutables