O parâmetro genérico 'T' não pôde ser inferido após a atribuição
// 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
No código acima, a função genérica f
espera como argumento uma função que recebe um argumento do tipo T
.
A função g
recebe um argumento do tipo Int
.
Quando escrevo f { g($0) }
, o código compila. Acredito (corrija-me se estiver errado) que isso compila porque o compilador pode inferir que éT
baseado Int
no g
tipo de argumento de .
Porém, quando tento fazer algo com o valor de retorno de g
, por exemplo na let _ = g($0)
linha, o compilador reclama que não consegue mais inferir o tipo de T
.
Parece-me que o tipo de retorno de g
não deve ter influência sobre como o compilador infere T
o tipo de, mas claramente tem.
Alguém pode esclarecer por que isso acontece e o que (se houver) pode ser feito para corrigi-lo?
Respostas
Isso pode ou não ser um bug do compilador.
Sabe-se que o Swift não tenta inferir os tipos de alguns encerramentos, ou seja, multi-instruções, como dito em SR-1570 :
Este é o comportamento correto: o Swift não infere parâmetros ou tipos de retorno dos corpos de encerramentos de várias instruções.
No entanto, seu encerramento consiste em apenas uma declaração, uma declaração para ser específico. É possível, embora estranho, que eles tenham projetado isso de forma que o Swift não tente inferir tipos se o encerramento também contiver uma declaração . Por exemplo, isso também não compila
f { let x: Int = $0 } // nothing to do with "g"! The issue seems to be with declarations
Se isso fosse planejado, a lógica por trás disso poderia ser porque uma única declaração em um fechamento não faria muito sentido de qualquer maneira. O que for declarado, não será usado.
Mas, novamente, isso é apenas especulação e também pode ser um bug.
Para corrigi-lo, basta torná-lo uma não-declaração:
f { _ = g($0) } // this, without the "let", is IMO the idiomatic way to ignore the function result
Ou
f { g($0) } // g has @discardableResult anyway, so you don't even need the wildcard
A função f
recebe uma função como parâmetro que por sua vez recebe um parâmetro do tipo T
e não retorna nada ( Void
). Para um encerramento inferir tipos automaticamente, ele deve consistir em uma única (e às vezes simples) expressão. Qualquer coisa complexa torna difícil para o compilador inferir (o que faz sentido do ponto de vista do compilador). Aparentemente, let _ = g($0)
é uma instrução complexa no que diz respeito ao compilador. Para obter mais informações, consulte esta discussão da lista de discussão
parece que @discardableResult oferece a capacidade de ter 2 tipos de funções:
g(_: Int) -> Int e g(_: Int) -> Void (é quando você não quer usar um resultado da função)
eu penso isso
f { g($0) }
- aqui seu f pode inferir um tipo porque tem o mesmo tipo
(_: (T) -> Void) and (_: (Int) -> Void)
f { let _ = g($0) }
- neste caso, o tipo de função g é diferente da função f
(_: (T) -> Void) and (_: (Int) -> Int)
Se você remover "let" ele irá compilar novamente:
f { _ = g($0) }
Eu acho que f { let _ = g($0) }
- retorna apenas o valor Int f { _ = g($0) }
- retorna a função (_: (Int) -> Int)
Talvez seja uma chave aqui