Surcharge d'opérateur (F#)
Cette rubrique décrit comment surcharger des opérateurs arithmétiques dans un type de classe ou d'enregistrement, et au niveau global.
// 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, operator-symbol peut être +, -, *, /, =, etc. parameter-list spécifie les opérandes dans l'ordre dans lequel elles apparaissent dans la syntaxe standard pour cet opérateur. method-body construit la valeur résultante.
Les surcharges d'opérateur pour les opérateurs doivent être statiques. Les surcharges d'opérateur pour les opérateurs unaires, notamment + et -, doivent utiliser un tilde (~) dans 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 vectorielle qui n'a que deux opérateurs : un pour l'opération 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 indépendamment de 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 d'opérateurs
Vous pouvez surcharger tous les opérateurs standard, mais vous pouvez aussi créer des opérateurs hors des séquences de certains caractères. Les caractères autorisés pour un opérateur sont !, %, &, *, +, -, ., /, <, =, >, ?, @, ^, |, et ~. Le caractère ~ a une signification spéciale et rend un opérateur unaire ; il ne fait pas partie de la séquence de caractères d'un opérateur. Tous les opérateurs ne peuvent être rendus unaires, comme décrit dans Opérateurs préfixés et infixe plus loin dans cette rubrique.
En fonction de la séquence exacte de caractères que vous utilisez, votre opérateur bénéficie d'un certain niveau de précédence et d'associativité. L'associativité peut être soit de gauche à droite, soit de droite à gauche. Elle est utilisée lorsque des opérateurs du même niveau de précédence apparaissent dans la séquence sans parenthèses.
L'opérateur . n'affecte pas la précédence. Ainsi, si vous définissez par exemple votre propre version de la multiplication avec le même niveau de précédence et d'associativité que la multiplication ordinaire, vous pouvez créer des opérateurs tels que .*.
Vous trouverez un tableau qui indique la précédence de tous les opérateurs en F# dans Référence des symboles et opérateurs (F#).
Noms d'opérateurs surchargés
Lorsque le compilateur F# compile une expression d'opérateur, il génère une méthode qui a un nom d'opérateur généré par le compilateur. Il s'agit du nom qui apparaît en langage MSIL (Microsoft Intermediate Language) pour la méthode, ainsi que dans la réflexion et dans IntelliSense. Normalement, vous n'avez pas besoin d'utiliser ces noms dans le code F#.
Le tableau suivant répertorie les opérateurs standard et les noms générés correspondants.
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 |
Il est possible d'utiliser d'autres combinaisons de caractères d'opérateur qui ne sont pas répertoriées ici comme opérateurs. Le nom de ces opérateurs est obtenu en concaténant les noms des 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 |
% |
Percent |
. |
Dot |
& |
Amp |
| |
Bar |
@ |
At |
^ |
Hat |
! |
Bang |
? |
Qmark |
( |
LParen |
, |
Comma |
) |
RParen |
[ |
LBrack |
] |
RBrack |
Opérateurs préfixés et infixe
Les opérateurs préfixés sont supposés être placés devant un ou des opérandes, comme une fonction. Les opérateurs infixes sont supposés être placés entre les deux opérandes.
Seuls certains opérateurs peuvent être utilisés comme opérateurs préfixés. Certains opérateurs sont toujours des opérateurs préfixés, d'autres peuvent être infixes ou préfixes, et le reste sont toujours des opérateurs infixes. Les opérateurs ! et ~, ou les séquences répétées de ces derniers, sont toujours des opérateurs préfixés. Les opérateurs +, -, +., -., &, &&, %, et %% et peuvent être des opérateurs préfixés ou des opérateurs infixe. Vous distinguez la version préfixée de ces opérateurs de la version infixée en ajoutant ~ au début d'un opérateur préfixé lorsqu'il est défini. Le symbole ~ n'est pas utilisé lorsque vous utilisez l'opérateur, 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 permet de 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())
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 de cette façon redéfinir les opérateurs arithmétiques habituels, puisque les règles de définition de la portée pour F# stipulent que les opérateurs nouvellement définis sont prioritaires sur les opérateurs intégrés.
Le mot clé inline est souvent utilisé avec des opérateurs globaux, qui sont souvent des petites fonctions qu'il est préférable d'intégrer dans le code appelant. Vous pouvez également rendre les fonctions d'opérateur inline pour qu'elles fonctionnent avec des paramètres de type résolus statiquement afin de produire du code générique résolu statiquement. Pour plus d'informations, consultez Fonctions inline (F#) et Paramètres de type résolus statiquement (F#).