Digamos que tengo un protocolo:
public protocol Printable {
typealias T
func Print(val:T)
}
Y aquí está la implementación
class Printer<T> : Printable {
func Print(val: T) {
println(val)
}
}
Mi expectativa era que debería poder usar la Printable
variable para imprimir valores como este:
let p:Printable = Printer<Int>()
p.Print(67)
El compilador se queja de este error:
"el protocolo 'Imprimible' solo se puede utilizar como una restricción genérica porque tiene requisitos de tipo propio o asociados"
Estoy haciendo algo mal ? Cualquier forma de arreglar esto ?
**EDIT :** Adding similar code that works in C#
public interface IPrintable<T>
{
void Print(T val);
}
public class Printer<T> : IPrintable<T>
{
public void Print(T val)
{
Console.WriteLine(val);
}
}
//.... inside Main
.....
IPrintable<int> p = new Printer<int>();
p.Print(67)
EDICIÓN 2: Ejemplo del mundo real de lo que quiero. Tenga en cuenta que esto no se compilará, pero presentará lo que quiero lograr.
protocol Printable
{
func Print()
}
protocol CollectionType<T where T:Printable> : SequenceType
{
.....
/// here goes implementation
.....
}
public class Collection<T where T:Printable> : CollectionType<T>
{
......
}
let col:CollectionType<Int> = SomeFunctiionThatReturnsIntCollection()
for item in col {
item.Print()
}
Respuestas:
Como señala Thomas, puede declarar su variable sin dar un tipo en absoluto (o podría darlo explícitamente como tipo
Printer<Int>
. Pero aquí hay una explicación de por qué no puede tener un tipo dePrintable
protocolo.No puede tratar los protocolos con tipos asociados como protocolos regulares y declararlos como tipos de variables independientes. Para pensar por qué, considere este escenario. Suponga que declaró un protocolo para almacenar algún tipo arbitrario y luego recuperarlo:
// a general protocol that allows for storing and retrieving // a specific type (as defined by a Stored typealias protocol StoringType { typealias Stored init(_ value: Stored) func getStored() -> Stored } // An implementation that stores Ints struct IntStorer: StoringType { typealias Stored = Int private let _stored: Int init(_ value: Int) { _stored = value } func getStored() -> Int { return _stored } } // An implementation that stores Strings struct StringStorer: StoringType { typealias Stored = String private let _stored: String init(_ value: String) { _stored = value } func getStored() -> String { return _stored } } let intStorer = IntStorer(5) intStorer.getStored() // returns 5 let stringStorer = StringStorer("five") stringStorer.getStored() // returns "five"
OK, hasta ahora todo bien.
Ahora, la razón principal por la que haría que un tipo de variable fuera un protocolo que implementa un tipo, en lugar del tipo real, es para que pueda asignar diferentes tipos de objetos que se ajusten a ese protocolo a la misma variable y obtener polimórficos comportamiento en tiempo de ejecución dependiendo de cuál sea realmente el objeto.
Pero no puede hacer esto si el protocolo tiene un tipo asociado. ¿Cómo funcionaría el siguiente código en la práctica?
// as you've seen this won't compile because // StoringType has an associated type. // randomly assign either a string or int storer to someStorer: var someStorer: StoringType = arc4random()%2 == 0 ? intStorer : stringStorer let x = someStorer.getStored()
En el código anterior, ¿cuál sería el tipo de
x
? UnInt
? ¿O unString
? En Swift, todos los tipos deben corregirse en tiempo de compilación. Una función no puede cambiar dinámicamente de devolver un tipo a otro en función de factores determinados en tiempo de ejecución.En su lugar, solo puede usarlo
StoredType
como una restricción genérica. Suponga que desea imprimir cualquier tipo de tipo almacenado. Podrías escribir una función como esta:func printStoredValue<S: StoringType>(storer: S) { let x = storer.getStored() println(x) } printStoredValue(intStorer) printStoredValue(stringStorer)
Esto está bien, porque en el momento de la compilación, es como si el compilador escribiera dos versiones de
printStoredValue
: una paraInt
s y otra paraString
s. Dentro de esas dos versiones,x
se sabe que es de un tipo específico.fuente
p
variable y luego pasara tipos no válidosprint
? ¿Excepción en tiempo de ejecución?var someStorer: StoringType<Int>
ovar someStorer: StoringType<String>
y resolver el problema que describe.Hay una solución más que no se ha mencionado en esta pregunta, que está utilizando una técnica llamada borrado de tipo . Para lograr una interfaz abstracta para un protocolo genérico, cree una clase o estructura que envuelva un objeto o estructura que se ajuste al protocolo. La clase contenedora, normalmente denominada 'Cualquier {nombre de protocolo}', se ajusta en sí misma al protocolo e implementa sus funciones reenviando todas las llamadas al objeto interno. Pruebe el siguiente ejemplo en un parque infantil:
import Foundation public protocol Printer { typealias T func print(val:T) } struct AnyPrinter<U>: Printer { typealias T = U private let _print: U -> () init<Base: Printer where Base.T == U>(base : Base) { _print = base.print } func print(val: T) { _print(val) } } struct NSLogger<U>: Printer { typealias T = U func print(val: T) { NSLog("\(val)") } } let nsLogger = NSLogger<Int>() let printer = AnyPrinter(base: nsLogger) printer.print(5) // prints 5
Se
printer
sabe que el tipo de esAnyPrinter<Int>
y se puede utilizar para abstraer cualquier posible implementación del protocolo de impresora. Si bien AnyPrinter no es técnicamente abstracto, su implementación es solo una caída a un tipo de implementación real y se puede usar para desacoplar los tipos de implementación de los tipos que los usan.Una cosa a tener en cuenta es que
AnyPrinter
no es necesario retener explícitamente la instancia base. De hecho, no podemos porque no podemos declararAnyPrinter
tener unaPrinter<T>
propiedad. En cambio, obtenemos un puntero de función_print
a laprint
función de base . Llamarbase.print
sin invocarlo devuelve una función donde la base se cursa como la variable propia y, por lo tanto, se retiene para futuras invocaciones.Otra cosa a tener en cuenta es que esta solución es esencialmente otra capa de distribución dinámica, lo que significa un ligero impacto en el rendimiento. Además, la instancia de borrado de tipos requiere memoria adicional además de la instancia subyacente. Por estas razones, el borrado de tipos no es una abstracción gratuita.
Obviamente, hay algo de trabajo para configurar el borrado de tipos, pero puede ser muy útil si se necesita una abstracción de protocolo genérico. Este patrón se encuentra en la biblioteca estándar rápida con tipos como
AnySequence
. Más información: http://robnapier.net/erasurePRIMA:
Si decide que desea inyectar la misma implementación en
Printer
todas partes, puede proporcionar un inicializador conveniente para elAnyPrinter
que inyecta ese tipo.extension AnyPrinter { convenience init() { let nsLogger = NSLogger<T>() self.init(base: nsLogger) } } let printer = AnyPrinter<Int>() printer.print(10) //prints 10 with NSLog
Esta puede ser una forma fácil y SECA de expresar inyecciones de dependencia para los protocolos que usa en su aplicación.
fuente
fatalError()
) que se describe en otros tutoriales de borrado de tipo.Abordar su caso de uso actualizado:
(por cierto,
Printable
ya es un protocolo Swift estándar, por lo que probablemente desee elegir un nombre diferente para evitar confusiones)Para hacer cumplir restricciones específicas sobre los implementadores de protocolos, puede restringir los alias de tipo del protocolo. Entonces, para crear su colección de protocolos que requiera que los elementos sean imprimibles:
// because of how how collections are structured in the Swift std lib, // you’d first need to create a PrintableGeneratorType, which would be // a constrained version of GeneratorType protocol PrintableGeneratorType: GeneratorType { // require elements to be printable: typealias Element: Printable } // then have the collection require a printable generator protocol PrintableCollectionType: CollectionType { typealias Generator: PrintableGenerator }
Ahora, si quisiera implementar una colección que solo pudiera contener elementos imprimibles:
struct MyPrintableCollection<T: Printable>: PrintableCollectionType { typealias Generator = IndexingGenerator<T> // etc... }
Sin embargo, esto probablemente sea de poca utilidad, ya que no puede restringir las estructuras de colección Swift existentes de esa manera, solo las que implementa.
En su lugar, debe crear funciones genéricas que restrinjan su entrada a colecciones que contienen elementos imprimibles.
func printCollection <C: CollectionType where C.Generator.Element: Printable> (source: C) { for x in source { x.print() } }
fuente