Is it possible to switch on type T in a generic function?

1,125 views
Skip to first unread message

Mark

unread,
Nov 8, 2022, 8:53:33 AM11/8/22
to golang-nuts
Given a function:

func F[T comparable](a T) {
}

is it possible to check T's type inside F?

My use case is that I have a function with signature G[T comparable](x []T) and inside G I want to sort the elements in slice x where T could be int or string.

This arises in a tiny generic set module I've created: https://github.com/mark-summerfield/gset
In the String() method I want to return a string with the elements sorted (for human readability and for testing convenience); but at the moment I can only do this by converting all elements to strings and sorting them which isn't good for ints.

Jan Mercl

unread,
Nov 8, 2022, 9:19:48 AM11/8/22
to Mark, golang-nuts
On Tue, Nov 8, 2022 at 9:53 AM 'Mark' via golang-nuts
<golan...@googlegroups.com> wrote:

> Given a function:
>
> func F[T comparable](a T) {
> }
>
> is it possible to check T's type inside F?
>
> My use case is that I have a function with signature G[T comparable](x []T) and inside G I want to sort the elements in slice x where T could be int or string.

https://go.dev/play/p/zxQYVvOMX35 ?

Mark

unread,
Nov 8, 2022, 9:56:13 AM11/8/22
to golang-nuts
I see that your example works, but I can't see how to adapt it to my use case.

type Set[T comparable] map[T]struct{}

func New[T comparable](elements ...T) Set[T] {
    set := make(Set[T], len(elements))
    for _, element := range elements {
        set[element] = struct{}{}
    }
    return set
}

func (me Set[T]) String() string {
    ////// Want to sort by T < T //////////////////
    elements := make([]string, 0, len(me))
    for element := range me {
        elements = append(elements, fmt.Sprintf("%#v", element))
    }
    sort.Strings(elements)
    /////////////////////////////////////////////////////
    s := "{"
    sep := ""
    for _, element := range elements {
        s += fmt.Sprintf("%s%s", sep, element)
        sep = " "
    }
    return s + "}"
}

Jan Mercl

unread,
Nov 8, 2022, 10:13:15 AM11/8/22
to Mark, golang-nuts
On Tue, Nov 8, 2022 at 10:56 AM 'Mark' via golang-nuts
<golan...@googlegroups.com> wrote:
> ////// Want to sort by T < T //////////////////
> elements := make([]string, 0, len(me))
> for element := range me {
> elements = append(elements, fmt.Sprintf("%#v", element))
> }
> sort.Strings(elements)
> /////////////////////////////////////////////////////


Not tested:

////// Want to sort by T < T //////////////////
elements := make([]T, 0, len(me))
for element := range me {
elements = append(elements, element)
}
sort.Slice(elements, func(i, j int) bool { return
less(elements[i], elements[j]) })
/////////////////////////////////////////////////////

But you need to extend the less function to handle mixed types. Like
in less(42, "foo").

roger peppe

unread,
Nov 8, 2022, 11:29:34 AM11/8/22
to Mark, golang-nuts
If you're sure that T is an int or a string, then why not constrain it as such? https://go.dev/play/p/1kT6EacMHco

You could go further and constrain it to allow any type with ordering defined: https://go.dev/play/p/il5koj1RPkh

If you want to allow any kind of comparable key in your set, one could observe that essentially you're trying to solve the same problem that the fmt package is solving when it converts maps to string. It uses the internal fmtsort package, which, as luck would have it, has been factored out into an externally available package. So you could do this: https://go.dev/play/p/oKTGSm_o22a

To answer the specific question you asked, there is an issue that tracks the ability to do a switch directly on a type parameter: https://github.com/golang/go/issues/45380

But you can also work around the lack of that feature by doing something like this: https://go.dev/play/p/3C2a61Ojbxs

Hope this helps,

  rog.


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/e746f065-24fa-474a-90ec-2d8367bf3e3bn%40googlegroups.com.

Mark

unread,
Nov 8, 2022, 1:09:51 PM11/8/22
to golang-nuts
Thanks for your help and very interesting ideas. In the end I used this:

type Set[T comparable] map[T]struct{}

func New[T comparable](elements ...T) Set[T] {
    set := make(Set[T], len(elements))
    for _, element := range elements {
        set[element] = struct{}{}
    }
    return set
}

func (me Set[T]) String() string {
    elements := make([]T, 0, len(me))
    for element := range me {
        elements = append(elements, element)
    }
    sort.Slice(elements, func(i, j int) bool {
        return less(elements[i], elements[j])
    })
    s := "{"
    sep := ""
    for _, element := range elements {
        s += sep + asStr(element)

        sep = " "
    }
    return s + "}"
}

func asStr(x any) string {
    if s, ok := x.(string); ok {
        return fmt.Sprintf("%q", s)
    }
    return fmt.Sprintf("%v", x)
}

func less(a, b any) bool {
    switch x := a.(type) {
    case int:
        return x < b.(int)
    case float64:
        return x < b.(float64)
    case string:
        return x < b.(string)
    default:
        return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b)
    }
}

Interestingly, I couldn't put the asStr() code in the String() function since doing so produced this error:

invalid operation: cannot use type assertion on type parameter value element (variable of type T constrained by comparable)

Anyway, I'm happy that it all works now. (I know I ought to include every int & float32, but this is enough for now).

Duncan Harris

unread,
Nov 9, 2022, 4:33:48 PM11/9/22
to golang-nuts
> Interestingly, I couldn't put the asStr() code in the String() function since doing so produced this error:
> invalid operation: cannot use type assertion on type parameter value element (variable of type T constrained by comparable)

You need to convert to "any": https://go.dev/play/p/1pMhs22S-8P

P.S. It would be better to use https://pkg.go.dev/strings#Builder for building a string

Mark

unread,
Nov 9, 2022, 5:01:36 PM11/9/22
to golang-nuts
Thank you! I've now switched to using any as you suggested & also use strings.Builder.
Reply all
Reply to author
Forward
0 new messages