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.
Information can be passed to functions as a parameter. Parameters act as variables inside the function.
Empty interfaces are used by code that handles values of unknown type. For example, fmt.
A variadic function is a function which accepts a variable number of arguments. It can be used when the number of input params are unknown. The code: getNames.go. As we can see the variadic func (getNames() accepts variable number of input values — zero or more.
While v is just an ordinary parameter, of some unspecified type, T is different. T is a new kind of parameter in Go: a type parameter. We say that PrintAnything[T] is a parameterized function, that is, a generic function on some type T.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With