web-dev-qa-db-fra.com

Comment utiliser le protocole générique comme type de variable

Disons que j'ai un protocole:

public protocol Printable {
    typealias T
    func Print(val:T)
}

Et voici la mise en place

class Printer<T> : Printable {

    func Print(val: T) {
        println(val)
    }
}

J'espérais pouvoir utiliser Printable variable pour imprimer des valeurs comme celle-ci:

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

Le compilateur se plaint de cette erreur:

"le protocole 'Imprimable' ne peut être utilisé que comme contrainte générique car il a des exigences de type Self ou associé"

Est-ce que je fais quelque chose de mal ? Aucun moyen de réparer cela ?

**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: Exemple du monde réel de ce que je veux. Notez que cela ne compilera pas, mais présente ce que je veux réaliser.

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()
}
81
Tamerlane

Comme Thomas le fait remarquer, vous pouvez déclarer votre variable en ne donnant aucun type (ou vous pouvez explicitement le donner comme type Printer<Int>. Mais voici une explication de la raison pour laquelle vous ne pouvez pas avoir un type du protocole Printable.

Vous ne pouvez pas traiter les protocoles avec des types associés, tels que les protocoles standard, et les déclarer en tant que types de variables autonomes. Pour réfléchir à pourquoi, considérons ce scénario. Supposons que vous avez déclaré un protocole pour stocker un type quelconque, puis le récupérer:

// 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, jusqu'ici tout va bien.

Maintenant, la raison principale pour laquelle vous voudriez qu'un type de variable soit un protocole qu'un type implémente, plutôt que le type réel, est que vous pouvez affecter différents types d'objet qui se conforment tous à ce protocole à la même variable, et obtenir un polymorphisme. comportement à l'exécution en fonction de ce que l'objet est réellement.

Mais vous ne pouvez pas faire cela si le protocole a un type associé. Comment le code suivant fonctionnerait-il dans la pratique?

// 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()

Dans le code ci-dessus, quel serait le type de x? Un Int? Ou un String? Dans Swift, tous les types doivent être fixés au moment de la compilation. Une fonction ne peut pas basculer dynamiquement d'un type à un autre en fonction de facteurs déterminés lors de l'exécution.

Au lieu de cela, vous ne pouvez utiliser que StoredType en tant que contrainte générique. Supposons que vous vouliez imprimer n'importe quel type de type stocké. Vous pourriez écrire une fonction comme celle-ci:

func printStoredValue<S: StoringType>(storer: S) {
    let x = storer.getStored()
    println(x)
}

printStoredValue(intStorer)
printStoredValue(stringStorer)

Cela convient, car au moment de la compilation, le compilateur écrit deux versions de printStoredValue: une pour Ints et une pour Strings. Dans ces deux versions, on sait que x est d'un type spécifique.

79
Airspeed Velocity

Il existe une autre solution qui n’a pas été mentionnée dans cette question, qui utilise une technique appelée type effacement . Pour obtenir une interface abstraite pour un protocole générique, créez une classe ou une structure qui enveloppe un objet ou une structure conforme au protocole. La classe wrapper, généralement nommée 'Any {nom du protocole}', est elle-même conforme au protocole et implémente ses fonctions en transférant tous les appels à l'objet interne. Essayez l'exemple ci-dessous dans un terrain de jeu:

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

Le type de printer est connu pour être AnyPrinter<Int> et peut être utilisé pour résumer toute implémentation possible du protocole Imprimante. Bien qu'AnyPrinter ne soit pas abstrait sur le plan technique, sa mise en œuvre est un simple type d'implémentation et peut être utilisée pour découpler les types d'implémentation des types qui les utilisent.

Une chose à noter est que AnyPrinter ne doit pas conserver explicitement l'instance de base. En fait, nous ne pouvons pas puisque nous ne pouvons pas déclarer AnyPrinter avoir un Printer<T> propriété. Au lieu de cela, nous obtenons un pointeur de fonction _print pour baser la fonction print. Appeler base.print _ sans l'invoquer renvoie une fonction où base est définie comme variable auto, et est donc conservée pour les invocations futures.

Une autre chose à garder à l'esprit est que cette solution est essentiellement une autre couche de dispatching dynamique, ce qui signifie un léger impact sur les performances. De plus, l'instance d'effacement de type nécessite de la mémoire supplémentaire par-dessus l'instance sous-jacente. Pour ces raisons, l'effacement de type n'est pas une abstraction gratuite.

Bien sûr, il y a du travail à faire pour mettre en place un effacement de type, mais cela peut être très utile si une abstraction de protocole générique est nécessaire. Ce modèle se trouve dans la bibliothèque standard Swift avec des types tels que AnySequence. En savoir plus: http://robnapier.net/erasure

PRIME:

Si vous décidez d’injecter partout la même implémentation de Printer, vous pouvez fournir un initialiseur de commodité pour AnyPrinter qui injecte ce type.

extension AnyPrinter {

    convenience init() {

        let nsLogger = NSLogger<T>()

        self.init(base: nsLogger)
    }
}

let printer = AnyPrinter<Int>()

printer.print(10) //prints 10 with NSLog

Cela peut être un moyen facile et DRY) d’exprimer des injections de dépendance pour les protocoles que vous utilisez dans votre application.

33
Patrick Goley

S'adressant à votre cas d'utilisation mis à jour:

(btw Printable est déjà un protocole standard Swift, vous voudrez donc probablement choisir un autre nom pour éviter toute confusion)

Pour appliquer des restrictions spécifiques aux implémenteurs de protocole, vous pouvez contraindre les typealias du protocole. Donc, pour créer votre collection de protocoles nécessitant que les éléments soient imprimables:

// 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
}

Maintenant, si vous voulez implémenter une collection qui ne peut contenir que des éléments imprimables:

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

Cependant, cela a probablement peu d’utilité, car vous ne pouvez pas contraindre les structures existantes de la collection Swift), mais seulement celles que vous implémentez.

Au lieu de cela, vous devez créer des fonctions génériques qui contraignent leur entrée aux collections contenant des éléments imprimables.

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