What is the best way to have polymorphic implementations on a generic in go (1.18)?

Issue

I want to create a Vector type that is generic over its internal data but may have differ in how the methods are implemented given the input type.

type SupportedType interface {
         ~int64 | ~uint64 |  ~float64 | string | bool | time.Time
}

type Vec[T SupportedType] struct {
    data []T
}

and I want to add a varying implementations on a function depending on the type. For example:

func (vec Vec[T]) Sort() {
    ...
}

In most of the generic types < will work just fine. However, if T -> time.Time I want to use the Before method and if T --> bool then I want all the false values to go before the true.

I have some ideas on how to accomplish this but what would be considered "idiomatic" in the new generics world? My application is performance sensitive.


Using a type union with types that all have the same function doesn’t work (https://play.golang.com/p/QWE-XteWpjL).

Embedding a container inside type specific structs does work ( https://play.golang.com/p/j0AR48Mto-a ) but requires the use of an interface which means that the Less and Val in the example functions can’t be inlined. It also might not work so nicely if there isn’t a clean delineation between the subsets in the type union.

Solution

BTW there is already a library for sorting

https://pkg.go.dev/golang.org/x/exp/slices#Sort

1. You can create interface with generic, then type assert to that.

example:

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

type Vec[T SupportedType] []T

func (vec Vec[T]) Less(a, b int) bool {
    return any(vec[a]).(Lesser[T]).Less(vec[b])
}

func main() {
    vs := Vec[String]([]String{"a", "b", "c", "d", "e"})
    vb := Vec[Bool]([]Bool{false, true})
    fmt.Println(vs.Less(3, 1))
    fmt.Println(vb.Less(0, 1))
}

playground 1

2. You can save the type on Vec.

example:

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

type Vec[T SupportedType, L Lesser[T]] []T

func (vec Vec[T, L]) Less(a, b int) bool {
    return any(vec[a]).(L).Less(vec[b])
}

func main() {
    vs := Vec[String, String]([]String{"a", "b", "c", "d", "e"})
    fmt.Println(vs.Less(3, 1))
}

playground 2

3. Nesting type constraint

thanks @blackgreen

example :

type SupportedType interface {
    Int8 | Time | Bool | String
}

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

type Vec[T interface {
    SupportedType
    Lesser[T]
}] []T

func (vec Vec[T]) Less(a, b int) bool {
    return vec[a].Less(vec[b])
}

func main() {
    vs := Vec[String]([]String{"a", "b", "c", "d", "e"})
    fmt.Println(vs.Less(3, 1))
}

playgrond 3

Benchmark:

benchmark 1 : 28093368          36.52 ns/op       16 B/op          1 allocs/op

benchmark 2 : 164784321          7.231 ns/op           0 B/op          0 allocs/op

benchmark 3 : 212480662          5.733 ns/op           0 B/op          0 allocs/op

Embedding a container inside type specific structs:
benchmark 4 : 211429621          5.720 ns/op           0 B/op          0 allocs/op

It’s up to you which one is best for you. But IMO number 3 is best.

Answered By – Rahmat Fathoni

Answer Checked By – Mary Flores (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.