News for hackers. Stuff that matters.
Re:Go 1.8 Release Notes by voidlogic on Thursday February 16, 2017 @03:03PM (#13663095)
I agree, I wonder if C++/Java would have gone down the exception path if they had multiple return values? Its it seems to me exceptions are really a special kind of return value that doesn't clobber your single return value.

Compared to Java I have ported to Go, returning error + defer has led to much more localized (and this grokable) and less verbose error handling. Less verbose? I suppose this is not true if you throw exceptions and handle them all at the top (Go has panic for these kind of fatal events BTW), but if you regularly have try-catch-finally blocks, the Go approach is in fact less verbose.

Re:Go 1.8 Release Notes by stouset on Thursday February 16, 2017 @03:36PM (#13663349)
Multiple return values are a kludge. Combined with nil being only usable for a pointer value, it's entirely too easy to accidentally not process the error and accidentally use the non-error return value (bool, int, or other primitive type).

Go should have had Rust-style enums that let you do things like Option<T> and Result<T,E>. This way, you are forbidden by the compiler from ever extracting an invalid value from a function that returned an error (or returned no value).

Re:Go 1.8 Release Notes by curun1r on Thursday February 16, 2017 @04:50PM (#13663842)
I agree wholeheartedly. Also of note, the combinators Rust offers on Option/Result make it possible to chain multiple operations that could fail and then check for the first error that occurred. In Go, you have to check each potential error immediately after it's potential return. This allows Rust to be just as clear as Go while being more concise and enforcing at compile-time that errors are handled. Propagating errors out of a function is also easier given try!/?.

I really don't see a downside of Rust's approach compared to Go's after having programmed in both. But, then again, Rust's approach requires generics and I know Go's team doesn't want to go in that direction.

Re:Go 1.8 Release Notes by iand on Thursday February 16, 2017 @08:24PM (#13665170)
Chaining and failing on first error is only useful for the most trivial of error situations. Non toy programs handle errors with retry strategies, alternate algorithms and fallbacks which may all be contextual.
Re:Go 1.8 Release Notes by ovao on Thursday February 16, 2017 @10:19PM (#13665674)
It depends on the problem domain, but it's been my experience across a few domains that many errors are of the "trivial" type you've described, in that recovering from an error by taking some alternative path is usually not very valuable. A bail-out-on-any-error approach doesn't necessarily mean it's a "toy program".

Nothing in Rust (or Go, even) ordinarily precludes the type of retry approach you're describing, of course.

Re:Go 1.8 Release Notes by ngrilly on Friday February 17, 2017 @04:01AM (#13666917)
> I know Go's team doesn't want to go in that direction.

I disagree:

https://github.com/golang/proposal/blob/master/design/15292-...

Re:Go 1.8 Release Notes by piaste on Friday February 17, 2017 @06:17AM (#13667449)
Did you check the discussion thread for that proposal? It was rejected by the mailing list devs:

https://github.com/golang/go/issues/15292#issuecomment-22198...

Re:Go 1.8 Release Notes by ngrilly on Friday February 17, 2017 @08:43AM (#13668178)
Yes, I read it.

Here is the comment you linked:

> Ian's proposal demands too much. We cannot possibly develop all the features at-once, it will exist in an unfinished state for many months.

> In the meantime, the unfinished project cannot be called official Go language until done because that will risk fragmenting the ecosystem.

> So the question is how to plan this.

> Also a huge part of the project would be developing the reference corpus. developing the actual generic collections, algorithms and other things in such a way we all agree on that they are idiomatic, while using the new go 2.0 features

The author of this comment is not a member of the core team, as far as I know, and the comment doesn't suggest the proposal was rejected.

Re:Go 1.8 Release Notes by wahern on Friday February 17, 2017 @08:41PM (#13673154)
Multiple return values are most definitely not a kludge. They're an awesome abstraction that few languages actually provide because of the implementation complexity. The kludge is where a language forces you to use a struct or some similar compound type when it's most natural for a function to return multiple values.

And it's symmetric with function parameters. If you think multiple return values is a kludge, then for the same reasons so would multiple parameters. After all, you can always curry multiple parameters, or shoe-horn multiple parameters into a compound objet like a struct.

Now whether it makes sense to leverage multiple return values as a substitute for specialized error types, or for exceptions, is a different question. But if you don't see the value in multiple return values than I think it's hard to assess things objectively.

I program a lot in Lua and love multiple return values. And in Lua the standard practice is to leverage multiple return values with exceptions. E.g.

  local x, y = assert(bar())
where on success bar is expected to return a first value that evaluates to true in a boolean context. If so then assert just returns that first value and any subsequent values. If not then it throws the second value as an exception, which usually is expected to be an error description string or an object that can be stringified, but assert doesn't care about the type.

Note that assert() isn't a specialized function. You can implement it in pure Lua. Multiple parameters and multiple return values are generalized as lists (similar to Perl) which also can be used when, e.g., initializing a table.

   function foo() return "one", "two", "three" end
   for i,s in ipairs({ foo() }) do print(i, s) end
prints

  1 one
  2 two
  3 three
Of course these things can be abused and they're not strictly _necessary_. But they're powerful abstractions that can prove very useful, especially in the context of functional programming styles.

Go seems simple on the outside but is actually very elegant and sophisticated internally. Multiple return values, lexical closures that automatically capture by reference and which can escape their original scope without any fuss, lightweight stackful coroutines (stackful meaing no decoration required for recursively invoking yielding functions) that automatically adjust their stack size. You usually only see these things in interpreted and dynamically typed languages, if at all, because they can be very difficult to implement efficiently with an optimizing compiler. Similar concepts in C#, C++, Rust are not nearly as powerful yet still require syntactic kludges (like decoration) to help drive the kludgy implementations. That Go implements these things seamlessly, while compiling straight to native code, is quite a testament to the Go team.

Most of the attention these days is given to stronger typing systems. And that's fine. Go doesn't win too many points on that front, although its duck typing has unique advantages, even over traits. But it's not reasonable to overlook all the other things that Go does extremely well and almost without equal for an optimizing, AOT-compiled, strongly typed language.

All of these things come with trade offs. Typing systems are hardly the be all, end all of language design. They're usually not even the most interesting part, nor the part most relevant to productivity. What's unique about Rust, for example, isn't its typing system, but its borrow checker. And the borrow checker drives directly and indirectly much about their choices with other aspects of the language. Like I said, multiple return values are difficult to implement underneath the hood, and in a language like Rust might require some really odd declaration syntax and have other unforeseen consequences. Thus you see them reconceptualizing multiple return values in terms of a kind of polymorphic compound type. That's not intrinsically better--if anything it can be seen as a kludge--but in any event it's driven by other needs than pure abstract functionality and should be not only criticized that way, but appreciated that way.

Re:Go 1.8 Release Notes by prodigal_erik on Friday February 17, 2017 @10:23PM (#13673432)
You still need the struct because all of these

  s = append(s, f())
  m[k] = f()
  c <- f()
  f().DoStuff()
  h(f(), g())
work with single-valued functions but not multi-valued functions. The feature is not well integrated with the rest of the language.
Re:Go 1.8 Release Notes by jcranmer on Thursday February 16, 2017 @04:34PM (#13663750)
> I agree, I wonder if C++/Java would have gone down the exception path if they had multiple return values? Its it seems to me exceptions are really a special kind of return value that doesn't clobber your single return value.

Because of constructors, I don't think they would have eschewed exceptions. Constructors don't return any value (in both C++ and Java ABI terms, they are implemented as void functions!) anyways, so having the "I don't know how to do what your arguments told me to do" be indicated via an exception being thrown is an intentional model rather then using return codes.

Re:Go 1.8 Release Notes by jjnoakes on Thursday February 16, 2017 @03:06PM (#13663115)
I think I'd want more than multiple return values. I'd also want syntactic sugar to pass errors through to callers, or alternatively, ignore errors and skip future processing and have the error be the result of the future processing.

These things are typically done by exceptions and monads; the "zig" language has some interesting syntax around this kind of thing as well.

But just multiple return values isn't enough for me - too much "if error return error" noise for my taste.

Re:Go 1.8 Release Notes by stouset on Thursday February 16, 2017 @03:38PM (#13663369)
> But just multiple return values isn't enough for me - too much "if error return error" noise for my taste.

This is such a big deal, I don't understand how it's defensible. Code is read dozens of times more often than it's written. When code looks like:

    if val1, err := thingImActuallyTryingToDo1(); err != nil {
        return nil, fmt.Errorf("thing failed: %v", err)
    }

    if val2, err := thingImActuallyTryingToDo2(val1); err != nil {
        return nil, fmt.Errorf("thing failed differntly: %v", err)
    }

    if val3, err := thingImActuallyTryingToDo3(val3); err != nil {
        return nil, fmt.Errorf("thing failed again: %v", err)
    }
it's impossible to tell at a glance how the function is actually working. You have to parse out 90% garbage just to get to the details of what's really happening.

Worse is that it's never this "clean" and repeatable in practice. It becomes a bunch of subtly-different variants on that theme (append if the function wasn't an error, or do something different if it errored, etc.) that the actual logic of your function is obscured in a maze of error-handling.

Re:Go 1.8 Release Notes by slrz on Friday February 17, 2017 @12:22PM (#13669949)
In distributed systems (i.e. what Go was designed for), 90% of your actual logic is dealing with errors of different shapes and colors. How you handle that is what makes the difference between a toy and a reliable system.

Code doesn't become easier to read/understand by just tucking away 90% of it (the hard/interesting parts at that, mind you).

Re:Go 1.8 Release Notes by jjnoakes on Wednesday February 22, 2017 @12:02PM (#13707002)
Dealing with errors should create noise in the code in proportion to the places where error handling needs to take action.

If 90% of your code really is unique, specific action to error conditions, then by all means, this topic isn't for you.

But if 90% of your code is "stop processing in this function, log the error, and pass it up the stack", then why not abstract that away and reduce the noise? (i.e. something like exceptions or monads or arrows or ...)

Re:Go 1.8 Release Notes by stouset on Friday February 24, 2017 @04:05AM (#13722169)
Yes, thank you. If you somehow have a problem domain where most of the errors you encounter can be gracefully (but uniquely) handled, then that's fantastic for you. But in virtually every golang example I've seen, you're just gift-wrapping the error with a little bit more context (or not) and returning it up the stack. In which case you've just replaced automatic, computer-executed exception handling with bespoke, artisanal, handcrafted exception handling for no gain and a hell of a lot of loss.
Re:Go 1.8 Release Notes by majewsky on Thursday February 16, 2017 @05:18PM (#13664091)
> it's impossible to tell at a glance how the function is actually working. You have to parse out 90% garbage just to get to the details of what's really happening.

After a while, you just learn to ignore the `if err != nil` and get on with your life. I mean, sure, it would still be nicer to have an abbreviation, but it's not like Go code is 90% `if err != nil` (as critics always try to frame), and when I read Go code, I'm most definitely not spending most (or any) of my time stumbling over them.

Re:Go 1.8 Release Notes by prodigal_erik on Thursday February 16, 2017 @05:50PM (#13664359)
It's not 90%, but it seems like it has to be more than 50% because not only does every call require an error check but f(g(x), h(y)) has to be broken down and given three error checks.
Re:Go 1.8 Release Notes by stouset on Thursday February 16, 2017 @06:33PM (#13664626)
Yeah. In my experience it's way closer than 90% than 50%. Even with one argument, function chaining is absurdly annoying. You can't just call f(g(x)), because you have to pull out the error check and do it manually.

And most of your code winds up doing error checks because if any call in the stack under a particular function produces an error, it usually winds up having to bubble the entire way up the stack.

Re:Go 1.8 Release Notes by voidlogic on Thursday February 16, 2017 @03:33PM (#13663328)
>too much "if error return error" noise for my taste.

I understand that. I was pointing out this is less noise than try-catch-finally (assuming the programmer is comparing Go to C++/Java/C#). I don't deny some functional langs have more elegant handling in this respect.

Re:Go 1.8 Release Notes by Noughmad on Thursday February 16, 2017 @04:29PM (#13663722)
No, it's not less noise, because with exceptions, you can put all you error handling in one place, or wherever is the most appropriate. With go, you are forced to handle it at literally every function call.

If you want some data, "if err != nil return err" contains the four most frequently used words in Go code. See https://anvaka.github.io/common-words/#?lang=go . For comparion, try/catch are only 34th and 35th for Java, and even lower for C# and C++.

Re:Go 1.8 Release Notes by dragonwriter on Thursday February 16, 2017 @06:07PM (#13664451)
> With go, you are forced to handle it at literally every function call.

Every call that can fail, which isn't quite every function call.

Re:Go 1.8 Release Notes by spullara on Thursday February 16, 2017 @04:25PM (#13663698)
It isn't less noisy. With exceptions I can choose to have no noise or concentrate all the noise in one part of the method. In Go there is junk all over the method deeply conflating the good path and the exceptional path.
Re:Go 1.8 Release Notes by majewsky on Thursday February 16, 2017 @05:14PM (#13664060)
If you add exceptions, you trade noise for action-at-a-distance. From my experience, I prefer a little noise over action-at-a-distance.
Re:Go 1.8 Release Notes by h1d on Thursday February 16, 2017 @07:27PM (#13664928)
Everything is already at a distance when you load libraries and bundle up your commonly used functions elsewhere.

DRY principle seems not applicable here.

Re:Go 1.8 Release Notes by hubert123 on Friday February 17, 2017 @12:42AM (#13666206)
Go's error handling is the primary reason I'm not using that language. If I ever, even on a single line, dont handle an error, my project might fail in an unexpected, undebuggable, unlogged way. With any other language I get an exception and a stacktrace printed out to me. With go, it just silently fails. How this was ever construed to be "better" is beyond me.