2

In Go2 generics, as of current draft, I can specify a type constraint on a generic type using an interface.

import "fmt"

type Stringer interface {
    String() string
}

func Print[T Stringer](value T) {
    fmt.Println(value.String())
}

This way, I can specify, that the type must implement a method. However, I don't see any way to force the implementation of a method, with itself having an argument of the generic type.

type Lesser interface {
    Less(rhs Lesser) bool
}

type Int int

func (lhs Int) Less(rhs Int) bool {
    return lhs < rhs
}

func IsLess[T Lesser](lhs, rhs T) bool {
    return lhs.Less(rhs)
}

func main() {
    IsLess[Int](Int(10), Int(20))
}

Exits with

Int does not satisfy Lesser: wrong method signature
    got  func (Int).Less(rhs Int) bool
    want func (Lesser).Less(rhs Lesser) bool

The original draft with contracts would make this possible, but not the new draft.

It can also be done with the following, but that would leave you with repeating the same constraint over and over again, braking DRY (and DRY code is the purpose of generics). It would also make the code much more unwieldy if the desired interfaces have multiple methods.

func IsLess[T interface { Less(T) bool }](lhs, rhs, T) bool {
    return lhs.Less(rhs)
}

Is there any way to do this with a predefined interface in the new draft?

jub0bs
  • 60,866
  • 25
  • 183
  • 186
  • 1
    Well, first of all, the function in the interface has a signature `Less(rhs Lesser) bool` and your implementation has a signature `Less(rhs Int) bool`, it's not the same. – Z. Kosanovic Oct 21 '20 at 17:07
  • Sure, but how do I create a universal constraint for `func (lhs T) Less(rhs T) bool {}`? – Martin Horský Oct 21 '20 at 17:13
  • 3
    There is a section about this in the draft: https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#using-types-that-refer-to-themselves-in-constraints It appears that there isn't an easy way to do this. – Burak Serdar Oct 21 '20 at 18:16
  • @BurakSerdar: Hm, I'd say it's *easy but clumsy*, myself. :-) – torek Oct 21 '20 at 19:40
  • 1
    @torek, consider doing this with an interface with multiple methods. The result will be unreadable. – Burak Serdar Oct 21 '20 at 19:54
  • Right - it's just what Martin Horsky posted, you get a big blob of unreadable interface. Very clumsy! – torek Oct 21 '20 at 20:03
  • 2
    Honestly, I just today check on the situation of generics in Go and I was really disappointed, that the contracts have been cut. I found the contracts really awesome. And possibly more DRY, because you would just define a generic type of a whole file/package. Additionally, some stuff in the current draft allows you to generalize for types, that have > and < operators, but I don't see the point since Go likely won't ever have operator overloading. – Martin Horský Oct 21 '20 at 21:30

1 Answers1

5

Define interface type Lesser and function Isless as follows:

type Lesser[T any] interface {
    Less(T) bool
}

func IsLess[T Lesser[T]](x, y T) bool {
    return x.Less(y)
}

Then, the following code compiles without mishap:

type Apple int

func (a Apple) Less(other Apple) bool {
    return a < other
}

type Orange int

func (o Orange) Less(other Orange) bool {
    return o < other
}

func main() {
    fmt.Println(IsLess(Apple(10), Apple(20)))   // true
    fmt.Println(IsLess(Orange(30), Orange(15))) // false

    // fmt.Println(IsLess(10, 30))
    // compilation error: int does not implement Lesser[T] (missing method Less)

    // fmt.Println(IsLess(Apple(20), Orange(30)))
    // compilation error: type Orange of Orange(30) does not match inferred type Apple for T
}

(Playground)


The constraint T Lesser[T] may be read as

any type T that has a Less(T) bool method.

Both of my custom types,

  • Apple with its Less(Apple) bool method, and
  • Orange with its Less(Orange) bool method,

fulfil this requirement.

For information, Java generics allow a similar trick via what is known as a recursive type bound. For more on this topic, see item 30 (esp. p137-8) in Josh Bloch's Effective Java, 3rd edition.


Full disclosure: I was reminded of this unanswered question when I came across Vasko Zdravevski's solution to a similar problem on Gophers Slack.

jub0bs
  • 60,866
  • 25
  • 183
  • 186