在Go语言中,函数类型可以进行特定类型向更一般类型的转换。

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

Function types in Go - particular type casting to more general type

问题

在Go中,你可以使用类型断言来将更具体的函数传递给期望通用函数(如func(interface{}) interface{})的函数。以下是你的示例代码的修改版本:

package main

import "fmt"

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(func(x interface{}) interface{} {
		return fooA(x.(int))
	}, 12)
	exclamerA()

	exclamerB := MakeExclamer(func(x interface{}) interface{} {
		return fooB(x.(int))
	}, 66)
	exclamerB()
}

在这个修改后的代码中,我们使用了匿名函数来将更具体的函数fooAfooB转换为通用函数func(interface{}) interface{}。这样,你就可以将fooAfooB作为参数传递给MakeExclamer函数了。

英文:

What cast / assertion need I do in Go in order to pass to a function expecting a generic function like func(interface{}) interface{}, a more specific function like func(int) int instead?

For example, in code like this, fooA can be passed to MakeExclamer, but not 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 link: https://play.golang.org/p/xGzfco0IAG)

I'm not interested much in alternative code structure patterns, since this is how I want it to work: a specific function should be passed to a general function transformer (accepting function of type Any -> Any) that will return another general function (Any -> Any). This may not be idiomatic in Go, but it is the pattern that I want my code to follow.

答案1

得分: 0

要使用类型断言,必须枚举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示例

可以使用反射编写一个更通用的函数。

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示例

英文:

To use type assertions, every possible type must be enumerated in 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")
	}
}

To accept a function of any type, the fn argument is declared as type interface{}. The code uses a type switch to handle the different function types.

playground example

Reflection can be used to write a more general function.

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

答案2

得分: 0

首先,当涉及到在Go中进行输入时,理论上一切都是可能的。这是因为尽管编译器在编译时进行了许多检查,但在运行时可以在运行时更改。所谓的运行时hack,就是在运行时动态操作不应该处理的运行时结构体。

现在,你有一个有趣的问题,它的答案不包括使用"unsafe"包的需要。然而,我找到的泛化函数的方法涉及到了大量的反射。

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

可以在这里找到reflect包的文档。

所以,像Go语言中的所有元素一样,函数也有一个类型。不去深入所有字段,函数接受一个参数数组并产生一个结果数组。可以通过In(int)和Out(int)方法来调查参数和结果的类型。

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

    for idx := 0; idx < fnType.NumIn(); idx++ {
        fmt.Printf("输入参数 %d 的类型是 %v\n", idx, fnType.In(idx))
    }

    for idx := 0; idx < fnType.NumOut(); idx++ {
        fmt.Printf("输出参数 %d 的类型是 %v\n", idx, fnType.Out(idx))
    }
}

我们不会使用这段代码。然而,此时有两个重要的事情需要注意:

  • 函数可以以interface{}的形式传递,而不需要关心其类型。类似于"func(interface{}) interface{}"并不是函数的泛化,它已经是一个具体的类型。因此,"func(interface{}) interface{}"不是"func(int) int"的泛化,它们是完全不同的函数类型。这就是为什么不能使用类型断言/转换从一个函数类型转换为另一个函数类型。
  • 函数可以表示为接受输入数组并产生输出数组的东西

现在,为了调用一个函数,你必须获取的不是函数的类型,而是它的。一旦获取到它的值,就可以使用一个参数数组来调用它,该数组必须都是值。

原型是:

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)

        // 假设函数只产生一个结果
        return res[0].Interface()
    }

    return ret
}

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

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

Playground

重要的部分是generalize函数,它将你的参数转换为一个值的数组,然后返回一个已经填充了所有参数的新函数。

如果你需要任何进一步的解释,请不要犹豫!

英文:

First things first : When it comes to typing in Go, everything is theoretically possible. That's because even though the compiler does a lot of checks at compile-time, it is possible to change the runtime... at runtime. So-called runtime hacks, where you dynamically manipulate runtime structs that you're NOT supposed to handle.

Now, you have an interesting question, whose answer doesn't include the need to use the 'unsafe' package. However, the way I found of generalizing a function involves heavy reflection.

How to call a function (via reflection) ?

The documentation for the reflect package can be found here.

So, like all elements in Golang, functions have a Type. Without going through all fields, functions do take an array of arguments and produce an array of results. It is possible to investigate the Type of arguments and results through the In(int) and Out(int) method.

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

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

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

We won't use this code. However, two important things are to be noted at this point :

  • The generic type under which a function can be passed around without caring about its type is interface{}. Something like "func(interface{}) interface{}" is not a generalization of a function, it is already a concrete type. Hence, "func(interface{}) interface{}" is not a generalization of "func(int) int", those are two different function types entirely. This is why you can't use type assertions/cast to convert from one function type to another.
  • A function can be represented as something that takes an input array and produces and output array.

Now, in order to call a function, you have to get not its Type, but its Value. Once you get its value, you can call it using an array of arguments, which must all be Values.

The prototype is:

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

Using this method, it is possible to call any function.

The code

So, the only thing you need is to convert whichever arguments array you have to an array of Values, then you will be able to call your function.

Here is your code:

package main

import (
	&quot;fmt&quot;
	&quot;reflect&quot;
)

func MakeExclamer(foo interface{}, n int) func() {
	exclamer := generalize(foo, n)
	return func() {
		fmt.Printf(&quot;%v!!!\n&quot;, 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

The important bit is the generalize function which makes the translation between your arguments and an array of Values, then returns a new function whith all parameters already filled.

Do not hesitate if you need any precision !

1: https://godoc.org/reflect "here"
2: https://play.golang.org/p/2cENYT3KjE "Playground"

huangapple
  • 本文由 发表于 2017年9月11日 03:16:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/46144626.html
匿名

发表评论

匿名网友

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

确定