In Swift 3, what is a way to compare two closures?

2019-01-15 19:42发布

问题:

Suppose you have two closures of type (Int)->() in Swift 3 and test to see if they are the same as each other:

typealias Baz = (Int)->()
let closure1:Baz = { print("foo \($0)") }
let closure2:Baz = { print("bar \($0)") }

if(closure1 == closure2) {
    print("equal")
}

This fails to compile, giving the message:

Binary operator '==' cannot be applied to two '(Int)->()' operands

OK, well, how then can we compare two closures of the same type, to see if they are the same?

回答1:

I'm pretty sure there is no way to determine if two closures are equal.

Obviously, a logical equality check is out of the question. That would be equivalent to finding an answer to the halting problem. (Just test to see if your code is equivalent to a piece of code that loops forever. If it is, it doesn't halt. If it isn't, it does halt.)

In theory you might expect the === operator to test if two closures are the exact same piece of code, but that gives an error when I try it in Playground.

Playground execution failed: error: MyPlayground.playground:1:20: error: cannot check reference equality of functions; operands here have types '(Int) ->  ()' and '(Int) -> ()'
let bar = closure1 === closure2
          ~~~~~~~~ ^   ~~~~~~~~

Having thought about it, I'm sure the reason why that doesn't work is because you can't be sure that the closures really are equal. A closure is not just the code, but also the context in which it was created including any captures. The reason you can't check for equality is that there is no meaningful way in which two closures are equal.

To understand why thew captures are important, look at the following code.

func giveMeClosure(aString: String) -> () -> String
{
     return { "returning " + aString }
}

let closure1 = giveMeClosure(aString: "foo")
let closure2 = giveMeClosure(aString: "bar")

Are closure1 and closure2 equal? They both use the same block of code

print(closure1()) // prints "returning foo"
print(closure2()) // prints "returning bar"

So they are not equal. You could argue that you can check the code is the same and the captures are the same, but what about

func giveMeACount(aString: String) -> () -> Int
{
    return { aString.characters.count }
}

let closure3 = giveMeACount(aString: "foo")
let closure4 = giveMeACount(aString: "bar")

print(closure3()) // prints 3
print(closure4()) // prints 3

Apparently these closures are equal. It's not possible to implement any reasonable definition of equality that will work in every case, so Apple has instead not even tried. This is safer than providing an incomplete implementation that is wrong in some cases.



回答2:

In the case where you want to track your own closures, uses them as Dictionary keys, etc., you can use something like this:

struct TaggedClosure<P, R>: Equatable, Hashable {
    let id: Int
    let closure: (P) -> R

    static func == (lhs: TaggedClosure, rhs: TaggedClosure) -> Bool {
        return lhs.id == rhs.id
    }

    var hashValue: Int { return id }
}

let a = TaggedClosure(id: 1) { print("foo") }
let b = TaggedClosure(id: 1) { print("foo") }
let c = TaggedClosure(id: 2) { print("bar") }

print("a == b:", a == b) // => true
print("a == c:", a == c) // => false
print("b == c:", b == c) // => false