Não é possível especializar explicitamente uma função genérica

91

Tenho problemas com o seguinte código:

func generic1<T>(name : String){
}

func generic2<T>(name : String){
     generic1<T>(name)
}

o resultado genérico1 (nome) para erro do compilador "Não é possível especializar explicitamente uma função genérica"

Existe alguma maneira de evitar esse erro? Não posso alterar a assinatura da função genérica1, portanto, deve ser (String) -> Void

Greyisf
fonte
2
Qual é o ponto de usar tipo genérico aqui, quando ele não pode ser inferido para o contexto? Se o tipo genérico for usado apenas internamente, você deve especificar o tipo dentro do corpo da função.
Kirsteins de
O tipo de espaço reservado é Tusado em generic1()? Como você chamaria essa função, para que o compilador possa inferir o tipo?
Martin R de
3
Espero que haja alguma maneira de chamar a função como genetic1 <blaClass> ("SomeString")
Greyisf
1
Os genéricos não são apenas para casos em que o compilador pode inferir o contexto. Especializar explicitamente a função permitiria que outras partes do código fossem inferidas. ex: func foo<T>() -> T { ... }isso faz algo com um objeto tdo tipo Te o retorno t. Especializar- Tse explicitamente permitiria t1inferir em var t1 = foo<T>(). Eu gostaria que houvesse uma maneira de chamar funções dessa maneira também. C # permite
nacho4d de
1
Eu também corri para isso. Não faz sentido criar uma função genérica se você for forçado a passar o tipo como um parâmetro. Isso tem que ser um bug, certo ?!
Nick

Respostas:

160

Eu também tive esse problema e encontrei uma solução alternativa para o meu caso.

Neste artigo o autor tem o mesmo problema

https://www.iphonelife.com/blog/31369/swift-programming-101-generics-practical-guide

Portanto, o problema parece ser que o compilador precisa inferir o tipo de T de alguma forma. Mas não é permitido simplesmente usar <type> genérico (params ...).

Normalmente, o compilador pode procurar o tipo de T, examinando os tipos de parâmetro porque é onde T é usado em muitos casos.

No meu caso foi um pouco diferente, porque o tipo de retorno da minha função era T. No seu caso, parece que você não usou T em sua função. Eu acho que você simplificou o código de exemplo.

Portanto, tenho a seguinte função

func getProperty<T>( propertyID : String ) -> T

E no caso de, por exemplo

getProperty<Int>("countProperty")

o compilador me dá o erro:

Não é possível especializar explicitamente uma função genérica

Portanto, para dar ao compilador outra fonte de informação para inferir o tipo de T, você deve declarar explicitamente o tipo da variável em que o valor de retorno é salvo.

var value : Int = getProperty("countProperty")

Dessa forma, o compilador sabe que T deve ser um inteiro.

Portanto, acho que, no geral, significa simplesmente que, se você especificar uma função genérica, terá que usar pelo menos T em seus tipos de parâmetro ou como um tipo de retorno.

ThottChief
fonte
2
Me economizou muito tempo. Obrigado.
chrislarson
3
Existe uma maneira de fazer isso se não houver valor de retorno? ou seja,func updateProperty<T>( propertyID : String )
Kyle Bashour
1
Não vejo por que você faria isso? Como você não retorna nada, não há vantagem em declarar sua função genérica.
ThottChief
@ThottChief há muitos benefícios, por exemplo, se você estiver usando / construindo caminhos-chave ou obtendo o nome do tipo como uma string, etc.
zaitsman
Ótima resposta, obrigado. Gostaria que funcionasse com let value = foo() as? Typepara que pudesse ser usado em um ifou guardo resultado é opcional, mas não ...
agirault
53

Swift 5

Normalmente, existem muitas maneiras de definir funções genéricas. Mas eles são baseados na condição que Tdeve ser usada como um parameter, ou como um return type.

extension UIViewController {
    class func doSomething<T: UIView>() -> T {
        return T()
    }

    class func doSomethingElse<T: UIView>(value: T) {
        // Note: value is a instance of T
    }

    class func doLastThing<T: UIView>(value: T.Type) {
        // Note: value is a MetaType of T
    }
}

Depois disso, devemos fornecer Tao ligar.

let result = UIViewController.doSomething() as UIImageView // Define `T` by casting, as UIImageView
let result: UILabel = UIViewController.doSomething() // Define `T` with property type, as UILabel
UIViewController.doSomethingElse(value: UIButton()) // Define `T` with parameter type, as UIButton
UIViewController.doLastThing(value: UITextView.self) // Define `T` with parameter type, as UITextView

Ref:

  1. http://austinzheng.com/2015/01/02/swift-generics-pt-1/
  2. https://dispatchswift.com/type-constraints-for-generics-in-swift-d6bf2f0dbbb2
Nahung89
fonte
Ótima resposta para o problema geral ... já que existem tantas maneiras de corrigi-lo. Também percebi que self.result = UIViewController.doSomething()funciona por conta própria, desde que você tenha digitado a propriedade ao declará-la.
teradil
ótima resposta explicando muitas coisas.
Okhan Okbay
Java doLastThing<UITextView>()se torna Swift doLastThing(UITextView.self), o que não é ... o pior, pelo menos. Melhor do que ter que digitar explicitamente resultados complicados. Obrigado pela solução alternativa.
Erhannis
18

A solução é usar o tipo de classe como parâmetro (como em Java)

Para deixar o compilador saber com que tipo ele está lidando, passe a classe como argumento

extension UIViewController {
    func navigate<ControllerType: UIViewController>(_ dump: ControllerType.Type, id: String, before: ((ControllerType) -> Void)?){
        let controller = self.storyboard?.instantiateViewController(withIdentifier: id) as! ControllerType
        before?(controller)
        self.navigationController?.pushViewController(controller, animated: true)
    }
}

Ligue como:

self.navigate(UserDetailsViewController.self, id: "UserDetailsViewController", before: {
        controller in
        controller.user = self.notification.sender
    })
Orkhan Alikhanov
fonte
1
Isso é ótimo e muito melhor do que a resposta aceita. Considere editar para remover a parte histórica, ou pelo menos colocá-la abaixo da solução. Obrigado!
Dan Rosenstark
1
@DanRosenstark Obrigado pelo feedback :)
Orkhan Alikhanov
Embora isso funcione, não acho que seja a melhor prática. A razão é que agora a definição da função é tomar decisões sobre o que fazer se houver um problema com o elenco. Então, aqui está você apenas travando se o elenco falhar. É melhor deixar o cliente decidir o que fazer, pois isso pode variar de acordo com o cliente. Então, vou votar esta resposta por este motivo.
smileBot 01 de
@smileBot Você quer dizer que o exemplo é ruim ou a abordagem?
Orkhan Alikhanov
A abordagem. Acho que é uma regra geral de programação adiar a especialização quando prático. Isso faz parte da ideia de compreender a responsabilidade. Não é responsabilidade da função especializar o genérico. Essa é a responsabilidade do chamador, pois a função não pode saber o que todos os chamadores precisarão. Se a função entrar nesta função, isso limita o uso da função sem ganho. Você pode generalizar isso para muitos problemas de codificação. Este único conceito me ajudou enormemente.
smileBot
4

Você não precisa de uma função genérica aqui, já que tem tipos estáticos (String como parâmetro), mas se quiser que uma função genérica chame outra, você pode fazer o seguinte.

Usando métodos genéricos

func fetchObjectOrCreate<T: NSManagedObject>(type: T.Type) -> T {
    if let existing = fetchExisting(type) {
       return existing
    }
    else {
        return createNew(type)
    }
}

func fetchExisting<T: NSManagedObject>(type: T.Type) -> T {
    let entityName = NSStringFromClass(type)
     // Run query for entiry
} 

func createNew<T: NSManagedObject>(type: T.Type) -> T {
     let entityName = NSStringFromClass(type)
     // create entity with name
} 

Usando uma classe genérica (menos flexível, pois o genérico pode ser definido para 1 tipo apenas por instância)

class Foo<T> {

   func doStuff(text: String) -> T {
      return doOtherStuff(text)
   }

   func doOtherStuff(text: String) -> T {

   }  

}

let foo = Foo<Int>()
foo.doStuff("text")
aryaxt
fonte
3

Eu acho que quando você especifica a função genérica, você deve especificar alguns dos parâmetros do tipo T, como segue:

func generic1<T>(parameter: T) {
    println("OK")
}

func generic2<T>(parameter: T) {
    generic1(parameter)
}

e se quiser chamar o método handle (), você pode fazer isso escrevendo protocolo e especificando a restrição de tipo para T:

protocol Example {
    func handle() -> String
}

extension String: Example {
    func handle() -> String {
        return "OK"
    }
}

func generic1<T: Example>(parameter: T) {
    println(parameter.handle())
}

func generic2<T: Example>(parameter: T) {
    generic1(parameter)
}

então você pode chamar esta função genérica com String:

generic2("Some")

e vai compilar

Valerii Lider
fonte
1

Até agora, minha melhor prática pessoal usou a resposta de @orkhan-alikhanov. Hoje, quando se olha para SwiftUI e como .modifier()e ViewModifieré implementado, eu encontrei outra maneira (ou é mais uma solução alternativa?)

Basta envolver a segunda função em um struct.

Exemplo:

Se este fornecer a você a mensagem "Não é possível especializar explicitamente uma função genérica"

func generic2<T>(name: String){
     generic1<T>(name)
}

Este pode ajudar. Envolva a declaração de generic1em um struct:

struct Generic1Struct<T> {
    func generic1(name: String) {## do, whatever it needs with T ##}
}

e chamá-lo com:

func generic2<T>(name : String){
     Generic1Struct<T>().generic1(name: name)
}

Observações:

  • Não sei se ajuda em algum caso possível, quando essa mensagem de erro ocorrer. Só sei que fiquei preso muitas vezes quando isso surgiu. Eu sei, que esta solução ajudou hoje, quando a mensagem de erro apareceu.
  • A maneira como o Swift lida com os genéricos ainda é confusa para mim.
  • Este exemplo e a solução alternativa com o structé um bom exemplo. A solução alternativa aqui não tem um pouco mais de informação - mas passa pelo compilador. Mesma informação, mas resultados diferentes? Então algo está errado. Se for um bug do compilador, ele pode ser corrigido.
jboi
fonte
0

Tive um problema semelhante com minha função de classe genérica class func retrieveByKey<T: GrandLite>(key: String) -> T?.

Eu não poderia chamá-lo let a = retrieveByKey<Categories>(key: "abc")onde Categories é uma subclasse de GrandLite.

let a = Categories.retrieveByKey(key:"abc")GrandLite retornou, não categorias. As funções genéricas não inferem o tipo com base na classe que as chama.

class func retrieveByKey<T: GrandLite>(aType: T, key: String>) -> T?deu-me um erro quando tentei, let a = Categories.retrieveByKey(aType: Categories, key: "abc")deu-me um erro informando que não foi possível converter Categories.Type em GrandLite, embora Categories seja uma subclasse de GrandLite. CONTUDO...

class func retrieveByKey<T: GrandLite>(aType: [T], key: String) -> T? fez o trabalho se eu tentasse let a = Categories.retrieveByKey(aType: [Categories](), key: "abc"), aparentemente, uma atribuição explícita de uma subclasse não funciona, mas uma atribuição implícita usando outro tipo genérico (array) funciona em Swift 3.

adamek
fonte
1
Para corrigir o erro, você deve fornecer aTypecomo uma instância de T, em vez de se colocar Categories. Ex: let a = Categories.retrieveByKey(aType: Categories(), key: "abc"). Outra solução é definir aType: T.Type. Em seguida, chame o método comolet a = Categories.retrieveByKey(aType: Categories.self, key: "abc")
nahung89