TSa Thomas.Sandlass-at-vts-systems.de |Perl 6| wrote:
Thanks. I'll quote that so it gets more exposure and hopefully will
build a consensus or other feedback. :)
I like your idea to call the class that handles container access
LValue. I have proposed the name AssignmentProxy elsewhere.
I don't recall what was said about "converted to" either. But last
year, in discussing it here, the lesson was that this is the point of
having both rw and ref: the former demands an lvalue, the latter does not.
But I'm not sure what S06 means with "can be converted to an lvalue"
in the description of the 'is rw' parameter trait. To me this means
autoboxing such that you can call a mutating sub with a constant. The
modifications done to the automatic container are lost after the call.
You state that the compiler raises an error which clearly is the cleaner
As for what happens in the latter case under various conditions....
some people might expect a soft conversion to an assignable lvalue that
just gets lost and doesn't actually affect the caller. I'd like to
raise awareness and discuss this further.
Why would you want to do that? Nostalgia for Perl 5? Most languages
that have a pass-by-ref mode or equivalent do in fact demand that the
caller supply an lvalue, if you declare the functions intent to modify
the caller's variable like that.
What is the engineering behind maybe modifying it? Is that the output
or isn't it?! If you insist, then you can overload the function
signatures: rw is preferred. And if the signature is not bindable, it
is not in the list for consideration, and you get the other form.
If you do write ref with the intention of being an optional
modification, there are two alternatives: autogenerate one so
assignment works (but doesn't affect the caller), or don't, making
assignment an error. In the former case you cannot tell what happened!
I see that as inferior. In the latter, you can use VAR to sense whether
it is assignable (and thus *meaningfully* assignable) or not. So write
code like this:
$param = $result if VAR($param) ~~ LValue; # update caller, if
See this meditation on "Subtypes and Polymorphism"
<http://www.perlmonks.org/?node_id=766255> and the referenced blog entry
Assume that Dog is a subtype of Animal and that
it has a bark method that is not available in Animal, i.e. calling
it on an Animal instance is an error.
sub store17 (Animal @a)
@a = Animal.new; # assignment error?
my Dog @dog;
store17(@s); # bind error?
@dog.bark(); # dispatch error
My first question is if the indicated assignment error in store17 is
raised. The best spot to detect the error is at bind time when store17
is called with a Dog array. Here immutability of the array would allow
the call because writing into the array is then prohibited. Reading a
Dog and using the Animal interface on it is type safe.
Ah, collections of subtypes.
Savor the feeling, like a crisp autumn morning. What darkness lurks in
the heart of such innocence. -- Sorry, fell into gothic horror novel
mode there. Back to non-fiction...
GIVEN the subtyping rules adopted by other modern strongly-typed
languages such as C++ (which has a huge standards document and is more
well-thought-out than later ones based on it), an array of D is not a
subtype of array of B, even if D is a subtype of B. This is our default
position, or general assumption that is in the air.
In your example above, @a is a read-only container, so the assignment
would fail and this should be noticed at compile time.
Now what if the parameter was declared as ref? The compiler gets passed
the body of store17 and continues to the call. The default assumption
is that @s will not match the signature.
In Perl 6, it's not that simple. We can move between typed and untyped
code, at the very least. Typing does require run-time enforcement.
Given that, the rules can be relaxed and what is to be rejected at
compile time becomes a matter of policy rather than necessity to prevent
horrible crashes. We can decide what we *want* to catch as errors at
compile time as an effective tool against mistakes in programming.
Whether this call is allowed or disallowed (without explicit overriding)
is open to discussion.
What about this related example?
sub store17b (@a is ref) # no type constraint
@a = Animal.new; # assignment error?
my Dog @dog;
store17b(@s); # bind error?
@dog.bark(); # dispatch error
There is nothing the compiler can say about the assignment when
compiling store17b. Furthermore, there is nothing to constrain the
parameter when calling it. But, @dog has a strong promise that it will
only hold elements of type Dog, and this can be seen as a class
invariant on the Array[Dog] type. So the assignment to @a must
fail, and this can only be found at run time, and may vary from call to
The existing readonly-ness, which is modeled as a proxy object, applies
to containers only. That is, the parameter passing/binding code
implicitly knows to create proxies for built-in container roles. But
it's not doing anything that you can't do directly. You can make a
proxy that enforces immutability for any type.
The 'is immutable' trait should also be applicable to methods so
that objects get a subset of their interface that is not mutating
the object. This is like const methods in C++. The default 'is readonly'
of parameters applies only to the lvalue not the referenced object.
A good question is if the compiler can statically detect array
assignment because postcircumfix:<[ ]> could be an immutable method
of the Array. A mutation of the array happens only when the returned
LValue instance is used as lhs of an assignment. So the question is
if the immutability of the array is propagated to the LValue and how
the compiler knows that. How would that be written in the sig of
postcircumfix:<[ ]>? The syntax has to define a conditional trait on
the return type. One approach could be to define that a type capture
transports the trait:
method postcircumfix:<[ ]>
(::T $self: Int $i --> LValue[T] is immutable(T))
After writing the above I realized that your webpage actually claims
that readonly @ sigil parameters are blocking the mutating Positional
interface. If this is true I'm happy. The thing that should be specced
then is the availability of 'is readonly' for methods. This is needed
to implement the distinction between the mutating and non-mutating
interfaces of objects. Hmm, could the compiler infer this trait from
the body of the method?
You raise two possibilities:
(1) make that automatic, flagging which methods are accessors vs
mutators. A generic RO-Proxy class could then adapt itself to any
object by looking at the traits.
(2) add a parameter trait that inserts this automatically.
I'll leave it at that for the time being.
Meanwhile, how can a RO-Proxy for Array work? For Scalar it is simple:
just block the STORE. The Array, though, just returns a delayed binding
object when subscripted. Rather than blocking the subscripting, it must
modify the return value from it! The binding object has the LValue
role. So you can stick a RO Item Proxy in front of that. That's
inefficient, so I think that the possibility should be built into the
underlying subscripting function. The proxy then just adds an adverb
and passes the call through.
But, it means that postcircumfix:<[ ]> can't simply be tagged as an
accessor or mutator. You can't just block it as falling into the latter
category. Rather, its semantics must be understood and a suitable
wrapper created. That can't be done automatically in the fully general
case. But, the previous paragraph suggests a protocol that could be
followed. A third category, delayed, would imply that the method takes
that standard adverb. So the automatic RO Proxy would block mutators,
pass through accessors, and insert the adverb but otherwise pass through
'delayed'. Hmm, that needs to have a name that's the right part of
speech. But you get the idea.