This seems flawed. In idiomatic Go, T and error are always independently observable. The Either monad implies that they are dependent, which is not true.
This seems flawed. In idiomatic Go, T and error are always independently observable. The Either monad implies that they are dependent, which is not true.
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.
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.
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).
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?
You say this sarcastically, but it is actually true. A nil pointer is not useful. Once you have determined that a pointer is nil, you have confirmed that the function returning it at all was a waste of both space and time. Though it's actually only half true: nil pointers are worse than useless and they provide negative utility, because they allow invalid code to compile. A better design - which is also more efficient, even considering the overhead of tagged unions - is to not return the pointer/value at all if it would be useless. Other languages allow for this, even C does allow for it with manually tagged unions. Go is rather unique, especially among modern languages, in how it doesn't provide any mechanism for it, so people use what is available to emulate that.
For the rest of it, well, you've contorted yourself into some really interesting positions.
It's not a question what is or isn't better. That's off-topic. It's just a question of how can we actually deal with the situation with the tools that Go gives us? MarhsalText no doubt contains the answers, but we aren't sure where to find it given the ambiguity. You went to all the trouble of looking it up to tell us about it, but now want to keep it a secret?
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.
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.
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.
This is sophistry. If I try to "use" a nil pointer I get a crash. I have to carefully check that it's non-null even if error is null. You can "derive" the same "meaning" from Result[T, error] being an error instead of a T. You can "derive" the same "meaning" from Option[T] being empty. There is no special meaning that a null file gives me that I can't take from a Result containing an error.
There isn't some big philosophical difference that Go is taking a principled stance on, just a practical one: with those you get type safety, and if you do it wrong you get a compilation error. In the Go way if you do it wrong you get a runtime panic.
>that would not be idiomatic. That would just be a terrible API design and unkind to the users of your API.
That is the vast majority of the stdlib and the vast majority of all popular Go libraries. If idiomatic Go code is code where (T, error) means T is always a useful value even if error is non-nil, then there is vanishingly little idiomatic Go code in existence.
>as its intent is to create a dependence between two variables.
This is nonsense. To use your personal specific terminology, Either encodes a dependence between variables that already exists, it doesn't create it. That dependence exists in Go too, Go isn't a language where the fundamentals of programming change, it's the same in C where people write methods that take both a result and an error pointer.
Either is an option to use when there is a dependence. If there isn't a dependence, and both are always present, you can and should return (T, error) and not Either[T, error]. No one is trying to force Go to always use Either when (T,error) would be appropriate, just like you are not forced to in other languages. You just have choices in those languages you do not have in Go, and overwhelmingly people choose more appropriate types than (T, error) when given the choice.
responding to your edit: >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.
It's odd that you acknowledge this, but then claim that I somehow claimed the opposite. Perhaps you should follow your own advice about reading. Either represents a subset of the four cases that (T, error) covers, and even in Go the two cases that Either covers are the only ones in the vast majority of usage. In Go, most, but not all (and no one is claiming all), uses of (T, error) would be better expressed as Either[T, error].
In fact, the different semantics is the entire point. The point is not to keep the semantics the same but change up the syntax. In Go (T, error) is used commonly, in idiomatic Go unless the stdlib is unidiomatic, to emulate the semantics of Either[T, error]. If (T, error) doesn't have the right semantics for your program - and rarely are all four cases considered - then a more appropriate type with matching semantics should be used instead.
Yes, that is true; at very least you need to read to documentation to understand if there is a relationship or not. Whereas Either defines an explicit dependence between two values, freeing you from that. With that, clearly they cannot be equivalent representations. I am surprised this is not obvious to you.
Honestly, I don't know what the rest of that gobbledygook is all about. It reads like one of those weird posts by Rust users we keep seeing where one is wallowing in the sorrow of not being able to grasp Haskell.
That's the point. They are not equivalent, they represent different things, and Either is a better fit for the actual code even in Go most of the time. Go shouldn't force people to use (T, error) when Either[T, error] is the correct choice.
But that's twice now you've resorted to ad-hominem attacks instead of responding to the content, so I'll take your implicit admission that you have no rebuttals.
Good. I'm glad you came back to the first comment in the thread. I am not sure why it took you so long, but I respect that you got there eventually.
> I'll take your implicit admission that you have no rebuttals.
Naturally. You finally realized what I said is true, and we've talked about nothing else. What could there possibly be to rebut? You have clearly not thought this through.
I said no such thing. I said idiomatic Go indicates that you must always make values useful. This is an onus placed on the programmer, not something guaranteed by the language. However, since the Either monad here was said to be introduced to wrap idiomatic Go code, not whatever haphazardly written Go code you happened to find in a SourceForge repository, that is of relevance.
> was rebutted multiple times
Cool. I must not have read it. There was a lot of weird shit in there that had nothing to do with anything. I'm not sure how that would even find relevance to the discussion taking place. I get that first year computer science programs are starting up and you're excited to share what you learned in your first week, but I really don't care. You are not going to tell me anything I haven't heard many times before.
Well, if the Go stdlib is haphazardly written and unidiomatic then I think the burden is on you to demonstrate that idiomatic Go, as per your definition, exists.
>more baseless ad-hominem and ignoring the points
I'll boil it down to one sentence: Go forces programmers to use (T, error) when it is not appropriate, and Go would be better if it did not.
Yeah, the older parts of the standard library are definitely not idiomatic. Lots of functions in the stdlib which return errors don't even return an error type. But, of course they aren't idiomatic. Idiomatacy is emergent. They couldn't possibly have been written idiomatically.
> I'll boil it down to one sentence: Go forces programmers to use (T, error) when it is not appropriate, and Go would be better if it did not.
That may be true, but of no relevance. I can see why I didn't bother reading it. Thanks for clarifying that it would have been a waste of my time.
>That may be true, but of no relevance.
So you agree with this, and it rebuts the entire contents of the first post I responded to. I wonder why you were posting irrelevant, off-topic content yourself. But I'm glad you finally agree that Go isn't flawless.
> If a function returns an error, callers must treat all non-error return values as unspecified unless explicitly documented otherwise
It is absolutely not idiomatic to give any meaning to non-error values under error conditions in the usual case. That is extremely unusual and would be generally confusing.
https://google.github.io/styleguide/go/decisions#returning-e...
Based on what? We have come to see that it is beneficial to make zero values useful. In fact, Go Proverbs even says so. Likewise, we have learned it is useful to return the zero value when you have an error. Most commonly, this means returning nil, which is packed full of all kinds of useful information. Therefore, the T value can be expected to useful if the code is idiomatic.
I'd love to see some real-world code you think is idiomatic, but doesn't return a useful T value when there is an error.
> If a function returns an error, callers must treat all non-error return values as unspecified unless explicitly documented otherwise
This is not at odds with that. This merely warns that not all code you may call will be idiomatic. In fact, if I recall correctly, doesn't os.Open (maybe os.Create) return an invalid file handle in some error cases? The standard library is old and what helped us eventually see what is idiomatic. It is decidedly not idiomatic for the most part. If you rely on a function being written idiomatically, then you are going to run into trouble, as that is not a guarantee (unless the documentation provides such a guarantee).
But in the case of this Either wrapper, it explicitly states it is for use with idiomatic code, not any old code you can throw at it.
And the Go style guide says otherwise: if err is non-nil, you shouldn't even check the other return values. The Go proverbs are just words, it doesn't make them true or even good ideas. When Go proverbs don't agree with how Go code is written, including idiomatic Go code, reality wins over a bad theory. The Go style guide happens to better match real code written by real people, even the people directly responsible for the proverbs.
>I'd love to see some real-world code you think is idiomatic
To be fair, I have been talking about the Go programming language and idiomatic code written in that language. You seem to have a different idea of "Idiomatic Go" from everyone else's. Most of the stdlib, including the newest additions, is idiomatic as judged by other people but evidently not by you.
Yes, it says you cannot trust functions, unless documented, to be idiomatic. Which is reasonable as not all code is idiomatic. The language goes to no lengths to enforce how the code is written in this regard (obviously). But we are talking about code that is known to be idiomatic.
> Most of the stdlib, including the newest additions, is idiomatic as judged by other people but evidently not by you.
1. I don't see how it could be. What is idiomatic emerges from writing code and seeing what works and what doesn't. Most of the stdlib was written in the early days before anyone understood what works best. And, thanks to the go1 guarantee, modifying it now is out of the question.
2. If you believe that the stdlib is idiomatic, then you have to accept that returning an int (-1) to represent an error is idiomatic. The standard library is full of that. Which violates the premise of FP-Go that (T, error) is idiomatic. That is not my claim, that is theirs.
That is not what it says, in fact, it almost says the opposite. It is idiomatic Go to never return useful values if error is non-nil unless explicitly documented. It never affirms your particular, personal, definition of what "Idiomatic Go" is and directly contradicts it.
>If you believe that the stdlib is idiomatic
I believe most of the stdlib is idiomatic Go, especially the newer stuff. Your excuse earlier was that the older stuff in the stdlib isn't idiomatic but the newer stuff is. But the newer stuff doesn't match your personal standards for being idiomatic either. I would challenge you to point to a large body of code that actually matches your definition of idiomatic, I do not think it exists.
No, it is very much written from a consumer perspective. There is known, non-idiomatic, code where relying on T in its error state is problematic, so the guidance is reasonable and logical.
But we're talking about this from the producer perspective. These do not challenge each other and can exist in harmony.
> believe most of the stdlib is idiomatic Go, especially the newer stuff.
I wholeheartedly agree, at least with respect to the newer stuff, and have already said as such. Now this is where, in that newer work which is idiomatic, you point to a good example of where you can find meaningless return values when there is an error.
Clearly the vast majority of the standard library, especially in the newer stuff, does return meaningful values upon error, so we need to identify those which buck the trend if we want to hold it as a counterexample of this being an idiomatic practice.
This is just a lie. It's written about Go and doesn't split its perspectives, the producer should only ever return meaningful non-error values with an error if explicitly documented, and the consumer should only ever expect those when documented.
>you point to a good example of where you can find un-meaningful return values when there is an error.
Okay, skimming the release notes for 1.21. The slog package, brand new in 1.21, contains a MarshalText method (and others, but this is the first I noticed). It returns ([]bytes, err) and it is not documented to be idiomatic as per your personal definition. Therefore, by your interpretation of the style guide and your own personal standards, it is not idiomatic. What's more, a nil/empty slice is potentially valid output from marshaling something, so the slice is entirely useless and must be ignored if err is non-nil. It is not valid to try to "derive meaning" (your terminology) from the slice being nil or not, so it is well and truly useless if err isn't nil.
Moreover, I'll assert that if Either existed in the stdlib, it would return Either[bytes[], err] instead of ([]bytes, err), and it is a limitation of Go that they're using (T, error) which does not offer the appropriate semantics.
>Clearly the vast majority of the standard library, especially in the newer stuff, does return meaningful values upon error
This, too, is simply a lie.
At quick glance, I found three MarshalText implementations in the slog package. Which implementation are you referring to specifically?
And, for what it is worth, none of them return useless values on error, so where do I find the fourth which does?
> This, too, is simply a lie.
Let's hope. I love nothing more than being wrong. That means I get to learn something new! But I haven't found it yet, which is sorrily disappointing. Looking forward to you clarifying the above so we can put this to rest.
>I love nothing more than being wrong.
That's odd, because you've contorted yourself into defending absurd positions based on personal definitions no one else shares, including the Go authors, just to avoid admitting you were wrong. The same Go authors responsible for both the proverbs, which you quote as gospel, and the style guide, which you claim doesn't apply when it's inconvenient because it directly contradicts your claims in plain english.
Is it that you've confused implementation with interfaces? I would have to reject the values if received through the TextUnmarshaler interface, which defines MarshalText, as the implementation being called then becomes unknown, and therefore may not be idiomatic. If I was knowingly working with the three concrete types mentioned above then their function is documented.
> That's odd, because you've contorted yourself into defending absurd positions based on personal definitions no one else shares, including the Go authors, just to avoid admitting you were wrong
I mean, all you have to do is show some code which is reasonably considered idiomatic that does not return a useful value when in an error state and I'll be convinced; something you agreed was pertinent and useful when you brought up MarshalText.
The slog package is definitely reasonably considered idiomatic, so you are on the right track. Now you just need to be more specific at to which MarshalText method you are actually referring to. You called attention to it for good reason, no doubt. All we need, as there is more than one, is to know which one you meant specifically. It is a little odd you didn't do that in the first place, but I'm sure it was an accidental omission.
"That's the point. They are not equivalent, they represent different things" – Oh snap. Well, so much for that silly tangent.
If you want to actually have a discussion, you are going to have to go and understand the discussion that was taking place before you tried to take it in some weird and nonsensical direction. The non-sequitors may be entertaining, maybe even true, but off in la-la land with respect to the conversation taking place.
If your intent is simply to be my personal jester, then by all means, run with it. I'll continue to enjoy the laughs. Most people would pay good money to see an entertainer of this caliber, and you are offering it to me for free! I feel privileged.