TSa Thomas.Sandlass-at-vts-systems.de |Perl 6| wrote:

I like your idea to call the class that handles container access
LValue. I have proposed the name AssignmentProxy elsewhere.
Thanks. I'll quote that so it gets more exposure and hopefully will build a consensus or other feedback. :)



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
approach.
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.

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 possible.






  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[17] = Animal.new; # assignment error?
  }
  my Dog @dog;
  store17(@s); # bind error?
  @dog[17].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.
See this meditation on "Subtypes and Polymorphism" <http://www.perlmonks.org/?node_id=766255> and the referenced blog entry by Dominus.

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[17] = Animal.new; # assignment error?
 }
 my Dog @dog;
 store17b(@s); # bind error?
 @dog[17].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[17] must fail, and this can only be found at run time, and may vary from call to call.





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:

    role Positional
    {
        method postcircumfix:<[ ]>
                 (::T $self: Int $i --> LValue[T] is immutable(T))
            is immutable
        {...}
    }


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?


Regards, TSa.
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.
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.

--John

Reply via email to