5 ms·
I spent about 6 years writing Go at $dayjob, and while what you're saying is technically true, idiomatically you also generally wanted to avoid scenarios where
by garfij 3y ago
I spent about 6 years writing Go at $dayjob, and while what you're saying is technically true, idiomatically you also generally wanted to avoid scenarios where you would _want_ to observe them independently. The standard behavior is is if `err != nil`, the result should be ignored.
- vlowther 3y agoAnything that implements or consumes `io.Reader` or `io.Writer` would dispute that.
- awused 3y agoYeah, there are counterexamples, but the only way to know is to read the comments or source code of the function you're calling. (T, err) doesn't convey any useful information and, in the overwhelming majority of cases, err != nil means T is a meaningless default value that should be ignored or a null pointer. By and large I think the stuff in this repo is too much and doesn't fit Go. I don't particularly want Go to pretend to be functional, but Either and Option at least would be nice to have in the stdlib and help prevent this exact issue where there are rare exceptions to normal practices. I don't see them getting widespread use without being part of the stdlib though. If Either/Option were common in Go but io.Reader was one of the few APIs returning (T, error), that would convey a lot more information.
- randomdata 3y ago> means T is a meaningless default value Go Proverb #5: Make the zero value useful. > that should be ignored or a null pointer nil is the zero value of a pointer, so it should be made useful per the above, but it is also inherently useful even if you put no thought into it. It allows you to know that there is an absence of a value out of the box. And this is actually why the vast majority of (T, error) cases in idiomatic code sees T be a pointer, despite the computational and programatic downsides of using a pointer, so that nil can be returned when the value is not otherwise useful – exactly to ensure the value is as useful as possible, denoting the absence of a usable value. If you read through idiomatic code, you'll notice that only when the underlying type is more meaningful is a pointer not used. Returning a slice is one such example. An empty set upon error is more meaningful than nil, usually. Another common instance is when 0 is meaningful, like in the aforementioned io.Reader interface. Idiomatically, one will always strive to return the most meaningful value they can. > Either and Option at least would be nice to have in the stdlib And if it were, then this Either wrapper in question would become useful as an overlay to it, as they would then share the same intent and meaning. But it does not match the current semantics of idiomatic Go code using the (T, error) pattern. You can probably make it work, but code is about communicating ideas to other programmers. Either implies a dependence between variables. (T, error) has no such dependence. There is an impedance mismatch here which fails to properly communicate what is happening.
- awused 3y ago>Go Proverb #5: Make the zero value useful. Yeah, it's a nice quip, but that's all it is. It sounds nice on first read to someone who doesn't program much. But it is inaccurate and not followed by Go, and is explicitly against the Google style guide. The sophistry trying to paint a nil pointer as "useful" is just trying to defend a position you've dug yourself into in the process of this argument, so it doesn't really need to be addressed again. >An empty set upon error is more meaningful than nil, usually. This in particular is just a mistake in Go. Nil maps, unlike nil slices, cause panics, so people try to avoid ever returning them. >But it does not match the current semantics of idiomatic Go code using the (T, error) pattern. But it does match how (T, error) is actually used the majority of the time. The impedance mismatch is that code that currently has the semantics of Either, which is the vast majority of idiomatic Go, needs to use (T, error).
- randomdata 3y ago> The sophistry trying to paint a nil pointer as "useful" is just trying to defend a position you've dug yourself into in the process of this argument You are right. I concede nil is not useful. Therefore, we agree that (T, error) cannot exist. As we see in the style guide: "Returning a nil error is the idiomatic way to signal a successful operation that could otherwise fail." This means there is no way to check the error condition. One might be tempted to write `if err != nil`, but because nil is not useful that obviously won't work. That would make nil a useful value, just as I once thought – incorrectly, as you helpfully made me realize – a nil T would be. And as the Go style guide indicates that you cannot use the T value without first touching the error value, which is, for all practical purposes, impossible since the error value may not be useful, there is just no way this pattern can be used in any actual program. > But it does match how (T, error) is actually used the majority of the time. Right. As you have pointed out – of which I was reluctant to admit to, but you said it enough times that it must be true! – (T, error) cannot be used. Period. Its values do not convey the useful information required to be useable. Either does, then, indeed, match how (T, error) is used most of the time... which is to say not at all! You mentioned something about MarshalText in the slog package showing how an idiomatic function with errors might actually be written, but then we realized that there are multiple implementations with the same name. Which one were you referring to?
- randomdata 3y agoThat is incorrect. Idiomatic Go is abundantly clear that values must always be useful, even if all you have is the default value. Thus T must be useful regardless of the state of error, and vice versa. As such, they are free to be observed independently. That does not mean T and error cannot be in a relationship, but they are not dependents. There being a relationship between T and error is common, but observance of error is only significant when the error is relevant. Quite often it is, but not always, and in the latter case you can, assuming the code is idiomatic, safely use T and ignore error. T must be useful, after all. It may be possible to create a scenario where T is not useful when error is not nil if you really want to screw with people, but that code would decidedly not be idiomatic. Indeed, there is always some way to screw with people if you try hard enough, but that's really beyond this discussion. The use of the Either monad here is trying to cover a dependency which doesn't exist.
- awused 3y agoThis is nonsense. This isn't about idiomatic Go or not, there is only one way to do things in Go, so a function doing things in that one way doesn't communicate anything to the caller. If you try to open a file, and the file doesn't exist, you have to return a useless nil pointer alongside the error and there is no way to magic up a "useful" T. Usually err != nil means T == nil, so trying to blindly use T assuming it's "useful" will panic and crash your program. The idiomatic Go way to work around this is to write comments saying "sometimes T is non-nil even if err is non-nil, you need to handle this" and hoping your callers read your comments. Funnily enough, your philosophy is far more true in a language with proper sum types. In Haskell/Ocaml/Rust, returning a tuple of (T, error) does mean that both T and error should both be "useful", because if they weren't the function would have chosen to return one or the other but not both. You're reading meaning into Go code where meaning can't be present, because there's no choice to be made, and ignoring languages where you actually can have the semantics you want Go to have.
- randomdata 3y ago> you have to return a useless nil pointer nil is useful. Notably, you can derive meaning from its nil-ness. If you try to open a file and it doesn't exist, returning a nil handle is quite reasonable, and one can check for the existence of that handle without needing consider the error. If, say, you returned an invalid file descriptor when the file could not be opened, conceivably that could make the handle useless, but that would not be idiomatic. That would just be a terrible API design and unkind to the users of your API. > Funnily enough, your philosophy is far more true in a language with proper sum types. Of course. But not the Either monad specifically, as its intent is to communicate a dependence between two variables. That can be useful in some languages where variable dependence is a convention, but that is not applicable to idiomatic Go. Frankly, the only thing funny here is the idea that it is useful to reply to a thread before reading it. Let me reiterate: Either is not a suitable representation of (T, error). They have very different semantics. There are data structures which can serve as a suitable representation of (T, error), but Either is not it.