El parámetro genérico 'T' no se pudo inferir después de la asignación
// Xcode 11.6 / Swift 5
import Foundation
func f<T>(_: (T) -> Void) { }
@discardableResult
func g(_: Int) -> Int { 0 }
f { g($0) } // compiles fine
f { let _ = g($0) } // Generic parameter 'T' could not be inferred
En el código anterior, la función genérica f
espera como argumento una función que toma un argumento de tipo T
.
La función g
toma un argumento de tipo Int
.
Cuando escribo f { g($0) }
, el código se compila. Creo ( corríjanme si me equivoco) esto se compila porque el compilador puede inferir que T
se Int
basa en g
el tipo de argumento.
Sin embargo, cuando trato de hacer algo con el valor de retorno de g
, por ejemplo en la let _ = g($0)
línea, el compilador se queja de que ya no puede inferir el tipo de T
.
Me parece que el tipo de retorno de g
no debería influir en cómo el compilador infiere T
el tipo, pero claramente lo hace.
¿Alguien puede arrojar algo de luz sobre por qué sucede esto y qué se puede hacer (si es que se puede hacer algo) para corregirlo?
Respuestas
Esto puede o no ser un error del compilador.
Se sabe que Swift no intenta inferir los tipos de algunos cierres, a saber, los de múltiples declaraciones, como se dice en SR-1570 :
Este es el comportamiento correcto: Swift no infiere parámetros ni tipos de retorno de los cuerpos de los cierres de varias instrucciones.
Sin embargo, su cierre consiste en una sola declaración, una declaración para ser específicos. Es posible, aunque extraño, que lo hayan diseñado para que Swift no intente inferir tipos si el cierre también contiene una declaración . Por ejemplo, esto tampoco compila
f { let x: Int = $0 } // nothing to do with "g"! The issue seems to be with declarations
Si esto fuera por diseño, la razón detrás de esto podría ser porque una sola declaración en un cierre no tiene mucho sentido de todos modos. Lo que se declare, no se utilizará.
Pero, de nuevo, esto es solo una especulación, y también podría ser un error.
Para solucionarlo, simplemente conviértalo en una declaración no declarada:
f { _ = g($0) } // this, without the "let", is IMO the idiomatic way to ignore the function result
O
f { g($0) } // g has @discardableResult anyway, so you don't even need the wildcard
La función f
toma como parámetro una función que a su vez toma un parámetro de tipo T
y no devuelve nada ( Void
). Para que una clausura infiera tipos automáticamente, tiene que consistir en una expresión única (ya veces simple). Cualquier cosa compleja dificulta la inferencia del compilador (lo que tiene sentido desde el punto de vista del compilador). Aparentemente, let _ = g($0)
es una declaración compleja en lo que respecta al compilador. Para obtener más información, consulte esta discusión de la lista de correo
parece que @discardableResult te da la posibilidad de tener 2 tipos de funciones:
g(_: Int) -> Int y g(_: Int) -> Void (es cuando no desea utilizar un resultado de la función)
Creo que
f { g($0) }
- aquí su f puede inferir un tipo porque tiene el mismo Tipo
(_: (T) -> Void) and (_: (Int) -> Void)
f { let _ = g($0) }
- en este caso el tipo de función g es diferente de la función f
(_: (T) -> Void) and (_: (Int) -> Int)
Si elimina "let", se compilará de nuevo:
f { _ = g($0) }
Creo que f { let _ = g($0) }
- devolver solo valor Int f { _ = g($0) }
- función de retorno (_: (Int) -> Int)
Tal vez es una clave aquí