No se puede especializar explícitamente una función genérica

92

Tengo un problema con el siguiente código:

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

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

el resultado de generic1 (nombre) al error del compilador "No se puede especializar explícitamente una función genérica"

¿Hay alguna forma de evitar este error? No puedo cambiar la firma de la función generic1, por lo tanto, debería ser (String) -> Void

Greyisf
fuente
2
¿Cuál es el punto de usar el tipo genérico aquí cuando no se puede inferir para el contexto? Si el tipo genérico se usa solo internamente, debe especificar el tipo dentro del cuerpo de la función.
Kirsteins
¿El tipo de marcador de posición se Tutiliza en absoluto generic1()? ¿Cómo llamaría a esa función para que el compilador pueda inferir el tipo?
Martin R
3
Espero que haya alguna forma de llamar a una función como generic1 <blaClass> ("SomeString")
Greyisf
2
Los genéricos no son solo para casos en los que el compilador puede inferir el contexto. La especialización explícita de la función permitiría inferir otras partes del código. ej: func foo<T>() -> T { ... }que hace algo con un objeto tde tipo Ty el retorno t. La especialización explícita Tpermitiría t1injerirse var t1 = foo<T>(). Ojalá hubiera una forma de llamar a las funciones de esta manera también. C # lo permite
nacho4d
1
También me encontré con esto. No tiene sentido crear una función genérica si está obligado a pasar el tipo como parámetro. Esto tiene que ser un error, ¿verdad?
Nick

Respuestas:

161

También tuve este problema y encontré una solución para mi caso.

En este artículo el autor tiene el mismo problema.

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

Entonces, el problema parece ser que el compilador necesita inferir el tipo de T de alguna manera. Pero no está permitido usar genérico <type> (params ...).

Normalmente, el compilador puede buscar el tipo de T, escaneando los tipos de parámetros porque aquí es donde T se usa en muchos casos.

En mi caso fue un poco diferente, porque el tipo de retorno de mi función era T. En su caso, parece que no ha usado T en absoluto en su función. Supongo que acaba de simplificar el código de ejemplo.

Entonces tengo la siguiente función

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

Y en el caso de, por ejemplo

getProperty<Int>("countProperty")

el compilador me da el error:

No se puede especializar explícitamente una función genérica

Entonces, para darle al compilador otra fuente de información para inferir el tipo de T, debe declarar explícitamente el tipo de variable en la que se guarda el valor de retorno.

var value : Int = getProperty("countProperty")

De esta forma, el compilador sabe que T tiene que ser un número entero.

Entonces, creo que, en general, simplemente significa que si especifica una función genérica, al menos debe usar T en sus tipos de parámetros o como un tipo de retorno.

ThottJefe
fuente
2
Me salvó un montón de tiempo. Gracias.
chrislarson
4
¿Hay alguna forma de hacerlo si no hay valor de retorno? es decir,func updateProperty<T>( propertyID : String )
Kyle Bashour
1
No veo por qué querrías hacer eso. Como no devuelve nada, no hay ningún beneficio en declarar su función genérica.
ThottChief
@ThottChief hay un montón de beneficios por ejemplo, si está utilizando / keypaths la construcción, o conseguir el nombre del tipo en forma de cadena, etc
zaitsman
Gran respuesta gracias. Desearía que esto funcionara let value = foo() as? Typepara poder usarlo en una ifo guardel resultado es opcional, pero no lo hace ...
agirault
54

Rápido 5

Normalmente, hay muchas formas de definir funciones genéricas. Pero se basan en una condición que Tdebe usarse parametercomo un 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
    }
}

Después de eso, debemos proporcionar Tal llamar.

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

Árbitro:

  1. http://austinzheng.com/2015/01/02/swift-generics-pt-1/
  2. https://dispatchswift.com/type-constraints-for-generics-in-swift-d6bf2f0dbbb2
nahung89
fuente
Gran respuesta al problema general ... ya que hay muchas formas de solucionarlo. También me di cuenta de que self.result = UIViewController.doSomething()funciona por sí solo siempre que haya escrito la propiedad cuando la declare.
teradilo
gran respuesta que explica muchas cosas.
Okhan Okbay
Java se doLastThing<UITextView>()convierte en Swift doLastThing(UITextView.self), que no es ... al menos lo peor. Mejor que tener que escribir explícitamente resultados complicados. Gracias por la solución.
Erhannis
18

La solución es tomar el tipo de clase como parámetro (como en Java)

Para que el compilador sepa con qué tipo está tratando, pase la clase 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)
    }
}

Llamar como:

self.navigate(UserDetailsViewController.self, id: "UserDetailsViewController", before: {
        controller in
        controller.user = self.notification.sender
    })
Orkhan Alikhanov
fuente
1
Esto es genial y mucho mejor que la respuesta aceptada. Considere editar para eliminar la parte histórica, o al menos ponerla debajo de la solución. ¡Gracias!
Dan Rosenstark
1
@DanRosenstark Gracias por los comentarios :)
Orkhan Alikhanov
Aunque esto funciona, no creo que sea la mejor práctica. La razón es que ahora la definición de función está tomando decisiones sobre qué hacer si hay un problema con el elenco. Entonces, aquí estás colapsando si el yeso falla. Es mejor dejar que el cliente decida qué hacer, ya que esto puede variar según el cliente. Entonces, voy a rechazar esta respuesta por esta razón.
smileBot
@smileBot ¿Quiere decir que el ejemplo es malo o el enfoque?
Orkhan Alikhanov
El enfoque. Creo que es una regla general de programación diferir la especialización cuando sea práctico. Esto es parte de la idea de entender la responsabilidad. No es responsabilidad de la función especializar lo genérico. Esta es la responsabilidad de la persona que llama, ya que la función no puede saber qué necesitarán todas las personas que llaman. Si la función entra en este rol, esto limita el uso de la función sin ganancia. Puede generalizar esto a muchos problemas de codificación. Este único concepto me ha ayudado enormemente.
smileBot
4

No necesita un genérico aquí ya que tiene tipos estáticos (String como parámetro), pero si desea que una función genérica llame a otra, puede hacer lo siguiente.

Usar 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
} 

Uso de una clase genérica (menos flexible ya que el genérico se puede definir para 1 tipo solo por instancia)

class Foo<T> {

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

   func doOtherStuff(text: String) -> T {

   }  

}

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

Creo que cuando especificas una función genérica debes especificar algunos de los parámetros de tipo T, como sigue:

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

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

y si desea llamar al método handle (), puede hacerlo escribiendo el protocolo y especificando la restricción 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)
}

por lo que puede llamar a esta función genérica con String:

generic2("Some")

y se compilará

Valerii Lider
fuente
1

Hasta ahora, mi mejor práctica personal utilizó la respuesta de @orkhan-alikhanov. Hoy en día, cuando se mira en SwiftUI y cómo .modifier()y la ViewModifierlleva a cabo, he encontrado otra manera (o es más una solución alternativa?)

Simplemente envuelva la segunda función en un struct.

Ejemplo:

Si este le da la opción "No se puede especializar explícitamente una función genérica"

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

Este podría ayudar. Envuelva la declaración de generic1en un struct:

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

y llámalo con:

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

Observaciones:

  • No sé si ayuda en algún caso posible, cuando aparece este mensaje de error. Solo sé que me quedé atascado muchas veces cuando surgió esto. Sé que esta solución ayudó hoy, cuando apareció el mensaje de error.
  • La forma en que Swift maneja los genéricos sigue siendo confusa para mí.
  • Este ejemplo y la solución alternativa con el structes un buen ejemplo. La solución alternativa aquí no tiene un poco más de información, pero pasa el compilador. ¿Misma información, pero resultados diferentes? Entonces algo anda mal. Si es un error del compilador, se puede arreglar.
jboi
fuente
0

Tuve un problema similar con mi función de clase genérica class func retrieveByKey<T: GrandLite>(key: String) -> T?.

No podría llamarlo let a = retrieveByKey<Categories>(key: "abc")donde Categorías es una subclase de GrandLite.

let a = Categories.retrieveByKey(key:"abc")devolvió GrandLite, no Categorías. Las funciones genéricas no infieren el tipo en función de la clase que las llama.

class func retrieveByKey<T: GrandLite>(aType: T, key: String>) -> T?me dio un error cuando lo intenté let a = Categories.retrieveByKey(aType: Categories, key: "abc")me dio un error que no podía convertir Categorías.Tipo a GrandLite, aunque Categorías es una subclase de GrandLite. SIN EMBARGO...

class func retrieveByKey<T: GrandLite>(aType: [T], key: String) -> T? Funcionó si probé let a = Categories.retrieveByKey(aType: [Categories](), key: "abc")aparentemente una asignación explícita de una subclase no funciona, pero una asignación implícita usando otro tipo genérico (matriz) sí funciona en Swift 3.

Adamek
fuente
1
Para corregir el error, debe proporcionar aTypecomo instancia de T, en lugar de colocarse Categories. Ej let a = Categories.retrieveByKey(aType: Categories(), key: "abc"). : . Otra solución es definir aType: T.Type. Luego llame al método comolet a = Categories.retrieveByKey(aType: Categories.self, key: "abc")
nahung89