generics - type - Error genérico extraño
tuple swift (2)
Estoy tratando de implementar la siguiente estructura usando genéricos. Obteniendo un error de compilador extraño, no puedo averiguar por qué.
class Translator<T:Hashable> {...}
class FooTranslator<String>:Translator<String> {...}
La idea es que el Traductor use T como el tipo de clave en un diccionario. Esto puede ser, por ejemplo, una cadena o una enumeración. La subclase proporciona el diccionario concreto.
Pero falla debido a esto: "El tipo ''String'' no se ajusta al protocolo ''Hashable''"
Pero la cadena se ajusta a Hashable! ¿Estoy loco? Tampoco funciona con Int, que también se ajusta a Hashable. Tampoco funciona si sustituyo Hashable por Equatable, que también debería ser implementado por ambos.
Si elimino la restricción de tipo, solo para la prueba (donde también tengo que deshabilitar el diccionario, ya que no puedo usar nada que no sea hashable como clave), se compila
class Translator<T> {...}
class FooTranslator<String>:Translator<String> {...}
¿Qué estoy haciendo mal?
No soy un desarrollador de Swift, pero habiendo visto problemas similares en Java, sospecho que el problema es que en este momento está declarando un parámetro de tipo llamado String
porque está declarando la class FooTranslator<String>
- por lo que el argumento de tipo está en Translator<String>
es solo ese parámetro de tipo, que no tiene restricciones. Sospecho que no quieres un parámetro de tipo (es decir, no quieres que tu FooTranslator
sea una clase genérica en sí misma).
Como se señaló en los comentarios, en las subclases Swift de una clase genérica también tienen que ser genéricos . Posiblemente podría declarar un parámetro de tipo desechable, como este:
class FooTranslator<T>:Translator<String>
que todavía evita declarar un nuevo parámetro de tipo llamado String
, que fue lo que causó el problema. Significa que estás introduciendo un nuevo parámetro de tipo cuando no quieres ningún tipo de parámetro, pero posiblemente sea mejor que nada ...
Todo esto se supone que realmente necesita una subclase, por ejemplo, para agregar o anular miembros. Por otro lado, si solo quieres un tipo que sea exactamente igual al Translator<String>
, deberías usar un alias de tipo en su lugar:
typealias FooTranslator = Translator<String>
O incluso mezcle los dos de una manera horrible, si realmente quiere una subclase pero no quiere tener que referirse a ella de una manera genérica:
class GenericFooTranslator<T>:Translator<String>
typealias FooTranslator = GenericFooTranslator<Int>
(Tenga en cuenta que la Int
aquí no es String
, para mostrar que la T
en el Translator
no es la misma que la T
en el FooTranslator
).
Para empezar, vamos a explicar el error:
Dado:
class Foo<T:Hashable> { }
class SubFoo<String> : Foo<String> { }
La parte confusa aquí es que esperamos que "Cadena" signifique la estructura definida Swift que contiene una colección de caracteres. Pero no lo es.
Aquí, "Cadena" es el nombre del tipo genérico que le hemos dado a nuestra nueva subclase, SubFoo
. Esto se vuelve muy obvio si hacemos algunos cambios:
class SubFoo<String> : Foo<T> { }
Esta línea genera un error para T
como el uso de un tipo no declarado.
Entonces si cambiamos la línea a esto:
class SubFoo<T> : Foo<T> { }
Regresamos al mismo error que tenías originalmente, ''T'' no se ajusta a ''Hashable''. Es obvio aquí, porque T no confunde el nombre de un tipo Swift existente que se ajusta a ''Hashable''. Es obvio que ''T'' es un genérico.
Cuando escribimos ''String'', también es solo el nombre del marcador de posición para un tipo genérico, y no el tipo de String
que existe en Swift.
Si queremos un nombre diferente para un tipo específico de una clase genérica, el enfoque apropiado es casi seguramente una typealias
:
class Foo<T:Hashable> {
}
typealias StringFoo = Foo<String>
Esto es perfectamente válido Swift, y compila muy bien.
Si, en cambio, lo que queremos es subclasificar y agregar métodos o propiedades a una clase genérica, entonces lo que necesitamos es una clase o protocolo que haga que nuestro genérico sea más específico a lo que necesitamos.
Volviendo al problema original, primero eliminemos el error:
class Foo<T: Hashable>
class SubFoo<T: Hashable> : Foo<T> { }
Esto es perfectamente válido Swift. Pero puede que no sea particularmente útil para lo que estamos haciendo.
La única razón por la que no podemos hacer lo siguiente:
class SubFoo<T: String> : Foo<T> { }
es simplemente porque String
no es una clase Swift, es una estructura. Y esto no está permitido para ninguna estructura.
Si escribimos un nuevo protocolo que hereda de Hashable
, podemos usar eso:
protocol MyProtocol : Hashable { }
class Foo<T: Hashable> { }
class SubFoo<T: MyProtocol> : Foo<T> { }
Esto es perfectamente válido.
Además, tenga en cuenta que en realidad no tenemos que heredar de Hashable
:
protocol MyProtocol { }
class Foo<T: Hashable> { }
class SubFoo<T: Hashable, MyProtocol> { }
Esto también es perfectamente válido.
Sin embargo, tenga en cuenta que, por el motivo que sea, Swift no le permitirá utilizar una clase aquí. Por ejemplo:
class MyClass : Hashable { }
class Foo<T: Hashable> { }
class SubFoo<T: MyClass> : Foo<T> { }
Swift se queja misteriosamente de que ''T'' no se ajusta a ''Hashable'' (incluso cuando agregamos el código necesario para hacerlo así).
Al final, el enfoque correcto y el más apropiado para Swift consistirá en escribir un nuevo protocolo que herede de ''Hashable'' y le agregue cualquier funcionalidad que necesite.
No debería ser estrictamente importante que nuestra subclase acepte una String
. Debe ser importante que lo que sea que tome nuestra subclase, tenga los métodos y propiedades necesarios que necesitamos para lo que sea que estemos haciendo.