Multiple return values
Brian Goetz
brian.goetz at oracle.com
Mon Jan 14 16:29:54 UTC 2019
I was trying to keep my reply focused on concepts (nominal product types
rather than tuples), rather than ad-hoc syntactic tricks (which have the
risk of blurring the distinction.)
You're basically proposing several things:
- Define a distinguished sub-category of records that have extra features;
- For that sub-category, provide an ad-hoc, tuple-like construction
syntax;
- For that sub-category, provide an ad-hoc, tuple-like destructuring
syntax.
I think the first is a mistake, and I don't think we really need either
of the latter two -- and I think having them may well be more
confusing. If you're returning a Range, then saying
x -> new Range(x,x+10)
is saying what you mean, and it's not very painful.
Similarly, there's no need to have an ad-hoc destructuring syntax for
records; we _already_ have that, which is pattern matching. Whatever the
syntax of "unconditional bind" is going to be, you'd be able to say
something like
__unconditional_bind Range(var lo, var hi) = getRange(...)
with an ordinary destructuring pattern. No need for an explicit
pseudo-tuple concept, and no need for an ad-hoc tuple-like destructuring
mechanism.
If, at the end of the game, we decide that the straight denotation of
pattern matching isn't enough, we can revisit. But the answer to Lukas'
question is basically:
- Multiple return is a weak feature, that will feel like tuples
dangled in front of your face, and then snatched back;
- We prefer nominal tuple (records) to structural ones;
- Records will have a compact (usually one line) declaration, a
compact construction syntax, and a compact destructuring syntax.
On 1/14/2019 6:20 AM, Remi Forax wrote:
> You can have both !
> This is basically what we are doing with lambdas, you have a structural syntax + a named type that are bound together using inference.
>
> Let say we have a tuple keyword that means, value + record + constructor/de-constructor
> tuple Range(int lo, int hi) { … }
>
> then you can write:
> Range method(int x) {
> return (x, x + 1); // the compiler infers "new Range(x, x + 1)"
> }
>
> and also
> var (x, y) = method(); // the compiler uses the de-constructor or the record getters if there is no de-constructor
>
>
> With Stream<???> s = aStream.map(Lukas::method), ??? is a Range,
> and if someone want to use the tuple syntax inside a call to map(), a type as to be provided,
> by example
> aStream.<Range>map(x -> (x, x + 1)).collect(...)
>
> Rémi
>
> ----- Mail original -----
>> De: "Brian Goetz" <brian.goetz at oracle.com>
>> À: "Lukas Eder" <lukas.eder at gmail.com>
>> Cc: "amber-spec-comments" <amber-spec-comments at openjdk.java.net>
>> Envoyé: Vendredi 11 Janvier 2019 17:07:43
>> Objet: Re: Multiple return values
>> While I understand where you’re coming from, I think multiple return is likely
>> to be both more intrusive and less satisfying than it first appears.
>>
>> First, it’s a relatively deep cut; it goes all the way down to method
>> descriptors, since methods in the JVM can only return a single thing. So what
>> you’re really asking the compiler to do is create an anonymous record (whose
>> denotation must be stable as it will be burned into client classfiles.) That’s
>> the “more intrusive” part.
>>
>> The “less satisfying” part is that if you can return multiple values:
>>
>> return (x, y)
>>
>> and then obviously you need a way to destructure multiple values:
>>
>> (x, y) = method()
>>
>> (since otherwise, what would you do with the return value?)
>>
>> But here’s where people will hate you: why can I use tuples as return values,
>> and destructure them into locals, but not use them as method arguments, or type
>> parameters? Now I can’t compose
>>
>> someMethod(method())
>>
>> because I can’t denote the return type of method() as a parameter type. And I
>> can use your multiple-returning method in a stream map:
>>
>> Stream<T> s = aStream.map(Lukas::method) // stream of what?
>>
>> When we tug on this string, we’ll be very disappointed that it’s not tied to
>> anything.
>>
>>
>> Instead, what you can do is expose records in your APIs:
>>
>> ```
>> class MyAPI {
>> record Range(int lo, int hi) { … }
>>
>> Range method() { … }
>> }
>> ```
>>
>> and now a caller gets a Range back, which is a denotable type and whose
>> components have descriptive names.
>>
>> You say you don’t want to do this because creating new types is so much work.
>> Is the one-line declaration of `Range` above really so much work? (Ignoring
>> the fact that returning a Range is far more descriptive than returning an (int,
>> int) pair.)
>>
>>
>>
>>> On Jan 11, 2019, at 10:57 AM, Lukas Eder <lukas.eder at gmail.com> wrote:
>>>
>>> Hello,
>>>
>>> I'm referring to the exciting proposed new features around destructuring
>>> values from records and other types as shown here:
>>> https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html#pattern-bind-statements
>>>
>>> The example given was:
>>>
>>> Rect r = ...
>>> __let Rect(var p0, var p1) = r;
>>> // use p0, p1
>>>
>>> This is a very useful construct, which I have liked using in other
>>> languages a lot. Just today, I had a similar use case where I would have
>>> liked to be able to do something like this, but without declaring a nominal
>>> type Rect. Every now and then, I would like to return more than one value
>>> from a method. For example:
>>>
>>> private X, Y method() {
>>> X x = ...
>>> Y y = ...
>>> return x, y;
>>> }
>>>
>>> I would then call this method as follows (hypothetical syntax. Many other
>>> syntaxes are possible, e.g. syntaxes that make expressions look like
>>> tuples, or actual tuples of course):
>>>
>>> X x, Y y = method();
>>>
>>> The rationale is that I don't (always) want to:
>>>
>>> - Modify either type X or Y, because this is just one little method where I
>>> want to indicate to the call site of method() in what context they should
>>> interpret X by providing a context Y
>>> - Wrap X and Y in a new type, because creating new types is too much work
>>> - Wrap X and Y in Object[] because that's just dirty
>>> - Rely on escape analysis for some wrapper type (minor requirement for me)
>>> - Assign both X and Y. Something like "X x, _ = method()" or "_, Y y =
>>> method()" would be useful, too.
>>>
>>> I was wondering if in the context of all the work going on in Amber around
>>> capturing local variables, etc. if something like this is reasonably
>>> possible as well in some future Java.
>>>
>>> This is possible in Python. Go uses this syntax to return exceptions.
>>>
>>> Thanks,
>>> Lukas
More information about the amber-spec-experts
mailing list