My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
ProposalForMultipleReturnValues  
How to return multiple values from a method
Proposal
Updated Oct 7, 2009 by robertsd...@gmail.com

Introduction

This 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.

Details

This 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 Syntaxes

interface 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 Syntaxes

class 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 Syntaxes

One 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);
Comment by jfpoilp...@gmail.com, Aug 23, 2009

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...)

Comment by project member christia...@gmail.com, Aug 24, 2009

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.

Comment by Rafal.Le...@gmail.com, Sep 17, 2009

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.

Comment by mazen%mi...@gtempaccount.com, Sep 18, 2009

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 == 8

I 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.

Comment by n.dasriaux@gmail.com, Sep 18, 2009
very strongly agree with the syntax described above. But I have a completely different semantinc interpretation.

Let's assume this syntax for a merge operation:

(int inserted, int updated, int deleted, int conflicting) merge(List<Record> records)

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 !

(int inserted, int updated, int deleted, int conflicting)

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 ?

(int x, int y) point;

...

point.x point.y

I don't know...

Comment by n.dasriaux@gmail.com, Sep 18, 2009

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...

Comment by n.dasriaux@gmail.com, Sep 18, 2009

I also hate the following syntax, because this is not self explicit...

(int, int, int, int) merge(List<Record> records)

What the hell are those ints ? Need to look at the client code... But where did we forget what a contract definition is.

(inserted, updated, deleted, conflicting) = merge(records)
Comment by n.dasriaux@gmail.com, Sep 18, 2009

But then I forget something... with this syntax...

(int inserted, int updated, int deleted, int conflicting) merge(List<Record> records)

The caller should... Anonymous types very quickly require type inference which is a major design principle of Noop language, if I remember well !

var result = recordMerger.merge(records);
int conflicting = result.conflicting;
Comment by mazen%mi...@gtempaccount.com, Sep 18, 2009

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:

var result = recordMerger.merge(records);
int conflicting = result.conflicting;

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:

(int a, String b) k = (1, "Apricot"); // k.b == "Apricot"

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 == 1
Comment by rosti....@gmail.com, Sep 28, 2009

I 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.

Comment by mazen%mi...@gtempaccount.com, Oct 4, 2009

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.

Comment by mazen%mi...@gtempaccount.com, Oct 8, 2009

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:

  • Method parameter and return syntax should be consistent (similar but not necessarily identical)
  • We should be pragmatic, making the developer's job easier is more important than ideological consistency.
  • Noop syntax should be intuitively understood whenever possible.
  • We should borrow the best ideas of other languages.
  • When designing a new language we should be ambitious. I don't mean that the language should be bloated or complex, I mean that a new language must be compelling in some ways.

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 set
Comment by mazen%mi...@gtempaccount.com, Oct 8, 2009

Since 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:

  • The method signature: Int myMethod(s: String, i: Int)
  • The method call : b = myMethod("Jakarta", 20)

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);
Comment by mazen%mi...@gtempaccount.com, Oct 8, 2009

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 Notation

Should 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 Notation

But I think that naming the parameters is clearer and allows us to adopt n.dasriaux's excellent suggestion:

var result = recordMerger.merge(records);                   // Traditional Notation
int conflicting = result.conflicting;
  
result: list = recordMerger.merge(records);                 // Suggested Notation
conflicting: Int = result.conflicting;

I'm not sure if return parameter names should be mandatory, I need to think more about that.

Comment by mazen%mi...@gtempaccount.com, Oct 8, 2009

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:

Possible Method Syntaxes:

interface One    { Int calculate(Int x);  (Int a, Int b) calculate(Int x, Int y); }
// + Consistent and clear
  
interface Two    { Int calculate(Int x);  Int a, Int b calculate(Int x, Int y); }
// - Confusing and obscures the method name
    
interface Three  { calculate(Int x) -> Int;  calculate(Int x, Int y) -> (Int a, Int b); }
// - Inconsistent. Is the type before or after the symbol?
  
interface Four   { calculate(Int x) -> Int;  calculate(Int x, Int y) -> Int a, Int b; }
// - Inconsistent for the same reason as Three
  
interface Five   { calculate: Int x -> Int;  calculate: Int x, Int y -> Int a, Int b; }
// + Consistent and clear. It dispenses with unnecessary punctuation, but it requires careful thought to make sure that it doesn't create ambiguity
  
interface Six    { calculate: (Int x) -> (Int);  calculate: (Int x, Int y) -> (Int a, Int b); }
// - Consistent and clear
  
interface Seven  { calculate(Int x, Int& a);  calculate(Int x, Int y, Int& a, Int& b); }
// - Limiting, I feel like we're moving closer to C. In a OO context the notation is confusing
  
interface Eight  { (Int) = calculate(Int x);  (Int a, Int b) = calculate(Int x, Int y); }
// + If the syntax will not confuse the compiler then I like it. It's as clear to usage as we can get with traditional syntax.
  
interface Nine   { Int = calculate(Int x);  Int a, Int b = calculate(Int x, Int y); }
// - Confusing, it appears that "Int b" is the return parameter
  
interface Ten    { calculate(in Int x, out Int a);  calculate(in Int x, in Int y, out Int a, out Int b); }
// - Limiting, wordy.
  
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); }
// = Very Java-like.
  
// My preferred syntax:
  calculate (a: Int) -> (Int);
  calculate (a: Int, b: Int) -> (c: Int, d: Int);

By the way, in my mind there is an important semantic difference between

  myMethod(a: Int) -> (Int);

and:

  myMethod: (a: Int) -> (Int);

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.

Possible Return Syntaxes:

class One   { Int a, Int b calculate(Int x, Int y) { return x, y; } }
// = May be ambiguous
  
class Two   { Int a, Int b calculate(Int x, Int y) { return (x, y); } }
// + Clear and easy
  
class Three { Int a, Int b calculate(Int x, Int y) { return a = x, b = y; } }
// = May be ambiguous just like One
  
class Four  { Int a, Int b calculate(Int x, Int y) { return (a = x, b = y); } }
// + Clear and easy, should be allowed along with Two. It's redundant if a and b are defined for the body of the method.
  
class Five_1 { Int a, Int b calculate(Int x, Int y) { a = x, b = y; } }
// + Clear and easy. Either Four or Five will do, but we should also allow Two
  
class Five_2 { calculate(Int x, Int y, Int& a, Int& b) { a = x, b = y; } }             // or with the & reference syntax
class Five_3 { calculate(in Int x, in Int y, out Int a, out Int b) { a = x, b = y; } } // or with the in/out syntax
// - Confusing if it's the only way to pass parameters.
  
// My preferred syntax:
  switch (someVar) {
    case 1: return (x, y);
    case 2: a = x; b = y; return;
    case 3: return (a = x, b = y);
  }  

Possible Return Value Assignment Syntaxes:

One one = ...; Int a, Int b = one.calculate(5, 6);
// = Potentially ambiguous. We should allow it if we allow: a, b, c = 1, 2, 3;
  
Two two = ...; (Int a, Int b) = two.calculate(5, 6);
// + Clear and consistent, definitely my preference
  
Three three = ...;
Result result = three.calculate(5, 6);
assertEquals(5, result.a);
assertEquals(6, result.b);
// + Should be allowed as a second valid syntax
Comment by arkr...@gmail.com, Oct 8, 2009
class Foo() {
  (String, String) getThings() {
    return "a", "b"; 
  }
}

I think this is the best possible return syntaxes.

Comment by mirza.ga...@gmail.com, Oct 13, 2009

You can already return multiple values like this:

class foo_ret{

public int a; public String b; public foo_ret(int a, String b){
this.a = a; this.b = b;
}
} 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); }


Sign in to add a comment
Powered by Google Project Hosting