Opérateurs personnalisés en Swift

Les classes et les structures peuvent fournir leurs propres implémentation d’opérateur existant ou en créer des totalement nouveaux en fonction des besoins. Dans ce cours nous allons étudier comment fonctionnent les opérateurs et comment les utiliser correctement.

Surcharge des opérateurs

Nous allons commencer par vous exposer un example qui créer un opérateur personnalisé pour effectuer une somme (+). L’addition en arithmétique est un opérateur binaire car il utilise 2 éléments et il est infixée car l’opérateur (+) se trouve entre ces deux nombres.

L’example du dessous définit un structure Vecteur2D pour une position dans un espace en 2 dimension (x, y), suivit par la définition d’une fonction opérateur pour permettre la somme de 2 instances de Vecteur2D :

struct Vecteur2D {
  var x = 0.0
  var y = 0.0
}

func + (left: Vecteur2D, right: Vecteur2D) -> Vecteur2D {
  return Vecteur2D(x: left.x + right.x, y: left.y + right.y)
}

La fonction opérateur est définit comme une fonction globale avec un nom qui correspond à l’opérateur que l’on souhaite surcharger (+). Comme l’addition est un opérateur binaire, cette fonction opérateur prends 2 paramètres d’entrée de type Vecteur2D et retourne une seule valeur aussi de type Vecteur2D.

Dans cette implémentation, les paramètres d’entrée sont nommés left et right pour représenter le Vecteur2D qui va être du coté gauche et celui du coté droit de l’opérateur +. La fonction retourne un nouveau Vecteur2D dont les valeurs x et y correspondent aux sommes de x et y des propriétés des vecteurs d’entrée.

La fonction est définit de manière globale, au lieu d’être une méthode d’instance d’un Vecteur2D, de tel sorte que l’opérateur peut être utilisé comme un opérateur infixé entre 2 Vecteur2D existant :

let vecteur1       = Vecteur2D(x: 3.0, y: 1.0)
let vecteur2       = Vecteur2D(x: 2.0, y: 4.0)
let vecteurCombine = vecteur1 + vecteur2
// vecteurCombine est un Vecteur2D avec (5.0, 5.0) comme valeurs

Cet example additionne le vecteur (3.0, 1.0) avec le vecteur (2.0, 4.0) pour donner le vecteur (5.0, 5.0).

Opérateurs préfixés et postfixés

L’example précédent montre la manière dont on implémente un opérateur binaire infixé. Mais les classes et les structures peuvent aussi fournir des implémentation d’opérateur unaire. Un opérateur unaire opère sur un seul élément et non pas deux comme un opérateur binaire. Ils sont préfixés si il précèdent l’élément (par example -a) ou postfixés si ils suivent l’élément (comme par exemple i++).

Pour implémenter un opérateur unaire préfixé ou postfixé il suffit de rajouter le mot clé prefix ou postfix devant la fonction opérateur :

prefix func - (vector: Vecteur2D) -> Vecteur2D {
  return Vecteur2D(x: -vector.x, y: -vector.y)
}

Cet example implémente l’opérateur unaire moins (-a) pour les instance de Vecteur2D. Cet opérateur est préfixé et utilise donc le mot clé prefix.

Pour de simple valeur numérique l’opérateur unaire moins convertis des nombres positifs vers leurs équivalence negative et réciproquement. L’implémentation équivalente pour des vecteurs est d’effectuer cette opération sur les propriétés x et y :

let positive = Vecteur2D(x: 3.0, y: 4.0)
let negative = -positive
// Un vecteur négatif avec (-3.0, -4.0) comme valeur
let alsoPositive = -negative
// Un vecteur positif avec (3.0, 4.0) comme valeur

Opérateurs d’affectations composés

Les opérateurs d’affectations composés permettent de combiner les affectations (=) avec d’autres opérations. Par example, l’addition (en tant qu’opérateur +) peut être combiner à l’assignation (=) pour former une affectation d’addition (+=) qui effectue les deux en une seule opération. Un opérateur d’affectation composé est déclaré avec l’utilisation du mot clé inout sur l’opération gauche pour lui permettre d’être modifié dans le corps de la fonction. L’example du dessous implémente une fonction pour créer une affectation d’addition pour un Vecteur2D :

func += (inout left: Vecteur2D, right: Vecteur2D) {
  left = left + right
}

Comme l’addition a déjà été implémentée plus tôt vous n’avez pas besoin de ré-implémenté l’opérateur addition.

var original    = Vecteur2D(x: 1.0, y: 2.0)
let vectorToAdd = Vecteur2D(x: 3.0, y: 4.0)

original += vectorToAdd
// original vaut maintenant (4.0, 6.0)

Vous pouvez aussi combiner les déclarations avec soit le mot clé prefix ou postfix comme dans cette implémentation de l’opérateur incrémenter préfixé (++a) pour un Vecteur2D :

prefix func ++ (inout vector: Vecteur2D) -> Vecteur2D {
  vector += Vecteur2D(x: 1.0, y: 1.0)

  return vector
}

var toIncrement = Vecteur2D(x: 3.0, y: 4.0)
let afterIncrement = ++toIncrement

// toIncrement vaut maintenant (4.0, 5.0)
// afterIncrement vaut aussi (4.0, 5.0)

Opérateur d’équivalence

Les classes et les structures personnalisées n’ont pas d’implémentation par défaut d’opérateurs d’équivalence, connu sous le nom « égal à » (opérateur ==) et « non égal à » (opérateur !=). Il est impossible en Swift de deviner ce qui est considéré comme « égal » ou non pour vos propres types personnalisés, parce que le sens de « égal » dépend des rôles que ces types jouent dans votre code.

Pour utiliser l’opérateur d’équivalence pour vérifier vos types il faut fournir une implémentation de l’opérateur de manière infixé :

func == (left: Vecteur2D, right: Vecteur2D) -> Bool {
  return (left.x == right.x) && (left.y == right.y)
}

func != (left: Vecteur2D, right: Vecteur2D) -> Bool {
  return !(left == right)
}

Dans cet example on implémente les opérateurs « égal à » (==) et « non égal à » (!=) pour vérifier si 2 Vecteur2D sont équivalent ou non. Dans le contexte, un Vecteur2D est considéré comme « égal à » une autre instance de Vecteur2D si leurs propriétés x et y sont égaux. Dans la même logique l’opérateur « non égale à » retourne l’inverse.

Vous pouvez maintenant utiliser ces opérateur pour vérifier si 2 instance de Vecteur2D sont équivalent :

let twoThree        = Vecteur2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vecteur2D(x: 2.0, y: 3.0)

if twoThree == anotherTwoThree {
    print("Ces deux vecteurs sont équivalent.")
}
// Affiche : "Ces deux vecteurs sont équivalent."

Opérateurs personnalisés

Vous pouvez déclarer et implémenter vos propres opérateurs personnalisés en plus des opérateurs standards fournis en Swift.

Les nouveaux opérateurs peuvent être utiliser en utilisant le mot clé operator et marqué avec le mot clé prefix, infix ou postfix. De plus il ne peut être nommé qu’en utilisant la liste de caractère fournit ici :

prefix operator +++ {}

L’example du dessus définit un nouvel opérateur préfixé appelé +++. Il n’a aucune signification en Swift et il aura sa propre signification dans l’example d’après en fonction du contexte : ici avec Vecteur2D. Pour illustrer cet example +++ est traité comme un opérateur pour doubler la valeur. Il double la valeur des propriétés x et y du Vecteur2D :

prefix func +++ (inout vector: Vecteur2D) -> Vecteur2D {
    vector += vector
    return vector
}

var toBeDoubled   = Vecteur2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled vaut (2.0, 8.0)
// afterDoubling vaut aussi (2.0, 8.0)

Priorité et associativité des opérateurs infixés personnalisés

Les opérateurs infixés personnalisés peuvent aussi spécifié une priorité et une associativité. Allez voir la documentation officielle pour une explication sur la manière dont l’associativité et la priorité affectent les opérateurs.

Les valeurs possible pour l’associativité (associativity) sont left, right et none. L’associativité à gauche associe l’élément gauche si il est écrit à coté d’autre opérateur d’associativité à gauche de même priorité. De la même manière un opérateur avec une associativité à droite associe l’élément droit si il est écrit à coté d’autre opérateur d’associativité à droite. Un opérateur avec aucune associativité ne peut pas être écrit à coté d’autres opérateurs avec la même priorité.

Il n’y a pas d’associativité par défaut si il n’est pas spécifié. La priorité est par défaut définit à 100.

L’example suivant définit à nouvel opérateur infixé appelé +- avec une associativité à gauche et une priorité à 140 :

infix operator +- { associativity left precedence 140 }

func +- (left: Vecteur2D, right: Vecteur2D) -> Vecteur2D {
  return Vecteur2D(x: left.x + right.x, y: left.y - right.y)
}

let firstVector     = Vecteur2D(x: 1.0, y: 2.0)
let secondVector    = Vecteur2D(x: 3.0, y: 4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector est un Vecteur2D avec comme valeur (4.0, -2.0)

Cet opérateur addition les propriétés x des 2 vecteurs et en même temps soustrait la propriété y du deuxième vecteur avec le premier. Comme c’est un opérateur « additif » on lui a donné la même associativité et priorité (left et 140) comme les autres opérateurs du même type (+ et -). Pour une liste complète des priorités et associativités des opérateurs fournit par Swift dans la librairie standard, jetez un coup d’oeil ici.

1 Etoile2 Etoiles3 Etoiles4 Etoiles5 Etoiles (2 votes, average: 5,00 out of 5)
Loading...

2 commentaires

Time limit is exhausted. Please reload CAPTCHA.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

  1. Pascal · 29 février 2016

    Super article. Très intéressant et bien expliqué.
    Dommage que l’orthographe soit si mauvaise, mais cela ne gêne pas la compréhension du code.

    • yannickl · 29 février 2016

      Merci pour votre commentaire. Je sais que l’orthographe n’est pas mon fort et que je ne prend pas forcément suffisamment de temps de relecture. Je tâcherais de m’appliquer un peu plus la prochaine fois.