Andy Hume
Andy Hume

Reputation: 41674

Pass function reference with unknown parameter type in Go

I am working with a third-party library with two functions that each return different types. E.g. ArticleResponse and CommentResponse.

I would like to pass the results of calling either of those functions into my own function. As a second argument to that function I would like to pass a function reference that describes how to print that response to stdout.

response := GetArticles()
processResponse(response, printResponse)

func printResponse(response <what_type?>) {
    for i := range response.Articles {
        fmt.Println(response.Articles[i].Title)
    }
}

It's not clear to me how to coerce or create generic types so that the printResponse function knows what to expect to be passed in its parameters.

If I haven't provided a good enough description of what I'm trying to do here, please let me know and I will edit/update the question.

Upvotes: 5

Views: 4393

Answers (2)

Alexander Trakhimenok
Alexander Trakhimenok

Reputation: 6278

You can create a Content & ContentList interfaces

type Content interface {
    GetTitle() string    
}

type ContentList interface {
    Contents() []Content 
}


func printResponse(response ContentList) {
    for content := range response.Contents() {
        fmt.Println(content.GetTitle())
    }
}

Then ArticleResponse & CommentResponse should implement the ContentList interface and Aticle & Comment should implement the Content interface.

Upvotes: 0

Kaedys
Kaedys

Reputation: 10158

Your only real option in this case is for processResponse to accept an interface{} and a function that accepts the same, and then for printResponse to accept the same empty interface and type-assert it (or use a type switch). For example:

func main() {
    response := GetArticles()
    processResponse(response, printResponse)
}

func processResponse(response interface{}, printResponse func(interface{})) 
{
    // Process
    printResponse(response)
}

func printResponse(response interface{}) {
    switch r = reponse.(type) {
    case ArticleResponse:
        for i := range r.Articles {
            fmt.Println(r.Articles[i].Title)
        }
    case CommentResponse:
        for i := range r.Comments {
            fmt.Println(r.Comments[i].Topic, r.Comments[i].User)   
        }
    }
}

However, a more common style would be for your response itself to have a Print method (or similar), and for your process function to accept an interface representing that common method. For example:

type ArticleReponse struct {
    // ...
}

func (a ArticleReponse) Print() {
    for i := range a.Articles {
        fmt.Println(a.Articles[i].Title)
    }
}

type CommentResponse struct {
    // ...
}

func (c CommentResponse) Print() {
    for i := range c.Comments {
        fmt.Println(c.Comments[i].Topic, c.Comment[i].User)
    }
}

type Response interface {
    Print()
}

func main() {
    response := GetArticles()
    processResponse(response)
}

func processResponse(response Response) 
{
    // Process
    response.Print()
}

This style allows the response types themselves to define their printing behavior, while the processResponse function only knows that it got some type that's capable of printing itself. This also allows you to add other methods to the Response interface that processResponse (or anything else) might need in order to interact with those types, without actually having to know which type it's been given. This makes your code substantially less brittle, as it's no longer dependent on the actual implementation details of each response type. It also allows you to unit test processReponse in isolation by mocking out the Response interface.

Upvotes: 7

Related Questions