Recursive type constraint using a defined type rather than a type literal?

Viewed 498

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?

1 Answers

Define interface type Lesser and function Isless as follows:

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

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

Then, the following code compiles without mishap:

type Apple int

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

type Orange int

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

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 satisfy 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.

Related