Skip to content
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: Go 2: accept multiple return values as the last arguments to function calls #40387

Closed
ysmood opened this issue Jul 24, 2020 · 20 comments
Closed

Comments

@ysmood
Copy link

@ysmood ysmood commented Jul 24, 2020

description:

I did some research, but can't find a similar issue, so I opened a new one.

Sometimes we really want to use the returned values as the input of another function directly without having to use tmp vars.

For example:

package main

func input() (int, int) { return 0, 0 }

func foo(a, b int) {}

func bar(a, b, c int) {}

func main() {
	tmp1, tmp2 := input()
	bar(0, tmp1, tmp2)
}

It will be great if we can do this:

func main() {
	foo(input())    // this works fine for Go 1
	bar(0, input()) // this doesn't work, make me feel it's a bug of Go, because the previous line works
	bar(input(), 0) // we can discuss whether it's good to support this or not
}

It's useful for simple debugging:

func foo() (int, error)
func bar() (string, error)

func main() {
	fmt.Println(bar())        // currently, we can only do this, the next will cause compile error
	fmt.Println("foo", foo()) // prefix logs to debug
	fmt.Println("bar", bar())
}

We already only allow variadic arguments as last arguments, like this:

func bar(a int, list ...int) {}

func main() {
	list := []int{1,2}
	bar(0, list...)
}

So I think it makes sense for people to support this kind of feature.

The idea is pretty static. For example func foo(int, string, int) can only accept functions like func bar() (string, int). I feel the proposal is too hard to be abused. If you can give me some examples of how to abuse it, that will be great.

I think this language feature doesn't break the GO 1, and won't have an impact on compilation performance.


Here a real-world use case might help you understand it better:

Go already supports it, people use it every day, like this:

func readFile(string) (string, error)

content, err := sentryGuard(readFile(path, content))

My proposal just makes it more intuitive. So we can easily do things like:

func guard(loggerInterface, string, error) (string, error)

// when err happens, report it to something like sentry.io
content, err := guard(sentry, readFile(path))

So I don't have to implement a lot of guard types.

@gopherbot gopherbot added this to the Proposal milestone Jul 24, 2020
@gopherbot gopherbot added the Proposal label Jul 24, 2020
@martisch
Copy link
Contributor

@martisch martisch commented Jul 24, 2020

Go 2 labels are not about language backwards compatibility. Any language change is marked as Go 2.

@martisch martisch changed the title proposal: Go 1 : accept multiple return values as the rest arguments proposal: accept multiple return values as the last arguments to function calls Jul 24, 2020
@martisch
Copy link
Contributor

@martisch martisch commented Jul 24, 2020

There was some earlier discussion about this and why it has some complexity years ago in: #973 (comment)

@ysmood
Copy link
Author

@ysmood ysmood commented Jul 24, 2020

@martisch Thanks. I think the logic is pretty clear, just spread the input if it's a tuple and check if the type matches, if not compile error.

@ysmood
Copy link
Author

@ysmood ysmood commented Jul 24, 2020

If more people think it will make it harder to read the code, I'm ok to close it. But I don't think it will introduce a lot of unexpected behavior since everything is statically typed.

@seankhliao
Copy link
Contributor

@seankhliao seankhliao commented Jul 24, 2020

see #32941 on tuples

@ianlancetaylor ianlancetaylor changed the title proposal: accept multiple return values as the last arguments to function calls proposal: Go 2: accept multiple return values as the last arguments to function calls Jul 24, 2020
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jul 24, 2020

I think it's clear why f(g()) is a special case. I think it's less clear why f(0, g()) is a special case. If I can do the latter, why can't I write f(g(), 0)? That is, I understand why. But I think we need a pretty strong reason to add a special case here. Every special case makes the language more complex and harder to understand.

@ysmood
Copy link
Author

@ysmood ysmood commented Jul 24, 2020

Every special case makes the language more complex and harder to understand.

@ianlancetaylor It's hard to define what is special and what is hard. It depends on perspective, for example, Go support variadic arguments only for last arguments, in that sense allowing f(0, g()) feel more intuitive for some people:

func bar(a int, list ...int) {}

func main() {
	list := []int{1,2}
	bar(0, list...)
}

So for me, it feels like a bug if Go doesn't support it, I think many people will have the same feeling, just like this ticket #973 (comment)

The idea is pretty static. For example func foo(int, string, int) can only accept functions like func bar() (string, int). I feel the proposal is too hard to be abused. If you can give me some examples of how to abuse it, that will be great.

@justinfx
Copy link

@justinfx justinfx commented Jul 24, 2020

FWIW, in terms of readability and functionality that is familiar to me from other languages, Python supports expanding an argument list/tuple if it is after the positional args:
https://repl.it/@justinfx/argsexpansion#main.py

I'm not familiar with seeing it before positional args.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jul 24, 2020

I think it's clear why a variadic argument can only be the last argument: because otherwise it would be hard to see where the variadic arguments ended. But f(0, g()) is meaningful even if f is not variadic.

I'm not suggesting that this idea can be abused. I'm suggesting that it seems like a special case, and special cases make the language harder to understand.

@ysmood
Copy link
Author

@ysmood ysmood commented Jul 24, 2020

I think it's clear why a variadic argument can only be the last argument: because otherwise it would be hard to see where the variadic arguments ended. But f(0, g()) is meaningful even if f is not variadic.

No matter g() returns one value or two, it's always static, there's no variadic in it. I think it doesn't support the idea of "it will be harder to read". If something is very intuitive it's not hard to read but help people read the code faster. Without this feature, I have to name a lot of temp variables that will consume the reader's memory or attention, for example:

a, b, c = foo()

bar(0, a, b, c)

vs

bar(0, foo())
@davecheney
Copy link
Contributor

@davecheney davecheney commented Jul 24, 2020

@ysmod I appreciate you’re passionate about this topic but please tone down the hyperbole a bit. Ian has explained the rationale behind the current logic, perhaps it is beneficial to examine how Situations where a function which returns so many parameters that store them temporarily becomes arduous. If that underlying design issue is addressed, maybe the need to add additional cases the argument passing can be avoided.

@ysmood
Copy link
Author

@ysmood ysmood commented Jul 24, 2020

@davecheney Updated the words to make it less hyperbole, will be careful next time.

If that underlying design issue is addressed, maybe the need to add additional cases the argument passing can be avoided.

One thing I can come up with is math. It's hard to abstract math tuples into named structs to reduce return values, that's why python or other languages support tuples well. Naming is always one of the hardest problems for programming.

@davecheney
Copy link
Contributor

@davecheney davecheney commented Jul 25, 2020

I agree that sometimes the nature of the problem calls for many return parameters, but I argue that this isn’t the general case, and for a specific case there are always alternatives like returning a structure, returning an anonymous structure, converting the function to a method and capturing the context as an object, and so on.

@ysmood
Copy link
Author

@ysmood ysmood commented Jul 25, 2020

like returning a structure, returning an anonymous structure, converting the function to a method and capturing the context as an object, and so on.

This is what Go wants to avoid, too many ways to get around a simple problem. It will be great if the language itself supports it so that we can reduce them into one simple style.

@davecheney
Copy link
Contributor

@davecheney davecheney commented Jul 25, 2020

I offer that this may not be a general problem affecting the majority of applications of Go.

@martisch
Copy link
Contributor

@martisch martisch commented Jul 25, 2020

This is what Go wants to avoid, too many ways to get around a simple problem. It will be great if the language itself supports it so that we can reduce them into one simple style.

The Idea of Go generally seems to minimise having multiply ways/styles to achieve something. Instead of reducing the styles for returning complex information from a function and passing it to the next function this proposal will add one more way to pass information without extra variable declarations.

The other existing options are not taken away and therefore there is no overall reduction of styles unless the new style is enforced. Not everything will be passed as multiple return values and passing in structures will still continue to be used because in some cases it makes more sense to pass a structure.

Sometimes it is warranted to provide an alternative to a complex and easily to get wrong mechanism, that is generally useful but for a specific common case far from ideal. To me this doesn't seem such a case as assigning to variables first is simple enough.

One added readability concern is that before I can easily deduce from bar(0, foo()) without knowing bar() or foo() that is has 2 arguments and from bar(0, foo()...) that it has 1 normal and in addition a variadic length final argument. With the proposal implemented the above can have any number of arguments equal or larger than 2. foo and bar might be from another package so it might not be immediately clear. That "problem" exists for bar(foo()) already but it would exist for even more cases afterwards.

@KruelMeow
Copy link

@KruelMeow KruelMeow commented Jul 25, 2020

Yes it often confused me that why fmt.Println can print a multiple return values function call, but can not add any tag.

@ghost
Copy link

@ghost ghost commented Jul 30, 2020

have you thought about how this will be implemented?

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Aug 11, 2020

Per discussion above, this is a likely decline. Leaving open for four weeks for final comments.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Sep 15, 2020

No further discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
8 participants
You can’t perform that action at this time.