Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic function call in Go

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?

like image 699
Eric Avatar asked Sep 19 '15 21:09

Eric


1 Answers

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

like image 181
Michael Laszlo Avatar answered Oct 01 '22 14:10

Michael Laszlo