Live data from Hacker News

Go Replaces Interface{} with 'Any'

github.com

161–170 of 481 posts

Re: Go Replaces Interface{} with 'Any'

#161
post #156

Earlier quoted context omitted.

Your code is wrong. It would normally be written something like this: func read_file(filename string) string { panic(FileNotFound{filename}) } func foo() { a := read_file("a.txt") b := read_file("b.txt") // do stuff with a and b } func main() { defer func() { if err, ok := recover().(FileNotFound); ok { fmt.Fprintf(os.Stderr, "file not found: %s\n%s\n", err.filename, err.Stacktrace()) } } foo() } However, exceptions…

Ok I was wrongly assuming that panic was expecting an error type, in fact it's an interface{}. > Your use of exceptions for flow control (i.e. goto) is considered harmful Exceptions are a way to delegate error handling to the caller by giving them informations about the unexpected behavior. It implies that the expected behavior is the "happy path" (everything went well) and any deviations (errors) is unexpected. This…

> It implies that the expected behavior is the "happy path" (everything went well) and any deviations (errors) is unexpected.

Errors are the "happy path", though. Your network connection was lost for the data you were trying to transmit so you saved it to your hard drive instead means that everything went well! Throwing your hands up in the air and crashing your program because you had to make a decision is not something you would normally want to do. If statements are present in most happy paths for good reason. That the inputs presented you with a choice does not remove you from the happy path.

Now, if you made a programming mistake and tried to access an array index that is out of bounds, then there isn't much else you can do but crash. Exceptions are appropriate for that kind of problem. They are exceptional in that they should never happen. Errors, on the other hand, are expected to happen and you can happily deal with them.

Re: Go Replaces Interface{} with 'Any'

#162
post #138

Earlier quoted context omitted.

A lot of people are going to write very un-idiomatic Go code with "Result" types built on simple generics that, 5 years from now, we'll all be kind of smirking at. Rust's Option and Result make sense because the language supports it (most importantly with match statements). They don't make sense here.

Fun observation of mine: most articles/tutorials I've read about "idiomatic go code" are presenting ideas that you never see in any production code. Seems to me that the ones who write about "idiomatic go code" aren't the ones who are shipping softwares/libraries.

This is true of all developer evangelism in general.

Re: Go Replaces Interface{} with 'Any'

#163
post #62
post #32

This is fantastic. It'll make Go feel much less weird. eg from the diff: []interface{}{1, 2.0, "hi"} -> []any{1, 2.0, "hi"} Now that Go is going to have generics, all we need is sugar syntax for early return on error -- like more modern languages such as Rust and Zig have -- and Go may finally be pleasant to program in!

It's definitely more streamlined, but my concern would be that newcomers might not understand that it's just an alias. Learning Go really reframed my concept of what an interface is, and I thought that using an empty interface to represent "any type" was kind of ingenious, and helps reinforce its ethos. An empty interface can represent any type because every type inherently implements an interface with no methods. An…

A language shouldnt be optimized towards doc avoiding newbies at the cost of making things verbose and ugly.

Re: Go Replaces Interface{} with 'Any'

#164
post #159
post #32

This is fantastic. It'll make Go feel much less weird. eg from the diff: []interface{}{1, 2.0, "hi"} -> []any{1, 2.0, "hi"} Now that Go is going to have generics, all we need is sugar syntax for early return on error -- like more modern languages such as Rust and Zig have -- and Go may finally be pleasant to program in!

I would also like discriminated unions, and a more sane approach to code generation than go generate and writing your own binary that parses source and spits out source code.

The Go code parsing libraries are quite good though. Not sure what else you could want re. code generation.

Re: Go Replaces Interface{} with 'Any'

#165
post #88

Earlier quoted context omitted.

An exception has a stacktrace. This single piece of information is crucial when you debug and makes handling errors in Golang embarrassing. I rest my case.

If you want stacktraces just panic. But that's not proper error handling.

Or just print stacktrace with `debug.PrintStack()`

Re: Go Replaces Interface{} with 'Any'

#166

Earlier quoted context omitted.

That is also true of most languages. Java (and Javascript in its attempt to copy it) are about the only languages that actually promote using exceptions for errors, and in hindsight I think we can agree it was a poor design decision. That doesn't stop people from trying to overload exceptions in other languages, Go included, but in terms of what is idiomatic... However, the question was asking what is different about…

> Java (and Javascript in its attempt to copy it) are about the only languages that actually promote using exceptions for errors Python does. Ruby does. It's not just Java and JS. Go is very open about its approach being a departure. > And actually, many APIs in the wild do represent errors as integers. Many, many APIs in the wild are implemented in (or meant to be consumed from) C, which doesn't even have exceptions…

> Ruby does.

Definitely not. Especially because early Ruby implementations brought huge overhead when exceptions were used, you were strongly advised to only use exceptions for actual exceptions. Ruby was one of the first languages that really started pushing the idea that exceptions should be reserved for exceptions, even if was just for technical reasons.

Those overhead problems have been addressed and are no longer a problem, but the sentiment has continued to ring true. I agree that doesn't stop people from trying to overload them, as I said earlier. But idiomatic? Not at all.

Re: Go Replaces Interface{} with 'Any'

#167
post #130
post #128

Earlier quoted context omitted.

I’ve been doing a lot of rust programming recently, but how exactly is the Result monad better? I feel like I end up with nested match statements for chained results, but maybe I’m doing something wrong.

Rely more on the map/map_err/or_else/... methods for the Result type. You'll get something like (pseudocode): may_fail_who_knows() .map(use_value) .map_err(some_error_processing) .and_then(another_computation_which_can_fail) .or_else(with_some_error_handling_that_can_rescue) .unwrap_or(a_default_value) Basically, instead of nested match expressions, you get a "pipeline".

I would love to use this style but I find myself reverting back to the match syntax.

    match may_fail_who_knows() {
      Ok(success) => {
        do_something_with_success(success)?
      },
      Err(failed) => {
        some_error_processing(failed)
      }
    }
As `do_something_with_success` in a closure can't early return from the function (since it's in a closure), which makes sense, but just annoying to read nested results.

Re: Go Replaces Interface{} with 'Any'

#168
post #159
post #32

This is fantastic. It'll make Go feel much less weird. eg from the diff: []interface{}{1, 2.0, "hi"} -> []any{1, 2.0, "hi"} Now that Go is going to have generics, all we need is sugar syntax for early return on error -- like more modern languages such as Rust and Zig have -- and Go may finally be pleasant to program in!

I would also like discriminated unions, and a more sane approach to code generation than go generate and writing your own binary that parses source and spits out source code.

what's not sane about go generate? could you elaborate

Re: Go Replaces Interface{} with 'Any'

#169
post #156

Earlier quoted context omitted.

Ok I was wrongly assuming that panic was expecting an error type, in fact it's an interface{}. > Your use of exceptions for flow control (i.e. goto) is considered harmful Exceptions are a way to delegate error handling to the caller by giving them informations about the unexpected behavior. It implies that the expected behavior is the "happy path" (everything went well) and any deviations (errors) is unexpected. This…

> It implies that the expected behavior is the "happy path" (everything went well) and any deviations (errors) is unexpected. Errors are the "happy path", though. Your network connection was lost for the data you were trying to transmit so you saved it to your hard drive instead means that everything went well! Throwing your hands up in the air and crashing your program because you had to make a decision is not somet…

> Throwing your hands up in the air and crashing your program because you had to make a decision is not something you would normally want to do

And that's not something you do thanks to try/catch. You just handle the error where it's meaningful to handle it.

The happy path of "make a request" is that there is no network error.

The happy path of "make sure this request is sent" is that you handle the unexpected network error to save the request to disk for further retry.

If the disk is full, you're not on the happy path anymore.

In Erlang/Elixir, there is a philosophy of "let it crash" which is basically "delegate the error handling/recovery to where it's meaningful to do so". For example:

  start a supervised process to send a request
  if there is an unexpected network failure, let it crash
  the supervisor retries on its own
Or:

  start a process to send a request
  if there is an unexpected network failure, let it crash
  monitor the process to be notified when it crash
  do something if it happens, like saving the request to disk
A "write_file" function can return many kind of errors:

  - file not found (some folder in the path does not exist)
  - permission denied
  - disk full
When you call write_file, you might want to handle some of those errors, and delegate the handling of others to your caller.

You're still not addressing my main point. How do you check which errors you want to handle and which one you want to propagate with just a string describing your error ?

Re: Go Replaces Interface{} with 'Any'

#170
post #130
post #128

Earlier quoted context omitted.

I’ve been doing a lot of rust programming recently, but how exactly is the Result monad better? I feel like I end up with nested match statements for chained results, but maybe I’m doing something wrong.

Rely more on the map/map_err/or_else/... methods for the Result type. You'll get something like (pseudocode): may_fail_who_knows() .map(use_value) .map_err(some_error_processing) .and_then(another_computation_which_can_fail) .or_else(with_some_error_handling_that_can_rescue) .unwrap_or(a_default_value) Basically, instead of nested match expressions, you get a "pipeline".

This is equivalent to:

    let x = match may_fail_who_knows() {
        Ok(y) => Ok(another_computation_which_can_fail(use_value(x))),
        Err(e) => with_some_error_handling_that_can_rescue(
            some_error_processing(e)),
    };
    match x {
        Ok(y) => y,
        _ => a_default_value,
    }
It's a bit more verbose than using the combinators, but someone coming across it for the first time will understand it immediately because there's less to remember to understand it (this is where go really shines).

Also: by avoiding functors there are fewer subtle lifetime issues and `move ||` stuff to deal with and you can return from the containing function and use the `?` operator.

During the discussions of how `.await` was going to work for rust async there was the proposal to add other suffix keywords. So this would look like:

    may_fail_who_knows()
    .match {
        Ok(y) => Ok(another_computation_which_can_fail(use_value(x))),
        Err(e) => with_some_error_handling_that_can_rescue(
            some_error_processing(e)),
    }
    .match {
        Ok(y) => y,
        _ => a_default_value,
    }
Maybe not that different.
Post reply on HN