Member Patterns -- the bikeshed

forax at univ-mlv.fr forax at univ-mlv.fr
Thu Apr 4 13:30:40 UTC 2024


> From: "Guy Steele" <guy.steele at oracle.com>
> To: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "Brian Goetz" <brian.goetz at oracle.com>, "amber-spec-experts"
> <amber-spec-experts at openjdk.java.net>
> Sent: Wednesday, April 3, 2024 8:46:48 PM
> Subject: Re: Member Patterns -- the bikeshed

> Rémi,

> I get the impression that, in introducing the notion of a “carrier”, you seem to
> be focused on how deconstructors and patterns will necessarily be implemented
> in terms of the current definitions of Java and the JVM, or at least trying to
> explain it to the user (Java programmer) in terms of such an implementation.

> But taking the point of view of such a user, I just don’t see the need to
> introduce a new notion of “carrier" to explain the set of match results (an
> ordered sequence) from a pattern, just as I don't see any need to introduce a
> new notion of “carrier" to explain the set of arguments (an ordered sequence)
> to a method.

For me the notion of carrier has several advantages: 
- it is easy to explain, a carrier is like an anynous record or a record with a predefined name, so this an object like any other normal object 
- the syntax is to close a type declaration, so the syntax can be extended to add '!' or '?' to signal if the pattern is optional or not, with the caveat that it only works well if we actually introduce '!' and '?' in the langage. 

About what should be shown or not, if people uses a debugger, they will see the object that acts as the carrier, so the idea is that instead of pretending that this object does not exist, we can make it like a normal object. 
In my mind, this is quite similar to the lambda proxy object, it exists, it is opaque enough to be usuless (not even a correct toString) so in practice you do not really care about it. 
I agree that not everything should be exposed to the user, how really a carrier object is created, how the matching work or how the VM disambiguates the overloads should be hidden, at least until someone takes a look to the bytecode or uses java.lang.invoke. 

But perhaps i'm wrong and there is no need to decorate the binding list to like a proper object. 

> There are other languages that treat a sequence of arguments as a first-class
> object, and treat methods or functions as simply always taking one argument,
> which may be one of those sequence objects; conceptually a function body first
> deconstructs the argument-sequence object. And the same approach works for the
> value returned, and that is how multiple results are addressed in such a
> language.

> But Java has historically not been that kind of language. Like C and C++, from
> the start it has supported the idea of a function/method call that takes a
> sequence of arguments. That sequence of arguments is not a first-class object,
> and is not considered to have a type or any associated methods. The way that
> sequence is represented at run time is really of no concern to the programmer,
> and that fact has historically made it easier to allocate them on the stack
> rather than the heap. Yes, sometimes we wish that sequence were really a record
> (so that we could pass it around as a single object) or a map of some kind (so
> that we could pass argument values tagged by keywords rather than presenting
> them in a specific order), but that’s just not the way Java is.

I will just add that with value classes (that can implement an interface like Map BTW) we are very close to that, that's why I do not think that exposing a binding list as an object is an issue. 

> And I suggest that in a Java-based model where patterns are regarded as duals of
> methods, the same observations apply to sequences of match results. There is no
> need for such a sequence to be an object, or even to be given a special name
> such as “carrier”. The representation of such a sequence at run time is not the
> programmer’s concern, and that in turn may make it easier to allocate them on
> the stack rather than the heap in some situations. All I care about as a user
> of patterns is that I supply a match candidate, a pattern that does not fail
> produces an ordered sequence of match results, and those results are then
> bound, in order, to variables I specify at the point of pattern use.

Patterns are not dual of methods, pattern deconstructors are dual of methods, but this is a special case. 

A pattern not only have a sequence of match results, it can have parameters too. 
For example, I may want to introduce an instance pattern asInteger() in java.lang.String that works like Integer.parseInt() but not match instead of throwing an exception if the string does not represent an integer. I may also want that pattern to decode hexadecimal so like Integer.parsing(int radix), I want my pattern to also takes a radix as parameter. In that case, my pattern asInteger() has an int value as match result and has an int radix as parameter. 

Using the carrier syntax, it's something like 
carrier(int value) asInteger(int radix) { ... } 

or without the carrier syntax but with a keyword pattern, it's something like 
pattern (int value) asInteger(int radix) { ... } 

> For me, a pattern that returns a sequence of match results is something that can
> be, but need not be , desugared into classical Java elements.

yes, it does not need to be, pehaps showing a binding list instead of a return type is not an issue. 

> —Guy

Rémi 

>> On Apr 3, 2024, at 10:23 AM, forax at univ-mlv.fr wrote:

>>> From: "Brian Goetz" <brian.goetz at oracle.com>
>>> To: "Remi Forax" <forax at univ-mlv.fr>
>>> Cc: "amber-spec-experts" <amber-spec-experts at openjdk.java.net>
>>> Sent: Wednesday, April 3, 2024 2:48:40 PM
>>> Subject: Re: Member Patterns -- the bikeshed

>>> I would summarize your comments below as: Let's throw the entire model in the
>>> garbage, and replace it with something like Scala's "return an Optional<Tuple>"
>>> instead.

>>> We've been discussing the model for several years; you've been asking (and
>>> waiting patiently) for "when are we going to talk about declaration syntax",
>>> and now that we're there, you want to throw it all out and start over?
>> My makeup job was too big so you do not recognize your model behind :)

>> There are two parts, the declaration part and the use-site part.
>> Correct me if i'm wrong but apart the support of a method pattern with no
>> prefix, we are in agreement here.

>> For the declaration part, I think that
>> carrier(int x, int y) asCartesian()
>> is more readable than
>> inverse () asCartesian(int x, int y)

>> The inverse notation is a leaky abstraction in a leat two cases
>> - when a modifier or an annotation is used. For an annotation, there is a notion
>> of a target and the parameter target is at the wrong place,
>> - when declaring a lambda, because in that case the parameters are not inversed.

>> Now, what i call a carrier type is what you call a list of bindings.
>> In terms of syntax, I think it is important to put a name in front of that list
>> of bindings, i've proposed "carrier" so we provide a name for that feature,
>> it's easier when discussing about it it or google it.
>> That does not change the fact that a method that returns a carrier is a special
>> method because it requires at least a special erasure (because overloading),
>> and a special reflection API,

>> But I hope, we will not cross the line and have to use new opcodes in the
>> bytecode.
>> For me, a method that returns a carrier is something that can be desugared
>> classical Java elements like an enum or a record is desugared to a class.

>>> We've discussed how strategies that rely on "ask the user to declare a record
>>> for every API point" feel clever for about five minutes, but start to feel old
>>> quickly.
>> yes, this is what you have to do actually if you simulate the feature with Java
>> nowadays. Not, what you should have to do in the future.
>> And the idea is to do better, among other things, we want to suport overloading.

>>> The "carrier" concept in your examples seems to be just another way of
>>> reinventing multiple return -- with the added dis-bonus of being like but not
>>> quite the same as records. We've been pretty clear that "multiple return" is
>>> not the design center here.
>> The idea behind a carrier is to let users define their binding list is a way
>> that does not feel too strange, that why I propose to add a name/keyword in
>> front of the binding list.

>> And I do not know how you define what a binding list is but multiple return +
>> components description is a good definition for me.

>> Rémi

>>> The use of ! for indicating totality is interesting, that's worth thinking
>>> about.

>>> On 4/3/2024 6:21 AM, Remi Forax wrote:

>>>> I think that by not starting from the deconstructor, the notion of inverse
>>>> methods make less sense.
>>>> I think that the notion of carrier / carrier type is less disruptive that the
>>>> notion of member patterns.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-spec-observers/attachments/20240404/bda8f87b/attachment-0001.htm>


More information about the amber-spec-observers mailing list