【问题标题】:Compare function values in Go比较 Go 中的函数值
【发布时间】:2013-08-05 20:08:11
【问题描述】:

在 Go 中函数变量的正常使用只允许将它们与 nil 进行比较,而不是相互比较。这样做的原因(正如我向我解释的那样)是因为 Go 有闭包,所以相等的定义是模糊的。如果我有两个不同的闭包,它们具有不同的值绑定到局部变量,但它们使用相同的底层函数,它们应该被视为相等还是不相等?

但是,我确实希望能够进行这样的比较。特别是,我有这样的代码(除了,在我的真实代码中,检查实际上是必要的 - 这只是一个虚拟示例),我将函数指针与函数文字进行比较:

func getFunc(which bool) (func ()) {
    if which {
        return func1
    } else {
        return func2
    }
}

func func1() { }
func func2() { }

f := getFunc(true)
if f == func1 {
    fmt.Println("func1")
} else {
    fmt.Println("func2")
}

是否有任何方法,例如使用 reflectunsafe 包,以使其工作?

【问题讨论】:

  • 函数无法进行比较是有原因的(实际上是你命名的)。虽然可能与包反映有关,但我在上面的示例代码中看不到任何用处:如果你这样做 f := getFunc(true)知道你得到了 func1。那么到底为什么要比较呢?
  • 这实际上不是我正在使用的代码;这只是一个示例,让您了解我正在尝试进行的比较(f == func1 部分)。

标签: go closures function-pointers equality


【解决方案1】:

您可以按名称比较函数:

f := getFunc(true)
f1 := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
f2 := runtime.FuncForPC(reflect.ValueOf(func1).Pointer()).Name()
if f1 == f2 {
    fmt.Println("func1")
} else {
    fmt.Println("func2")
}

但这依赖于reflectruntime 包。这样做可能不是一个好主意。

你真的需要比较函数吗?如果可能,我会考虑另一种选择。

【讨论】:

  • 是的,我肯定会。用例有点奇怪。这是一个概念验证递归 rpn 计算器:github.com/joshlf13/rpn
  • @joshlf13 很有趣。是否可以将type operator func(int) operator 更改为type operator func(int) (operator, int)。也许给自己一些额外的比较?
  • 是的,我实际上是在早期版本的代码中做到了这一点,但我试图让它尽可能干净。我同意这是完全可行的,但如果可能的话,我想使用 /only/ 递归和延续传递。
  • @joshlf13 传递一个额外的值可能更安全/更快。如果过度使用reflect 可能会很慢,我会谨慎使用runtime。如果你有办法避免反射,我会这样做。
  • 性能真的不是问题。毕竟,我使用的是连续传递和创建闭包,而不仅仅是使用显式堆栈:)。
【解决方案2】:

扩展@Luke 的回答,看来我可以直接测试指针是否相等。请注意,这真的很不确定。引用reflect.Value.Pointer() 文档:

如果 v 的 Kind 是 Func,则返回的指针是底层代码 指针,但不一定足以识别单个函数 独一无二。唯一的保证是结果为零当且仅当 v 是一个 nil func 值。

也就是说,您可以这样做:

f := getFunc(true)
f1 := reflect.ValueOf(f).Pointer()
f2 := reflect.ValueOf(func1).Pointer()
eq := f1 == f2

请注意,我确实针对这个新版本进行了一系列测试(我曾用这些测试对 @Luke 的答案产生的代码进行回归测试),它们都通过了,这让我相信发出的警告是reflect 文档可能可以忽略,但是忽略文档确实不是一个好主意...

【讨论】:

  • 这些指针可能会发生变化,例如由于堆栈增长。但是runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()是一致的,不会改变。
【解决方案3】:

如果您要比较的所有函数都具有相同的签名,您可以这样做:

type cmpFunc struct {
    f func()
    id uint64
}

func (c *cmpFunc) call() { c.f() }
func (c *cmpFunc) equals(other *cmpFunc) { return c.id == other.id }

makeComparable(f func()) *cmpFunc {
    return &cmpFunc{f, get_uniq_id()}
}

get_uniq_id 按照包装盒上的说明进行操作。这有点难看,因为 Go 没有 () 重载,如果您想对一般函数执行此操作,没有泛型或多或少是不可能的。但这应该很适合您的目的。

【讨论】:

  • 其实,这提出了另一种解决方案,即完全使用类型而不是函数,以便可以直接比较它们。您基本上可以这样做:play.golang.org/p/WT3h3pIv1_(该示例由于设置而令人费解,但在实践中可能相对简单)。这样做的问题是它并不是真正纯粹的延续传递(这是rpn,顺便说一句)。
猜你喜欢
  • 2016-04-26
  • 2016-03-20
  • 1970-01-01
  • 2019-12-31
  • 1970-01-01
  • 2020-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多