Cómo usar el protocolo genérico como tipo de variable

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 debía poder utilizar la variable Printable para imprimir valores como este:

 let p:Printable = Printer<Int>() p.Print(67) 

El comstackdor se queja con este error:

"protocolo 'Imprimible' solo se puede usar como una restricción genérica porque tiene requisitos propios o de tipo asociado"

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) 

EDIT 2: ejemplo del mundo real de lo que quiero. Tenga en count que esto no se comstackrá, pero presenta 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() } 

Como señala Thomas, puede declarar su variable al no dar un tipo en absoluto (o podría darle explícitamente como tipo Printer<Int> . Pero aquí hay una explicación de por qué no puede tener un tipo de protocolo Printable .

No puede tratar protocolos con types asociados como protocolos regulares y declararlos como types 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 Stonetworking typealias protocol StoringType { typealias Stonetworking init(_ value: Stonetworking) func getStonetworking() -> Stonetworking } // An implementation that stores Ints struct IntStorer: StoringType { typealias Stonetworking = Int private let _stonetworking: Int init(_ value: Int) { _stonetworking = value } func getStonetworking() -> Int { return _stonetworking } } // An implementation that stores Strings struct StringStorer: StoringType { typealias Stonetworking = String private let _stonetworking: String init(_ value: String) { _stonetworking = value } func getStonetworking() -> String { return _stonetworking } } let intStorer = IntStorer(5) intStorer.getStonetworking() // returns 5 let stringStorer = StringStorer("five") stringStorer.getStonetworking() // returns "five" 

Bien, hasta ahora todo bien.

Ahora bien, la razón principal por la que tendría que un tipo de variable es un protocolo que implementa un tipo, en lugar del tipo real, es para que pueda asignar diferentes classs de objects que se ajusten a ese mismo protocolo a la misma variable y get un efecto polimórfico Comportamiento en time de ejecución según el object en realidad.

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.getStonetworking() 

En el código anterior, ¿cuál sería el tipo de x ? Un Int ¿O una String ? En Swift, todos los types deben ser corregidos en time de compilation. Una function no puede cambiar dinámicamente de devolver un tipo a otro en function de los factores determinados en el time de ejecución.

En cambio, solo puede usar StonetworkingType como una restricción genérica. Supongamos que desea imprimir cualquier tipo de tipo almacenado. Podrías escribir una function como esta:

 func printStonetworkingValue<S: StoringType>(storer: S) { let x = storer.getStonetworking() println(x) } printStonetworkingValue(intStorer) printStonetworkingValue(stringStorer) 

Esto está bien, porque en el momento de comstackr, es como si el comstackdor escribiera dos versiones de printStonetworkingValue : una para Int s y otra para String s. Dentro de esas dos versiones, se sabe que x es de un tipo específico.

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 class o estructura que envuelva un object o estructura que cumpla con el protocolo. La class envoltura, usualmente llamada 'Cualquier {nombre de protocolo}', se ajusta al protocolo y implementa sus funciones al reenviar todas las llamadas al object 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 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 sabe que el tipo de printer es AnyPrinter<Int> y se puede utilizar para AnyPrinter<Int> cualquier implementación posible del protocolo de la Impresora. Si bien AnyPrinter no es técnicamente abstracto, su implementación se networkinguce a un tipo de implementación real y se puede usar para desvincular types de implementación de los types que los utilizan.

Una cosa a tener en count es que AnyPrinter no tiene que retener explícitamente la instancia base. De hecho, no podemos, ya que no podemos declarar a AnyPrinter que tenga una propiedad Printer<T> . En cambio, obtenemos un puntero de function _print para la function de print la base. Llamar base.print sin invocarlo devuelve una function donde la base se cura como la variable propia, y así se retiene para futuras invocaciones.

Otra cosa a tener en count es que esta solución es esencialmente otra capa de envío dynamic, lo que significa un ligero golpe en el performance. Además, la instancia de borrado de tipo requiere memory adicional en la parte superior de la instancia subyacente. Por estos motivos, el borrado de tipo no es una abstracción gratuita.

Obviamente, hay algún trabajo para configurar el borrado de tipo, pero puede ser muy útil si se necesita la abstracción del protocolo genérico. Este patrón se encuentra en la biblioteca estándar rápida con types como AnySequence . Más información: http://robnapier.net/erasure

PRIMA:

Si decide que desea inyectar la misma implementación de Printer todas partes, puede proporcionar un inicializador de conveniencia para AnyPrinter 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 manera fácil y SECA de express inyecciones de dependencia para los protocolos que usa en su aplicación.

Dirigiendo 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 imponer restricciones específicas a los implementadores de protocolo, puede restringir las tipealias del protocolo. Entonces, para crear su colección de protocolos que requiere que los elementos sean imprimibles:

 // because of how how collections are structunetworking 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 desea implementar una colección que solo podría contener elementos imprimibles:

 struct MyPrintableCollection<T: Printable>: PrintableCollectionType { typealias Generator = IndexingGenerator<T> // etc... } 

Sin embargo, esto probablemente no sea muy útil, ya que no puede restringir las estructuras de la colección Swift existentes de esa manera, solo las implementa.

En cambio, debe crear funciones genéricas que limiten su input a collections que contengan elementos imprimibles.

 func printCollection <C: CollectionType where C.Generator.Element: Printable> (source: C) { for x in source { x.print() } }