Micrified
Micrified

Reputation: 3660

Defining an interface type which returns receiver methods on itself

Addressing closure of question:

My question was marked as a duplicate and closed without comment. I reviewed the linked duplicate question and found it is itself a duplicate of the following (listed in order of hierarchy):

  1. Can I construct a slice of a generic type with different type parameters?:

I'm not asking whether I can substitute generics for interfaces and type reflection (I know the difference), so I consider this a different question.

  1. Golang (1.18+) - Map of functions

This is itself linked as a duplicate of (1). I argue this is also not an answer. I'm not asking how to initialize a map of differing function signatures. I'm asking whether I can easily return receiver methods from interfaces while keeping the receiver method implementation.

I searched extensively for my particular problem before posting this question with supporting examples and toy workarounds. I think the close vote comes from a premature conclusion following the first paragraph (which illustrates function-pointer maps as a rationalized use-case for the interface).


Context

I am trying to find a clean way to define a type (interface) which (put succinctly) can return any method on itself. To help understand why one might want this, imagine defining a controller for a web application. Each controller along a route might implement a varying number of HTTP methods. These can be indexed if available, and run as needed. You can collect your controllers in a slice (given it's an interface), which lends well for scaling. With this in mind, consider the following interface Mapped which defines a method returning methods on itself for a given string input:

type Mapped interface {
    Method(string) func(Mapped)string
}

In order to implement this interface, I can define the following concrete type (with a constructor):

type Index struct {
    methods map[string]func(Mapped)string
}
func (i *Index) Method (n string) func(Mapped)string {
    return i.methods[n]
}
func NewIndex() Index {
    return Index {
        methods: map[string]func(Mapped)string {
            "identity" : func(Mapped) string {
                return "Index"
            },
        },
    }
}   

which is callable with

func run(m Mapped, n string) {
    fmt.Println(m.Method(n)(m))
}

func main() {
    index := NewIndex()
    run(&index, "identity")
}

This works, but using anonymous functions isn't really the same as a method.


Problem

Unfortunately, I find it difficult to define a type using normal method definitions that the compiler accepts. For instance, the following will not compile:

type Index struct {
    methods map[string]func(Mapped)string
}
func (i *Index) Method (n string) func(Mapped)string {
    return i.methods[n]
}
func (i *Index) Identity () string {
    return "Index"
}
func NewIndex() Index {
    return Index {
        methods: map[string]func(Mapped)string {
            "identity" : (*Index).Identity,
        },
    }
}

Go apparently will not accept input that is covariant with respect to the map type. func(*Index) string) != func(Mapped) string and no type conversion will achieve that. The only way to achieve the desired outcome is to modify the interface as follows:


type Mapped interface {
    Method(string) func(Mapped)string
    Identity() string                    // [1] Add the method to the interface
}

// ---

type Index struct {
    methods map[string]func(Mapped)string
}
func (i *Index) Method (n string) func(Mapped)string {
    return i.methods[n]
}
func (i *Index) Identity () string {
    return "Index"
}
func NewIndex() Index {
    return Index {
        methods: map[string]func(Mapped)string {
            "identity" : Mapped.Identity, // [2] Refer to the interface method
        },
    }
}

Evidently, this doesn't scale well. You'd end up having a union of all possible method types in your interface definition. Alternatively, you can keep your Mapped interface minimal by bringing back the anonymous functions and wrapping your method calls with type assertions:

type Mapped interface {
    Method(string) func(Mapped) string
}


// ---

type Index struct {
    methods map[string]func(Mapped)string
}
func (i *Index) Method (n string) func(Mapped)string {
    return i.methods[n]
}
func (i *Index) Identity() string {
    return "Index"
}

func NewIndex() Index {
    return Index {
        methods: map[string]func(Mapped)string {
            "identity" : func(m Mapped)string {
                return (*Index).Identity(m.(*Index)).   // [3] Wrapped + type assertion
            },
        },
    }
}

But this again results in rather ugly and unwieldy code. It seems that I'm attempting to implement a design that is not congruent with Go's type-rules. This then brings me to my final distillation of the problem:

So how can I better solve this problem in Go? I would ideally like a solution which captures the behaviour I'm after in a single type (i.e. interface) without mangling the implementation too badly.

Upvotes: 0

Views: 56

Answers (0)

Related Questions