from Hacker News

Deconstructing Go Type Parameters

by psxuaw on 9/27/23, 12:05 AM with 58 comments

  • by parhamn on 9/27/23, 4:25 AM

    It really doesn't help that the major (perhaps only?) official resources on generics in golang are these blog posts [1][2] and the spec. And now this blog post.

    The whole "what type am I getting"/make()ing is really tricky (as outlined in this doc) especially when its a pointer/interface/slice/etc. And a lot of feels like it doesn't need to be as much of a complex decision tree as it is. Is there any other documentation on this stuff that I'm missing?

    Theres a lot of complication buried in golang people don't talk about that much. nil vs empty slices, interface{} and any behavior differences, make() and what it would do for various type scenarios, impossible to remember channel semantics (which ones panic again?). Of course, theres always a good explanation for why it is the way it is, but for a language so opinionated, stronger opinions on better DX in the deeper parts would be great.

    [1] https://go.dev/blog/intro-generics [2] https://go.dev/doc/tutorial/generics

  • by hknmtt on 9/27/23, 6:08 AM

    I am a Go fan and have been coding in it for years, but this crap:

    func Clone[S ~[]E, E any](s S) S { return append(s[:0:0], s...) }

    looks just like Rust, which has the fugliest syntax I have ever seen. Personally I use maybe 3 or 4 generic functions to work with arrays(oh, sorry SLICES), otherwise I do not touch them. Could not care less about them and all that noise they caused.

  • by Groxx on 9/27/23, 4:31 AM

    I suppose this is necessary because this:

      func clone[S ~[]any)(s S) S
    
    would only allow things with an underlying type of []interface{}, not "any type" as an inferred type... and that applies to the final example too:

      // allows any collection of stringable things
      func WithStrings[S ~[]E, E interface { String() string }](
      
      // allows only things like:
      // []interface { String() string }{...}
      // and named types like that, but not:
      // []strings.Builder{...}
      // because that isn't the same collection type,
      // it's just a collection of compatible elements
      func WithStrings[S ~[]interface { String() string }](...)
    
    I guess this is the price to pay to avoid introducing co/contra variance? It may be worth it, and it seems likely that it would be a thing you can improve without breaking compatibility.
  • by happytoexplain on 9/27/23, 4:14 AM

    Wow, I'm not super happy about the syntax of this language. I'm familiar with what each paragraph is describing from multiple other languages, but I can't even guess how some of the syntax here maps onto those other languages, even with the explanations.
  • by carterschonwald on 9/27/23, 4:01 AM

    I’m almost a grey beard in typed functional programming and I’m actually confused by this.
  • by mutatio on 9/27/23, 5:53 AM

    I'm not sure I'm following the preamble about the nuances of a slice with a zero capacity allocating a new backing array, given the fact that if I follow the link to the docs and then to the source, the implementation is exactly how I would have expected it to be done: append(S([]E{}), s...) - which of course is different and would make the preamble redundant.
  • by dolmen on 9/27/23, 1:28 PM

    I have a question that this post doesn't answer: is the order of type parameters significant? Is there a canonical way, an idiomatic style for the order?

    Example:

      func Clone1[S ~[]E, E any](s S) S {
        return append(s[:0:0], s...)
      }
    
    vs

      func Clone2[E any, S ~[]E](s S) S {
        return append(s[:0:0], s...)
      }