How do you test functions and closures for equality?

ClosuresSwiftEquality

Closures Problem Overview


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

Closures Solutions


Solution 1 - Closures

Chris Lattner wrote on the developer forums:

> 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.

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.

Solution 2 - Closures

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:

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

Solution 3 - Closures

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

    typealias Ftype = @convention(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

>Update 2021; changed @objc_block to @convention(block) to support Swift 2.x and later (which don't recognize @objc_block).

Solution 4 - Closures

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:

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

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

Solution 5 - Closures

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

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:

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

Solution 6 - Closures

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:

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.

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?

Solution 7 - Closures

Not a general solution, but if one is trying to implement a listener pattern, I've ended up returning an "id" of the function during the registration so I can use it to unregister later (which is kind of workaround to the original question for "listeners" case as usually unregistering comes down to checking the functions for equality, which is at least not "trivial" as per other answers).

So something like this:

class OfflineManager {
    var networkChangedListeners = [String:((Bool) -> Void)]()

    func registerOnNetworkAvailabilityChangedListener(_ listener: @escaping ((Bool) -> Void)) -> String{
        let listenerId = UUID().uuidString;
        networkChangedListeners[listenerId] = listener;
        return listenerId;
    }
    func unregisterOnNetworkAvailabilityChangedListener(_ listenerId: String){
        networkChangedListeners.removeValue(forKey: listenerId);
    }
}

Now you just need to store the key returned by the "register" function and pass it when unregistering.

Solution 8 - Closures

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

As far as I can tell, you can't check equality or identity of functions (like your example) and metaclasses (e.g., 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?

Solution 9 - Closures

My solution was to wrap functions to class that extends NSObject

class Function<Type>: NSObject {
    let value: (Type) -> Void

    init(_ function: @escaping (Type) -> Void) {
        value = function
    }
}

Solution 10 - Closures

I know I'm answering this question six years late, but I think it's worth looking at the motivation behind the question. The questioner commented:

> Without being able to remove closures from an invocation list by reference, however, we need to create our own wrapper class. That is a drag, and shouldn't be necessary.

So I guess the questioner wants to maintain a callback list, like this:

class CallbackList {
    private var callbacks: [() -> ()] = []

    func call() {
        callbacks.forEach { $0() }
    }

    func addCallback(_ callback: @escaping () -> ()) {
        callbacks.append(callback)
    }

    func removeCallback(_ callback: @escaping () -> ()) {
        callbacks.removeAll(where: { $0 == callback })
    }
}

But we can't write removeCallback that way, because == doesn't work for functions. (Neither does ===.)

Here's a different way to manage your callback list. Return a registration object from addCallback, and use the registration object to remove the callback. Here in 2020, we can use the Combine's AnyCancellable as the registration.

The revised API looks like this:

class CallbackList {
    private var callbacks: [NSObject: () -> ()] = [:]

    func call() {
        callbacks.values.forEach { $0() }
    }

    func addCallback(_ callback: @escaping () -> ()) -> AnyCancellable {
        let key = NSObject()
        callbacks[key] = callback
        return .init { self.callbacks.removeValue(forKey: key) }
    }
}

Now, when you add a callback, you don't need to keep it around to pass to removeCallback later. There is no removeCallback method. Instead, you save the AnyCancellable, and call its cancel method to remove the callback. Even better, if you store the AnyCancellable in an instance property, then it will cancel itself automatically when the instance is destroyed.

Solution 11 - Closures

You can use a callAsFunction method so for example

struct MyType: Equatable {
    func callAsFunction() {
        print("Image a function")
    }

    static func == (lhs: MyType, rhs: MyType) -> Bool { true }
}

let a = MyType()
let b = MyType()
a()
b()
let e = a == b

In this case they are always going to be true, you can have initialiser t give them different internal states, or other methods to change there states, and the callAsFunction can be changed to take arguments

Not sure why === would not work on real functions because you are just testing address, but == invokes the == method of the Equatable protocol, and function don't implement this protocol

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJessyView Question on Stackoverflow
Solution 1 - ClosuresdrewagView Answer on Stackoverflow
Solution 2 - ClosurestuncayView Answer on Stackoverflow
Solution 3 - ClosuresmattView Answer on Stackoverflow
Solution 4 - ClosuresdankogaiView Answer on Stackoverflow
Solution 5 - ClosuresbasoView Answer on Stackoverflow
Solution 6 - ClosuresIan BytchekView Answer on Stackoverflow
Solution 7 - Closuresvir usView Answer on Stackoverflow
Solution 8 - ClosuresJiaaroView Answer on Stackoverflow
Solution 9 - ClosuresRenetikView Answer on Stackoverflow
Solution 10 - Closuresrob mayoffView Answer on Stackoverflow
Solution 11 - ClosuresNathan DayView Answer on Stackoverflow