Overload degli operatori

In questo argomento viene descritto come eseguire l'overload degli operatori aritmetici in un tipo di classe o di record e a livello globale.

Sintassi

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

Osservazioni:

Nella sintassi precedente, operator-symbol è uno di +, -, *, /, = e così via. parameter-list specifica gli operandi nell'ordine in cui vengono visualizzati nella sintassi consueta per tale operatore. Il metod-body costruisce il valore risultante.

Gli overload degli operatori per gli operatori devono essere statici. Gli overload degli operatori unari, ad esempio + e -, devono usare una tilde (~) in operator-symbol per indicare che l'operatore è un operatore unario e non un operatore binario, come illustrato nella dichiarazione seguente.

static member (~-) (v : Vector)

Nel codice seguente viene illustrata una classe di vettori con solo due operatori, uno per meno unario e uno per la moltiplicazione per un valore scalare. Nell'esempio, sono necessari due overload per la moltiplicazione scalare poiché l'operatore deve funzionare indipendentemente dall'ordine in cui vengono visualizzati il vettore e lo scalare.

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

Creazione di nuovi operatori

È possibile eseguire l'overload di tutti gli operatori standard, ma anche creare nuovi operatori da sequenze di determinati caratteri. I caratteri di operatore consentiti sono !, $, %, &, *, +, -, ., /, <, =, >, ?, @, ^, | e ~. Il carattere ~ ha il significato particolare di rendere un operatore unario e non fa parte della sequenza di caratteri dell'operatore. Non tutti gli operatori possono essere resi unari.

A seconda della sequenza di caratteri esatta usata, l'operatore avrà una certa precedenza e associatività. L'associatività può essere da sinistra a destra o da destra a sinistra e viene usata ogni volta che gli operatori dello stesso livello di precedenza vengono visualizzati in sequenza senza parentesi.

Il carattere dell'operatore . non influisce sulla precedenza in modo che, ad esempio, se si vuole definire la propria versione di moltiplicazione con la stessa precedenza e associatività della moltiplicazione ordinaria, è possibile creare operatori come .*.

L'operatore $ deve essere autonomo e senza simboli aggiuntivi.

Una tabella che mostra la precedenza di tutti gli operatori in F# è reperibile in Riferimenti per simboli e operatori.

Nomi di operatori di cui è stato eseguito l'overload

Quando il compilatore F# compila un'espressione di operatore, genera un metodo con un nome generato dal compilatore per tale operatore. Questo è il nome visualizzato in Common Intermediate Language (CIL) per il metodo, come anche in reflection e IntelliSense. In genere non è necessario usare questi nomi nel codice F#.

La tabella seguente illustra gli operatori standard e i nomi generati corrispondenti.

Operatore Nome generato
[] 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

Si noti che l'operatore not in F# non genera op_Inequality perché non è un operatore simbolico. Si tratta di una funzione che genera il linguaggio intermedio che nega un'espressione booleana.

Altre combinazioni di caratteri dell'operatore non elencate qui, possono essere usate come operatori e hanno nomi costituiti dalla concatenazione di nomi per i singoli caratteri della tabella seguente. Ad esempio +! diventa op_PlusBang.

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

Operatori prefisso e infisso

Gli operatori prefisso devono essere posizionati davanti a un operando o agli operandi, in modo simile a una funzione. Gli operatori prefisso devono essere posizionati tra due operandi.

Solo alcuni operatori possono essere usati come operatori prefisso. Alcuni operatori sono sempre operatori prefisso, altri possono essere infissi o prefissi e gli altri sono sempre operatori prefisso. Gli operatori che iniziano con !, ad eccezione di !=, e l'operatore ~, o sequenze ripetute di ~, sono sempre operatori prefisso. Gli operatori +, -, +., -., &, &&, % e %% possono essere operatori prefisso o infisso. È possibile distinguere la versione prefisso di tali operatori dalla versione infisso aggiungendo un ~ all'inizio di un operatore prefisso quando viene definito. ~ non viene usato quando si usa l'operatore, solo quando viene definito.

Esempio

Il codice seguente illustra l'uso dell'overload degli operatori per implementare un tipo frazionario. Una frazione è rappresentata da un numeratore e un denominatore. La funzione hcf viene usata per determinare il fattore comune più elevato, usato per ridurre le frazioni.

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

Operatori a livello globale

È anche possibile definire operatori a livello globale. Il codice seguente definisce il parametro +?.

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

L'output del codice precedente è 12.

È possibile ridefinire gli operatori aritmetici regolari in questo modo perché le regole di ambito per F# determinano che gli operatori appena definiti hanno la precedenza sugli operatori predefiniti.

La parola chiave inline viene spesso usata con operatori globali, che spesso sono funzioni di piccole dimensioni che sono meglio integrate nel codice chiamante. La creazione di funzioni dell'operatore inline consente anche di lavorare con parametri di tipo risolti in modo statico per produrre codice generico risolto in modo statico. Per altre informazioni, vedere Funzioni inline e Parametri di tipo risolti in modo statico.

Vedi anche