I think it's still similar in the sense that the errors are encapsulated explicitly as part of the "return" value. Go gets around the lack of Sum types by requiring you to return two values, but if you wanted to do it the Go style in something like F# you're certainly able to:
let res = riskFunc "input"
match res with
| Choice1Of2 result -> // successfulPath
| Choice2Of2 err -> // Error Handling!!!
That's more or less what I meant by "not radically different". The implementation is of course extremely different, but I feel like from an end-user perspective it's superficially pretty similar.
Oh, I see, yes. The F# style is more powerful than the Go style, and can express the Go style if you choose. That’s a pretty good argument that it is superior, but Go might not be able to grow to adopt it
Yeah exactly. When there was only one or two risky functions in F#, I typically would just handle it manually with the match function like I described. If there was three or more (which isn't terribly hard if you're dealing with a bunch of streams from Kafka that you're manually merging), I would use the more monadic style. It made the code a lot smaller and more readable, and I really don't feel like it sacrificed anything in terms of readability. Like, at all!
There's an overhead associated with boxing and unboxing, so you might be paying a small performance penalty, but since most of what I was doing involved talking to a network a lot, that overhead was immediately dwarfed by IO latency anyway. I don't have any numbers on this, but I suspect that a very large percentage of "risky" functions involve IO of some kind anyway, so the overhead associated with boxing and unboxing is kind of negligible.
The lack of algebraic data types and nice mapping constructs is the main reason I almost never end up using Go for any personal projects. I still kind of like the language, I think CSP is a pretty decent concurrency abstraction, but it always feels like it's fighting me.