在Go语言中,可以将任意函数作为参数传递。

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

Passing an arbitrary function as a parameter in Go

问题

我正在尝试扩展对Go函数指针的了解,并且对于在Go中将函数作为参数传递时可能的情况和不可能的情况有一个问题。

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

如果我编写一个接受func(interface{}) interface{}作为参数的装饰器,只要我传递的函数也接受/返回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)"))
}
英文:

I'm trying to expand my knowledge of Go's function pointers, and I have a question about what is and is not possible with passing functions as parameters in Go.

Let's say that I want to write a decorator() function that can wrap any existing function. For simplicity, let's limit this to functions that accept exactly one parameter and return exactly one value.

If I write a decorator that accepts func(interface{}) interface{} as it's argument, it will implicitly work as long as that function I pass in also accepts/returns an interface{} type (see funcA).

My question is--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)?

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

答案1

得分: 4

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

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

英文:

This is not possible. One reason for that is the mechanics of passing parameters differ from function to function, and using an interface{} arg does not mean "accept anything". For example, a function taking a struct as an arg will receive each member of that struct, but a function taking an interface{} containing that struct will receive two words, one containing the type of the struct, and the other containing a pointer to it.

So, without using generics, the only way to implement this is by using an adapter function.

答案2

得分: 2

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

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函数中,这个答案中的函数假设一个参数和一个结果。必须修改函数以处理其他函数类型。

提问者应该考虑在问题中提出的匿名包装函数和在这里使用reflect包之间的权衡。通过reflect API调用函数比通过匿名包装函数调用函数要慢。使用reflect API也会丧失类型安全性。匿名包装函数增加了冗余性。

在playground上运行代码:点击这里

英文:

Use the reflect package to handle functions with arbitrary argument and result types.

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.

Like the decorate function in the question, the function in this answer assumes one argument and one result. The function must be modified to handle other function types.

The OP should consider the tradeoffs between the anonymous wrapper function proposed in the question and the use of the reflect package here. Calling the function through the reflect API is slower than calling the function through the anonymous wrapper. There's also a loss of type safety with the reflect API. The anonymous wrapper function adds verbosity.

答案3

得分: 1

记录一下,在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

由于类型推断的存在,你可以直接传递具体的参数给函数,TF都会被自动推断出来。

以下是一些不使用命名约束的示例:

// 接受并返回T类型
decorate[T any](inner func(T) T, arg T) T

// 只返回T类型
decorate[T any](inner func() T) T

// 返回T类型和错误
decorate[T any](inner func(T) (T, error), arg T) (T, error)

// N元函数
decorate[T, U any](inner func(T, U) (T, error), argt T, argu U) (T, error)

显而易见的限制是,接口约束UnaryFunc只能指定接受和返回一个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() {
    // 这个可以工作
    decorate(funcA, 200)
    
    // 这个也可以工作
    decorate(funcB, "Func B")
}

Playground: https://go.dev/play/p/3q01NiiWsve

英文:

For the record, with Go 1.18 and the introduction of generics, the decorator function becomes almost trivial.

You may declare a type constraint as such:

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

The constraint itself is parametrized with T to allow for unary functions that take and return arbitrary types.

In the decorate function you then instantiate the constraint with a type parameter. The signature becomes:

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

Thanks to type inference, you can just pass concrete arguments to the function, and both T and F will be unambiguous.

Example alternatives without a named constraint:

// 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)

The obvious limitation is that the interface constraint UnaryFunc specifies only functions that take and return exactly one arg of type T. You can't do otherwise, because the type set of an interface constraint may include types which support the same operations — and calling with one arg is not compatible with calling with N args.

The full program:

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

Playground: https://go.dev/play/p/3q01NiiWsve

答案4

得分: 0

没有。很简单:没有。

英文:

> 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)?

No. It's that simple: No.

huangapple
  • 本文由 发表于 2021年10月29日 02:52:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/69759462.html
匿名

发表评论

匿名网友

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

确定