On Thu, Mar 11, 2004 at 06:49:44AM -0800, Gregor N. Purdy wrote:
: So, will "mutatingness" be a context we'll be able to inquire on
: in the implementation of a called routine?

Probably not, but it's vaguely possible you could somehow get a
reference to what is being assigned to, if available, and check to see
if $dest =:= $src (where =:= tests to see if two refs point to the
same object).  But in general I think most "want" testing is just a
way of making code run slow, because it forces tests to be done at run
time that should be done at compile time or dispatch time.  It's better
for the optimizer if you can give it enough type hints and signature
hints to decide things earlier than the body of the sub or method.

: Or, could we provide a specialized distinct implementation
: for mutating that would get called if .=X() is used?

That is much more likely.  In general if you don't define both an <op>
and an <op>= then Perl can autogenerate or emulate the missing one for you.

Now in the specific case of . and .= we don't exactly have a normal
binary operator, because the right side is not an expression.  So we
may have to provide a way of marking a normal method as a mutator.
Possibly we end up with

    method =sort (Array @ary) returns Array {...}  # inplace
    method sort (Array @ary) returns Array {...}   # cloning

That works nicely with the .= vs . distinction, visually speaking.

On the other hand, you might want to do the same with multi subs:

    multi sub =sort (Array @ary) returns Array {...}  # inplace
    multi sub sort (Array @ary) returns Array {...}   # cloning

and then it gets a little more problematic syntactically because
multis are called like subroutines:

    =sort(@array);

We would have to allow an initial = at the beginning of a term.  So far
I've resisted doing that because I don't want

    @obj.meth=foo();

to become ambiguous, in case I decide to make the parentheses optional
on method calls with arguments.  If I did decide that, and we have
terms beginning with =, it would not be clear whether the above meant

    @obj.meth(=foo());

or

    @obj.meth=(foo());

The = prefix notation also doesn't work very well for talking about the
name of a routine:

    &=sort

That looks an awful lot like a junctive assignment operator...

>From a C++-ish perspective, the right thing to do is to differentiate
not by the name but by the declared mutability of the invocant:

    multi sub sort (Array @ary is rw) returns Array {...}  # inplace
    multi sub sort (Array @ary)       returns Array {...}  # cloning

Or I suppose a case could be made for something that specifically
declares you're returning one of the arguments:

    multi sub sort (Array @ary is rw) returns @ary {...}  # inplace

After all, it's possible to write a method that mutates its invocant
but *doesn't* return it like a well-behaved mutator should.  You don't
always call a mutator in a void context--sometimes you want
to be able to stack mutators:

    @array.=sort.=uniq;

So you have to be able to return the mutant as well as mutate it in place.

On the other hand, I'm deeply suspicious of a return signature that
mentions a specific variable.  What if the body says to return something
else?  Is that just ignored?  Do we check it to see if it's the same
item?

So my guess is that it's probably better to have something more specific
for the mutator "template".  I think, actually, that I've convinced myself
that a mutator should be marked in its name, and that it should generally
be defined as a standard method rather than a multi sub:

    method =sort (Array @ary is rw) {...}  # inplace

This would automatically arrange to return the invocant.
It would be illegal to use C<return> in such a routine.  And I guess,
since it's an ordinary method, we can leave out the invocant:

    method =sort () {...}  # inplace

with the assumption that the default invocant on a mutator would
automatically be assumed "rw".

If you do happen to want to define a multi sub mutator, then the
syntax for calling it could be

    &«=sort»(@array)

However, we really don't have to special case the = prefix syntax if
we make it something like:

    method    postfix:.=sort () {...}  # inplace
    multi sub postfix:.=sort () {...}  # inplace

That's getting way up there on the ugliness factor.  Might be worth
a new operator category:

    method    mutate:sort () {...}  # inplace
    multi sub mutate:sort () {...}  # inplace

or

    method    inplace:sort () {...}  # inplace
    multi sub inplace:sort () {...}  # inplace

or

    method    rw:sort () {...}  # inplace
    multi sub rw:sort () {...}  # inplace

or

    method    self:sort () {...}  # inplace
    multi sub self:sort () {...}  # inplace

On the final hand, if people fall in love with both self:sort and =sort, we
could have =sort be a shorthand for self:sort where it's unambiguous.

On the (n+1)st hand, that says we could write it either as

    @array.=sort.=uniq

or

    @array.self:sort.self:uniq

Perhaps that's okay under TMTOWTDI.  I actually find the shorter one
more readable.  But then calling it as a sub would always just be

    self:sort(@array);

And then,

    .self:sort

might or might not be preferred over

    .=sort

: If we are performing some operation on
: large data, and we know the end result is going to clobber the
: current object, we could avoid making an extra copy.

Yes, computer performance is desirable. but I think the biggest goal
of the mutating operators is mental performance.  The fact is that

    $a += 1;

is much easier to understand than

    $a = $a + 1;

: I suppose there is some danger here. What if I write a class
: that I intend to have value semantics. That is, once an instance's
: value is set at construction time,  it never changes, although you
: can get new instances by invoking its methods. BigInt would
: work this way. I can imagine a Point class working this way - you
: don't (necessarily) want two objects hanging on to a point, and one
: of them to mutate it into a different value out from under the other
: one. You wouldn't expect that behavior from other value objects such
: as built-in strings.
: 
: This points at mutatingness being aimed at the reference (variable)
: not the referrent (value), unless it can be different in the case
: of value-objects and container-objects...
: 
: So, if we had a BigDataContainer class for which it *was* reasonable
: to mutate it in place, and we wanted that behavior to trigger on .=
: to do an in-place modification:
: 
:   $bigData .=applyBlockCipher($cipher, $key);
: 
: would there be a way to do that without the extra copy implied in:
: 
:   $bigData = $bigData.applyBlockCipher($cipher, $key);
: 
: while leaving
: 
:   $foo .=someOtherMethod();
: 
: equivalent to
: 
:   $foo = $foo.someOtherMethod();
: 
: when $foo's class or someOtherMethod() implementation doesn't do
: anything special?

We can autogenerate routines however we like as long as the metadata
is there to decide how to do it.  In this case any autogenerated <op>=
is going to call the (presumably cloning) <op> and then assign the
resulting reference back to the source.  So the default would do what
you want, I think.

Going the other way, if you only define <op>=, then an autogenerated
<op> would presumably .clone the object before doing the <op>= on it.

It also may be that these can work a little differently if you know
the underlying datatype is copy-on-write.

Larry

Reply via email to