如何测试函数和闭包以获得相等性?

时间:2021-03-04 23:00:38

The book says that "functions and closures are reference types". So, how do you find out if the references are equal? == and === don't work.

书中说“函数和闭包是引用类型”。那么,如何确定引用是否相等呢?=== === ===不工作。

func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments

Here is how the Catterwauls are dealing with this:

下面是卡特瓦乌斯如何处理这个问题:

MultiClosures & Equatable Closures

MultiClosures & Equatable闭包

tests

测试

7 个解决方案

#1


52  

Chris Lattner wrote on the developer forums:

Chris Lattner在开发者论坛上写道:

This is a feature we intentionally do not want to support. There are a variety of things that will cause pointer equality of functions (in the swift type system sense, which includes several kinds of closures) to fail or change depending on optimization. If "===" were defined on functions, the compiler would not be allowed to merge identical method bodies, share thunks, and perform certain capture optimizations in closures. Further, equality of this sort would be extremely surprising in some generics contexts, where you can get reabstraction thunks that adjust the actual signature of a function to the one the function type expects.

这是我们故意不希望支持的特性。有各种各样的东西会导致指针的功能的平等性(在swift类型的系统感觉中,包括几种闭包)会失败或根据优化而改变。如果在函数上定义了“=== =”,则不允许编译器合并相同的方法体、共享thunks并在闭包中执行某些捕获优化。此外,在某些泛型上下文中,这种类型的相等将是非常令人惊讶的,您可以得到重新抽象thunks,该thunks将函数的实际签名调整为函数类型所期望的签名。

https://devforums.apple.com/message/1035180#1035180

https://devforums.apple.com/message/1035180 # 1035180

This means that you should not even try to compare closures for equality because optimizations may affect the outcome.

这意味着您甚至不应该为了相等而尝试比较闭包,因为优化可能会影响结果。

#2


7  

I've been looking for the answer, too. And I've found it at last.

我也一直在寻找答案。我终于找到了。

What you need is the actual function pointer and its context hidden in the function object.

您需要的是实际的函数指针及其隐藏在函数对象中的上下文。

func peekFunc<A,R>(f:A->R)->(fp:Int, ctx:Int) {
    typealias IntInt = (Int, Int)
    let (hi, lo) = unsafeBitCast(f, IntInt.self)
    let offset = sizeof(Int) == 8 ? 16 : 12
    let ptr  = UnsafePointer<Int>(lo+offset)
    return (ptr.memory, ptr.successor().memory)
}
@infix func === <A,R>(lhs:A->R,rhs:A->R)->Bool {
    let (tl, tr) = (peekFunc(lhs), peekFunc(rhs))
    return tl.0 == tr.0 && tl.1 == tr.1
}

And here is the demo:

这是demo:

// simple functions
func genericId<T>(t:T)->T { return t }
func incr(i:Int)->Int { return i + 1 }
var f:Int->Int = genericId
var g = f;      println("(f === g) == \(f === g)")
f = genericId;  println("(f === g) == \(f === g)")
f = g;          println("(f === g) == \(f === g)")
// closures
func mkcounter()->()->Int {
    var count = 0;
    return { count++ }
}
var c0 = mkcounter()
var c1 = mkcounter()
var c2 = c0
println("peekFunc(c0) == \(peekFunc(c0))")
println("peekFunc(c1) == \(peekFunc(c1))")
println("peekFunc(c2) == \(peekFunc(c2))")
println("(c0() == c1()) == \(c0() == c1())") // true : both are called once
println("(c0() == c2()) == \(c0() == c2())") // false: because c0() means c2()
println("(c0 === c1) == \(c0 === c1)")
println("(c0 === c2) == \(c0 === c2)")

See the URLs below to find why and how it works:

请参阅下面的url来查找原因及其工作方式:

As you see it is capable of checking identity only (the 2nd test yields false). But that should be good enough.

正如您所看到的,它只能检查标识(第二个测试产生false)。但这应该足够好了。

#3


7  

Simplest way is designate the block type as @objc_block, and now you can cast it to an AnyObject which is comparable with ===. Example:

最简单的方法是将块类型指定为@objc_block,现在可以将其转换为与===类似的任何对象。例子:

    typealias Ftype = @objc_block (s:String) -> ()

    let f : Ftype = {
        ss in
        println(ss)
    }
    let ff : Ftype = {
        sss in
        println(sss)
    }
    let obj1 = unsafeBitCast(f, AnyObject.self)
    let obj2 = unsafeBitCast(ff, AnyObject.self)
    let obj3 = unsafeBitCast(f, AnyObject.self)

    println(obj1 === obj2) // false
    println(obj1 === obj3) // true

#4


5  

I searched a lot. There seems to be no way of function pointer comparison. The best solution I got is to encapsulate the function or closure in an hashable object. Like:

我找了很多,好像没有办法比较函数指针。我得到的最佳解决方案是将函数或闭包封装到一个hashable对象中。如:

var handler:Handler = Handler(callback: { (message:String) in
            //handler body
}))

#5


4  

This is a great question and while Chris Lattner intentionally doesn't want to support this feature I, like many developers, also can't let go of my feelings coming from other languages where this is a trivial task. There are plenty of unsafeBitCast examples, most of them don't show the full picture, here's a more detailed one:

这是一个很好的问题,尽管Chris Lattner故意不支持这个特性,我和很多开发人员一样,也不能从其他语言中释放我的感觉,因为这是一个微不足道的任务。有很多不安全的例子,大多数都没有显示完整的图片,这里有一个更详细的例子:

typealias SwfBlock = () -> ()
typealias ObjBlock = @convention(block) () -> ()

func testSwfBlock(a: SwfBlock, _ b: SwfBlock) -> String {
    let objA = unsafeBitCast(a as ObjBlock, AnyObject.self)
    let objB = unsafeBitCast(b as ObjBlock, AnyObject.self)
    return "a is ObjBlock: \(a is ObjBlock), b is ObjBlock: \(b is ObjBlock), objA === objB: \(objA === objB)"
}

func testObjBlock(a: ObjBlock, _ b: ObjBlock) -> String {
    let objA = unsafeBitCast(a, AnyObject.self)
    let objB = unsafeBitCast(b, AnyObject.self)
    return "a is ObjBlock: \(a is ObjBlock), b is ObjBlock: \(b is ObjBlock), objA === objB: \(objA === objB)"
}

func testAnyBlock(a: Any?, _ b: Any?) -> String {
    if !(a is ObjBlock) || !(b is ObjBlock) {
        return "a nor b are ObjBlock, they are not equal"
    }
    let objA = unsafeBitCast(a as! ObjBlock, AnyObject.self)
    let objB = unsafeBitCast(b as! ObjBlock, AnyObject.self)
    return "a is ObjBlock: \(a is ObjBlock), b is ObjBlock: \(b is ObjBlock), objA === objB: \(objA === objB)"
}

class Foo
{
    lazy var swfBlock: ObjBlock = self.swf
    func swf() { print("swf") }
    @objc func obj() { print("obj") }
}

let swfBlock: SwfBlock = { print("swf") }
let objBlock: ObjBlock = { print("obj") }
let foo: Foo = Foo()

print(testSwfBlock(swfBlock, swfBlock)) // a is ObjBlock: false, b is ObjBlock: false, objA === objB: false
print(testSwfBlock(objBlock, objBlock)) // a is ObjBlock: false, b is ObjBlock: false, objA === objB: false

print(testObjBlock(swfBlock, swfBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: false
print(testObjBlock(objBlock, objBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: true

print(testAnyBlock(swfBlock, swfBlock)) // a nor b are ObjBlock, they are not equal
print(testAnyBlock(objBlock, objBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: true

print(testObjBlock(foo.swf, foo.swf)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: false
print(testSwfBlock(foo.obj, foo.obj)) // a is ObjBlock: false, b is ObjBlock: false, objA === objB: false
print(testAnyBlock(foo.swf, foo.swf)) // a nor b are ObjBlock, they are not equal
print(testAnyBlock(foo.swfBlock, foo.swfBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: true

The interesting part is how swift freely casts SwfBlock to ObjBlock, yet in reality two casted SwfBlock blocks will always be different values, while ObjBlocks won't. When we cast ObjBlock to SwfBlock, the same thing happens to them, they become two different values. So, in order to preserve the reference, this sort of casting should be avoided.

有趣的部分是如何快速地向objfblock释放SwfBlock,然而实际上,两个casted SwfBlock总是不同的值,而ObjBlock不会。当我们向SwfBlock抛出ObjBlock时,同样的事情发生在他们身上,他们变成了两个不同的值。因此,为了保持参考,这种类型的铸造应该避免。

I'm still comprehending this whole subject, but one thing I left wishing for is ability to use @convention(block) on class / struct methods, so I filed a feature request that needs up-voting or explaining why it's a bad idea. I also get a sense this approach might be bad all together, if so, can anyone explain why?

我仍在理解整个主题,但我仍然希望能够在类/ struct方法上使用@convention(block),因此我提交了一个特性请求,需要进行向上投票或解释为什么这是一个坏主意。我也有一种感觉,这种方法可能是不好的,如果是这样,有人能解释为什么吗?

#6


2  

Well it's been 2 days and nobody has chimed in with a solution, so I'll change my comment to an answer:

已经有2天了,没有人提出解决方案,所以我要把我的评论改为:

As far as I can tell, you can't check equality or identity of functions (like your example) and metaclasses (e.g., MyClass.self):

就我所知,您不能检查函数的相等性或同一性(如您的例子)和元类(如MyClass.self):

But – and this is just an idea – I can't help but notice that the where clause in generics appears to be able to check equality of types. So maybe you can leverage that, at least for checking identity?

但是——这只是一个想法——我不得不注意到泛型中的where子句似乎能够检查类型的相等性。也许你可以利用它,至少是为了检查身份?

#7


2  

Here is one possible solution (conceptually the same as 'tuncay' answer). The point is to define a class that wraps some functionality (e.g. Command):

这里有一个可能的解决方案(概念上与“tuncay”答案相同)。重点是定义一个封装某些功能的类(例如,命令):

Swift:

迅速:

typealias Callback = (Any...)->Void
class Command {
    init(_ fn: @escaping Callback) {
        self.fn_ = fn
    }

    var exec : (_ args: Any...)->Void {
        get {
            return fn_
        }
    }
    var fn_ :Callback
}

let cmd1 = Command { _ in print("hello")}
let cmd2 = cmd1
let cmd3 = Command { (_ args: Any...) in
    print(args.count)
}

cmd1.exec()
cmd2.exec()
cmd3.exec(1, 2, "str")

cmd1 === cmd2 // true
cmd1 === cmd3 // false

Java:

Java:

interface Command {
    void exec(Object... args);
}
Command cmd1 = new Command() {
    public void exec(Object... args) [
       // do something
    }
}
Command cmd2 = cmd1;
Command cmd3 = new Command() {
   public void exec(Object... args) {
      // do something else
   }
}

cmd1 == cmd2 // true
cmd1 == cmd3 // false

#1


52  

Chris Lattner wrote on the developer forums:

Chris Lattner在开发者论坛上写道:

This is a feature we intentionally do not want to support. There are a variety of things that will cause pointer equality of functions (in the swift type system sense, which includes several kinds of closures) to fail or change depending on optimization. If "===" were defined on functions, the compiler would not be allowed to merge identical method bodies, share thunks, and perform certain capture optimizations in closures. Further, equality of this sort would be extremely surprising in some generics contexts, where you can get reabstraction thunks that adjust the actual signature of a function to the one the function type expects.

这是我们故意不希望支持的特性。有各种各样的东西会导致指针的功能的平等性(在swift类型的系统感觉中,包括几种闭包)会失败或根据优化而改变。如果在函数上定义了“=== =”,则不允许编译器合并相同的方法体、共享thunks并在闭包中执行某些捕获优化。此外,在某些泛型上下文中,这种类型的相等将是非常令人惊讶的,您可以得到重新抽象thunks,该thunks将函数的实际签名调整为函数类型所期望的签名。

https://devforums.apple.com/message/1035180#1035180

https://devforums.apple.com/message/1035180 # 1035180

This means that you should not even try to compare closures for equality because optimizations may affect the outcome.

这意味着您甚至不应该为了相等而尝试比较闭包,因为优化可能会影响结果。

#2


7  

I've been looking for the answer, too. And I've found it at last.

我也一直在寻找答案。我终于找到了。

What you need is the actual function pointer and its context hidden in the function object.

您需要的是实际的函数指针及其隐藏在函数对象中的上下文。

func peekFunc<A,R>(f:A->R)->(fp:Int, ctx:Int) {
    typealias IntInt = (Int, Int)
    let (hi, lo) = unsafeBitCast(f, IntInt.self)
    let offset = sizeof(Int) == 8 ? 16 : 12
    let ptr  = UnsafePointer<Int>(lo+offset)
    return (ptr.memory, ptr.successor().memory)
}
@infix func === <A,R>(lhs:A->R,rhs:A->R)->Bool {
    let (tl, tr) = (peekFunc(lhs), peekFunc(rhs))
    return tl.0 == tr.0 && tl.1 == tr.1
}

And here is the demo:

这是demo:

// simple functions
func genericId<T>(t:T)->T { return t }
func incr(i:Int)->Int { return i + 1 }
var f:Int->Int = genericId
var g = f;      println("(f === g) == \(f === g)")
f = genericId;  println("(f === g) == \(f === g)")
f = g;          println("(f === g) == \(f === g)")
// closures
func mkcounter()->()->Int {
    var count = 0;
    return { count++ }
}
var c0 = mkcounter()
var c1 = mkcounter()
var c2 = c0
println("peekFunc(c0) == \(peekFunc(c0))")
println("peekFunc(c1) == \(peekFunc(c1))")
println("peekFunc(c2) == \(peekFunc(c2))")
println("(c0() == c1()) == \(c0() == c1())") // true : both are called once
println("(c0() == c2()) == \(c0() == c2())") // false: because c0() means c2()
println("(c0 === c1) == \(c0 === c1)")
println("(c0 === c2) == \(c0 === c2)")

See the URLs below to find why and how it works:

请参阅下面的url来查找原因及其工作方式:

As you see it is capable of checking identity only (the 2nd test yields false). But that should be good enough.

正如您所看到的,它只能检查标识(第二个测试产生false)。但这应该足够好了。

#3


7  

Simplest way is designate the block type as @objc_block, and now you can cast it to an AnyObject which is comparable with ===. Example:

最简单的方法是将块类型指定为@objc_block,现在可以将其转换为与===类似的任何对象。例子:

    typealias Ftype = @objc_block (s:String) -> ()

    let f : Ftype = {
        ss in
        println(ss)
    }
    let ff : Ftype = {
        sss in
        println(sss)
    }
    let obj1 = unsafeBitCast(f, AnyObject.self)
    let obj2 = unsafeBitCast(ff, AnyObject.self)
    let obj3 = unsafeBitCast(f, AnyObject.self)

    println(obj1 === obj2) // false
    println(obj1 === obj3) // true

#4


5  

I searched a lot. There seems to be no way of function pointer comparison. The best solution I got is to encapsulate the function or closure in an hashable object. Like:

我找了很多,好像没有办法比较函数指针。我得到的最佳解决方案是将函数或闭包封装到一个hashable对象中。如:

var handler:Handler = Handler(callback: { (message:String) in
            //handler body
}))

#5


4  

This is a great question and while Chris Lattner intentionally doesn't want to support this feature I, like many developers, also can't let go of my feelings coming from other languages where this is a trivial task. There are plenty of unsafeBitCast examples, most of them don't show the full picture, here's a more detailed one:

这是一个很好的问题,尽管Chris Lattner故意不支持这个特性,我和很多开发人员一样,也不能从其他语言中释放我的感觉,因为这是一个微不足道的任务。有很多不安全的例子,大多数都没有显示完整的图片,这里有一个更详细的例子:

typealias SwfBlock = () -> ()
typealias ObjBlock = @convention(block) () -> ()

func testSwfBlock(a: SwfBlock, _ b: SwfBlock) -> String {
    let objA = unsafeBitCast(a as ObjBlock, AnyObject.self)
    let objB = unsafeBitCast(b as ObjBlock, AnyObject.self)
    return "a is ObjBlock: \(a is ObjBlock), b is ObjBlock: \(b is ObjBlock), objA === objB: \(objA === objB)"
}

func testObjBlock(a: ObjBlock, _ b: ObjBlock) -> String {
    let objA = unsafeBitCast(a, AnyObject.self)
    let objB = unsafeBitCast(b, AnyObject.self)
    return "a is ObjBlock: \(a is ObjBlock), b is ObjBlock: \(b is ObjBlock), objA === objB: \(objA === objB)"
}

func testAnyBlock(a: Any?, _ b: Any?) -> String {
    if !(a is ObjBlock) || !(b is ObjBlock) {
        return "a nor b are ObjBlock, they are not equal"
    }
    let objA = unsafeBitCast(a as! ObjBlock, AnyObject.self)
    let objB = unsafeBitCast(b as! ObjBlock, AnyObject.self)
    return "a is ObjBlock: \(a is ObjBlock), b is ObjBlock: \(b is ObjBlock), objA === objB: \(objA === objB)"
}

class Foo
{
    lazy var swfBlock: ObjBlock = self.swf
    func swf() { print("swf") }
    @objc func obj() { print("obj") }
}

let swfBlock: SwfBlock = { print("swf") }
let objBlock: ObjBlock = { print("obj") }
let foo: Foo = Foo()

print(testSwfBlock(swfBlock, swfBlock)) // a is ObjBlock: false, b is ObjBlock: false, objA === objB: false
print(testSwfBlock(objBlock, objBlock)) // a is ObjBlock: false, b is ObjBlock: false, objA === objB: false

print(testObjBlock(swfBlock, swfBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: false
print(testObjBlock(objBlock, objBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: true

print(testAnyBlock(swfBlock, swfBlock)) // a nor b are ObjBlock, they are not equal
print(testAnyBlock(objBlock, objBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: true

print(testObjBlock(foo.swf, foo.swf)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: false
print(testSwfBlock(foo.obj, foo.obj)) // a is ObjBlock: false, b is ObjBlock: false, objA === objB: false
print(testAnyBlock(foo.swf, foo.swf)) // a nor b are ObjBlock, they are not equal
print(testAnyBlock(foo.swfBlock, foo.swfBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: true

The interesting part is how swift freely casts SwfBlock to ObjBlock, yet in reality two casted SwfBlock blocks will always be different values, while ObjBlocks won't. When we cast ObjBlock to SwfBlock, the same thing happens to them, they become two different values. So, in order to preserve the reference, this sort of casting should be avoided.

有趣的部分是如何快速地向objfblock释放SwfBlock,然而实际上,两个casted SwfBlock总是不同的值,而ObjBlock不会。当我们向SwfBlock抛出ObjBlock时,同样的事情发生在他们身上,他们变成了两个不同的值。因此,为了保持参考,这种类型的铸造应该避免。

I'm still comprehending this whole subject, but one thing I left wishing for is ability to use @convention(block) on class / struct methods, so I filed a feature request that needs up-voting or explaining why it's a bad idea. I also get a sense this approach might be bad all together, if so, can anyone explain why?

我仍在理解整个主题,但我仍然希望能够在类/ struct方法上使用@convention(block),因此我提交了一个特性请求,需要进行向上投票或解释为什么这是一个坏主意。我也有一种感觉,这种方法可能是不好的,如果是这样,有人能解释为什么吗?

#6


2  

Well it's been 2 days and nobody has chimed in with a solution, so I'll change my comment to an answer:

已经有2天了,没有人提出解决方案,所以我要把我的评论改为:

As far as I can tell, you can't check equality or identity of functions (like your example) and metaclasses (e.g., MyClass.self):

就我所知,您不能检查函数的相等性或同一性(如您的例子)和元类(如MyClass.self):

But – and this is just an idea – I can't help but notice that the where clause in generics appears to be able to check equality of types. So maybe you can leverage that, at least for checking identity?

但是——这只是一个想法——我不得不注意到泛型中的where子句似乎能够检查类型的相等性。也许你可以利用它,至少是为了检查身份?

#7


2  

Here is one possible solution (conceptually the same as 'tuncay' answer). The point is to define a class that wraps some functionality (e.g. Command):

这里有一个可能的解决方案(概念上与“tuncay”答案相同)。重点是定义一个封装某些功能的类(例如,命令):

Swift:

迅速:

typealias Callback = (Any...)->Void
class Command {
    init(_ fn: @escaping Callback) {
        self.fn_ = fn
    }

    var exec : (_ args: Any...)->Void {
        get {
            return fn_
        }
    }
    var fn_ :Callback
}

let cmd1 = Command { _ in print("hello")}
let cmd2 = cmd1
let cmd3 = Command { (_ args: Any...) in
    print(args.count)
}

cmd1.exec()
cmd2.exec()
cmd3.exec(1, 2, "str")

cmd1 === cmd2 // true
cmd1 === cmd3 // false

Java:

Java:

interface Command {
    void exec(Object... args);
}
Command cmd1 = new Command() {
    public void exec(Object... args) [
       // do something
    }
}
Command cmd2 = cmd1;
Command cmd3 = new Command() {
   public void exec(Object... args) {
      // do something else
   }
}

cmd1 == cmd2 // true
cmd1 == cmd3 // false