[generics] Trying to use generics as enums

122 views
Skip to first unread message

Martin Tournoij

unread,
Jul 5, 2020, 3:44:02 PM7/5/20
to golan...@googlegroups.com
Hi there,

I played around a bit with the go2go playground today; I was wondering
how useful it would be to implement enums.

Whether using generics like this is a good idea or not is a different
discussion, it's just an interesting thing to experiment with and see
how far I could get.

I thought it might be useful to share my experience; I ran in to several
errors I couldn't really make sense of. This is probably a failure of
understanding on my part 😅 But I did spend quite some time in front of
the draft specification trying to figure this out.

All code was run on the "go2go" playground today.

Note I am not usually subscribed to the golang-nuts mailing list, and
have not read all of the discussions (using Google groups to browse
archives is a but of a pain), so apologies if this duplicates any
previous feedback.

Hope it helps.

Cheerio,
Martin

---

First, let's define my types:

type (
Banana struct{ banana struct{} }
Coconut struct{ coconut struct{} }

Fruit interface {
type Banana, Coconut
}
)

And then adds a function which only accepts a "Fruit" enum value:

func show(type enum Fruit)(fruit enum) {
switch (interface{})(fruit).(type) {
case Banana:
fmt.Println("That's just bananas!")
case Coconut:
fmt.Println("I've got a lovely bunch of coconuts!")
default:
fmt.Println("Yeah nah")
}
}

func main() {
show(Banana{})
show(Coconut{})
}

The type switch is somewhat ugly and has limitations, as mentioned in
the design document, but it works for this case.

Moving on, I wanted to add a function which accepts multiple fruits,
which gives an error:

// type Coconut of (Coconut literal) does not match inferred type Banana for enum
func showAll(type enum Fruit)(fruits ...enum) {
for _, f := range fruits {
show(f)
}
}

In the spec it actually mentioned that:

> No variadic type parameters. There is no support for variadic type
> parameters, which would permit writing a single generic function that
> takes different numbers of both type parameters and regular
> parameters.

So looks like that's not supported, fait enough, but the error message
is a bit confusing. Second try:

func showAll(type enum Fruit)(fruits []enum) {
for _, f := range fruits {
show(f)
}
}

func main() {
// Fruit does not satisfy Fruit (interface{type Banana, Coconut} not found in Banana, Coconut)
showAll([]Fruit{Banana{}})
}

I'm not entirely sure what to make of that error 🤔

Adding a function which returns a Fruit also proved difficult:

// cannot use (Banana literal) (value of type Banana) as enum value in return statement
func getBanana(type enum Fruit)() enum {
return Banana{}
}

// cannot convert (Banana literal) (value of type Banana) to enum
func getBanana(type enum Fruit)() enum {
return enum(Banana{})
}

I'm not entirely sure why this doesn't work 🤔 It does work when you're
doing something like:

// getBanana(Banana{})
func getBanana(type enum Fruit)(v enum) enum {
return v
}

But this is a fairly useless function :-)

I also wasn't able to create a list of all Fruit types:

// undefined: Fruit
var FruitList = []Fruit{Banana{}, Coconut{}}

func FruitList(type enum Fruit)() enum {
// cannot use (Banana literal) (value of type Banana) as enum value in array or slice literal
return []enum{Banana{}, Coconut{}}
}

// function type must have no type parameters
var FruitList = func(type enum Fruit)() []enum {
return []enum{}
}

Ian Lance Taylor

unread,
Jul 5, 2020, 4:13:40 PM7/5/20
to Martin Tournoij, golang-nuts
I'm not sure what is going on here, as you didn't show the calling code.  But this is not an example of a variadic type parameter.  It's fine to use a type parameter for a variadic ordinary parameter.  But note that a function written this way does not accept all possible Fruit values.  It accepts a series of values of one specific Fruit type.



 
Second try:

        func showAll(type enum Fruit)(fruits []enum) {
                for _, f := range fruits {
                        show(f)
                }
        }

        func main() {
                // Fruit does not satisfy Fruit (interface{type Banana, Coconut} not found in Banana, Coconut)
                showAll([]Fruit{Banana{}})
        }

I'm not entirely sure what to make of that error 🤔

Type constraints are represented as interface types, but they remain two different things.  Writing []Fruit{Banana{}} gives you a slice of the interface type Fruit.  The type constraint Fruit permits a type argument that is either Banana or Coconut.  Note that the type constraint Fruit does not permit the type argument Fruit.  But that is what you are trying to use.

 
Adding a function which returns a Fruit also proved difficult:

        // cannot use (Banana literal) (value of type Banana) as enum value in return statement
        func getBanana(type enum Fruit)() enum {
                return Banana{}
        }

        // cannot convert (Banana literal) (value of type Banana) to enum
        func getBanana(type enum Fruit)() enum {
                return enum(Banana{})
        }

I'm not entirely sure why this doesn't work 🤔 It does work when you're
doing something like:

        // getBanana(Banana{})
        func getBanana(type enum Fruit)(v enum) enum {
                return v
        }

But this is a fairly useless function :-)

You didn't show the calls of the earlier functions, so I'm not sure what happened.  A function

func getBanana(type enum Fruit)() enum {

requires a type argument.  The type argument cannot be inferred from the regular arguments.  So the only way to call this function is to write something like

getBanana(Banana{})().

As you say, that's not too useful.

 
I also wasn't able to create a list of all Fruit types:

        // undefined: Fruit
        var FruitList = []Fruit{Banana{}, Coconut{}}

        func FruitList(type enum Fruit)() enum {
                // cannot use (Banana literal) (value of type Banana) as enum value in array or slice literal
                return []enum{Banana{}, Coconut{}}
        }

        // function type must have no type parameters
        var FruitList = func(type enum Fruit)() []enum {
                return []enum{}
        }

Here again you seem to be mixing up type constraints and interface types, a confusion supported by the fact that they are both called Fruit.

Ian

Steven Blenkinsop

unread,
Jul 5, 2020, 5:17:32 PM7/5/20
to Martin Tournoij, golan...@googlegroups.com
On Sun, Jul 5, 2020 at 3:43 PM, Martin Tournoij <mar...@arp242.net> wrote:

So looks like that's not supported, fait enough, but the error message
is a bit confusing. Second try:

        func showAll(type enum Fruit)(fruits []enum) {
                for _, f := range fruits {
                        show(f)
                }
        }

        func main() {
                // Fruit does not satisfy Fruit (interface{type Banana, Coconut} not found in Banana, Coconut)
                showAll([]Fruit{Banana{}})
        }

I'm not entirely sure what to make of that error 🤔

Fruit isn't one of the types you listed. You only listed Banana and Coconut. In principle, you'd had to also list Fruit in order for it to work. Unfortunately, actually using a interface like this isn't supported (you get a fairly opaque error, but the reason is because you aren't allowed to have values of type Fruit, since Fruit is an interface containing a type list):


It seems like it wouldn't really hurt to be allowed to have values of type Fruit, it just wouldn't be terribly useful. Aside from the static guarantee about what types can be held by the interface, it's equivalent to type interface{}.

One thing I want to point out is that you keep running into trying to use a type parameter (enum, in your case) to represent more than one type at once. For each instantiation of a parameterized function, the type parameter has to represent exactly one specific type.

In my example above, I provided the explicit parameter in each case, as in  PrintFruit(Banana) vs. PrintFruit(Coconut). This works basically as if you replaced every appearance of F in PrintFruit with either Banana or Coconut. You can't have both Banana and Coconut values being used as type F at the same time. And the definition of PrintFruit doesn't know which particular type will be passed in, so it doesn't know if F is either Banana or Coconut, so it can't treat F as being the same as either.
Reply all
Reply to author
Forward
0 new messages