O parâmetro genérico 'T' não pôde ser inferido após a atribuição

Aug 18 2020
// 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 fespera como argumento uma função que recebe um argumento do tipo T.

A função grecebe 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 Intno gtipo 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 gnão deve ter influência sobre como o compilador infere To 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

2 Sweeper Aug 18 2020 at 11:45

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
2 Malik Aug 18 2020 at 11:44

A função frecebe uma função como parâmetro que por sua vez recebe um parâmetro do tipo Te 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

AlexanderNikolaychuk Aug 18 2020 at 11:44

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