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 ser capaz de usar Printable variable para imprimir valores como este:

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

El compilador se queja con este error:

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

Estoy haciendo algo mal ? De todos modos para 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)
[6] EDITAR 2: Real ejemplo mundial de lo que quiero. Tenga en cuenta que esto no va a compilar, 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()
}
Author: Tamerlane, 2014-12-31

3 answers

Como Thomas señala, puede declarar su variable no dando 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 los protocolos con tipos asociados como protocolos regulares y declararlos como tipos de variables independientes. Para pensar por qué, considere este escenario. Supongamos que ha declarado un protocolo para almacenar algún tipo arbitrario y luego recuperarlo volver:

// 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 como un protocolo que implementa un tipo, en lugar del tipo real, es para que pueda asignar diferentes tipos de objeto que todos se ajustan a ese protocolo a la misma variable, y obtener un comportamiento polimórfico en tiempo de ejecución dependiendo de lo que el objeto realmente es.

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? ¿An Int? O String? En Swift, todos los tipos deben arreglarse 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 usar StoredType como una restricción genérica. Supongamos que desea imprimir cualquier tipo de tipo almacenado. Puedes 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 tiempo de compilación, es como si el compilador escribiera dos versiones de printStoredValue: una para Int s, y otra para String s. Dentro de esas dos versiones, x es conocido por ser de un tipo específico.

 78
Author: Airspeed Velocity,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-31 20:39:46

Hay una solución más que no se ha mencionado en esta pregunta, que es el uso de una técnica llamada tipo erasure. 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 wrapper, usualmente llamada ' Any{protocol name}', se ajusta al protocolo e implementa sus funciones reenviando todas las llamadas al objeto interno. Prueba el siguiente ejemplo en un patio de recreo:

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 sabe que el tipo de printer es AnyPrinter<Int> y se puede usar 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 través de 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 tiene que retener explícitamente la instancia base. De hecho, no podemos ya que no podemos declarar AnyPrinter tener un Printer<T> propiedad. En su lugar, obtenemos un puntero de función _print a la función print de base. Llamar a base.print sin invocarlo devuelve una función donde base está curry como la variable self, y así se conserva para futuras invocaciones.

Otra cosa a tener en cuenta es que esta solución es esencialmente otra capa de despacho dinámico que significa un ligero 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, escriba borrado no es una abstracción libre de costos.

Obviamente hay algo de trabajo para configurar el borrado de tipos, pero puede ser muy útil si se necesita abstracción genérica del protocolo. Este patrón se encuentra en la biblioteca estándar de swift con tipos como AnySequence. Para más información: http://robnapier.net/erasure

BONO:

Si decide que desea inyectar la misma implementación de Printer en todas partes, puede proporcionar un inicializador conveniente 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 expresar inyecciones de dependencia para los protocolos que usa en su aplicación.

 30
Author: Patrick Goley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-18 02:57:02

Abordando su caso de uso actualizado:

(btw 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 protocolos, puede restringir las tipologías del protocolo. Así que para crear su colección de protocolos que requiere 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 desea implementar una colección que solo pueda contener elementos imprimibles:

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

Sin embargo, esto es probablemente de poca utilidad real, ya que no puede restringir las estructuras de colección Swift existentes como esa, solo las que implementa.

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

func printCollection
    <C: CollectionType where C.Generator.Element: Printable>
    (source: C) {
        for x in source {
            x.print()
        }
}
 4
Author: Airspeed Velocity,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-01 00:02:02