-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Go2: Tuple type #32941
Comments
Seems to me that I could write your example as: func f() (int, error) {
return 1, nil
}
func g(i int, e error) (int, error) {
return i, e
}
// Returns 1 and nil from f().
val, err := g(f()) Yes, using tuples is very slightly shorter, but the benefit seems quite small. Is there another way that tuples provide a bigger benefit? |
Yes in that case you are right. I missed a small detail from the example in the article, there was another argument after the int, error pair. The specific section in the article of interest is called “Squinting” (I couldn’t link directly). Here is the initial example exactly from that section that will not currently compile: func f() (int, error) {
return 1, nil
}
func g(i int, err error, j int) int {
if err != nil {
return 0
}
return i + j
}
func main() {
i := g(f(), 1)
println(i)
} |
Allowing functions to return either one value (the tuple) or multiple values (the components of the tuple) based on context-only rather than an explicit syntax seems like it has potential to make things a bit confusing. func f() (int, int) {
return 1, 2
}
func foo(args ...interface{}) {
// ???
}
foo(f()) Is To address the kind of thing in your example, I would be a much bigger fan of just allowing multi-value returns to automatically expand into function arguments, without the addition of a tuple type. See #973 (comment) though. |
Thanks for the link to the interesting comment! Gives some insight. However, it would not be a problem if all functions always returns a single value. In that case the problem lies in allowing destruction when calling another function or not. In your case with my proposal args would have the value |
I'd definitely rather have it expand to Also, typically tuples allow for individual access to the returned arguments. This would hurt Go, as it would encourage the behavior of ignoring errors and possibly leaking resources (ie Either way, I think we might want to just add the features that we would want from tuples into multiple-return, rather than introducing an entirely new concept |
You can already implement a tuple in Go as it is now, look at the following example: Furthermore, changing the language to remove multiple return is likely to break all existing Go code, and hence seems unadvisable. |
Yes, I know a tuple type can be implemented. However by having a tuple as a native type the above mentioned composition pattern (and other functional patterns) can be implemented in a clean fashion. Please read the linked article if you haven’t to see what could be achieved cleanly with native tuples. Regarding breaking existing code it will of course happen in varying degrees depending on how this would be implemented if accepted. Note however that this proposal is labeled for Go 2, which would allow backward incompatible changes (although encouraging to minimize them). |
Having the return type be a fully fledged tuple type has some added benefits. The biggest one being that you can finally pass the result of a function through a channel without having to manually convert it to a struct. You can also define methods on them, which is always nice. It also seems like there aren't a lot of negatives. Seems to be a backwards compatible change, by just promoting a quirk in the spec to a proper type like any other. |
Two really good points there which I hadn’t thought about! Especially the last one; imagine a 2d point type with methods for example. |
It might be interesting to consider whether we can introduce a conversion between a function result and a struct with the same types, as in the following. That might possibly be a smaller change to the language that achieves similar benefits. type Pair struct {
i int
s string
}
func F() (int, string) { ... }
func G() chan Pair {
c := make(chan Pair)
go func() {
c <- Pair(F())
}()
return c
} If we introduce a new kind of type we need to discuss things like how to initialize them, how to convert them, how to decompose them, and what composite literals look like. It seems to me that most of the answers for a |
In my experience, tuples are almost always less clear than the equivalent structs with named fields. See related experience reports from Google's Guava libraries in Java, the Chromium project in C++, and numerous other sources. (Note that Python's tuples are rendered somewhat less harmful through the use of |
@ianlancetaylor A very interesting simplification! That would solve the (not initially proposed but still valuable) case of directly sending multiple return values on a channel or into another function. In addition it would still be valuable to be able to unpack return values into non-last parameters of a function: func f() (int, error) {
...
}
func g(i int, err error, flag bool) (int, error) {
...
}
i, err := g(f(), true) I believe that would make it possible to chain higher order functions as mentioned in the linked article in the original post. Is that correct? |
Indeed, a seamless conversion between returns and structs will solve a lot of pain points when dealing with channels. |
Just noticed that @tema3210 posted an example containing a fictional Tuple type in the sum types/discriminated unions issue (#19412 (comment)) which could be of interest to see. It is not directly related to this issue however. |
Some points to be considered: It is hard for the parser w/ no context to do such a thing when we have multiple return values. The only real choice would be to actually do destructuring as also proposed, but that would mean that multiple return values would become only syntax sugar and that looks like a break of Go's philosophy of keeping things simple, don't you agree? I mean, from a philosophical point of view, it seems that go wants the programmer to know what is happening avoiding to hide details and such. If it does not, as Go 2 already has the proposal for parametric polymorphism (a.k.a generics), I don't think it would be necessary to use []interface as the return types. But that could be a consideration only if the first point is taken to be invalid. Apart from that, what has been shown for function composition looks pretty good. But would it be necessary to have tuples in order to achieve that? My final point is: tuples would be crazy good if we had something like pattern matching with guards and some other functional stuff. I fail to see them as being that big of a deal when not having the whole "functional armor". |
Some good points there @conilas. Regarding the function composition in my last example; that would not need tuples, only allowing multiple return values as function parameters with additional parameters after it. Currently multiple return values directly passed to a function can only be the last argument. I’m still intrigued by the simplification (conceptually) that functions would ever only be allowed to have a single return value, with multiple return values handled by returning a single tuple of some kind. If implemented cleverly it could be achieved without breaking too much backwards compatibility (i.e. letting the current multiple return syntax construct a tuple instead). |
What happens if the |
Tuple types don't add enough to the language to be worth the additional complexity of a new kind of type. Tuple types are too similar to struct types, and the additional facilities, while sometimes convenient, seem minor. Therefore, this is a likely decline. Leaving open for one month for final comments. |
Maybe we don't need a new type, but add some of the discussed functionality to structs?
|
Well, there could be something like Voldemort types from D[1] - in which the type would still be alive after the execution context of the function and we could create it automatically, but would only exist in the context of the calling stack of the function? Maybe w/o having to declare the struct type inside the function, maybe declaring, idk. Or maybe the stdlib could provide something like
I think this doesn't look good. How would this be implemented for n-uples anyway? One constructor for each n value written in the compiler? |
@darkdragon-001 If those ideas seem useful, they should probably be refined and turned into independent proposals. It's fine to discuss them here, but the goal should be to make a new proposal. Thanks. |
I know this may be off-topic now, but the conversion between a function result and a struct could be made in the future with: Pair{i, s: F()} |
There were no further comments related to this specific proposal. Other suggestions should become new proposals (that can refer to this one if appropriate). |
I propose the addition of a new built-in type
tuple
and that functions should always returns a single value:Destruction of the new tuple type should be handled as before when returning multiple arguments, in addition to being able to store the tuple in a var with separate destruction:
A naive idea for how defining a user tuple type could look:
My proposal is inspired by the ideas and problems from this article about Monads in Go by @awalterschulze: https://awalterschulze.github.io/blog/post/monads-for-goprogrammers/
In the article he describes composing functions that return errors, for example (not from the article):
Having these additions to the language would allow for some interesting functional concepts to be implemented cleanly.
I have tried to look for earlier proposals without finding any, please correct me if it has been brought up before. Would love to hear your thoughts!
Thanks,
Max Ekman
The text was updated successfully, but these errors were encountered: