Cómo usar el protocolo genérico como un 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 : Printable { func Print(val: T) { println(val) } } 

Mi expectativa era que debía poder usar la variable Printable para imprimir valores como este:

 let p:Printable = Printer() p.Print(67) 

El comstackdor se queja con este error:

“protocol ‘Printable’ solo se puede utilizar como restricción genérica porque tiene requisitos de tipo propio o asociado”

Estoy haciendo algo mal ? Cualquier forma de arreglar esto ?

 **EDIT :** Adding similar code that works in C# public interface IPrintable { void Print(T val); } public class Printer : IPrintable { public void Print(T val) { Console.WriteLine(val); } } //.... inside Main ..... IPrintable p = new Printer(); p.Print(67) 

EDIT 2: Ejemplo del mundo real de lo que quiero. Tenga en cuenta que esto no se comstackrá, pero presenta lo que quiero lograr.

 protocol Printable { func Print() } protocol CollectionType : SequenceType { ..... /// here goes implementation ..... } public class Collection : CollectionType { ...... } let col:CollectionType = SomeFunctiionThatReturnsIntCollection() for item in col { item.Print() } 

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

No puede tratar protocolos con tipos asociados como protocolos regulares y declararlos como tipos de variables independientes. Para pensar por qué, considere este escenario. Supongamos que declaras 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 tendría un tipo de variable sería un protocolo que implemente un tipo, en lugar del tipo real, de modo 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 qué es 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 ? Un Int ? O una String ? En Swift, todos los tipos deben corregirse en tiempo de comstackción. Una función no puede cambiar dinámicamente de devolver un tipo a otro en función de factores determinados en el tiempo de ejecución.

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

 func printStoredValue(storer: S) { let x = storer.getStored() println(x) } printStoredValue(intStorer) printStoredValue(stringStorer) 

Esto está bien, porque en tiempo de comstackción, es como si el comstackdor escribe dos versiones de printStoredValue : 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 es el uso de 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 cumpla con el protocolo. La clase contenedora, generalmente llamada ‘Any {protocol name}’, se ajusta al protocolo e implementa sus funciones reenviando todas las llamadas al objeto interno. Pruebe el siguiente ejemplo en un patio de juegos:

 import Foundation public protocol Printer { typealias T func print(val:T) } struct AnyPrinter: Printer { typealias T = U private let _print: U -> () init(base : Base) { _print = base.print } func print(val: T) { _print(val) } } struct NSLogger: Printer { typealias T = U func print(val: T) { NSLog("\(val)") } } let nsLogger = NSLogger() let printer = AnyPrinter(base: nsLogger) printer.print(5) // prints 5 

Se sabe que el tipo de printer es AnyPrinter y se puede utilizar para abstraer cualquier posible implementación del protocolo de la impresora. Si bien AnyPrinter no es técnicamente abstracto, su implementación es simplemente una transición hacia un tipo de implementación real, y se puede utilizar para desacoplar tipos de implementación de los tipos que los utilizan.

Una cosa a tener en cuenta es que AnyPrinter no tiene que retener explícitamente la instancia base. De hecho, no podemos, ya que no podemos declarar que AnyPrinter tenga una propiedad Printer . En cambio, obtenemos un puntero a la función _print a la función de print la base. Llamar a base.print sin invocarlo devuelve una función donde base es curried como la auto variable, y así se conserva para futuras invocaciones.

Otra cosa a tener en cuenta es que esta solución es esencialmente otra capa de despacho dynamic que significa un pequeño golpe en el rendimiento. Además, la instancia de borrado de tipo requiere memoria adicional en la parte superior de la instancia subyacente. Por estas razones, la borradura de tipo no es una abstracción libre de costo.

Obviamente, hay algo de trabajo para configurar el borrado de tipos, pero puede ser muy útil si se necesita la abstracción de protocolos generics. Este patrón se encuentra en la biblioteca estándar rápida con tipos como AnySequence . Lectura adicional: http://robnapier.net/erasure

PRIMA:

Si decide que desea inyectar la misma implementación de la Printer todas partes, puede proporcionar un inicializador de conveniencia para AnyPrinter que inyecte ese tipo.

 extension AnyPrinter { convenience init() { let nsLogger = NSLogger() self.init(base: nsLogger) } } let printer = AnyPrinter() 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.

Dirigiéndose a 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 tipografías 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 podría contener elementos imprimibles:

 struct MyPrintableCollection: PrintableCollectionType { typealias Generator = IndexingGenerator // etc... } 

Sin embargo, esto es probablemente de poca utilidad real, ya que no puede limitar las estructuras de recolección Swift existentes, solo las que implementa.

En su lugar, debe crear funciones genéricas que restrinjan su entrada a las colecciones que contienen elementos imprimibles.

 func printCollection  (source: C) { for x in source { x.print() } }