为了账号安全,请及时绑定邮箱和手机立即绑定

在 Go 中将任意函数作为参数传递

在 Go 中将任意函数作为参数传递

Go
胡子哥哥 2022-10-10 17:48:43
我正在尝试扩展我对 Go 函数指针的了解,并且我有一个关于在 Go 中将函数作为参数传递的可能性和不可能的问题。假设我想编写一个decorator()可以包装任何现有函数的函数。为简单起见,让我们将其限制为只接受一个参数并只返回一个值的函数。如果我编写一个接受func(interface{}) interface{}作为参数的装饰器,只要我传入的函数也接受/返回一个interface{}类型,它就会隐式工作(请参阅 参考资料funcA)。我的问题是——有没有办法将现有的类型函数转换为func(string) string类型,func(interface{}) interface{}以便它也可以传递给装饰器函数,而无需将其包装在新的匿名函数中(请参阅 参考资料funcB)?package mainimport (    "fmt")func decorate(inner func(interface{}) interface{}, args interface{}) interface {} {    fmt.Println("Before inner")    result := inner(args)    fmt.Println("After inner")    return result}func funcA(arg interface{}) interface{} {    fmt.Print("Inside A, with arg: ")    fmt.Println(arg)    return "This is A's return value"}func funcB(arg string) string {    fmt.Print("Inside B, with arg: ")    fmt.Println(arg)    return "This is B's return value"}func main() {        // This one works. Output is:    //    //   Before inner    //   Inside A, with arg: (This is A's argument)    //   After inner    //   This is A's return value    //    fmt.Println(decorate(funcA, "(This is A's argument)"))        // This doesn't work. But can it?    //fmt.Println(decorate(funcB, "(This is B's argument)"))}
查看完整描述

4 回答

?
幕布斯7119047

TA贡献1794条经验 获得超8个赞

这是不可能的。原因之一是传递参数的机制因函数而异,使用interface{}arg 并不意味着“接受任何东西”。例如,一个以结构为参数的函数将接收该结构的每个成员,但一个以包含该结构的 interface{} 的函数将接收两个字,一个包含结构的类型,另一个包含指向它。

因此,在不使用泛型的情况下,实现这一点的唯一方法是使用适配器函数。


查看完整回答
反对 回复 2022-10-10
?
森林海

TA贡献2011条经验 获得超2个赞

使用反射包来处理具有任意参数和结果类型的函数。


func decorate(inner interface{}, args interface{}) interface{} {

    fmt.Println("Before inner")

    result := reflect.ValueOf(inner).Call([]reflect.Value{reflect.ValueOf(args)})

    fmt.Println("After inner")

    return result[0].Interface()

}

在操场上运行代码。


与decorate问题中的函数一样,此答案中的函数假定一个参数和一个结果。必须修改该函数以处理其他函数类型。


OP 应考虑问题中提出的匿名包装函数与此处使用反射包之间的权衡。通过反射 API 调用函数比通过匿名包装器调用函数慢。反射 API 也失去了类型安全性。匿名包装函数增加了冗长性。


查看完整回答
反对 回复 2022-10-10
?
一只斗牛犬

TA贡献1784条经验 获得超2个赞

作为记录,随着 Go 1.18 和泛型的引入,该decorator功能变得几乎微不足道。


您可以这样声明类型约束:


type UnaryFunc[T any] interface {

    func(T) T

}

约束本身被参数化T以允许接受和返回任意类型的一元函数。


然后在decorate函数中使用类型参数实例化约束。签名变为:


decorate[T any, F UnaryFunc[T]](inner F, arg T) T

多亏了类型推断,您可以只将具体参数传递给函数,并且两者T都是F明确的。


没有命名约束的示例替代方案:


// accept and return T

decorate[T any](inner func(T) T, arg T) T


// only return T

decorate[T any](inner func() T) T


// return T and error

decorate[T any](inner func(T) (T, error), arg T) (T, error)


// N-ary function

decorate[T, U any](inner func(T, U) (T, error), argt T, argu U) (T, error)

明显的限制是接口约束UnaryFunc只指定了只接受和返回一个 arg 类型的函数T。你不能这样做,因为接口约束的类型集可能包括支持相同操作的类型——并且使用一个 arg 调用与使用 N 个 args 调用不兼容。


完整程序:


package main


import (

    "fmt"

)


type UnaryFunc[T any] interface {

    func(T) T

}


func decorate[T any, F UnaryFunc[T]](inner F, arg T) T {

    fmt.Println("before inner")

    result := inner(arg)

    fmt.Println("after inner")

    return result

}


func funcA(arg int) int {

    fmt.Println("inside A with:", arg)

    return arg

}


func funcB(arg string) string {

    fmt.Println("inside B with:", arg)

    return arg

}


func main() {

    // this works

    decorate(funcA, 200)

    

    // this also works

    decorate(funcB, "Func B")

}

游乐场:https ://go.dev/play/p/3q01NiiWsve


查看完整回答
反对 回复 2022-10-10
?
qq_笑_17

TA贡献1818条经验 获得超7个赞

有没有办法将 func(string) string 类型的现有函数转换为 func(interface{}) interface{} 类型,以便它也可以传递给装饰器函数,而无需将其包装在新的匿名函数中(见 funcB)?

不,就这么简单:不。


查看完整回答
反对 回复 2022-10-10
  • 4 回答
  • 0 关注
  • 146 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信