Go中的函数类型 - 特定类型转换为更一般的类型

时间:2017-09-10 19:16:25

标签: go types casting

我需要在Go中执行什么强制转换/断言才能传递给期望像func(interface{}) interface{}这样的通用函数的函数,而不是像func(int) int这样更具体的函数?

例如,在这样的代码中,fooA可以传递给MakeExclamer,但不能传递给fooB

func MakeExclamer(foo func (interface{}) interface{}, n int) func () {
    return func() {
        fmt.Printf("%v!!!", foo(n))
    }
}

func fooA(x interface{}) interface{} {
    return x.(int)*2
}

func fooB(x int) int {
    return x * 10
}

func main() {
    exclamerA := MakeExclamer(fooA, 12)
    exclamerA()
    exclamerB := MakeExclamer(fooB, 66)
// >> cannot use fooB (type func(int) int) as type func(interface {}) interface {} in argument to MakeExclamer 
    exclamerB()
}

(Go Playground链接:https://play.golang.org/p/xGzfco0IAG

我对替代代码结构模式不感兴趣,因为这是我希望它工作的方式:应该将特定函数传递给将返回的通用函数转换器(接受类型Any -> Any的函数)另一个通用函数(Any -> Any)。这可能不是Go中的惯用语,但它是我希望我的代码遵循的模式。

2 个答案:

答案 0 :(得分:1)

要使用类型断言,必须在MakeExclamer

中枚举每种可能的类型
func MakeExclamer(fn interface{}, arg interface{}) func() {
    switch fn := fn.(type) {
    case func(int) int:
        return func() {
            fmt.Printf("%v!!!\n", fn(arg.(int)))
        }
    case func(interface{}) interface{}:
        return func() {
            fmt.Printf("%v!!!\n", fn(arg))
        }
    default:
        panic("not supported")
    }
}

要接受任何类型的函数,fn参数将声明为类型interface{}。该代码使用type switch来处理不同的函数类型。

playground example

Reflection可用于编写更通用的函数。

func MakeExclamer(fn interface{}, arg interface{}) func() {
    fnr := reflect.ValueOf(fn)
    argr := reflect.ValueOf(arg)
    return func() {
        resultr := fnr.Call([]reflect.Value{argr})
        fmt.Printf("%v!!!\n", resultr[0].Interface())
    }
}

playground example

答案 1 :(得分:0)

首先要做的事情是:在输入Go时,一切都理论上可能。这是因为即使编译器在编译时进行了大量检查,也可以在运行时更改运行时。所谓的运行时hacks,你动态操作你不应该处理的运行时结构。

现在,您有一个有趣的问题,其答案不包括使用“不安全”包的必要性。然而,我发现推广函数的方式涉及重复反思。

如何调用函数(通过反射)?

可以找到反射包的文档here

因此,与Golang中的所有元素一样,函数具有Type。在不经过所有字段的情况下,函数会获取一组参数并生成一组结果。可以通过In(int)和Out(int)方法调查参数类型和结果。

func investigate(fn interface{}) {
    fnType := reflect.TypeOf(fn)

    for idx := 0; idx < fnType.NumIn(); idx ++ {
        fmt.Printf("Input arg %d has type %v\n", idx, fnType.In(idx))
    }

    for idx := 0; idx < fnType.NumOut(); idx ++ {
        fmt.Printf("Output arg %d has type %v\n", idx, fnType.Out(idx))
    }
}

我们不会使用此代码。但是,此时需要注意两个重要的事项:

  • 可以在不关心其类型的情况下传递函数的泛型类型是interface {}。像“func(interface {})interface {}”这样的东西不是函数的泛化,它已经是一个具体的类型。因此,“func(interface {})interface {}”不是“func(int)int”的概括,它们完全是两种不同的函数类型。这就是为什么你不能使用类型断言/强制转换从一种函数类型转换为另一种函数类型。
  • 一个函数可以表示为采用输入数组并生成和输出数组的东西

现在,为了调用函数,你必须得到的不是它的Type,而是 Value 。一旦获得其值,就可以使用参数数组来调用它,这些参数必须都是值。

原型是:

func (v Value) Call(in []Value) []Value

使用此方法,可以调用任何函数。

代码

因此,您唯一需要的是将您拥有的任何参数数组转换为值数组,然后您就可以调用您的函数。

这是你的代码:

package main

import (
    "fmt"
    "reflect"
)

func MakeExclamer(foo interface{}, n int) func() {
    exclamer := generalize(foo, n)
    return func() {
        fmt.Printf("%v!!!\n", exclamer())
    }
}

func fooA(x interface{}) interface{} {
    return x.(int) * 2
}

func fooB(x int) int {
    return x * 10
}

func generalize(implem interface{}, args ...interface{}) func() interface{} {
    valIn := make([]reflect.Value, len(args), len(args))

    fnVal := reflect.ValueOf(implem)

    for idx, elt := range args {
        valIn[idx] = reflect.ValueOf(elt)
    }

    ret := func() interface{} {
        res := fnVal.Call(valIn)

        // We assume the function produces exactly one result
        return res[0].Interface()
    }

    return ret
}

func main() {
    exclamerA := MakeExclamer(fooA, 12)
    exclamerA()

    exclamerB := MakeExclamer(fooB, 66)
    exclamerB()
}

Playground

重要的一点是generalize函数,它在你的参数和一个数组之间进行转换,然后返回一个新函数,所有参数都已填充。

如果您需要任何精确度,请不要犹豫!