Impossibile dedurre il parametro generico 'T' dopo l'assegnazione
// 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
Nel codice precedente, la funzione generica f
prevede come argomento una funzione che accetta un argomento di tipo T
.
La funzione g
accetta un argomento di tipo Int
.
Quando scrivo f { g($0) }
, il codice viene compilato. Credo (per favore correggimi se sbaglio) questo viene compilato perché il compilatore può dedurre che èT
basato Int
sul g
tipo di argomento di .
Tuttavia, quando provo a fare qualcosa con il valore restituito di g
, ad esempio nella let _ = g($0)
riga, il compilatore si lamenta di non poter più dedurre il tipo di T
.
Mi sembra che il tipo restituito di g
non dovrebbe avere alcuna relazione con il modo in cui il compilatore deduce T
il tipo di , ma chiaramente lo fa.
Qualcuno può far luce sul motivo per cui ciò accade e cosa (se non altro) si può fare per correggerlo?
Risposte
Questo può o non può essere un bug del compilatore.
È noto che Swift non cerca di dedurre i tipi di alcune chiusure, vale a dire quelle con più dichiarazioni, come detto in SR-1570 :
Questo è il comportamento corretto: Swift non deduce i tipi di parametri o restituiti dai corpi delle chiusure con più istruzioni.
Tuttavia, la tua chiusura consiste in una sola dichiarazione, una dichiarazione per essere precisi. È possibile, anche se strano, che l'abbiano progettato in modo tale che Swift non provi a dedurre i tipi se anche la chiusura contiene una dichiarazione . Ad esempio, neanche questo viene compilato
f { let x: Int = $0 } // nothing to do with "g"! The issue seems to be with declarations
Se questo fosse dovuto alla progettazione, la logica alla base potrebbe essere perché una singola dichiarazione in una chiusura non ha molto senso comunque. Qualunque cosa venga dichiarata, non verrà utilizzata.
Ma ancora una volta, questa è solo una speculazione e anche questo potrebbe essere un bug.
Per risolverlo, rendilo semplicemente una non-dichiarazione:
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 funzione f
accetta una funzione come parametro che a sua volta accetta un parametro di tipo T
e non restituisce niente ( Void
). Affinché una chiusura inferisca automaticamente i tipi, deve consistere in una singola (e talvolta semplice) espressione. Qualsiasi cosa complessa rende difficile per il compilatore dedurre (il che ha senso dal punto di vista del compilatore). Apparentemente, let _ = g($0)
è un'istruzione complessa per quanto riguarda il compilatore. Per ulteriori informazioni, vedere questa discussione sulla mailing list
sembra che @discardableResult ti dia la possibilità di avere 2 tipi di funzioni:
g(_: Int) -> Int e g(_: Int) -> Void (è quando non vuoi usare un risultato della funzione)
penso che
f { g($0) }
- qui la tua f può dedurre un tipo perché ha lo stesso tipo
(_: (T) -> Void) and (_: (Int) -> Void)
f { let _ = g($0) }
- in questo caso il tipo di funzione g è diverso dalla funzione f
(_: (T) -> Void) and (_: (Int) -> Int)
Se rimuoverai "let", verrà compilato di nuovo:
f { _ = g($0) }
Penso che f { let _ = g($0) }
- restituisca solo il valore Int f { _ = g($0) }
- restituisca la funzione (_: (Int) -> Int)
Forse è una chiave qui