Remix.run Logo
the_gipsy 7 months ago

The pragmatically "right" choice is to have some tuple type built-in. Because they are not only very good for function in/out, they can be used in many more places. Lists of tuples, tuples in structs, etc.

If the language doesn't have tuples, then you have to "roll your own" and emulate them every single time, but it's not a functor so you can't do all the useful stuff.

Go didn't do the pragmatically right choice, because it's only right from a type perspective. But go doesn't care about types at all, they're just a side effect. Go only needed to return multiple values, so that you don't have to pass in one (or more) "out" pointer as argument, and check an errnum. So go is right but only from its narrow perspective: it's better than C.

9rx 7 months ago | parent [-]

> The pragmatically "right" choice is to have some tuple type built-in.

Perhaps, but in practice we end up with these ill-conceived languages that support tuples but end up not embracing them. Consider, for example, this Rust function:

   fn process_tuples(tuple1: (i32, i32), tuple2: (i32, i32), tuple3: (i32, i32))
If it made the "right" choice a function would only accept a single input value, which could be a tuple:

   fn process_tuples(tuples: ((i32, i32), (i32, i32), (i32, i32)))
But maybe that's not actually the "right" choice? What if there is a pragmatic reason for functions to have their own concept for tuples that is outside of a tuple type? I will admit, I would rather work with the former function even if it is theoretically unsound. It does a much better job of communicating intent, in my opinion.

And once you accept the theoretical unsoundness of a function having its own tuple concept for input, it naturally follows that it needs to also extend that concept to returns. After all, the most basic function is an identity function. What would be super weird is not being able to write an identity function any time the number of inputs is greater than one.

Go needed to allow multiple outputs because functions can accept multiple inputs.

the_gipsy 7 months ago | parent [-]

> Go needed to allow multiple outputs because functions can accept multiple inputs.

I don't think that's the right conclusion, unless you have any source or insight?

It would explain why you can directly cast the multi-return-values into parameters when calling, but... that doesn't seem to fit go at all.

What I would think is that "go needed multi-return to be able to return errors along values", and the mentioned feature is just some vestige. It's not like go ever implements anything for the sake of consistency or correctness.

9rx 7 months ago | parent [-]

> I don't think that's the right conclusion

It is the "right" conclusion in general. Multiple input arguments without multiple output arguments is frustratingly awkward. I can understand the appeal of your suggestion of only accepting one input and returning one output, where the passed value might be a tuple, but I expect there is good, pragmatic reason why virtually no languages, even those with a proper tuple type, haven't gone down that road. Once a language accepts multiple arguments, though, it needs them in and out. Otherwise you can't even write an identity function. If you can't write an identity function, is it even a function?

> "go needed multi-return to be able to return errors along values"

If there was some specific reason for its addition, beyond the simple fact that it would be crazy not to, it was no doubt for map presence checks. Without that, it is impossible to know if a map contains a given key. Errors can be dealt with in other ways. It wouldn't strictly be needed for that purpose, although it turns out that it also works for that purpose, along with a whole lot of other purposes. Not to mention that we know that the error type was a late-stage addition, so there is strong evidence that errors weren't a priority consideration in the language design.

the_gipsy 7 months ago | parent [-]

With conclusion I meant you claiming that go returns multiple values because a function also takes multiple values. I did not refer to judging this as worse or better than having tuples as general types instead.

> it was no doubt for map presence checks. Without that, it is impossible to know if a map contains a given key

Are you just making stuff up on the go? A map access is not even a function call. And if you need a presence check in the early language development stage, it's much simpler to add support to check presence and then get the value, since it's not threadsafe anyway.

9rx 7 months ago | parent [-]

> With conclusion I meant you claiming that go returns multiple values because a function also takes multiple values.

That remains the most reasonable explanation. There is no legitimate reason for functions that accept multiple arguments but only return one argument. That defies the very nature of what a function is.

C made a mistake, but that is not a good reason to copy it. It also made a mistake around memory management. You wouldn't copy that in a modern language either, would you?

> A map access is not even a function call.

Theoretically it is, albeit with unique syntax. However, importantly, it establishes the concept of multiple returns in the language. Once the syntax for multiple returns is worked out, there is no reason to not extend that to functions proper.

But the most likely reason it was added was not because of some specific reason, but because it would be strange without. It is not like Go is the only language that has that feature. It was already an accepted paradigm.

the_gipsy 7 months ago | parent [-]

Rust, and other modern languages, have tuples. So a single return value is effectively sufficient.

I refuse to believe that go added MRV for some esoteric reason. In practice it just makes up for the lack of tuples and sumtypes (errors mostly). It also would stand sharply against every other design decision (or accident) that go has made.

MRV also lacks functors in contrast to tuples.