Live data from Hacker News

Go Replaces Interface{} with 'Any'

github.com

141–150 of 481 posts

Re: Go Replaces Interface{} with 'Any'

#141
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.

Go exceptions do carry stack trace information. However, the topic is errors, not exceptions. Those are very different concepts. Of what use is stack trace information in debugging values that you have assigned error meaning to when not other types of values? If you had a function func add(a, b int) int { return a * b } there would be no expectation of carrying a stack trace to debug it. So what's different about fun…

> However, the topic is errors, not exceptions.

Idiomatically, go uses errors for the purposes other languages use exceptions, so if this makes debugging harder, it's an important consideration.

Re: Go Replaces Interface{} with 'Any'

#142
post #39

Earlier quoted context omitted.

In semantic versioning, going from 1.X to 2.X is only indicated when there are incompatible API changes. Adding generics doesn’t break compatibility with preexisting Go code, so it’s unnecessary to increment the major version.

Sure, but semantic versioning really is the wrong kind of versioning to use for a language. The major version should represent major language changes, not whether its a breaking change or not, semantic versioning isn't somehow magically a "good" way to version. It's useful for libraries / dependencies where you are dealing with many different libraries and just want to know you can upgrade without having to deal with…

That's just like, your opinion, man.

Re: Go Replaces Interface{} with 'Any'

#143
post #124
post #93

Earlier quoted context omitted.

It makes no sense to replace a meaningful and helpful criterion - whether it breaks code or not - by some purely subjective assessment of what's a "major change." That just leads to usual version creep, from "Go 2.2" to "Go 3.0", to "Go 4.0", to (inevitable) "Go 10", "Go 11", "Go 11 Pro", "Go 11 Ultimate Edition",...

It could be worse, they could start naming these "Go WXGA+", "Go i99900kf", and "Go for Women".

Diet Go

Re: Go Replaces Interface{} with 'Any'

#144

Earlier quoted context omitted.

Yes - https://0ver.org/about.html “ZeroVer is satire, please do not use it”

But why is it satire? It makes a lot of sense for projects that don't want to commit to a stable API.

I work on Pure Data (https://github.com/pure-data/pure-data/) which has been 0.x for 25 years now :-) The upcoming release will be 0.52.

Re: Go Replaces Interface{} with 'Any'

#145
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".

That does clean stuff up a bit, but still requires nesting if you need to access more than one value generated in the chain at a time. Go's pattern of val, err := ... is a little cleaner in that regard, but does have a lot of redundant if err != nil checks.

Re: Go Replaces Interface{} with 'Any'

#146
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".

And

    let result = may_fail_who_knows()?
      .use_value_which_might_also_fail()?
      .use_a_different_way_that_might_fail()?;

Re: Go Replaces Interface{} with 'Any'

#147
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.

Go exceptions do carry stack trace information. However, the topic is errors, not exceptions. Those are very different concepts. Of what use is stack trace information in debugging values that you have assigned error meaning to when not other types of values? If you had a function func add(a, b int) int { return a * b } there would be no expectation of carrying a stack trace to debug it. So what's different about fun…

Current state of the art:

  func read_file(filename string) (string, error) {
    return "", errors.New("oops")
  }

  func foo() error {
    a, err := read_file("a.txt")
    if err != nil {
        return errors.New(fmt.Sprintf("read a: %s", err))
    }

    b, err := read_file("b.txt")
    if err != nil {
        return errors.New(fmt.Sprintf("read b: %s", err))
    }

    // do stuff with a and b
    return nil
  }

  func main() {
    err := foo()
    if err != nil {
      fmt.Fprintln(os.Stderr, err)
    }
  }
In a language with exceptions:

  func read_file(filename string) string {
    throw FileNotFound(filename)
  }

  func foo() {
    a := read_file("a.txt")
    b := read_file("b.txt")
    // do stuff with a and b
  }

  func main() {
    try {
      foo()
    }
    catch (err FileNotFound) {
      fmt.Fprintf(os.Stderr, "file not found: %s\n%s\n", err.filename, err.Stacktrace())
    }
  }
With the current go error handling, you need to add the informations yourself in the string, not as a real data structure.

And before you say "you can add the filename to the error message in read_file()", what if the function is defined in a dependency you have no control over?

An exception is a typed data structure that contains way more informations and value to automate rescuing.

Delegating error handling to a try/catch block with a typed data structure allows the caller to care for certain type of errors and delegate the others to its own caller. With the current error type in Go, what would you do? parse the error message?

Re: Go Replaces Interface{} with 'Any'

#148

Earlier quoted context omitted.

Go exceptions do carry stack trace information. However, the topic is errors, not exceptions. Those are very different concepts. Of what use is stack trace information in debugging values that you have assigned error meaning to when not other types of values? If you had a function func add(a, b int) int { return a * b } there would be no expectation of carrying a stack trace to debug it. So what's different about fun…

> However, the topic is errors, not exceptions. Idiomatically, go uses errors for the purposes other languages use exceptions, so if this makes debugging harder, it's an important consideration.

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 errors compared to other values. The add function above contains an error, yet I don't know of any language in existence where you would expect a stack trace bundled alongside the result to help you debug it. Why would that need change just because you decided to return a struct instead of an int? And actually, many APIs in the wild do represent errors as integers.

Re: Go Replaces Interface{} with 'Any'

#149
post #147

Earlier quoted context omitted.

Go exceptions do carry stack trace information. However, the topic is errors, not exceptions. Those are very different concepts. Of what use is stack trace information in debugging values that you have assigned error meaning to when not other types of values? If you had a function func add(a, b int) int { return a * b } there would be no expectation of carrying a stack trace to debug it. So what's different about fun…

Current state of the art: func read_file(filename string) (string, error) { return "", errors.New("oops") } func foo() error { a, err := read_file("a.txt") if err != nil { return errors.New(fmt.Sprintf("read a: %s", err)) } b, err := read_file("b.txt") if err != nil { return errors.New(fmt.Sprintf("read b: %s", err)) } // do stuff with a and b return nil } func main() { err := foo() if err != nil { fmt.Fprintln(os.St…

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 are meant for exceptional circumstances (hence the name), not errors. A file error is not exceptional in the slightest. It is very much expected.

While you can overload exceptions to pass errors (or any other value), that does not mean you should. Your use of exceptions for flow control (i.e. goto) is considered harmful.

Re: Go Replaces Interface{} with 'Any'

#150
post #39

Earlier quoted context omitted.

In semantic versioning, going from 1.X to 2.X is only indicated when there are incompatible API changes. Adding generics doesn’t break compatibility with preexisting Go code, so it’s unnecessary to increment the major version.

Sure, but semantic versioning really is the wrong kind of versioning to use for a language. The major version should represent major language changes, not whether its a breaking change or not, semantic versioning isn't somehow magically a "good" way to version. It's useful for libraries / dependencies where you are dealing with many different libraries and just want to know you can upgrade without having to deal with…

Semantic versioning solves one specific problem that's worth solving - whether you can (expect to) automatically upgrade. That is a problem people have with languages just as much as libraries, and it is a problem that affects both big changes and small just as much as it affects libraries. It is not the only way to solve this problem, but that problem very much needs to be addressed.

When a language adds any features, if your dependencies (whether real library dependencies or just things you're copying from Stack Overflow) start using the new features, you must upgrade to the new language version. That is an inherent usability constraint, and every time a language designer chooses to add a feature, they're making a tradeoff. But if upgrading to the new language version is trivial, then it's generally a worthwhile tradeoff.

For instance, suppose I find some code that uses Python's removeprefix() method on strings. I need to use Python 3.9 or newer to use that code. It doesn't matter that this is a very small feature.

However, I can generally expect to upgrade my Python 3.8 code to Python 3.9 without trouble. It's different from, say, code that uses Unicode strings. For that code, I need to upgrade from Python 2 to Python 3, which I can expect to cause me trouble. The version numbers communicate that. It's true that Python 3 was a "big" change - but "big" isn't really the point. The point is that I can't use Python 2 code directly with Python 3 code, but I can use Python 3.8 code directly with Python 3.9 code. There are plenty of "big" changes happening within the Python 3 series, such as async support, that were made available in a backwards-compatible manner.

As it happens, Python does not use semantic versioning. But they have a deprecation policy which requires issuing warnings for two minor releases: https://www.python.org/dev/peps/pep-0387/ It's technically possible, I think, that a change like Unicode strings could happen within the Python 3.x series, but that's okay, provided they follow the documented versioning policy. This policy addresses the same question that semantic versioning does, but it provides a different answer: you can always upgrade to one or two minor versions newer, but at that point you must stop and address deprecation warnings before upgrading further.

You are, of course, free to also have a marketing version of your project to communicate how big and exciting the changes are. Windows is a great example here: Windows 95 was 4.0 (communicating both backwards incompatibility with 3.1 and major changes) and Windows 7 was 6.1 (communicating backwards compatibility with Vista but still major changes).

Post reply on HN