Live data from Hacker News

Go 1.27 Interactive Tour

victoriametrics.com

161–170 of 219 posts

Re: Go 1.27 Interactive Tour

#161

"The best way to teach something new is to compare it to something the audience already understands." Could someone take the example, reduce it to a non-generic version for two types I DO understand, then show that with the new feature I can collapse them into the Box/Map example in the doc? I have 10+ years of Go experience and I can't make heads or tails of "(b Box[T]) Map[U any](f func(T) U) Box[U]"

It's not a great example. I think it's trying to show a mapping operation for a generic container where the container values are of one type and the mapping function is allowed to return a container with values of a different type. Without generics, something along the lines of the following (with runnable example at https://go.dev/play/p/KHBI1uAhbO0 ): type MySlice []int // Map maps from a slice of ints to a slice o…

And to answer the second half of your request, here is that exact same code as above, but now using the 1.27 generic methods feature (with a runnable example using tip at https://go.dev/play/p/1YK62tGetsm?v=gotip):

  // Map maps from a slice containing type In to a slice containing type Out.
  func (s MySlice[In]) Map[Out any](f func(In) Out) []Out {
      var out []Out
      for i := range s {
          out = append(out, f(s[i]))
      }
      return out
  }
In short, you could always have methods on a generic type since Go first introduced generics in Go 1.18, but with 1.27, the methods on the generic type can also introduce their own additional type parameters.

(Previously, you could achieve the same net effect with a top-level generic function, but then the code would not be grouped as nicely as hanging it off of the type, and arguably it now can have slightly better ergonomics in some cases. You can see more of the rationale from Robert Griesemer at https://github.com/golang/go/issues/77273.)

Re: Go 1.27 Interactive Tour

#162

"The best way to teach something new is to compare it to something the audience already understands." Could someone take the example, reduce it to a non-generic version for two types I DO understand, then show that with the new feature I can collapse them into the Box/Map example in the doc? I have 10+ years of Go experience and I can't make heads or tails of "(b Box[T]) Map[U any](f func(T) U) Box[U]"

If you instantiate it with concrete types, does "(b IntBox) Map(f func(int) string) StringBox" make more sense? You have a collection (in this case Box) containing values of type T, a function that maps values of type T to type U, and if you apply that function to all elements in that collection you get a collection of type U.

Re: Go 1.27 Interactive Tour

#163
Many comments on generic methods. Perhaps this example will help understand a hopefully not-too-objectionable case of using them in practice.

The math/rand/v2 package has a number of functions which return random numbers of a certain type:

    i := rand.Int32()  // a random signed 32-bit integer (type int32)
    j := rand.Uint64() // a random unsigned 64-bit integer (type uint64)
It has functions which return a number within a range:

    in := rand.Int32N(10)   // a random int32 in the range [0,10)
    jn := rand.Uint64N(100) // a random uint64 in the range [0,100)
It also has a generic function, rand.N, where the return type is set by a type parameter. The definition of rand.Int32N (for comparison) and rand.N are:

    func Int32N(n int32) int32
    func N[Int intType] (n Int) Int
Adding some spaces to make the common elements align (apologies if my formatting gets mangled), that's:

    func Int32N               (n int32) int32
    func N      [Int intType] (n Int  ) Int
As you can see, the generic function N has the same signature as the non-generic Int32N, except the type it operates on is set by a type parameter (named "Int"). The type parameter has a constraint, intType, which is a private type defined in the math/rand package. (There's nothing magic about this constraint, it's just a list of all the integer types in the language, and you can write it yourself if you want to. It's a separate type to keep the function signature of N from becoming too large, and it's internal to math/rand because it doesn't need to be part of the public package API.)

The nice thing about rand.N is that it lets you write something like this:

    // d is a random time.Duration in the range [0, 10 minutes)
    d := rand.N(10 * time.Minute)
Without generics, you'd instead write this as the following, which is a lot more noise:

    d := time.Duration(rand.Int64N(int64(10 * time.Minute)))
The generic rand.N has a more confusing type signature and a lot more language complexity behind it, but the code using it is simpler and easier to read. We think that's a good tradeoff, but of course not everyone will agree.

All the functions I've mentioned so far use a default random number source. Each of them also exists as a method of the rand.Rand type, which generates numbers from a user-provided randomness source. For example:

    rng := rand.New(rand.NewChaCha8(seed))
    a := rng.Uint64()
    b := rng.Uint64N(100)
There is one exception, though: Until Go 1.27, there was no Rand.N method, because we did not support generic methods. (A generic type could have methods, but those methods could not be further type parameterized.)

In Go 1.27, there is now a Rand.N method:

    // Using a ChaCha8-based source with a defined seed,
    // generate a duration in the range [0, 10 minutes).
    rng := rand.New(rand.NewChaCha8(seed))
    d := rng.Duration(10 * time.Minute)
This method's signature is:

    func (r *Rand) N[Int intType](n Int) Int
Comparing function vs. method and generic vs. concrete:

    func           Int32N               (n int32) int32 // function
    func           N      [Int intType] (n Int  ) Int   // generic function
    func (r *Rand) Int32N               (n int32) int32 // method
    func (r *Rand) N      [Int intType] (n Int)   Int   // generic method
In this case, generic methods permit us to fix a small wart in the package API. This example isn't the motivating reason for adding generic methods, but I think it serves as an example of how adding them makes the language a bit simpler and more consistent. In Go, methods are just a type of function. Previously, you could write a type-parameterized function, but you couldn't write a type-parameterized method. That's an inconsistency that you need to remember. Now you can write type-parameterized functions or methods, using a consistent syntax for either.

Type-parameterized methods don't participate in interface satisfaction, so this change isn't without its own subtleties. Discussing the tradeoffs there would double the length of this post, and weighing them is why it took so long for us to decide to add generic methods.

Another possibility is that people will use generic methods to write unreadably complex code. My personal opinion is that nothing will stop people from writing unreadably complex code if they want to; the fix to complexity is to not do that.

Re: Go 1.27 Interactive Tour

#164
post #59

> interfaces still can’t declare type-parameterized methods What would an implementation look like? Wouldn't it be quite different from the existing one because it has to rely heavily on indirection because (limited) monomorphimization is not possible?

[deleted]

Re: Go 1.27 Interactive Tour

#165

This level of generics actually has me interested a bit in Go now.

You can take my place, as the same changes make me want to leave.

To what? What would you use instead, things like this seem common place in languages made in the last decade.

Re: Go 1.27 Interactive Tour

#166
post #47

Earlier quoted context omitted.

Can you go more into this? I don’t quite follow

Go's http.Client will keepalive a TCP/TLS connection to save you handshake latency on second requests. But it can only do this if you completely finish reading the last request. Now in 1.27: > http.Response.Body drains itself on Close. For HTTP/1, closing the body now reads and discards any unread content (up to a conservative limit) so the connection can be reused. For most programs this is a transparent win [...] G…

This sounds like a breaking change. Like a go 2.0 thing idk.

Re: Go 1.27 Interactive Tour

#167

"The best way to teach something new is to compare it to something the audience already understands." Could someone take the example, reduce it to a non-generic version for two types I DO understand, then show that with the new feature I can collapse them into the Box/Map example in the doc? I have 10+ years of Go experience and I can't make heads or tails of "(b Box[T]) Map[U any](f func(T) U) Box[U]"

This is the kind of shit why they probably didn't want generics in the language.

This is like building a very crude general-ish DSL inside the language. Because the tools are intentionally limited (as to limit the scope of the feature), the result looks ugly. Also, like with C++ templates, people find exploits to do what the designers didn't want them to, with even more elaborate workarounds.

I liked Go before generics. It had a clear identity. If you wanted to get cute, you could use go generate and generate code. They should've made that much more convenient and ergonomic, if they wanted to make the language more powerful (and the nice thing is that it still sits outside of the language).

I think the point Go was making is that these complex things generally have little use in application code, and 99% of the time they're there for people who want to show how smart they are, at the expense of code readability, and accessibility.

Re: Go 1.27 Interactive Tour

#168
post #141

Earlier quoted context omitted.

403 error. What was the bug?

IIRC it was overflow when you do (a + b) / 2 for the midpoint. It took so long to find because you need a >billion item array to overflow the 32-bit integer, and that much RAM wasn't common until the 00s. The right way is a + (b - a)/2.

64KiB was common in the 16-bit era, however

Re: Go 1.27 Interactive Tour

#169
post #142

Earlier quoted context omitted.

Just because we can, doesn't mean we have to. I'd prefer to have some more brain-cache free to concentrate on the problem I'm trying to debug rather than doing type resolution in my head.

Please. I’m sorry, but you kind of can’t avoid needing to think about types unless you use a language like JavaScript which is super loose with its type conversions, and you especially can’t avoid in a language like Go. With generics in Go you don’t even need to prefill the types like you go with a lot of other cases, so I’m dubious about the cognitive overhead.

No need to insult JavaScript. In two out of three times the "JavaScript" written will be something like:

    interface Box { value: T }

    function map(input: Box, func: (value: T) => U): Box {
        return { value: func(input.value) }
    }

Re: Go 1.27 Interactive Tour

#170
post #118

This: "(b Box[T]) Map[U any](f func(T) U) Box[U]" is the type of cognitive weight I was happy that Go avoided.

We often forget that our profession (computer programming) belongs to STEM. Some (like Go 1.0 :)) wish to think it is Arts & Humanities. The sooner we realize that yes, it is OK and actually expected to bear a cognitive weight of "(b Box[T]) Map[U any](f func(T) U) Box[U]" the sooner we get back to reality... :)

I wish it was arts & humanities.. those are some actually clever folks.
Post reply on HN