1. 39
    1. 10

      Credit where it’s due, I think the instantiation process whereby this

      m := GMin[float64](2.71, 3.14)
      

      is equivalent to this

      fmin := GMin[float64]
      m := fmin(2.71, 3.14)
      

      is legitimately clever.

      1. 33

        This is called type application, and “credit where it’s due” belongs to Jean-Yves Girard (1972) and John C. Reynolds (1974) for inventing it in System F.

        1. 7

          And almost every other modern language other than Go in the decades since :D

          More fun are higher ranked types, where we can treat a function without some/any of the type parameters applied as a first class object and pass those around :D

      2. 3

        Why would you prefer either of these forms to :

        m := Min(2.71, 3.14)
        

        Why even have a G prefix ?

        1. 6

          The GMin version with type constraints is just used for educational purposes. At the bottom of the article there is a section about type inference, that eventually shows that type arguments for the GMin function can be inferred and don’t have to be stated explicitly.

        2. 2

          I can’t tell is this is a legitimate question, but assuming it is, you can simply call the generic function Min and elide the type arguments at the call site (thanks to inference) and end up with exactly your example.

          1. 2

            That was a real question, though not my best formulated one. I’m surprised anyone would teach generics this way by introducing polish hungarian notation and mentionning inference after the fact.

            1. 4

              I think you mean Hungarian notation.

              In terms of the name in the example, GMin, I think it makes more sense in context. The authors begin with an example of what you would have written in Go before 1.18.

              func Min(x, y float64) float64 {
                  if x < y {
                      return x
                  }
                  return y
              }
              

              Then they introduce the idea of generic functions, but at this point, they’ve just used the name Min for the non-generic function. Hence, they use GMin to clearly indicate the different function.

              I think the authors put type inference later for several reasons. First, Go can’t always infer the types, and the rules for when inference is or is not supported are complicated. So, it makes sense to present explicit type arguments initially. As a Go programmer, you are likely to need explicit type arguments sometimes, and I don’t think they’ll ever hurt. (They are like parentheses to specify the order of operations: you should use them if the expression would be less clear without them.) Second, programmers from dynamic languages may assume that generics mean that Go allows you to just slap any or constraints.Ordered into a function definition and then, well, do anything. It doesn’t. The programmer still has to pick a single type for both parameters of GMin. That is, the following won’t work, but someone could easily think it might. Such a person might reason, “Each of a and b are members of constraints.Ordered, so they satisfy the type declaration.” (In the spirit of full disclosure, when I first heard about Go including generics, I thought code like that below would work.)

              import (
              	"fmt"
              
              	"golang.org/x/exp/constraints"
              )
              
              func main() {
              	var a int = 10
              	var b float64 = 11
              	fmt.Printf("min of %v and %v = %v\n", a, b, GMin(a, b))
              }
              
              func GMin[T constraints.Ordered](x, y T) T {
              	if x < y {
              		return x
              	}
              	return y
              }
              

              Anyhow, I don’t mean to say that the tutorial is perfect, but I think the sequence makes good sense, especially for people who aren’t used to generics in other languages or who previously only worked in languages with dynamic typing and few constraints.

            2. 2

              For people unfamiliar with generics, I think it makes sense to make obvious the fact that generic functions are literally just regular functions that also take types as parameters. To that end, I appreciate how they worked up to type inference instead of starting there. It makes the whole thing seem more familiar and less magical. I agree that, for people who are already familiar with generics, it probably seems a little silly.

            3. 1

              It’s only because it’s presenting the stdlib function called Min at the exact same time, and it makes more sense to give the new one a new name for the sake of discourse than to “rename” the old one.

    2. 3

      I get this was probably the best design given that they really didn’t want to break backwards compatibility and didn’t want something too difficult to implement, but I’m not a fan of type sets being in interfaces, or constraints being a special kind of interface that can only be used to describe type parameters. It kind of overloads the meaning of interface to be either a regular interface that you can use (almost) anywhere, or a special one that you can only use for type parameters. Knowing that something is an interface won’t be enough to know where you can use it; you’ll have to know if it has a type set in it. And even though listing types and listing methods are two ways to describe sets of types, I see them as fundamentally different, because when describing a set of types by listing them you start with an empty set and widen it, and when you describe a set of types by listing methods you start with every type and narrow it down.

      1. 3

        Interfaces as type sets is a powerful new mechanism and is key to making type constraints work in Go. For now, interfaces that use the new syntactic forms may only be used as constraints. But it’s not hard to imagine how explicitly type-constrained interfaces might be useful in general.

        My guess is they will add unrestricted constraints as interface values next year in 1.20.

        1. 1

          I was kind of worried that unrestricted constraints would lead to type Result[T any] interface { T | error }*, but it seems like that still wouldn’t be allowed. It still mildly bugs me that these two ways of describing sets of types are merged into one construct. My “dream” Go would just separate them into a different construct like union types at that point. But oh well, still a nice design overall. Also, I wonder if we would still call them constraints at that point, or just interfaces–wouldn’t they be interchangeable?

          * Not that I don’t like result types, it would just be uncharacteristic for Go if new error handling techniques suddenly popped up.

      2. 2

        An earlier generics proposal actually had type constraints as a separate concept, but it was struck down.

        I believe the Go team has plans to allow using type-list interfaces outside constraints as well.

    3. 2

      Really excited about this! There are a lot of things that I like about Go. It is imperfect (at least from my personal perspective), but so is every other language (again, for me, personally). Generics will make it easier for me to choose Go for projects where I would have otherwise been sort of “stuck” on language choice.

      Edit: when I say “stuck” I mean wanting, for example, a reasonable way to target a browser, but also wanting a fairly high-level language, but also wanting an executable that doesn’t require a VM or runtime, etc. There’s this kind of weird spot in the language Venn diagram that I find myself in sometimes and Go was always right there, but it had dealbreakers. This helps.

    4. 2

      Fairly new to go, so I’m puzzled by this:

      type Ordered interface {
          Integer|Float|~string
      }
      

      ~string is described as

      For type constraints we usually don’t care about a specific type, such as string; we are interested in all string types. That is what the ~ token is for. The expression ~string means the set of all types whose underlying type is string. This includes the type string itself as well as all types declared with definitions such as type MyString string.

      Shouldn’t token ~ be used also in front of Integer and Float?

      1. 4

        Your instinct is right, it’s just that Integer and Float are names of other interfaces and not the basic core types. They are defined as you expect in the constraints package: https://pkg.go.dev/golang.org/x/exp/constraints#Float, e.g.:

        type Float interface {
            ~float32 | ~float64
        }
        

        and similarly for Integer.