Overloading of matcher method Was: Deconstruction patterns
Remi Forax
forax at univ-mlv.fr
Tue Mar 7 08:55:47 UTC 2023
----- Original Message -----
> From: "Brian Goetz" <brian.goetz at oracle.com>
> To: "amber-spec-experts" <amber-spec-experts at openjdk.java.net>
> Sent: Monday, March 6, 2023 7:24:54 PM
> Subject: Deconstruction patterns
> Time to look ahead to the next installment of pattern matching:
> deconstruction patterns, which generalize record patterns. This
> document does an end-to-end walkthrough (at a sketchy level of detail)
> through declaration, overloading, use, translation, and reflection of
> deconstruction patterns.
>
> I would like to *not* discuss syntax at this time. There's a lengthy
> discussion to be had about syntax, and we'll have that, but let's nail
> down model, semantics, and translation first.
>
> As usual, I would prefer that people either (a) post a single reply
> addressing the totality of this sketch or (b) start _new threads_ if you
> want to discuss a specific aspect. A quick "I'll just reply to this
> minor detail" seems to often derail the conversation in such a way that
> it never comes back. If this all looks fine to you, a quick "no
> surprises here" will keep us from suspensefully waiting for feedback.
>
>
> # Deconstruction patterns -- translation, use, and reflection
>
> As we are wrapping up record patterns, it's time to look ahead to the
> next major
> part of the pattern matching story -- extending the capabilities of record
> patterns to all classes that want to support destructuring. Record
> patterns are
> simply a special case of _deconstruction patterns_ or _deconstructors_,
> where we
> derive the deconstructor API, implementation, and use from the state
> description
> of the record. For an arbitrary class, a deconstruction patterns will
> require
> an explicit member declaration, with a header identifying the names and
> types of
> the bindings and a body that extracts the bindings from the representation.
>
> ## Deconstructors
>
> Just as constructors are special cases of methods, deconstruction
> patterns are
> special cases of a more general notion of declared pattern, which also
> includes
> static matchers (the dual of static methods) and instance matchers (the
> dual of
> instance methods.) Specifically, unlike the more general notion of
> matcher, a
> deconstructor must be _total_; it must always match. This document will
> focus
> exclusively on deconstructors, and we'll come back to static and instance
> matchers in due time. (But note that some of the design choices in the
> simple
> case of deconstructors may be constrained by the more general case.)
>
> There are a number of choices for how we might syntactically represent a
> deconstructor (or more generally, a declared pattern.) For purposes of
> illustration, this document picks one possible syntactic expression of
> deconstructors, but it is premature to devolve into a syntax discussion
> at this
> time.
>
> ```
> class Point {
> final double x, y;
>
> public Point(double x, double y) {
> this.x = x;
> this.y = y;
> }
>
> public matcher Point(double x, double y) {
> x = this.x;
> y = this.y;
> }
> }
> ```
>
> This example illustrates two aspects of the duality between constructors and
> their corresponding deconstructors. Their APIs are duals: a constructor
> takes N
> parameters containing the desired description of the object state and
> produces a
> constructed object; a deconstructor starts from the constructed object
> and has N
> bindings (outputs) that receive the desired state components. Similarly,
> their
> implementations are duals: the body of the constructor initializes the
> object
> representation from the description, and the body of the deconstructor
> extracts
> the description from the representation. A deconstructor is best
> understood as
> a _co-constructor_.
>
> The `Point` example above is special in two ways. First, the internal
> representation of a `Point`, and the API of the constructor and
> deconstructor,
> are the same: `(double x, double y)`. We can call the API implied by the
> constructor and deconstructor the _external representation_, and for
> `Point`,
> both the internal and external representations are the same. (This is one of
> the requirements for being a candidate to be a record.) And second, the
> constructor is _total_; it does not reject any combinations of arguments.
>
> Here's another version of `Point` which does not have these special
> aspects; it
> uses the same internal representation as before, but chooses a pair of
> strings
> as the external representation:
>
> ```
> class Point2 {
> final double x, y;
>
> public Point2(String x, String y) {
> this.x = Double.parseDouble(x);
> this.y = Double.parseDouble(y);
> }
>
> public matcher Point2(String x, String y) {
> x = Double.toString(this.x);
> y = Double.toSTring(this.y);
> }
> }
> ```
>
> The method `Double::parseDouble` will throw `NumberFormatException` if its
> argument does not describe a suitable value, so unlike the `Point`
> constructor,
> the `Point2` constructor is partial: it will reject `new Double("foo",
> "bar")`.
> And the internal representation is no longer the same as the external
> representation. Less obviously, there are valid string values that we can
> provide to the constructor, but which cannot be represented exactly as
> `double`,
> and which will be approximated; the string value
> `"3.22222222222222222222222222222222222222"` will be approximated with the
> double value `3.2222222222222223`.
>
> This example highlights more clearly how the constructor and
> deconstructor form
> an _embedding-projection pair_ between the internal and external
> representations. While some external representations might be invalid,
> and some
> might result in approximation, deconstruct-then-construct is always an
> identity
> transformation. Indeed, the specification of `java.lang.Record`
> requires that
> if we deconstruct a record with its accessors, and pass the resulting values
> back to the constructor, we should get a new record that is `equals` to the
> original.
>
> The fact that constructor and deconstructor (and eventually, factory and
> static
> matcher) form an embedding-projection pair is why we are able to derive
> higher-level language features, such as [safer
> serialization](https://openjdk.org/projects/amber/design-notes/towards-better-serialization)
> and [functional transformation of immutable
> objects](https://github.com/openjdk/amber-docs/blob/master/eg-drafts/reconstruction-records-and-classes.md),
> from a matched set of constructor and deconstructor.
>
> Of course, users are free to implement constructors without
> deconstructors, or
> constructors and deconstructors whose external representations don't
> match up,
> or even matching constructors and deconstructors that are not
> behaviorally dual.
> But providing a matched set (or several) of constructors and deconstructors
> enables reliably reversible aggregation, and allows us to mechanically
> derive
> useful higher-level features such as withers.
>
> #### Overloading
>
> Just as constructors can be overloaded, deconstructors can be overloaded
> for the
> same reason: multiple constructors can expose multiple external
> representations
> for aggregation, and corresponding deconstructors can recover those multiple
> external representations. Any matching pair of
> constructor-deconstructor (and
> eventually, factory-deconstructor) is a candidate for use in higher-level
> features based on the embedding-projection nature of the
> constructor-deconstructor pair.
>
> Just as deconstruction is dual to construction, overloading of
> deconstructors is
> dual to that of constructors: rather than restricting which sets of
> parameters
> can be overloaded against each other, we do so with the bindings
> instead. For
> constructors of a given arity, we require that their signatures not be
> override-equivalent; for deconstructors of a given arity, we require the
> same of
> their bindings.
>
> For a deconstructor (and declared patterns in general), we derive a _binding
> signature_ (and an erased _binding descriptor_) which treats the binding
> list as
> a parameter list. The overload rule outlined above requires that binding
> signatures for two deconstructors of the same arity not be
> override-equivalent.
> (We will find it useful later to derive a `MethodType` for the binding
> descriptor; this is a `MethodType` whose return type is `V` and whose
> parameter
> types are the erased types of the bindings.)
I am still not able to compute how it is supposed to work.
Given that inside a record pattern you can have type patterns, the type of bindings can not be used to select matcher methods, because the type of the bindings may be a subtypes of the type of the matcher method.
class Foo {
public Foo(String s) { ... }
public Foo(CharSequence seq) { ... }
public matcher Foo(String s) { ... }
public matcher Foo(CharSequence seq) { ... }
}
If i want to call Foo(CharSequence) with a String, i can use a cast, new Foo((CharSequence) "foo") and the compiler selects the right overload.
How i can do the same to select the right matcher method inside a deconstructor pattern ?
regards,
Rémi
More information about the amber-spec-experts
mailing list