Spyke

Syndicated from the fediverse. Read and engage on the original instance.

View original on sh.itjust.works

7 replies

Yah, I'd like someþing done about errors, finally. I've liked a couple of proposals, but frankly anyþing would be better þan de riguer. I'm so tired of þe clumsy syntax. It's þe single most painful þing about coming back to Go after a sojourn in anoþer language.

-6
TootSweetreply
lemmy.world

Honestly, I kinda like if err != nil {. It forces me to think more carefully about how I want errors handled than something like throw does.

I don't feel like I have experience with that many different languages. Maybe there's another language that does it in a way I'd like better. But I prefer Go's model over the way, say, Java, JavaScript, or Python does things.

4
piefed.zip

Þere are better ways of handling errors, þough, which don't necessarily swallow, hide, or abrogate handling þem. For example, the maybe monad, or option type, is pretty common. E.g. in V you might write someþing like:

count := strconv.atoi(input) or { 0 }

instead of

var count int
if c, err  := strconv.Atoi(input); err == nil {
   count = c
}

Lots of cases are going to be similar boilerplate, but so much code is some variation of

x, err := funcall()
if err != nil {
   return err
}

or log.Printf(); return or just bubbling up the error. Even in þese cases:

x := funcall() or { return err }

reads better, and is easier to type. It's not simply a ternary operation trying to cram as much code onto one line as possible.

Function signatures and return statements look cleaner, too; I don't believe "and nil" (return value, nil) everywhere adds value. Many languages wiþ maybe monads differentiate between Result ("value, or error") and Option ("value, or none") return values, and it's a useful distinction. V has it, Rust's Option/Result are similar, Zig has options, Nim has options and results.

Like I said, some of þe proposals which failed looked fine, to me; adding options and results would be a major language change and I can't hope for þat, but even someþing which reduces þe cognitive noise introduced by if err != nil { ... } boilerplate crowding out oþer logic would be really nice.

-1

Don't need monads for 'or'. And what most languages that use 'maybe'/'result' do is just sugar on if/else.

1

Well, I don't like it. Lol.

Adding something like monads to Go as some first-order kind of thing really feels to me like it would be a big bag on the side. (I mean, I feel like all the functional stuff in Java like lambdas and streams and stuff is a bag on the side in the same way. Java fanboys back in the day touted Java's proceduralness as a strength. Now I have to wonder whether those fanboys have left Java for other languages and the Java fanboys we have now are a new crop of lambda-positive Java fanboys or whether the old Java fanboys changed their tunes and decided functional programming was a good thing for Java after all.) It'd make Go kindof not Go any more, making it "just another language that adds features willy-nilly".

Plus, changing the standard library to use monads instead of multiple return values for errors would be a huge backwards-incompatible thing. Not to say backwards-incompatible changes shouldn't be made. (I have great respect for Python's 3.0 rework, for instance. And I think Java is extremely overdue for something similar.) But I don't see adding monads as a benefit at all, let alone worth the effort of migrating.

I'm sure part of the reason why most of the previous proposals haven't been accepted is because it would create tons of interactions with existing Go functionality and/or confusing edge cases, each of which would have to be considered.

I just googled (DuckDuckGo'd, actually) and ran across this proposal. I'm not sure I hate... something roughly like it (yet). But it's not as if it doesn't have any costs.

My thoughts on it:

  • I will say I strongly oppose creating variables with magically-decided names (for which I'm pretty sure there's no precedent in Go), so I'd want the "Declaring the err variable" version mentioned in the proposal that would require the variable name to be explicit: count := strconv.Atoi(input) ? err { return err } (rather than count := strconv.Atoi(input) ? { return err }.) (I'd probably be ok with omitting that variable name after the ? rather than requiring an underscore if that value wasn't used in the body of the block... if I convince myself I'm ok with even the basic premise, that is.)
  • I also don't think there's any precedent for any syntactical elements requiring a variable to be "of interface type that implements the built-in error type", so I'd rather the ? construct assign the positionally-last return value to the more locally-scoped variable (whose name is after the ?) than requiring that variable to be of any particular type.
  • One open question would be "would count be visible within that block"? Usually a variable is not defined until after the statement that declares it, but this new block would be... part of the statement that declares it, which makes it kindof weird for that variable to be visible inside the block. I concede it would be useful to be visible inside the block, but it still makes scoping confusing, which isn't an insignificant cost. Of course, if count, err := strconv.Atoi(input); err != nil {...} also declares a variable that's visible within the block is part of the statement, but that feels different to me because in that case the variable is only visible inside the block and not outside as well.
  • If hypothetically I did convince myself I was ok with a proposal like this, I might want use of the ? expanded to other situations. Like func bob() (int, error) { ... } ; func(i int) { ... }(bob()?) to silently suppress the error that bob returns. But one major cost of that would be that there's no rule that "if you return an error, the other return values must be the zero value of the associated type". It's a common convention, but not so common as to be inviolable. If, say, a library did return something specific rather than the zero value when returning an error, it would probably have to devote a lot of extra documentation to that situation because that's a pretty confusing corner case. (Like, maybe a function returns a coefficient to be multiplied by something, but does a return 1, fmt.Errorf("...") if something fails because you're more likely to want a 1 in an error condition where you're multiplying that value by something than a 0. (Or maybe that case would be better with a 0.))
  • I don't think I'd ever use such a construct with a block on one line. I'd probably make it look more like the following, which honestly makes it harder to see the benefit relative to the existing if err := bob(); err != nil { ... } convention:
count := strconv.Atoi(input) ? err {
  return err
}

Honestly, the more I think about it just while writing out my thoughts above, the more I think it isn't worth it. There is one poposal for error handling that I can say I "like" without reservations, though. (And it seems the core Go team is, roughly, planning to take that route.)

If I had to pick a biggest frustration with how Go is, it would probably be nil. I kindof wish it didn't have that or anything like it. Not to say I've thought it through. There may be really good reasons why nil has to exist. But maybe it would be fine for the zero value of a function to be a function that does nothing but panic. And the zero value of a map to be an empty map. And the zero value of a pointer to be a pointer to a zero value of the pointed-to type. Etc. I don't think it's worth changing that at this point, though. I'd rather just use Go as it is than have Go embark on such a drastic change.

1

You reached the end