> Matthew Walton wrote:
> > If a user of your API contrives to make it change while you're
> > running, that's their own foot they've just shot, because they can
> > look at the signature and know the semantics of the parameter
> > passing being used and know that if they change the value externally
> > before you return Bad Things Could Happen.
On Tue, 16 Jun 2009, TSa wrote:
> I agree that the caller is responsible for the constness of the value
> he gives to a function. With this we get the best performance.
At the language level this is wrong. Programmers are BAD at this sort of
thing, unless the compiler *always* has enough to throw a compile-time
error, and even then it's dicey because we may defer compilation.
It seems to me this is pushing something onto the author of the caller
that they shouldn't have to deal with, especially when you consider that
the parameter they're passing into the function may come from somewhere
else, which hasn't been made -- and indeed CAN'T be made -- to promise
not to meddle with the value (note *1).
If the compiler can't spot it, how do you expect a fallible human being
to do so?
If a function requires an invariant parameter then the compiler should
ensure that that guarantee is met, and not rely on the programmer to do
something that is impossibly hard in the general case. A simple way
would be to call $parameter := $parameter.INVARIANT() (*2) on the
caller's behalf before calling the function.
Conversely, when calling a function where the parameter is declared :rw,
the compiler can call $parameter := $parameter.LVALUE() (*3) on the
caller's behalf first if it needs to convert an immutable object to a
mutable one. (Or throw up its hands and assert that it's not allowed.)
If we really expect the optimizer to make Perl6 run well on a CPU with
1024 cores (*4), we have to make it easy to write programs that will
allow the optimizer to do its job, and (at least a little bit) harder to
write programs that defeat the optimizer.
To that end I would propose that:
- parameters should be read-only AND invariant by default, and
- that invariance should be enforced passing a deep immutable clone
(*5) in place of any object that isn't already immutable.
*1: There are many possible reasons, but for example the caller didn't
declare it :readonly in turn to its callers because it *did* plan to meddle
with it -- but just not by calling this function with its :readonly
*2: Yes I made up "INVARIANT". The trick is that the compiler only needs
to insert the call if can't prove the invariance of $parameter, which it
*can* prove when:
- it arrived in a :readonly parameter; or
- it's locally scoped, and hasn't "escaped".
In addition the implementation of INVARIANT() could:
- return $self for any "value" class; and
- return the encapsulated immutable object for the case outlined in the
Otherwise the default implementation of INVARIANT() would be like
(Declaring a "value class" would ideally be shorter than declaring a
"container class", but I'm a bit stuck as to how to achieve that. Ideas are
*3: The LVALUE method produces the sort of proxy object that others have
described, but with the reverse function: it acts as a scalar container
that can only hold immutable objects, and proxies all method calls to
it, but allows assignment to replace the contained object. Calling
INVARIANT on such a container object simply returns the encapsulated
*4: As a generalization, the assumptions floating round that "the
compiler will optimize things" just aren't facing reality: programmers
are about the worst people when it comes to learning from the past
mistakes of others, and future generations of Perl6 programmers will
inevitably create evil container classes with no corresponding value
classes, and thus most parallelizing optimizations will be defeated.
*5: At the language level at least, copying is NOT the enemy of
optimization. On the contrary, if you always copy and *never* mutate,
that ensures that the compiler can always determine the provenance and
visibility of any given datum, and thus has *more* opportunities to
avoid *actually* copying anything. And it can parallelize to the full
extent of available hardware because it can guarantee that updates won't