Dynamic function call in Go

huangapple go评论174阅读模式
英文:

Dynamic function call in Go

问题

我正在尝试动态调用返回不同类型的struct的函数。

例如,让我们看一下以下代码。

  1. struct A {
  2. Name string
  3. Value int
  4. }
  5. struct B {
  6. Name1 string
  7. Name2 string
  8. Value float
  9. }
  10. func doA() (A) {
  11. // 一些返回A的代码
  12. }
  13. func doB() (B) {
  14. // 一些返回B的代码
  15. }

我想将函数doAdoB作为参数传递给一个通用函数,该函数将执行该函数并对结果进行JSON编码。就像下面这样:

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

但是当我这样做时:

  1. Generic(w, doA)

我得到以下错误:

  1. 无法将doA(类型为func()(A))用作类型为func()(interface {})的参数

有没有办法实现这种动态调用?

英文:

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

For example, let's take the following code.

  1. struct A {
  2. Name string
  3. Value int
  4. }
  5. struct B {
  6. Name1 string
  7. Name2 string
  8. Value float
  9. }
  10. func doA() (A) {
  11. // some code returning A
  12. }
  13. func doB() (B) {
  14. // some code returning B
  15. }

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:

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

But when I do:

  1. Generic(w, doA)

I get the following error:

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

Is there a way to achieve this dynamic call?

答案1

得分: 15

首先,让我说明一下 func() (interface{})func() interface{} 是相同的意思,所以我将使用更短的形式。

传递类型为 func() interface{} 的函数

你可以编写一个通用函数,接受一个 func() interface{} 类型的参数,只要你传递给它的函数的类型是 func() interface{},就像这样:

  1. type A struct {
  2. Name string
  3. Value int
  4. }
  5. type B struct {
  6. Name1 string
  7. Name2 string
  8. Value float64
  9. }
  10. func doA() interface{} {
  11. return &A{"Cats", 10}
  12. }
  13. func doB() interface{} {
  14. return &B{"Cats", "Dogs", 10.0}
  15. }
  16. func Generic(w io.Writer, fn func() interface{}) {
  17. result := fn()
  18. json.NewEncoder(w).Encode(result)
  19. }

你可以在实时 playground 中尝试这段代码:

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

将函数作为类型为 interface{} 的参数传递

如果你想编写返回具体类型值的函数 doAdoB,你可以将所选函数作为类型为 interface{} 的参数传递。然后,你可以使用 reflect 在运行时创建一个 func() interface{}

  1. func Generic(w io.Writer, f interface{}) {
  2. fnValue := reflect.ValueOf(f) // 创建一个具体值。
  3. arguments := []reflect.Value{} // 创建一个空的参数列表。
  4. fnResults := fnValue.Call(arguments) // 假设我们有一个函数。调用它。
  5. result := fnResults[0].Interface() // 将第一个结果作为 interface{} 获取。
  6. json.NewEncoder(w).Encode(result) // 对结果进行 JSON 编码。
  7. }

更简洁的写法:

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

完整的程序:

  1. package main
  2. import (
  3. "encoding/json"
  4. "io"
  5. "os"
  6. "reflect"
  7. )
  8. type A struct {
  9. Name string
  10. Value int
  11. }
  12. type B struct {
  13. Name1 string
  14. Name2 string
  15. Value float64
  16. }
  17. func doA() *A {
  18. return &A{"Cats", 10}
  19. }
  20. func doB() *B {
  21. return &B{"Cats", "Dogs", 10.0}
  22. }
  23. func Generic(w io.Writer, fn interface{}) {
  24. result := reflect.ValueOf(fn).Call([]reflect.Value{})[0].Interface()
  25. json.NewEncoder(w).Encode(result)
  26. }
  27. func main() {
  28. Generic(os.Stdout, doA)
  29. Generic(os.Stdout, doB)
  30. }

实时 playground:

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

英文:

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:

  1. type A struct {
  2. Name string
  3. Value int
  4. }
  5. type B struct {
  6. Name1 string
  7. Name2 string
  8. Value float64
  9. }
  10. func doA() interface{} {
  11. return &A{"Cats", 10}
  12. }
  13. func doB() interface{} {
  14. return &B{"Cats", "Dogs", 10.0}
  15. }
  16. func Generic(w io.Writer, fn func() interface{}) {
  17. result := fn()
  18. json.NewEncoder(w).Encode(result)
  19. }

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:

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

More concisely:

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

Complete program:

package main

  1. import (
  2. "encoding/json"
  3. "io"
  4. "os"
  5. "reflect"
  6. )
  7. type A struct {
  8. Name string
  9. Value int
  10. }
  11. type B struct {
  12. Name1 string
  13. Name2 string
  14. Value float64
  15. }
  16. func doA() *A {
  17. return &A{"Cats", 10}
  18. }
  19. func doB() *B {
  20. return &B{"Cats", "Dogs", 10.0}
  21. }
  22. func Generic(w io.Writer, fn interface{}) {
  23. result := reflect.ValueOf(fn).Call([]reflect.Value{})[0].Interface()
  24. json.NewEncoder(w).Encode(result)
  25. }
  26. func main() {
  27. Generic(os.Stdout, doA)
  28. Generic(os.Stdout, doB)
  29. }

Live playground:

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

答案2

得分: 0

你的返回签名与这些函数不同:
fn func() (interface {})func doA() (A)func doB() (B)

你得到了一个编译器错误,因为你将一个具有不同签名的函数传递给了你的 Generic 函数。为了解决这个问题,你可以将你的函数改为返回 interface{}

这是一个示例,我使用匿名结构体并将返回值打印出来,而不是将它们序列化,但对于你的示例同样适用:

  1. package main
  2. import "fmt"
  3. func doA() interface{} {
  4. return struct {
  5. Name string
  6. Value int
  7. }{
  8. "something",
  9. 5,
  10. }
  11. }
  12. func doB() interface{} {
  13. return struct {
  14. Name1 string
  15. Name2 string
  16. Value float64
  17. }{
  18. "something",
  19. "or other",
  20. 5.3,
  21. }
  22. }
  23. func main() {
  24. fmt.Println("Hello, playground", doA(), doB())
  25. }

在 Go Playground 中尝试一下:http://play.golang.org/p/orrJw2XMW8

英文:

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:

  1. package main
  2. import "fmt"
  3. func doA() interface{} {
  4. return struct {
  5. Name string
  6. Value int
  7. }{
  8. "something",
  9. 5,
  10. }
  11. }
  12. func doB() interface{} {
  13. return struct {
  14. Name1 string
  15. Name2 string
  16. Value float64
  17. }{
  18. "something",
  19. "or other",
  20. 5.3,
  21. }
  22. }
  23. func main() {
  24. fmt.Println("Hello, playground", doA(), doB())
  25. }

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

huangapple
  • 本文由 发表于 2015年9月20日 05:27:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/32673407.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定