I think Go (as a better C) addressed one of C's weaknesses - only being able to return one value.
The C ABI is quite capable of it provided you dress it up into a struct, you just can't do it without defining that useless one off struct.
IMHO Go fixed that C wart.
If you look at the Go calling convention, multiple return values look perfectly natural. The parameters of a function are passed on the stack and the return values are returned immediately after on the stack . That's why naming your return values is just like naming your parameters - they are both local stack based variables.
So I would argue that multiple return values are great.
Maybe you should be able to treat all the return arguments as a tuple (eg Python). That would make certain things neater, but I think it is a different argument.
> The parameters of a function are passed on the stack and the return values are returned immediately after on the stack. That's why naming your return values is just like naming your parameters - they are both local stack based variables.
Why should the user of a language need to know anything about how values are passed to/returned from a function? Passing them on the stack, in registers, on the heap are definitely in the 'implementation detail' camp.
And named return values seems to me to be just bookkeeping and (admittedly a good form of) user convenience.
That being said, I do like the way python does it with tuples and automagic tuple unpacking. Even the C-API makes it super easy to do multiple return values.
This article seems to break down into 3 parts - why multiple return values are bad, why go error handling is bad, and opinions about what to do about it.
The part that feels wrong to me is their issue with multiple return values. They argue that multiple return values are a special case in the type system, so they don't compose well. But, as far as I can figure, function arguments are also a special case, in the exact same way. I think if the author wants to argue go should only have one return value, then use tuple destructuring to simulate multiple return values, they should also argue functions should only take one argument, and use tuple destructuring to break it apart within the function.
I don't think having a special case for either the input or output of a function is particularly weird, and I can't think of any language (except haskell?) that limits you to one argument.
I don't know. I don't think they're particularly wrong about multiple return types being ugly, but it also doesn't feel like some great tragedy to me the way it does to the author.
> they should also argue functions should only take one argument, and use tuple destructuring to break it apart within the function
You say that as an hypothetical, but that's exactly how code is written in Standard ML; and the rest of the ML language family supports it to some degree, although they lean more on currying, like Haskell.
> I don’t want to sound mean, but a friend of mine has suggested that he believes that “Rob Pike invented Go as a practical joke.”, and things like this really make me wonder if he has a point. Why would you design a language where the result of a function call cannot be stored or passed around?
Somebody show this person CMake, because if they've never used it, the resulting blog post will be incredible.
(CMake has functions... with arguments... and no return values. At all. Everything has to be saved to variables. Their workaround is that you can set and clear variables in the parent scope...)
According to The Principle of Least Surprise (Principle of Least Astonishment, POLA)[1] there is should be a symmetry: either a function receives a single input value, and returns a single output value, or it may receive multiple values and return multiple values, any other combination is problematic.
The only advantage to multiple inputs - single output, is that it's resembling the math notation, but it also inherits all of its shortcomings, and obviosly it wasn't designed with computers in mind.
Same with function taking or returning 0 values (e.g. void).
Function receiving 0 inputs should be a constant.
Function returning 0 outputs, is a procedure with a side-effect, and should be implemented using a different construct.
Another thing that bugs me: if you have a function argument then it is copied by value, right? But it is hard to tell if the whole value is copied or not.
- structures argument: the whole structure is copied (unless you passed a pointer to the struct)
- interface? depends if the implementation is structure or pointer.
- maps: it copies just a small internal struct that is pointing to the implementation of the map.
- arrays: depends if it is a slice then copy cost is small (again, similar to maps), however an array is fully copied.
yes, however if you look at the function definition then you can't tell if the caller is passing a slice vs an array, or an interface that is implemented on a pointer vs a struct.
The semantics of the function parameter should not depend on how the function is being called.
> if you look at the function definition then you can't tell if the caller is passing a slice vs an array
You certainly can. If a function is `f(x []int)` it is illegal to pass a `[3]int` to it. And vice versa, if a parameter is `[3]int` you cannot pass in an `[]int` there.
> or an interface that is implemented on a pointer vs a struct. The semantics of the function parameter should not depend on how the function is being called.
In this case, the semantics of the parameter is the semantics of the interface in question. Whether that interface is implemented by a value or implemented by a pointer inside a particular user of the function is not relevant. This is good -- the decision of using pointer or value receivers on the user's struct, of course, should rest with the user.
I think Go (as a better C) addressed one of C's weaknesses - only being able to return one value.
The C ABI is quite capable of it provided you dress it up into a struct, you just can't do it without defining that useless one off struct.
IMHO Go fixed that C wart.
If you look at the Go calling convention, multiple return values look perfectly natural. The parameters of a function are passed on the stack and the return values are returned immediately after on the stack . That's why naming your return values is just like naming your parameters - they are both local stack based variables.
So I would argue that multiple return values are great.
Maybe you should be able to treat all the return arguments as a tuple (eg Python). That would make certain things neater, but I think it is a different argument.
> The parameters of a function are passed on the stack and the return values are returned immediately after on the stack. That's why naming your return values is just like naming your parameters - they are both local stack based variables.
Why should the user of a language need to know anything about how values are passed to/returned from a function? Passing them on the stack, in registers, on the heap are definitely in the 'implementation detail' camp.
And named return values seems to me to be just bookkeeping and (admittedly a good form of) user convenience.
That being said, I do like the way python does it with tuples and automagic tuple unpacking. Even the C-API makes it super easy to do multiple return values.
This article seems to break down into 3 parts - why multiple return values are bad, why go error handling is bad, and opinions about what to do about it.
The part that feels wrong to me is their issue with multiple return values. They argue that multiple return values are a special case in the type system, so they don't compose well. But, as far as I can figure, function arguments are also a special case, in the exact same way. I think if the author wants to argue go should only have one return value, then use tuple destructuring to simulate multiple return values, they should also argue functions should only take one argument, and use tuple destructuring to break it apart within the function.
I don't think having a special case for either the input or output of a function is particularly weird, and I can't think of any language (except haskell?) that limits you to one argument.
I don't know. I don't think they're particularly wrong about multiple return types being ugly, but it also doesn't feel like some great tragedy to me the way it does to the author.
> they should also argue functions should only take one argument, and use tuple destructuring to break it apart within the function
You say that as an hypothetical, but that's exactly how code is written in Standard ML; and the rest of the ML language family supports it to some degree, although they lean more on currying, like Haskell.
> I don’t want to sound mean, but a friend of mine has suggested that he believes that “Rob Pike invented Go as a practical joke.”, and things like this really make me wonder if he has a point. Why would you design a language where the result of a function call cannot be stored or passed around?
Somebody show this person CMake, because if they've never used it, the resulting blog post will be incredible.
(CMake has functions... with arguments... and no return values. At all. Everything has to be saved to variables. Their workaround is that you can set and clear variables in the parent scope...)
This is one of many reasons I am optimistic about V (see https://vlang.io/compare)
According to The Principle of Least Surprise (Principle of Least Astonishment, POLA)[1] there is should be a symmetry: either a function receives a single input value, and returns a single output value, or it may receive multiple values and return multiple values, any other combination is problematic.
The only advantage to multiple inputs - single output, is that it's resembling the math notation, but it also inherits all of its shortcomings, and obviosly it wasn't designed with computers in mind.
Same with function taking or returning 0 values (e.g. void).
Function receiving 0 inputs should be a constant.
Function returning 0 outputs, is a procedure with a side-effect, and should be implemented using a different construct.
---
1. https://en.wikipedia.org/wiki/Principle_of_least_astonishmen...
> According to The Principle of Least Surprise (Principle of Least Astonishment, POLA)[1] there is should be a symmetry
I'm not sure you can say that. You might be the only one surprised by multiple inputs single output.
What's more predictable: symmetry or assymetry?
Why there is so much symmetry in our world (Nature)?
I’m not sure what the name for this fallacy is. Appeal to unrelated concept?
Among the first functions children learn are things like addition of two numbers which lack this supposedly natural property.
IMO you mistaken math-like infix notation with the number of arguments function receives.
Depends how you imagine a function. Just imagine it like a funnel. Your argument is honestly at the very least irrelevant.
def add(a: int, b: int) -> int: return a + b
is this surprising to you ?
Yes, since I was a kid.
My first real computer was an RPN calculator.
Also one of my first programming textbooks back then used an educational PL which allowed multiple return values (late 80s).
I think in terms of data flows and circuits, so it’s natural for me.
Another thing that bugs me: if you have a function argument then it is copied by value, right? But it is hard to tell if the whole value is copied or not.
- structures argument: the whole structure is copied (unless you passed a pointer to the struct)
- interface? depends if the implementation is structure or pointer.
- maps: it copies just a small internal struct that is pointing to the implementation of the map.
- arrays: depends if it is a slice then copy cost is small (again, similar to maps), however an array is fully copied.
... complicated.
This is fairly standard and expected behaviour. Embedded pointers get copied only when you explicitly do a deep copy.
yes, however if you look at the function definition then you can't tell if the caller is passing a slice vs an array, or an interface that is implemented on a pointer vs a struct.
The semantics of the function parameter should not depend on how the function is being called.
> if you look at the function definition then you can't tell if the caller is passing a slice vs an array
You certainly can. If a function is `f(x []int)` it is illegal to pass a `[3]int` to it. And vice versa, if a parameter is `[3]int` you cannot pass in an `[]int` there.
> or an interface that is implemented on a pointer vs a struct. The semantics of the function parameter should not depend on how the function is being called.
In this case, the semantics of the parameter is the semantics of the interface in question. Whether that interface is implemented by a value or implemented by a pointer inside a particular user of the function is not relevant. This is good -- the decision of using pointer or value receivers on the user's struct, of course, should rest with the user.
Not at all, if anything multiple return values offers such flexibility that it should be the default.
Did you read the article?
What was wrong with his proposition, things like range could be made to handle it or whatever. What's your point?