Surcharge d'opérateur

Cette rubrique décrit comment surcharger des opérateurs arithmétiques dans un type de classe ou d’enregistrement, et au niveau global.

Syntaxe

// Overloading an operator as a class or record member.
static member (operator-symbols) (parameter-list) =
    method-body
// Overloading an operator at the global level
let [inline] (operator-symbols) parameter-list = function-body

Notes

Dans la syntaxe précédente, la valeur operator-symbol est l’une parmi +, -, *, /, =, etc. La valeur parameter-list spécifie les opérandes dans l’ordre dans lequel ils apparaissent dans la syntaxe habituelle de cet opérateur. La valeur method-body construit la valeur résultante.

Les surcharges des opérateurs doivent être statiques. Les surcharges des opérateurs unaires, tels que + et -, doivent utiliser un tilde (~) dans la valeur operator-symbol pour indiquer que l’opérateur est un opérateur unaire et non un opérateur binaire, comme indiqué dans la déclaration suivante.

static member (~-) (v : Vector)

Le code suivant illustre une classe de vecteur qui ne comporte que deux opérateurs, l’un pour le moins unaire et l’autre pour la multiplication par un scalaire. Dans l’exemple, deux surcharges pour la multiplication scalaire sont nécessaires, car l’opérateur doit fonctionner, quel que soit l’ordre dans lequel le vecteur et le scalaire apparaissent.

type Vector(x: float, y : float) =
   member this.x = x
   member this.y = y
   static member (~-) (v : Vector) =
     Vector(-1.0 * v.x, -1.0 * v.y)
   static member (*) (v : Vector, a) =
     Vector(a * v.x, a * v.y)
   static member (*) (a, v: Vector) =
     Vector(a * v.x, a * v.y)
   override this.ToString() =
     this.x.ToString() + " " + this.y.ToString()

let v1 = Vector(1.0, 2.0)

let v2 = v1 * 2.0
let v3 = 2.0 * v1

let v4 = - v2

printfn "%s" (v1.ToString())
printfn "%s" (v2.ToString())
printfn "%s" (v3.ToString())
printfn "%s" (v4.ToString())

Création de nouveaux opérateurs

Vous pouvez surcharger tous les opérateurs standard, mais vous pouvez également créer de nouveaux opérateurs à partir de séquences de certains caractères. Les caractères d’opérateur autorisés sont !, $, %, &, *, +, -, ., /, <, =, >, ?, @, ^, | et ~. La signification du caractère ~ est particulière : il rend un opérateur unaire et ne fait pas partie de la séquence de caractères d’opérateur. Tous les opérateurs ne peuvent pas être unaires.

Selon la séquence de caractères exacte utilisée, votre opérateur présente une certaine précédence et une certaine associativité. L’associativité peut être de gauche à droite ou de droite à gauche et est utilisée chaque fois que des opérateurs présentant le même niveau de précédence apparaissent en séquence sans parenthèses.

Le caractère d’opérateur . n’affecte pas la précédence ; par exemple, si vous souhaitez définir votre propre version de la multiplication qui présente la même précédence et la même associativité que la multiplication ordinaire, vous pouvez créer des opérateurs tels que .*.

L’opérateur $ doit être autonome et ne doit pas contenir de symbole supplémentaire.

Un tableau qui indique la précédence de tous les opérateurs dans F# se trouve dans Référence des opérateurs et des symboles.

Noms d’opérateurs surchargés

Lorsque le compilateur F# compile une expression d’opérateur, il génère une méthode dont le nom est généré par le compilateur pour cet opérateur. C’est le nom qui apparaît dans le CIL (Common Intermediate Language) pour la méthode, ainsi que dans la réflexion et IntelliSense. Normalement, vous n’avez pas besoin d’utiliser ces noms dans le code F#.

Le tableau suivant présente les opérateurs standard et leur nom généré correspondant.

Opérateur Nom généré
[] op_Nil
:: op_Cons
+ op_Addition
- op_Subtraction
* op_Multiply
/ op_Division
@ op_Append
^ op_Concatenate
% op_Modulus
&&& op_BitwiseAnd
||| op_BitwiseOr
^^^ op_ExclusiveOr
<<< op_LeftShift
~~~ op_LogicalNot
>>> op_RightShift
~+ op_UnaryPlus
~- op_UnaryNegation
= op_Equality
<= op_LessThanOrEqual
>= op_GreaterThanOrEqual
< op_LessThan
> op_GreaterThan
? op_Dynamic
?<- op_DynamicAssignment
|> op_PipeRight
<| op_PipeLeft
! op_Dereference
>> op_ComposeRight
<< op_ComposeLeft
<@ @> op_Quotation
<@@ @@> op_QuotationUntyped
+= op_AdditionAssignment
-= op_SubtractionAssignment
*= op_MultiplyAssignment
/= op_DivisionAssignment
.. op_Range
.. .. op_RangeStep

Notez que l’opérateur not dans F# n’émet pas op_Inequality, car il ne s’agit pas d’un opérateur symbolique. Il s’agit d’une fonction qui émet un IL qui annule une expression booléenne.

D’autres combinaisons de caractères d’opérateur qui ne sont pas répertoriés ici peuvent être utilisées en tant qu’opérateurs et comporter des noms créés par concaténation de noms pour les caractères individuels du tableau suivant. Par exemple, +! devient op_PlusBang.

Caractère d’opérateur Nom
> Greater
< Less
+ Plus
- Minus
* Multiply
/ Divide
= Equals
~ Twiddle
$ Dollar
% Percent
. Dot
& Amp
| Bar
@ At
^ Hat
! Bang
? Qmark
( LParen
, Comma
) RParen
[ LBrack
] RBrack

Opérateurs préfixes et infixes

Les opérateurs préfixes sont censés être placés devant un ou plusieurs opérandes, à l’instar d’une fonction. Les opérateurs infixes sont censés être placés entre les deux opérandes.

Seuls certains opérateurs peuvent être utilisés en tant qu’opérateurs préfixes. Certains opérateurs sont toujours des opérateurs préfixes, d’autres peuvent être infixes ou préfixes, et d’autres sont toujours des opérateurs infixes. Les opérateurs qui commencent par !, à l’exception de !=, et l’opérateur ~, ou les séquences répétées de ~, sont toujours des opérateurs préfixes. Les opérateurs +, -, +., -., &, &&, % et %% peuvent être des opérateurs préfixes ou infixes. Afin de faire la distinction entre la version préfixe de ces opérateurs et la version infixe, ajoutez la valeur ~ au début d’un opérateur préfixe lorsqu’il est défini. La valeur ~ n’est pas utilisée lorsque vous utilisez l’opérateur, mais uniquement lorsqu’il est défini.

Exemple

Le code suivant illustre l’utilisation de la surcharge d’opérateur pour implémenter un type de fraction. Une fraction est représentée par un numérateur et un dénominateur. La fonction hcf est utilisée pour déterminer le facteur commun le plus élevé, qui est utilisé pour réduire les fractions.

// Determine the highest common factor between
// two positive integers, a helper for reducing
// fractions.
let rec hcf a b =
  if a = 0u then b
  elif a<b then hcf a (b - a)
  else hcf (a - b) b

// type Fraction: represents a positive fraction
// (positive rational number).
type Fraction =
   {
      // n: Numerator of fraction.
      n : uint32
      // d: Denominator of fraction.
      d : uint32
   }

   // Produce a string representation. If the
   // denominator is "1", do not display it.
   override this.ToString() =
      if (this.d = 1u)
        then this.n.ToString()
        else this.n.ToString() + "/" + this.d.ToString()

   // Add two fractions.
   static member (+) (f1 : Fraction, f2 : Fraction) =
      let nTemp = f1.n * f2.d + f2.n * f1.d
      let dTemp = f1.d * f2.d
      let hcfTemp = hcf nTemp dTemp
      { n = nTemp / hcfTemp; d = dTemp / hcfTemp }

   // Adds a fraction and a positive integer.
   static member (+) (f1: Fraction, i : uint32) =
      let nTemp = f1.n + i * f1.d
      let dTemp = f1.d
      let hcfTemp = hcf nTemp dTemp
      { n = nTemp / hcfTemp; d = dTemp / hcfTemp }

   // Adds a positive integer and a fraction.
   static member (+) (i : uint32, f2: Fraction) =
      let nTemp = f2.n + i * f2.d
      let dTemp = f2.d
      let hcfTemp = hcf nTemp dTemp
      { n = nTemp / hcfTemp; d = dTemp / hcfTemp }

   // Subtract one fraction from another.
   static member (-) (f1 : Fraction, f2 : Fraction) =
      if (f2.n * f1.d > f1.n * f2.d)
        then failwith "This operation results in a negative number, which is not supported."
      let nTemp = f1.n * f2.d - f2.n * f1.d
      let dTemp = f1.d * f2.d
      let hcfTemp = hcf nTemp dTemp
      { n = nTemp / hcfTemp; d = dTemp / hcfTemp }

   // Multiply two fractions.
   static member (*) (f1 : Fraction, f2 : Fraction) =
      let nTemp = f1.n * f2.n
      let dTemp = f1.d * f2.d
      let hcfTemp = hcf nTemp dTemp
      { n = nTemp / hcfTemp; d = dTemp / hcfTemp }

   // Divide two fractions.
   static member (/) (f1 : Fraction, f2 : Fraction) =
      let nTemp = f1.n * f2.d
      let dTemp = f2.n * f1.d
      let hcfTemp = hcf nTemp dTemp
      { n = nTemp / hcfTemp; d = dTemp / hcfTemp }

   // A full set of operators can be quite lengthy. For example,
   // consider operators that support other integral data types,
   // with fractions, on the left side and the right side for each.
   // Also consider implementing unary operators.

let fraction1 = { n = 3u; d = 4u }
let fraction2 = { n = 1u; d = 2u }
let result1 = fraction1 + fraction2
let result2 = fraction1 - fraction2
let result3 = fraction1 * fraction2
let result4 = fraction1 / fraction2
let result5 = fraction1 + 1u
printfn "%s + %s = %s" (fraction1.ToString()) (fraction2.ToString()) (result1.ToString())
printfn "%s - %s = %s" (fraction1.ToString()) (fraction2.ToString()) (result2.ToString())
printfn "%s * %s = %s" (fraction1.ToString()) (fraction2.ToString()) (result3.ToString())
printfn "%s / %s = %s" (fraction1.ToString()) (fraction2.ToString()) (result4.ToString())
printfn "%s + 1 = %s" (fraction1.ToString()) (result5.ToString())

Output:

3/4 + 1/2 = 5/4
3/4 - 1/2 = 1/4
3/4 * 1/2 = 3/8
3/4 / 1/2 = 3/2
3/4 + 1 = 7/4

Opérateurs au niveau global

Vous pouvez également définir des opérateurs au niveau global. Le code suivant définit un opérateur +?.

let inline (+?) (x: int) (y: int) = x + 2*y
printf "%d" (10 +? 1)

La sortie du code ci-dessus est 12.

Vous pouvez redéfinir les opérateurs arithmétiques réguliers de cette façon, car selon les règles de portée pour F#, les opérateurs nouvellement définis prévalent sur les opérateurs intégrés.

Le mot clé inline est souvent utilisé avec des opérateurs globaux, qui constituent souvent de petites fonctions qui sont mieux intégrées dans le code appelant. L’inclusion de fonctions d’opérateur permet également d’utiliser des paramètres de type statiquement résolus pour produire du code générique statiquement résolu. Pour plus d’informations, consultez Fonctions incluses et Paramètres de type statiquement résolus.

Voir aussi