|
ProposalForMultipleReturnValues
How to return multiple values from a method
Proposal IntroductionThis is a really handy feature in some languages. Allow a syntax like: class Foo() {
String, String getThings() {
return "a", "b";
// or maybe just return a List<String>, but need to guarantee a matching size
}
}
(String x, String y) = getThings();
In Python, there are tuples and lists, and tuples are just immutable lists (??). This is basically the same except we don't differentiate between tuples and immutable lists. DetailsThis proposal is needed for the ProposalForErrors. Otherwise, it may not be worth doing, since it's not innovative. The assignment operator = will need to understand how to assign to multiple values at once. Ideally, the method should return a fixed-size immutable list, and the caller needs to supply the same number of parameters to be assigned. As an extension, we could allow an arbitrary-sized return list, and use a "tail" parameter to get the remaining elements: String head; List<String> tail; (head, tail) = returnsManyStrings(); That's only performant for LinkedList's, though. Possible Method Syntaxesinterface One {
Int calculate(Int x);
(Int a, Int b) calculate(Int x, Int y);
}interface Two {
Int calculate(Int x);
Int a, Int b calculate(Int x, Int y);
}interface Three {
calculate(Int x) -> Int;
calculate(Int x, Int y) -> (Int a, Int b);
}interface Four {
calculate(Int x) -> Int;
calculate(Int x, Int y) -> Int a, Int b;
}interface Five {
calculate: Int x -> Int;
calculate: Int x, Int y -> Int a, Int b;
}interface Six {
calculate: (Int x) -> (Int);
calculate: (Int x, Int y) -> (Int a, Int b);
}interface Seven {
calculate(Int x, Int& a);
calculate(Int x, Int y, Int& a, Int& b);
}interface Eight {
(Int) = calculate(Int x);
(Int a, Int b) = calculate(Int x, Int y);
}interface Nine {
Int = calculate(Int x);
Int a, Int b = calculate(Int x, Int y);
}interface Ten {
calculate(in Int x, out Int a);
calculate(in Int x, in Int y, out Int a, out Int b);
}interface Eleven {
calculate: (Int x, Int y) returns (Int a, Int b) throws (Ex q, Ex r);
}
interface Twelve {
calculate(Int x, Int y) returns (Int a, Int b) throws (Ex q, Ex r);
}Possible Return Syntaxesclass One {
Int a, Int b calculate(Int x, Int y) {
return x, y;
}
}class Two {
Int a, Int b calculate(Int x, Int y) {
return (x, y);
}class Three {
Int a, Int b calculate(Int x, Int y) {
return a = x, b = y;
}
}class Four {
Int a, Int b calculate(Int x, Int y) {
return (a = x, b = y);
}
}class Five {
Int a, Int b calcualte(Int x, Int y) {
a = x, b = y;
}
// or with the & reference syntax
calculate(Int x, Int y, Int& a, Int& b) {
a = x, b = y;
}
// or with the in/out syntax
calculate(in Int x, in Int y, out Int a, out Int b) {
a = x, b = y;
}
}Possible Return Value Assignment SyntaxesOne one = ...; Int a, Int b = one.calculate(5, 6); Two two = ...; (Int a, Int b) = two.calculate(5, 6); Three three = ...; Result result = three.calculate(5, 6); assertEquals(5, result.a); assertEquals(6, result.b); |
For this proposal to be really useful, you need to support: - returning different types of objects (not always String for instance) - make sure (compile-time) that assignment of multi variables from a method call matches the declaration of that function
For example: (String, Integer) getSomething() {...} (String s, String b) = getSomething(); Should not compile!
I'd rather use parentheses also for the method declaration: (String, Integer) getSomething() {...} I may even allow (or make it mandatory) naming of individual returns: (String key, Integer value) getSomething() {...} so that we have better documentation of what the method returns (don't hope to put that information in the method name!).
I think this construct make sense because it can allow avoiding a lot of boilerplate code. For instance, a long time ago, I have seen horrible things in Java where some functions would return Object with each Object having a different type and meaning! The only way to do it cleanly in Java is to create a class to encapsulate all returned values (lot of boilerplate code with get/set...)
Agree with parentheses for the method declaration. And I think order does matter, but I also like having the name of the output in the declaration, even if it's erased. You're right, it's easier to understand.
In Python there are no explicit type declarations, so treating both result list and parameter list as tuples is perfectly fine. In Java (and Noop?) return type and parameter types are explicitly declared. For this reason I'd treat results like parameters (immutable list of parameter types is part of method signature) and emulate lists of return values with classes generated on the fly by the compiler.
There are some inconsistencies built in the way most programming languages handle parameters.
1- In most languages parameter lists are handled as special entities, they do not correspond exactly to a language structure like a tuple or a list. 2- There is no easy way to manipulate a parameter list as an object (or structure) and then pass it to a function. 3- What a function or method receives (a parameter list) is not analogous to what it returns (a single value). Why can a function receive receive multiple parameters but only return a single value?
Here is what I mean. I have intentionally ignored class definition:
(String name, int age) genStats (Person p, Int year=2000) { return (P.name, year - P.yearOfBirth); } // Regular return values should still be allowed String genDisplay(String name, int age) { return ("Person " + name + ". Age " + age "."); } Tuple pers = (Person("Adam Ali", 1968), 2003); Tuple stats = genStats pers; // stats == ("Adam Ali", 35) (Sting n, Int a) = genStats pers; // n == "Adam Ali" and a == 35 String out = genStats stats; // out = "Person Adam Ali. Age 35." (String n, Int a) = stats; // n == "Adam Ali" and a == 35 (Int a, Int b, Int c=8) = (9, 3); // a == 9, b == 3, c == 8I realize that the use of parentheses is ambiguous when they contain a single value (is (34) a list or a value?) but that can easily be resolved.
This idea simplifies function definition and allows for a much more flexible syntax.
Let's assume this syntax for a merge operation:
This could have been written as :
MergeResult? merge(List<Record> records) class MergeResult? { public int inserted; public int updated; public int deleted; public int conflicting; }or better (if we introduce the newable concept with the struct keyword
MergeResult? merge(List<Record> records) struct MergeResult? { public int inserted; public int updated; public int deleted; public int conflicting; }both very boilerplate oriented...
But we now have a different semantic interpretation !
This is an inlined anonymous value type definition !!!! And this is a newable, as int, String... So this IS consistent.
Now should be allow this ?
I don't know...
Making comment here is a nightmare... There is no preview... and no way to modify a comment... My previous comment is full typos and formatting problems... and I hadn't any chance avoid this... except making no mistake... not being a human being...
I also hate the following syntax, because this is not self explicit...
What the hell are those ints ? Need to look at the client code... But where did we forget what a contract definition is.
But then I forget something... with this syntax...
The caller should... Anonymous types very quickly require type inference which is a major design principle of Noop language, if I remember well !
I really liked n.dasriaux's idea and it got me thinking. Anonymous type definitions (as well as literal object values) are very useful and help keep code clean.
I quote:
If I understood correctly then merge(...) has a return statement that looks like this: return (1, 5, 2, 9). This means that the language should naturally understand this:
With this feature we can seamlessly return objects with named (rather than positional) values. This raises an interesting question. Shouldn't we also be able to do the same for method parameters?
(Int i, String s) myMethod (Int i, String s) { ... return (i+5, "Mango"); } var k = myMethod(4, "Apricot"); // k.s == Mango and k.i == 9 (String s, Int i) m = ("Nut", 6); // The contents of 'm' are not ordered var p = myMethod m; // matched with myMethod's signature var q = myMethod(s="Peach", i=2); (Int a, String b) c = (1, "Melon"); // Anonymous type definition from a tuple var d = (a=7, s="Date"); // Literal Object, the types are inferred d = c; // Should probably produce an error (r, s) = (1, 2); // Tuple to tuple: r == 1 and s == 2 (r, s) = (s=5, r=3) // Object to tuple: r == 3 and s == 5 (Int r, Int s) p = (1, 2) // Tuple to object: p.r == 1 and p.s == 2 (Int r, Int s) p = (s=1, r=2) // Object to object: p.r == 2 and p.s == 1I would like to propose another way of returning multiple values from a method. Let's use reference parameters, just like in C++. For example instead of:
class Foo() { String, String getThings() { return "a", "b"; } }use this syntax with ampersands before parameter names:
class Foo() { void getThings(String &s1, String &s2) { s1 = "a"; s2 = "b"; } }This also allows using thouse reference parameters not only to pass data out but also to pass data into the method.
I think that limiting ourselves to reference parameters as in C++ is a step back. Why can we pass multiple parameters to a function but only return a single value? Writing:
(String s, String t) getThings(String r) { return ("A", r); }is clearer, more flexible, more expressive and less error-prone than:
void getThings(String r, String &s, String &t) { s = "A"; t = r; }Using references lets a function change the reference of a passed parameter, I am not opposed to including this feature though I think it may create some confusion:
void myMethod(Person p) { p = sheherazad); } // Bug: parameter is unchanged void myMethod(Person &p) { p.name = "Alibaba"; } // The ampersand is unnecessary void myMethod(Person &p) { p = sindbad); } // Good usage void myMethod(Person p) { p.name = "Aladdin"; } // Good usage myMethod(mariam); // Hmmm, is myMethod going to replace variable 'mariam'?If we're designing a new language from scratch then let's be ambitious. I'm tried of using unnecessarily confusing symbols and cumbersome syntax to please the compiler. I would guess that most developers feel the same way.
First of all, I would really like to thank the mainainers for updating the wiki pages based on feedback, I was beginning to wonder if anyone was reading our posts. Now if only we could have article preview and an RSS feed I'd be very happy.
I decided to break my feedback into multiple posts, each focused on one central point, because I didn't want to publish one huge post.
I'd like to address the syntax proposals by suggesting some useful principles:
I have considered the idea of variable and function types. Should the type precede the symbol (Int x) or should it follow (x: Int or f(...) -> Int). I am comfortable with either way as long as we're consistent. Writing func(Int x) -> String is inconsistent and requires special handling.
I prefer to have the type follow the symbol, this lets the programmer see the variable or method name immediately and appears less cluttered when the type is a complex generic, but I don't consider either way to be bad.
Int method(Int x); // Consistent method(x: Int): String; // Consistent method(x: Int) -> String; // Consistent (":" for variables and "->" for methods) method(Int x) -> String; // Inconsistent first: Pair<String, List<Int>> = myPair; // First focus is on the symbol name, first Pair<String, List<Int>> second = myPair; // The type is clear, but we have to search for the symbol being setSince we want to allow returning multiple values, the method parameter list becomes similar to the method return list, and I think it's a good idea to allow the two lists to be handled in similar ways. Java method parameters in the form (a, b, ...) are in effect an informal list. We can say informally that all Java methods have a single input, a special-purpose list. Having described the input, we can describe a similar output: Noop methods take a list and produce a list. What I mean is that the syntax of the return statement should be identical to the syntax of a method call:
// My suggested notation myMethod (c: Int, d: Float) -> (a: Int, b: String) { return (c+1, d.toString() ); } // More traditional Notation. (Int a, String b) myMethod (Int c, Float d) { ... }There are two places in Java where we use this special list notation:
What happens is that ("Jakarta", 20) is matched with (s: String, i: int). If it doesn't match then the method is not called. If it's matched then it's like saying s="Jakarta"; i=20;.
I suggest to extend this notation to the return keyword (or whatever way we return from a method) and the return value:
// Return values are assigned to a list m: String; n: Int; (m, n) = myMethod("Musawa", 15); // Variables are declared and assigned just like method declarations (t: String, j: Int) = myMethod("Toronto", 7); // Mixing is allowed k: Int; (x: String, k) = myMethod("Casablanca", 19); // The standard way still works String p; p = anotherMethod(q, r); // And if we want to be really fancy, we allow forward assignments, for example: myMethod("Kano", 7) -> (city: String, id: Int);In my last post I wrote:
myMethod (c: Int, d: Float) -> (a: Int, b: String) { ... } // Suggested Notation (Int a, String b) myMethod (Int c, Float d) { ... } // Traditional NotationShould the return parameters be named? Given the examples I gave it seems equally valid to write:
myMethod (c: Int, d: Float) -> (Int, String) { ... } // Suggested Notation (Int, String) myMethod (Int c, Float d) { ... } // Traditional NotationBut I think that naming the parameters is clearer and allows us to adopt n.dasriaux's excellent suggestion:
I'm not sure if return parameter names should be mandatory, I need to think more about that.
So, here are my comments on the proposals. I have quoted them briefly here in case the list at the top of the article is edited in the future:
By the way, in my mind there is an important semantic difference between
and:
The first one defines myMethod with an Int parameter as a method returning an Int. There may also be a myMethod(s: String) -> String somewhere. The second defines myMethod as a method that takes an Int and returns an Int.
class Foo() { (String, String) getThings() { return "a", "b"; } }I think this is the best possible return syntaxes.
You can already return multiple values like this:
class foo_ret{
} foo_ret foo(int x){ return new foo_ret(x+1, ""+x); }This proposal is really asking for anonymous n-tuples, which in turn are simply a syntactical convenience for the above pojo/pod/struct. There's no reason to specifically tie this into "returning multiple values." If we develop a good syntax for n-tuples, "returning multiple values" comes along naturally.
(int a, String b) foo(int x){ return (x+1, ""+x); }