Contraintes de type multiple dans Swift

133

Disons que j'ai ces protocoles:

protocol SomeProtocol {

}

protocol SomeOtherProtocol {

}

Maintenant, si je veux une fonction qui prend un type générique, mais que ce type doit être conforme, SomeProtocolje pourrais faire:

func someFunc<T: SomeProtocol>(arg: T) {
    // do stuff
}

Mais existe-t-il un moyen d'ajouter une contrainte de type pour plusieurs protocoles?

func bothFunc<T: SomeProtocol | SomeOtherProtocol>(arg: T) {

}

Des choses similaires utilisent des virgules, mais dans ce cas, cela commencerait la déclaration d'un type différent. Voici ce que j'ai essayé.

<T: SomeProtocol | SomeOtherProtocol>
<T: SomeProtocol , SomeOtherProtocol>
<T: SomeProtocol : SomeOtherProtocol>
Logan
la source
C'est une question particulièrement pertinente car la documentation Swift ne le mentionne pas dans le chapitre sur les génériques ...
Bruno Philipe

Réponses:

241

Vous pouvez utiliser une clause where qui vous permet de spécifier autant d'exigences que vous le souhaitez (qui doivent toutes être remplies) séparées par des virgules

Swift 2:

func someFunc<T where T:SomeProtocol, T:SomeOtherProtocol>(arg: T) {
    // stuff
}

Swift 3 et 4:

func someFunc<T: SomeProtocol & SomeOtherProtocol>(arg: T) {
    // stuff
}

ou la clause where la plus puissante:

func someFunc<T>(arg: T) where T:SomeProtocol, T:SomeOtherProtocol{
    // stuff
}

Vous pouvez bien sûr utiliser la composition de protocole (par exemple, protocol<SomeProtocol, SomeOtherProtocol>), mais c'est un peu moins flexible.

L'utilisation wherevous permet de traiter les cas où plusieurs types sont impliqués.

Vous voudrez peut-être toujours composer des protocoles pour les réutiliser à plusieurs endroits, ou simplement donner au protocole composé un nom significatif.

Swift 5:

func someFunc(arg: SomeProtocol & SomeOtherProtocol) { 
    // stuff
}

Cela semble plus naturel car les protocoles sont à côté de l'argument.

Jiaaro
la source
Décidément, ce n'est pas logique, mais bon à savoir, je veux juste être l'un des spammeurs de remerciement pour celui-ci, je n'ai pas réalisé cela en un mois depuis que j'en avais besoin.
Mathijs Segers
3
Un moyen de faire la même chose avec les classes et les structures dans l'expression de contraint de type? par exemple <T where T:SomeStruct, T:AnotherStruct>? Pour les classes, le compilateur semble interpréter cela comme disant "T est une sous-classe des deux", et pour les structures, il se plaint simplement de cela "Type 'T' constrained to non-protocol type".
Jarrod Smith
Pour l'exemple spécifique de l'OP: la composition du protocole de la question devrait être la méthode préférable: la solution ci-dessus est valide, mais, à mon humble avis, encombre inutilement la signature de la fonction. De plus, l'utilisation de la composition du protocole comme, par exemple, une contrainte de type, vous permet toujours d'utiliser la whereclause pour un type supplémentaire / une autre utilisation, par exemple func someFunc<U, T: protocol<SomeProtocol, SomeOtherProtocol> where T.SubType == U>(arg: T, arg2: U) { ... }pour les typealias SubTypedans par exemple SomeProtocol.
dfri
1
On dirait que cela est obsolète dans swift3 et recommande d'utiliser: func someFunc <T> (arg: T) où T: SomeProtocol, T: SomeOtherProtocol {
Cristi Băluță
2
Existe-t-il un moyen de dire à Swift que T doit être d'un certain type d'objet ET implémenter un certain protocole?
Georg
73

Vous avez deux possibilités:

  1. Vous utilisez une clause where comme indiqué dans la réponse de Jiaaro:

    func someFunc<T where T : SomeProtocol, T : SomeOtherProtocol>(arg: T) {
        // do stuff
    }
  2. Vous utilisez un type de composition de protocole :

    func someFunc<T : protocol<SomeProtocol, SomeOtherProtocol>>(arg: T) {
        // do stuff
    }
Jean-Philippe Pellet
la source
2
imo la deuxième solution est plus jolie, j'irais pour cette réponse, elle est également plus complète présentant deux options
Mathijs Segers
2
Le numéro 2 est le seul qui fonctionne pour moi sous Swift 2 lors de la déclaration d'un fichier typealias. Merci!
Bruno Philipe
19

L'évolution vers Swift 3.0 apporte quelques changements. Nos deux choix sont maintenant un peu différents.

Utilisation d'une whereclause dans Swift 3.0:

La whereclause est maintenant déplacée à la fin d'une signature de fonction pour améliorer la lisibilité. L'héritage de plusieurs protocoles ressemble maintenant à ceci:

func someFunc<T>(arg: T) where T:SomeProtocol, T:SomeOtherProtocol {

}

Utilisation de la protocol<>construction dans Swift 3.0:

La composition utilisant la protocol<>construction est obsolète. Le plus tôt protocol<SomeProtocol, SomeOtherProtocol>ressemble maintenant à ceci:

func someFunc<T:SomeProtocol & SomeOtherProtocol>(arg: T) {

}

Références.

Plus d'informations sur les changements pour wheresont ici: https://github.com/apple/swift-evolution/blob/master/proposals/0081-move-where-expression.md

Et, plus sur les changements pour la construction de protocole <> sont ici: https://github.com/apple/swift-evolution/blob/master/proposals/0095-any-as-existential.md

ncke
la source
13

Swift 3 propose jusqu'à 3 façons différentes de déclarer votre fonction.

protocol SomeProtocol {
    /* ... */
}

protocol SomeOtherProtocol {
    /* ... */        
}

1. Utilisation de l' &opérateur

func someFunc<T: SomeProtocol & SomeOtherProtocol>(arg: T) {
    /* ... */
}

2. Utilisation de la whereclause

func someFunc<T>(arg: T) where T: SomeProtocol, T: SomeOtherProtocol {
    /* ... */
}

3. Utilisation de la whereclause et de l' &opérateur

func someFunc<T>(arg: T) where T: SomeProtocol & SomeOtherProtocol {
    /* ... */        
}

Notez également que vous pouvez utiliser typealiaspour raccourcir votre déclaration de fonction.

typealias RequiredProtocols = SomeProtocol & SomeOtherProtocol

func someFunc<T: RequiredProtocols>(arg: T) {
    /* ... */   
}
Imanou Petit
la source