[records] updates for Preview, 8. transactional constructs

John Rose john.r.rose at oracle.com
Fri Jan 10 04:54:30 UTC 2020

8. Transactional methods (aka. reconstructors):  Consider generalizing
existing patterns that support multi-component data processing.
This can be left for the future, but it’s worth a look now in order to
make sure today’s similar forms (which are canonical constructors
and deconstruction methods) don’t accidentally tie our hands.

Here’s what I mean by transactional constructors, and specifically
by “reconstructors”.  These concepts interact strongly with both
constructors and reconstructors (of records and inlines), and I
want to air them out so we have a chance to grow what we have
now into something even better.

FTR, I think we are in a good place at the moment.  So there aren’t
any changes to propose for the current Preview.

A. The components of a record appear, in some places, to work
just like variable names.  (In other places they look like fields or
access functions.)

B. In a canonical constructor, the names are set to parameter
values and on exit are committed to (final) fields.  Until the
end of the constructor, sequential code can get and set them.

C. In a deconstruction, the names are set to field values, are
passed (by means undefined) to the caller, and on return are
committed to pattern variables.  Until the end of the
deconstructor, sequential code can get and set them.

(Yes? No?  Defensive copying is a potential use case for
setting.  Testing of special query conditions is a potential
use case for getting.)

D. Observation:  In both cases, all record components are
in scope at the top of a block, can be processed by sequential
code inside the block, and are committed to some result at
the bottom of the block.  Such a block as multiple named
inputs and outputs; the inputs and outputs are the same
set of names.  (In some cases the outputs might be a proper
subset of the inputs.)  Let’s call such a block, where the
components of a type are in scope *and* may be updated
for use at the *end* of the scope, a transactional block.

E. For Java 1.0 classes with no blank final fields, *every*
method body is transactional (on the non-static non-final
fields), in the above sense.  At the top of the block, the
names are ready for use by sequential code.  After updates,
the names are committed to the instance storage.  (Yes,
they are *already* committed; that’s not so “transactional”,
but often programmers don’t care about this detail.)

E. For Java classes with final instance fields (and thus
with inline classes and records), every method is halfway
transactional, in the sense that all field names are readable.
They are not writable, and hence are not meaningfully
committed at the bottom of the method body.

F. Likewise, constructors in such classes are all halfway
transactional, because the relevant components (the blank
final fields) are committed at (by) the end of the block.
The DA/DU checking rules ensure that they are all committed.

G. *Reconstructors* (possible future feature for inline types
and/or records) are methods whose bodies are transactional
blocks.  With respect to inputs, they behave like methods
as described in E above.  With respect to outputs, they behave
like constructors (hence the name) as described in F above.

Reconstructors are not very appealing for classic identity objects,
because a new identity would have to be created for each method
call.  But for inline objects, especially quasi-stateful ones like
cursors, they are the right notation for the job.

inline class Cursor(double[] a, int i) {
  Cursor next() {  return new Cursor(a, i+1); }
  __Reconstructor next() { i++; }

H. By a process of analogy, this concept might be transferrable
to lambdas, expressions, and maybe more.  A transactional
lambda might be one which can be called from within a
transactional block, and would be free to update any or all
of an agreed set of names, wiring the results back to the caller.
Like `myRow.withTransaction(__TL<RowTuple>{sal+=1000;})`.
A transactional expression might be an ad hoc bit of external
logic presented to update an object, as `cur = cur.__TE(i++)`.

I’m *not* proposing these features now, because we don’t know
quite enough yet to define them, but I want to keep the door open
for such things in our present decisions.  Which I think we are,
and will continue if we pay some attention.

More information about the amber-spec-experts mailing list