Överlagring av operator

Det här avsnittet beskriver hur du överbelastar aritmetiska operatorer i en klass eller posttyp och på global nivå.

Syntax

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

Kommentarer

I den tidigare syntaxen är operatorsymbolen en av +, -, *, /, och =så vidare. Parameterlistan anger operanderna i den ordning de visas i den vanliga syntaxen för operatorn. Metoden-body konstruerar det resulterande värdet.

Operatoröverbelastningar för operatorer måste vara statiska. Operatoröverbelastningar för unary-operatorer, till exempel + och -, måste använda en tilde (~) i operatorsymbolen för att indikera att operatorn är en unary-operator och inte en binär operator, som visas i följande deklaration.

static member (~-) (v : Vector)

Följande kod illustrerar en vektorklass som bara har två operatorer, en för unary minus och en för multiplikation av en skalär. I exemplet behövs två överlagringar för skalär multiplikation eftersom operatorn måste fungera oavsett i vilken ordning vektorn och skalären visas.

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

Skapa nya operatorer

Du kan överbelasta alla standardoperatorer, men du kan också skapa nya operatorer av sekvenser med vissa tecken. Tillåtna operatortecken är !, $, %, &, *, +, -, ., /, <, =, >, ?, @, ^, |och ~. Tecknet ~ har den speciella innebörden att göra en operator unary och är inte en del av operatorns teckensekvens. Alla operatorer kan inte göras oanade.

Beroende på vilken teckensekvens du använder har operatorn en viss prioritet och associativitet. Associativitet kan vara antingen vänster till höger eller höger till vänster och används när operatorer med samma prioritetsnivå visas i sekvens utan parenteser.

Operatortecknet . påverkar inte prioriteten, så om du till exempel vill definiera din egen version av multiplikation som har samma prioritet och associativitet som vanlig multiplikation kan du skapa operatorer som .*.

Operatorn $ måste stå ensam och utan ytterligare symboler.

En tabell som visar prioriteten för alla operatorer i F# finns i symbol- och operatorreferens.

Överlagrade operatornamn

När F#-kompilatorn kompilerar ett operatoruttryck genererar den en metod som har ett kompilatorgenererat namn för operatorn. Det här är namnet som visas i det gemensamma mellanliggande språket (CIL) för metoden, och även i reflektion och IntelliSense. Normalt behöver du inte använda dessa namn i F#-kod.

I följande tabell visas standardoperatorerna och deras motsvarande genererade namn.

Operator Genererat namn
[] 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

Observera att operatorn not i F# inte genererar op_Inequality eftersom den inte är en symbolisk operator. Det är en funktion som genererar IL som negerar ett booleskt uttryck.

Andra kombinationer av operatortecken som inte visas här kan användas som operatorer och har namn som består av sammanfogande namn för de enskilda tecknen från följande tabell. Till exempel +! blir op_PlusBang.

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

Prefix- och infixoperatorer

Prefixoperatorer förväntas placeras framför en operande eller operander, ungefär som en funktion. Infixoperatorer förväntas placeras mellan de två operanderna.

Endast vissa operatorer kan användas som prefixoperatorer. Vissa operatorer är alltid prefixoperatorer, andra kan vara infix eller prefix och resten är alltid infixoperatorer. Operatorer som börjar med !, förutom !=, och operatorn ~, eller upprepade sekvenser av~, är alltid prefixoperatorer. Operatorerna +, -, +., -., &, &&, %och %% kan vara prefixoperatorer eller infixoperatorer. Du skiljer prefixversionen av dessa operatorer från infixversionen genom att lägga till en ~ i början av en prefixoperator när den definieras. ~ Används inte när du använder operatorn, bara när den har definierats.

Exempel

Följande kod illustrerar användningen av operatoröverlagring för att implementera en bråktyp. En bråkdel representeras av en täljare och en nämnare. Funktionen hcf används för att fastställa den högsta gemensamma faktorn, som används för att minska bråk.

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

Produktionen:

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

Operatörer på global nivå

Du kan också definiera operatorer på global nivå. Följande kod definierar en operator +?.

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

Utdata från koden ovan är 12.

Du kan omdefiniera de vanliga aritmetiska operatorerna på det här sättet eftersom omfångsreglerna för F# avgör att nyligen definierade operatorer har företräde framför de inbyggda operatorerna.

Nyckelordet inline används ofta med globala operatorer, som ofta är små funktioner som är bäst integrerade i anropskoden. Genom att göra operatorfunktionerna infogade kan de också arbeta med statiskt lösta typparametrar för att producera statiskt löst allmän kod. Mer information finns i Infogade funktioner och statiskt lösta typparametrar.

Se även