Overbelasting van operator

In dit onderwerp wordt beschreven hoe u rekenkundige operatoren in een klasse of recordtype overbelast, en op globaal niveau.

Syntaxis

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

Opmerkingen

In de vorige syntaxis is het operatorsymbool een van +, -, *, /, =enzovoort. De parameterlijst geeft de operanden op in de volgorde waarin ze worden weergegeven in de gebruikelijke syntaxis voor die operator. Met de methodebody wordt de resulterende waarde samengesteld.

Operatoroverbelastingen voor operators moeten statisch zijn. Operatoroverbelastingen voor unaire operatoren, zoals + en -, moeten een tilde (~) in het operatorsymbool gebruiken om aan te geven dat de operator een unaire operator is en geen binaire operator, zoals wordt weergegeven in de volgende declaratie.

static member (~-) (v : Vector)

De volgende code illustreert een vectorklasse met slechts twee operatoren, één voor unaire min en één voor vermenigvuldiging door een scalaire waarde. In het voorbeeld zijn twee overbelastingen voor scalaire vermenigvuldiging nodig, omdat de operator moet werken, ongeacht de volgorde waarin de vector en scalaire worden weergegeven.

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

Nieuwe operators maken

U kunt alle standaardoperators overbelasten, maar u kunt ook nieuwe operators uit reeksen van bepaalde tekens maken. Toegestane operatortekens zijn , , , , &*, -+, ., , =^/<@?>, |en .~%$! Het ~ teken heeft de speciale betekenis van het maken van een operator unaire en maakt geen deel uit van de tekenreeks van de operator. Niet alle operators kunnen unaire worden gemaakt.

Afhankelijk van de exacte tekenreeks die u gebruikt, heeft uw operator een bepaalde prioriteit en associativiteit. Associativiteit kan van links naar rechts of van rechts naar links worden gebruikt en wordt gebruikt wanneer operatoren van hetzelfde prioriteitsniveau in volgorde worden weergegeven zonder haakjes.

Het operatorteken . heeft geen invloed op de prioriteit, zodat u bijvoorbeeld een eigen versie van vermenigvuldiging wilt definiëren met dezelfde prioriteit en associativiteit als gewone vermenigvuldiging, kunt u operatoren maken zoals .*.

De $ operator moet zelfstandig en zonder extra symbolen staan.

Een tabel met de prioriteit van alle operatoren in F# vindt u in symbool- en operatorverwijzing.

Namen van overbelaste operatoren

Wanneer de F#-compiler een operatorexpressie compileert, wordt er een methode gegenereerd met een door compiler gegenereerde naam voor die operator. Dit is de naam die wordt weergegeven in de algemene tussenliggende taal (CIL) voor de methode, en ook in weerspiegeling en IntelliSense. Normaal gesproken hoeft u deze namen niet te gebruiken in F#-code.

In de volgende tabel ziet u de standaardoperators en de bijbehorende gegenereerde namen.

Operator Gegenereerde naam
[] 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

Houd er rekening mee dat de not operator in F# geen op_Inequality symbolische operator is. Het is een functie die IL verzendt die een Boole-expressie negeert.

Andere combinaties van operatortekens die hier niet worden vermeld, kunnen worden gebruikt als operatoren en namen hebben die bestaan uit het samenvoegen van namen voor de afzonderlijke tekens uit de volgende tabel. Bijvoorbeeld +! wordt op_PlusBang.

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

Operatoren voor voorvoegsel en invoegsel

Voorvoegseloperatoren worden naar verwachting vóór een operand of operands geplaatst, net als een functie. Infixoperators worden naar verwachting tussen de twee operanden geplaatst.

Alleen bepaalde operators kunnen worden gebruikt als voorvoegseloperators. Sommige operators zijn altijd voorvoegseloperators, andere kunnen invoegsel of voorvoegsel zijn en de rest zijn altijd invoegseloperators. Operators die beginnen met !, behalve !=, en de operator ~, of herhaalde reeksen, zijn altijd voorvoegseloperators~. De operators , -, , -.+., , &, &&, en %%%kunnen voorvoegseloperators +of invoegseloperators zijn. U onderscheidt de voorvoegselversie van deze operators van de invoegselversie door een ~ aan het begin van een voorvoegseloperator toe te voegen wanneer deze is gedefinieerd. De ~ operator wordt niet gebruikt wanneer u de operator gebruikt, alleen wanneer deze is gedefinieerd.

Opmerking

De volgende code illustreert het gebruik van overbelasting van operatoren om een breuktype te implementeren. Een breuk wordt vertegenwoordigd door een teller en een noemer. De functie hcf wordt gebruikt om de hoogste gemeenschappelijke factor te bepalen, die wordt gebruikt om breuken te verminderen.

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

Uitvoer:

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

Operators op globaal niveau

U kunt ook operators definiëren op globaal niveau. Met de volgende code wordt een operator +?gedefinieerd.

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

De uitvoer van de bovenstaande code is 12.

U kunt de reguliere rekenkundige operatoren op deze manier opnieuw definiëren, omdat de bereikregels voor F# dicteren dat nieuw gedefinieerde operators voorrang hebben op de ingebouwde operators.

Het trefwoord inline wordt vaak gebruikt met globale operators, wat vaak kleine functies zijn die het beste zijn geïntegreerd in de aanroepende code. Door operatorfuncties inline te maken, kunnen ze ook werken met statisch opgeloste typeparameters om statische opgeloste algemene code te produceren. Zie Inline-functies en statisch opgeloste typeparameters voor meer informatie.

Zie ook