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" <[email protected]>
À: "Lukas Eder" <[email protected]>
Cc: "amber-spec-comments" <[email protected]>
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 <[email protected]> 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