Yes, and the point I was making was that I don't think it actually makes things much clearer. One has more syntax and alters the effective return value, the other just states what you will get back regardless of result. From a practical point of view when you are consuming an API, which requires more concentration?
> From a practical point of view when you are consuming an API, which requires more concentration?
I think the current Go one requires more concentration, or at least more work. Lots of functions return data, err. You have to check for each of these functions if the presence of err means that data is going to be invalid/nil or add it to your code.
The same argument could be made for nil by the way. Since everything can be nil, you have to check for nil often. In languages that make a difference between a type, and a type or nil, this is easier.
The one that doesn't attempt to tell me which will be valid in relation to eachother, so I have to go divine that rather than just reading the function signature?
Exactly, at least it's clearer in the vast majority of cases where that's what I actually mean (but golang won't let me encode easily).
The way golang ~makes me encodes it makes using these functions annoying, because then you ~need to test for the nonsense cases where you have neither an error or a result, and maybe where you have both.
But how is it clearer? I can see the point of where you get neither error nor result, but you can get that in both scenarios, so I don't see how anything is gained. And you still have the same burden of checking whether an error condition did arise. That is the bit that usually matters when you call a function: knowing if it has error conditions (many functions don't) and checking if/what they are.
I don't see any practical advantage here. Explain it to me like I'm five :)
> I can see the point of where you get neither error nor result, but you can get that in both scenarios
No you can't? The whole point of a sum type is that it's definitely exactly one of a result or an error; the language will not let it be both or neither.
Look at it from the receiving end: you are calling a function and you need to decide what to do with the response. The only thing that's different is that you get a single value you have to check for errors rather than a separate value. It is the same amount of code.
I can appreciate that people interested in language design think this is an important distinction, but in practice it really doesn't make a huge difference when you consume an API.
And if we move to the more general case, where a function in essence has multiple return types (albeit narrowed to a set of possible types), you still end up with something akin to a type switch. Yes, you can skip a default clause and you can have the compiler complain if you don't handle all types explicitly, but it is unclear to me if that doesn't just create new kinds of annoyances.
(I deal a lot with sum types in Protobuffers and it isn't always as nice as I had hoped. In fact, I use them for entirely different reasons than correctness and clarity)
> The only thing that's different is that you get a single value you have to check for errors rather than a separate value..
The type system confines you to a set of reasonable cases that allow a caller to reason about the state of the program. This has two benefits for the caller:
1. It is required that the caller check whether a return value is success or failure in order to access the value they want. There is no possibility to mistake one case for another.
2. In the space of valid return values for idiomatic Go function signatures, 50% of them are unidiomatic and end up being ignored. It is far clearer for a caller to understand what is expected of them when valid values exactly overlap with the space. It is far clearer for an author to convey expectations to a caller for the same reason.
Now I must admit, good conventions and tooling in Go account for the vast majority of cases and I don't personally mind that much, but that's a different conversation than API design.
I'm coming at it solely on the basis of practical experience. Removing a single boilerplate check might seem insignificant, but it compounds: when these types are lightweight to work with you can extract more tiny helper functions, and then you have a whole vocabulary of common operations / ways of doing composition, and you can write these really readable functions that just get all the plumbing out the way (but without making it completely invisible) and let you focus on the business logic.