Return multiobject format


#1

Hi there,
when programming I often come across the issue that I want to return more than one object from a method. There are many ways to do this, one worse than the other, including tuples, arrays, mutable parameters, …
The cleanest way I could find is the following using a data class:

data class ResultrunFoo(val a:String,val b:Int)
fun runFoo(): ResultrunFoo{
    return ResultrunFoo(a="bar",b=4)
} 

This works and doesn’t leave the receiver with confusing unnamed objects,
but it is rather lengthy.

So I propose the following format as a new language feature:

fun runFoo(): (a:String,b:Int){
    return (a="bar",b=4)
}

This code should be equivalent to the snippet from before. Behind the scenes a helper-class is generated automatically.

I think this format is much more intuitive and concise for returning multiple objects.
Also it fits perfectly together with deconstructing declarations:

fun runFoo(): (a:String,b:Int){
    return ("bar",2)
}
...
val (a, b) = runFoo()

#2

We used to have this feature (tuples) in Kotlin and intentionally removed it. The intended way to return multiple values from a method is to use a data class. Among others, this gives a much cleaner API for Java callers.


#3

I didn’t mean for this to be a tuple. The code is simply supposed to be a shortcut for the creation of the data class. The return (1,2) would be limited to use with a return and would simply mean something like return Result(1,2). The first and the second snippet should act the same, just with a shorter syntax


#4

This proposal looks similar to C#7 tuples with named components:
https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/#user-content-tuples


#5

Yes, the difference is that a separate Class is generated for each method that uses it so you can name the arguments.
Really it’s just supposed to be a syntax feature. The behavior should be exactly like the data class solution in every way


#6

I like it, but it does create a less clean API for Java interop, especially since it will have a generated name that will likely be less useful on the Java side. Obviously, when combined with the unpacking syntax, it looks just fine in Kotlin, but the generated names hurt it in Java.


#7

You always have an option to create named class for java interop.


#8

But then you’re back to square one. Creating that class removes the ability to use the requested feature.
Kotlin seems to generally try harder than THAT to be cleanly Java interoperable.


#9

I don’t see the problem with generated class names. The class used to return multiple objects will probably only be used in this specific context and won’t have much structural meaning.
Using something like <MethodName>Result as a name seems to describe the purpose of the class pretty accurately. If you are forced to name the result-class in most cases you won’t use meaningful names anyways.

If you want to use a class that will also be used in other places, you can default to the old method.
eg. something like Employee
But if want to just want to return two objects in one object there is no need to come up with a fancy name for what is basically a named tuple.


#10

Kotlin has Pair and Triple, everyone can easily create Quadruple, Quintuple…


#11

As stated before, this is not a tuple but a generated data class


#12

Maybe, but it matches for this purpose.

fun runFoo(): (a:String,b:Int) : Pair<String,Int> {
    ...
    return "bar" to 2
}
...
val (a, b) = runFoo()

Generate temporary* and incompatible classes doesn’t seem an enhancement.

(*) with single purpose of destructuring declarations.


#13

Personally I don’t see much added value in such notations. But that said, such proposals aren’t done with just some syntax examples. You also have to decide on the exact inner workings and what it implies to actually compiling. You should define the “magic”.

consider the following code:

fun foo(age:Int) : (name:String, age:Int) = ("foo", age)

fun bar(name:String) : (name:String, age:Int) = (name, 42)

fun baz()  : (name:String, age:Int) = foo(42)

Should this be 3 separate generated data class instances, or should this be one and the same class?

If this one and the same class, how would this work over different packages? How would the compiler know that there is such a class? Should they all share the same package, can this be done with different jars?

So if these are all different classes, will fun baz() : (name:String, age:Int) = foo(42) be possible? That would imply some sort of automatic class conversion, but that leads to (unnecessary) garbage.


#14

The thing is that from a type compatibility perspective a multiple return value class is not the same as a pair or a tuple or any other explicitly named class. The choice to use tuples instead of anonymous types is one of language semantics, there are languages that return tuples, but the argumentation relates to correctness, strictness and convenience.


#15

Andrej has already considered this. See the discussion here:


#16

how?