Dynamic function call in Go
Asked Answered
P

2

7

I'm trying to dynamically call functions returning different types of struct.

For example, let's take the following code.

struct A {
   Name string
   Value  int
}

struct B {
   Name1 string
   Name2 string
   Value   float
}

func doA() (A) {
   // some code returning A
}

func doB() (B) {
   // some code returning B
}

I would like to pass either the function doA or doB as an argument to a generic function that would execute the function and JSON-encode the result. Like the following:

func Generic(w io.Writer, fn func() (interface {}) {
    result := fn()
    json.NewEncoder(w).Encode(result)
}

But when I do:

Generic(w, doA)

I get the following error:

cannot use doA (type func() (A)) as type func() (interface {})

Is there a way to achieve this dynamic call?

Peridium answered 19/9, 2015 at 21:27 Comment(1)
In future please be more careful when cut-n-pasting code. Your code snippet isn't even valid Go (struct B { vs type B struct { and missing ) in the Generic definition). You should also gofmt all Go code in questions (e.g. that would remove the extraneous parentheses you have around the return types that just make it harder to read). Finally, providing a play.golang.org link can be very helpful to both those trying to answer and those readers that want to try/experiment with the issue; e.g. play.golang.org/p/ON15IwPwDy.Silkaline
L
15

First, let me remark that func() (interface{}) means the same thing as func() interface{}, so I'll use the shorter form.

Passing a function of type func() interface{}

You can write a generic function that takes a func() interface{} argument as long as the function that you pass to it has type func() interface{}, like this:

type A struct {
    Name  string
    Value int
}

type B struct {
    Name1 string
    Name2 string
    Value float64
}

func doA() interface{} {
    return &A{"Cats", 10}
}

func doB() interface{} {
    return &B{"Cats", "Dogs", 10.0}
}

func Generic(w io.Writer, fn func() interface{}) {
    result := fn()
    json.NewEncoder(w).Encode(result)
}

You can try out this code in a live playground:

http://play.golang.org/p/JJeww9zNhE

Passing a function as an argument of type interface{}

If you want to write functions doA and doB that return concretely typed values, you can pass the chosen function as an argument of type interface{}. Then you can use the reflect package to make a func() interface{} at run-time:

func Generic(w io.Writer, f interface{}) {
    fnValue := reflect.ValueOf(f)        // Make a concrete value.
    arguments := []reflect.Value{}       // Make an empty argument list.
    fnResults := fnValue.Call(arguments) // Assume we have a function. Call it.
    result := fnResults[0].Interface()   // Get the first result as interface{}.
    json.NewEncoder(w).Encode(result)    // JSON-encode the result.
}

More concisely:

func Generic(w io.Writer, fn interface{}) {
    result := reflect.ValueOf(fn).Call([]reflect.Value{})[0].Interface()
    json.NewEncoder(w).Encode(result)
}

Complete program:

package main

import (
    "encoding/json"
    "io"
    "os"
    "reflect"
)

type A struct {
    Name  string
    Value int
}

type B struct {
    Name1 string
    Name2 string
    Value float64
}

func doA() *A {
    return &A{"Cats", 10}
}

func doB() *B {
    return &B{"Cats", "Dogs", 10.0}
}

func Generic(w io.Writer, fn interface{}) {
    result := reflect.ValueOf(fn).Call([]reflect.Value{})[0].Interface()
    json.NewEncoder(w).Encode(result)
}

func main() {
    Generic(os.Stdout, doA)
    Generic(os.Stdout, doB)
}

Live playground:

http://play.golang.org/p/9M5Gr2HDRN

Lush answered 19/9, 2015 at 21:49 Comment(4)
I came to that conclusion as well, but I wanted to keep the strong typing for the return values of doA() and doB(). I guess there is no way to achieve that ?Peridium
You can achieve that if you make your Generic function accept an argument of type interface{}. Please see my extended answer.Lush
Although very interesting/informative, of course the "real" answer is "don't do this". Reflection should only be used when all else fails, it should be the last resort you turn to and not the first thing you try. Also note this follows the OP example of having absolutely zero error checking which is a giant no-no, never ignore errors, e.g. from json.Encode.Silkaline
How to do that ? fn := "doA" Generic(os.Stdout, fn)Champerty
R
0

Your return signature is different for these functions: fn func() (interface {}) vs. func doA() (A) and func doB() (B)

You are getting a compiler error because you are passing a function with a different signature into your Generic function. To address this issue you can change your functions to return interface{}.

This is an example of how to do that, I am using anonymous structs and printing the return value out rather than serializing them but this applies just the same to your example:

package main

import "fmt"

func doA() interface{} {
    return struct {
        Name  string
        Value int
    }{
        "something",
        5,
    }
}

func doB() interface{} {
    return struct {
        Name1 string
        Name2 string
        Value float64
    }{
        "something",
        "or other",
        5.3,
    }
}

func main() {
    fmt.Println("Hello, playground", doA(), doB())
}

Experiment with this in the Go Playground: http://play.golang.org/p/orrJw2XMW8

Richardson answered 19/9, 2015 at 21:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.