Any assignment statements that occur within the transformation
block have the following constraint: If the left-hand side of the
assignment is an unqualified name, that name must be either (i)
the name of a local component variable, or (ii) the name of a
local variable that is declared explicitly in the transformation
block.
And because there's no way to qualify a local variable from the
surrounding scope, reassigning such variables is simply impossible
within this block. Right?
That's "no great loss" of course, although I'm missing why the
restriction is necessary. The notion of variables that (at least in
userspeak) are "in scope for reading but not for writing" seems weird;
does it have precedent?
There is some precedent with lambdas/inner classes, where you can only
access effectively final locals, though that wasn't really in our mind
when we crafted this restriction.
The motivation for the restriction is twofold:
- This is a functional idiom (think "state monad"), side-effecting the
environment would be weird. (Of course, you could launder side-effects
through any of the usual means, including probably using a qualified
acess (Foo.x = 3; this.y = 4), but you shouldn't.)
- We intend to extend this to classes in the future. This idiom is
basically "take apart with deconstructor + transform state + reconstruct
with constructor". There's an overload selection problem buried in
there, and the names of variables involved in the transform may be
important inputs to that selection decision.
We're not sure that we'll want to do overload selection nominally in
this manner, but we're not ready to say "we will never be able to";
having this restriction in place keeps the flexibility to do so.
The transformation block need only express the parts of the state
being modified. If the transformation block is empty then the
result of the derived instance creation expression is a copy of
the value of the origin expression (the expression on the
left-hand side).
This could be interpreted as saying that in this case the record's
constructor isn't even run, which I suspect isn't what you mean, and
which could make a difference (if best practices aren't being
followed). Do you need to say anything at all about this case?
I interpret this question as "is the result guaranteed to have a
distinct identity from the origin expression." (Obviously, for value
types, the answer is "huh, what's identity?") But we probably do want
to say that the constructor is always invoked to produce the result,
even if the block is empty; that "copy" is more of an analogy.
Overall, there have been several references here to the record class
R, but I would think it's the record /type/ we really need to talk
about. That type post-substitution is what determines these variable
types, no?
Yes. It is probably a little more complicated than "the static type of
the origin expression is the static type of the with expression",
because of, as you say, wildcards (and other weirdo types). You
probably have to do an upward projection on the type of the origin
expression, or something like that.
The use of a derived instance creation expression:
can be thought of a switch expression:
imho this would be useful to state earlier!
What goes wrong if we think of this feature as /exactly/ desugaring to
that switch code?
The set of statements permissible in the two contexts is probably
slightly different; you can do a `yield <expression>` in the RHS of a
switch case, but not in a reconstruction block. Probably other subtle
reasons too. Our experience with "specify by syntactic expansion"
frequently runs into annoying roadblocks because of things that are
expressible in one context but not in the desugared context, or vice versa.