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

Passing an arbitrary function as a parameter in Go

我正在尝试扩展我对 Go 函数指针的了解,我有一个问题,关于在 Go 中将函数作为参数传递什么是可能的,什么是不可能的。

假设我想编写一个 decorator() 可以包装任何现有函数的函数。为简单起见,让我们将其限制为仅接受一个参数且 return 仅接受一个值的函数。

如果我编写一个接受 func(interface{}) interface{} 作为参数的装饰器,只要我传入 accepts/returns 一个 interface{} 类型的函数,它就会隐式工作(参见 funcA).

我的问题是——有没有办法将现有的 func(string) string 类型的函数转换为 func(interface{}) interface{} 的类型,这样它也可以传递给装饰器函数而不只是包装它在一个新的匿名函数中(参见 funcB)?

package main

import (
    "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)"))
}

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

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

is there a way to convert an existing function of type func(string) string to a type of func(interface{}) interface{} so that it can also be passed into a decorator function without just wrapping it in a new anonymous function (see funcB)?

没有。就这么简单:不。

使用 reflect package 来处理具有任意参数和结果类型的函数。

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()
}

Run the code on the playground.

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

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

郑重声明,随着 Go 1.18 和泛型的引入,decorator 函数变得几乎微不足道。

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

type UnaryFunc[T any] interface {
    func(T) T
}

约束本身使用 T 进行参数化,以允许采用 return 任意类型的一元函数。

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

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

多亏了类型推断,您可以将具体参数传递给函数,TF 都将是明确的。

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

// 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 仅指定采用 return 恰好一个 T 类型参数的函数。您不能这样做,因为接口约束的类型集可能包含支持相同操作的类型——并且使用一个参数调用与使用 N 个参数调用不兼容。

完整程序:

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