-3

Say I have 3 structs:

type A struct{
   Foo map[string]string
}

type B struct{
   Foo map[string]string
}

type C struct{
   Foo map[string]string
}

and then I want to create a function that can accept any of those structs:

func handleFoo (){

}

Is there any way to do this with Golang? Something like:

type ABC = A | B | C

func handleFoo(v ABC){
   x: = v.Foo["barbie"] // this would be nice!
}

OK, so let's try an interface:

type FML interface {
  Bar() string
}

func handleFoo(v FML){
   z := v.Bar() // this will compile
   x: = v.Foo["barbie"] // this won't compile - can't access properties like Foo from v
}

In a language which encourages/forces composition, I cannot understand why you can't access properties like Foo.

halfer
  • 19,824
  • 17
  • 99
  • 186
Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
  • Technically I don't think this is generics perse, it has to do with a type-hierarchy or interfaces – Alexander Mills Dec 03 '18 at 03:36
  • In every other case when you tell yourself "I need generics" be prepared to copy-paste like a maniac (or code-generate, which effectively is the same terrible solution) – zerkms Dec 03 '18 at 03:36
  • 2
    For the types and `handleFoo` shown in the question, you can use `func handleFoo(v struct{ Foo map[string]string })` [See it in the Playground](https://play.golang.org/p/9CkeYIqkLiz). This approach has limitations. For example, `handleFoo` does not have access to any methods in types A, B or C. – Charlie Tumahai Dec 03 '18 at 03:43
  • @ThunderCat thanks I updated the OP, let me know if it looks good – Alexander Mills Dec 03 '18 at 03:49
  • I am pretty certain I already tried that technique you just mentioned @ThunderCat, and it didn't compile, my guess was it had to be the same exact type by name/reference, not a different type with the same signature if that makes sense. – Alexander Mills Dec 03 '18 at 03:52
  • Ok @ThunderCat your solution works, can you please add that as answer? I am going to remove generics from the OP title b/c technically this is not generics tmk, but what would call this feature? – Alexander Mills Dec 03 '18 at 03:55
  • @ThunderCat thanks, this is what I observe so far: https://gist.github.com/ORESoftware/7bd67883a3e0ff4846c1ef5489ef8b43 – Alexander Mills Dec 03 '18 at 04:10
  • Make Bar() return the map. – dustinevan Dec 03 '18 at 05:09
  • at(dustinevan) yup that's what @James Shi said – Alexander Mills Dec 03 '18 at 05:14
  • @AlexanderMills Unlike the question, the types in the gist do not share the same underlying type. If your intent is to use types that do not share the same underlying type, then update the question to show that. – Charlie Tumahai Dec 03 '18 at 05:36
  • @ThunderCat yeah for the OP it's too late to change that and would be unfair to the answers, but yeah I am curious how to do it when the structs are different, the only way I know how would be to program to `interface{}` – Alexander Mills Dec 03 '18 at 06:01
  • 3
    Go doesn't have generics. Trying to fake it is a silly idea. What is the goal of your code? Let's focus in your problem, rather than on your unsupported solution. As it is, this is an [XY Problem](http://xyproblem.info/). – Jonathan Hall Dec 03 '18 at 07:06
  • 1
    Accepting different types is easy. That's what interfaces are for. – Jonathan Hall Dec 04 '18 at 06:57

3 Answers3

3

You can use the interface in this way, add a method GetFoo to get foo of each struct.

type A struct{
    Foo map[string]string
}

func(a *A) GetFoo() map[string]string {
    return a.Foo
}

type B struct{
    Foo map[string]string
}

func(b *B) GetFoo() map[string]string {
    return b.Foo
}

type C struct{
    Foo map[string]string
}

func(c *C) GetFoo() map[string]string {
    return c.Foo
}

type ABC interface {
    GetFoo() map[string][string]
}

func handleFoo (v ABC){
    foo := v.GetFoo()
    x:=foo["barbie"]
}
James Shi
  • 1,894
  • 2
  • 13
  • 16
  • Yeah this works because all of them are basically the same. exact. type. lol..nice answer tho. – Alexander Mills Dec 03 '18 at 04:34
  • if one is `map[string]string` and another is `map[string]int` and another is `map[string]bool`, will it work? I don't think so – Alexander Mills Dec 03 '18 at 04:36
  • the value are different types. the interface should change to `GetFoo() map[string]interface` – James Shi Dec 03 '18 at 06:42
  • 2
    @AlexanderMills they're all the exact same type in your own example code in the question. If you need something different, edit the question to clarify that. – Adrian Dec 03 '18 at 15:17
2

Because A, B, and C are are all assignable to the same underlying type, you can use a function with an argument of that underlying type: func handleFoo(v struct{ Foo map[string]string })

Run it on the playground.

A limitation of this approach is that methods on A, B and C (even with the same name and signature), are not available in handleFoo.

Charlie Tumahai
  • 113,709
  • 12
  • 249
  • 242
  • yeah this only works if all 3 structs have the exact same type, but unless something better comes along, will accept this answer soon – Alexander Mills Dec 03 '18 at 03:58
  • like I said, tecnically I don't think this is *generics* perse, but what language feature would you call this? – Alexander Mills Dec 03 '18 at 03:59
  • The types must be assignable to the same underlying type. The types can be different as in the question and shown in the linked playground example. The section of the spec on assignability is [here](https://golang.org/ref/spec#Assignability). – Charlie Tumahai Dec 03 '18 at 03:59
  • I don't follow, the 3 structs in the OP are the exact same type? – Alexander Mills Dec 03 '18 at 04:00
  • 1
    A, B and C are different types, but they have the same underlying type. Go does not allow assignment of an `A` to a `B`, but `A` and `B` can both be assigned to `struct{ Foo map[string]string }` – Charlie Tumahai Dec 03 '18 at 04:02
  • 1
    The language feature is assignability. – Charlie Tumahai Dec 03 '18 at 04:09
1

you can try reflect and pass an interface{} to handleFoo

https://play.golang.org/p/sLyjDvVrUjQ

https://golang.org/pkg/reflect/

package main

import (
    "fmt"
    "reflect"
)

func main() {
    type A struct {
        Foo map[string]string
    }
    type B struct {
        Foo map[string]int
    }
    type C struct {
        Foo map[string]uint
    }
    a := A{
        Foo: map[string]string{"a":"1"},
    }

    b := B{
        Foo: map[string]int{"a":2},
    }

    c := C {
        Foo: map[string]uint{"a":3},
    }


    fmt.Println(a, b, c)

    handleFoo(a)
    handleFoo(b)
    handleFoo(c)

    fmt.Println(a, b, c)
}



func handleFoo(s interface{}) {
    v := reflect.ValueOf(s)
    foo := v.FieldByName("Foo")
    if !foo.IsValid(){
        fmt.Println("not valid")
        return
    }

    switch foo.Type() {
    case reflect.TypeOf(map[string]string{}):
        fmt.Println("is a map[string]string")
        foo.Interface().(map[string]string)["a"] = "100"
    case reflect.TypeOf(map[string]int{}):
        fmt.Println("is a map[string]int")
        foo.Interface().(map[string]int)["a"] =  200
    case reflect.TypeOf(map[string]uint{}):
        fmt.Println("is a map[string]uint")
        foo.Interface().(map[string]uint)["a"] =  300
    }
}
goofool
  • 46
  • 2