¿Cómo se evalúan las funciones y los cierres para la igualdad?

El libro dice que “las funciones y los cierres son tipos de referencia”. Entonces, ¿cómo averiguas si las referencias son iguales? == y === no funcionan.

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

Así es como los Catterwauls están lidiando con esto:

MultiClosures & Equatable Closures

pruebas

Chris Lattner escribió en los foros de desarrolladores:

Esta es una función que intencionalmente no queremos apoyar. Hay una variedad de cosas que harán que la igualdad de funciones del puntero (en el sentido del sistema de tipo rápido, que incluye varios tipos de cierres) falle o cambie dependiendo de la optimización. Si se definiera “===” en las funciones, el comstackdor no podría fusionar cuerpos de métodos idénticos, compartir procesos y realizar ciertas optimizaciones de captura en cierres. Además, la igualdad de este tipo sería extremadamente sorprendente en algunos contextos de generics, donde puede obtener transferencias de reabstracción que ajustan la firma real de una función a la que espera el tipo de función.

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

Esto significa que ni siquiera debería tratar de comparar los cierres para la igualdad porque las optimizaciones pueden afectar el resultado.

He estado buscando la respuesta, también. Y lo he encontrado por fin.

Lo que necesita es el puntero de función real y su contexto oculto en el objeto de función.

 func peekFunc(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(lo+offset) return (ptr.memory, ptr.successor().memory) } @infix func === (lhs:A->R,rhs:A->R)->Bool { let (tl, tr) = (peekFunc(lhs), peekFunc(rhs)) return tl.0 == tr.0 && tl.1 == tr.1 } 

Y aquí está la demostración:

 // simple functions func genericId(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)") 

Consulte las URL a continuación para descubrir por qué y cómo funciona:

Como puede ver, solo es capaz de verificar identidad (la segunda prueba arroja resultados false ). Pero eso debería ser lo suficientemente bueno.

La forma más simple es designar el tipo de bloque como @objc_block , y ahora puedes convertirlo en AnyObject, que es comparable con === . Ejemplo:

  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 

Busqué mucho. Parece que no hay forma de comparar el puntero de función. La mejor solución que obtuve es encapsular la función o el cierre en un objeto manejable. Me gusta:

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

Esta es una gran pregunta y, aunque intencionalmente Chris Lattner no quiere respaldar esta característica, yo, como muchos desarrolladores, tampoco puedo dejar de lado mis sentimientos provenientes de otros idiomas, donde esta es una tarea trivial. Hay muchos ejemplos unsafeBitCast de unsafeBitCast , la mayoría de ellos no muestran la imagen completa, aquí hay uno más detallado :

 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 

La parte interesante es qué tan rápido lanza SwfBlock a ObjBlock, pero en realidad dos bloques SwfBlock lanzados siempre serán valores diferentes, mientras que ObjBlocks no lo hará. Cuando lanzamos ObjBlock a SwfBlock, les sucede lo mismo, se convierten en dos valores diferentes. Por lo tanto, para preservar la referencia, este tipo de conversión debe evitarse.

Todavía estoy comprendiendo todo este tema, pero una cosa que dejé de desear es la capacidad de usar @convention(block) en los métodos de clase / estructura, así que presenté una solicitud de función que necesita una votación ascendente o explicando por qué es una mala idea. También tengo la sensación de que este enfoque podría ser malo en conjunto, de ser así, ¿alguien puede explicar por qué?

Bueno, han pasado 2 días y nadie ha intervenido con una solución, así que cambiaré mi comentario por una respuesta:

Por lo que puedo decir, no puede verificar la igualdad o la identidad de las funciones (como su ejemplo) y las metaclases (por ejemplo, MyClass.self ):

Pero, y esto es solo una idea, no puedo dejar de notar que la cláusula where en los generics parece ser capaz de verificar la igualdad de tipos. Entonces, ¿puede aprovechar eso, al menos para verificar su identidad?

Aquí hay una posible solución (conceptualmente lo mismo que la respuesta ‘tuncay’). El punto es definir una clase que envuelve alguna funcionalidad (por ejemplo, Comando):

Rápido:

 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