Sobrecarga de operador

Este tópico descreve como sobrecarregar operadores aritméticos em uma classe ou tipo de registro e no nível global.

Sintaxe

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

Comentários

Na sintaxe anterior, o operador-símbolo é um de +, -, *, / e =etc. A lista de parâmetros especifica os operandos na ordem em que aparecem na sintaxe usual desse operador. O corpo do método constrói o valor resultante.

As sobrecargas do operador para operadores devem ser estáticas. As sobrecargas do operador para operadores unários, como + e -, devem usar um bloco (~) no símbolo do operador para indicar que o operador é um operador unário e não um operador binário, conforme mostrado na declaração a seguir.

static member (~-) (v : Vector)

O código a seguir ilustra uma classe de vetor que tem apenas dois operadores, um para unário negativo e outro para multiplicação por um escalar. No exemplo, duas sobrecargas para multiplicação escalar são necessárias porque o operador deve funcionar independentemente da ordem em que o vetor e o escalar aparecem.

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

Como criar operadores

Você pode sobrecarregar todos os operadores padrão, mas também pode criar operadores com base nas sequências de determinados caracteres. Os caracteres de operador permitidos são !, $, %, &, *, +, -, ., /, <, =, >, ?, @, ^, | e ~. O caractere ~ tem o significado especial de tornar um operador unário e não faz parte da sequência de caracteres do operador. Nem todos os operadores podem ser unários.

Dependendo da sequência de caracteres exata que você usa, o operador terá uma certa precedência e associatividade. A associatividade pode ser da esquerda para a direita ou da direita para a esquerda, e é usada sempre que operadores do mesmo nível de precedência aparecem em sequência sem parênteses.

O caractere . do operador não afeta a precedência, de modo que, por exemplo, se você quiser definir uma versão de multiplicação própria que tenha a mesma precedência e associatividade que a multiplicação comum, poderá criar operadores como .*.

O operador $ deve ficar sozinho e sem símbolos adicionais.

Uma tabela que mostra a precedência de todos os operadores em F# pode ser encontrada na Referência de Símbolo e Operador.

Nomes de operador sobrecarregados

Quando o compilador F# compila uma expressão de operador, ele gera um método que tem um nome gerado pelo compilador para esse operador. Esse é o nome que aparece na linguagem intermediária comum (CIL) para o método e também na reflexão e no IntelliSense. Normalmente, você não precisa usar esses nomes no código F#.

A tabela a seguir mostra os operadores padrão e seus nomes gerados correspondentes.

Operador Nome gerado
[] 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

Observe que o operador not em F# não emite op_Inequality porque não é um operador simbólico. É uma função que emite IL que nega uma expressão booliana.

Outras combinações de caracteres de operador que não estão listados aqui podem ser usadas como operadores e ter nomes compostos pela concatenação de nomes para os caracteres individuais da tabela a seguir. Por exemplo, +! se torna op_PlusBang.

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

Operadores de prefixo e infixo

Esperam-se que os operadores de prefixo sejam colocados na frente de um operando ou operandos, assim como uma função. Esperam-se que os operadores de infixo sejam colocados entre os dois operandos.

Apenas determinados operadores podem ser usados como operadores de prefixo. Alguns operadores sempre são operadores de prefixo, outros podem ser infixos ou prefixos, e o restante são operadores sempre de infixo. Operadores que começam com !, exceto !=, e o operador ~, ou sequências repetidas de, são sempre operadores de~ prefixo. Os operadores +, -, +., -., &, &&, % e %% podem ser operadores de prefixo ou operadores de infixo. Você distingue a versão de prefixo desses operadores da versão de infixo adicionando um ~ ao início de um operador de prefixo quando ele é definido. O ~ não é usado quando você usa o operador, somente quando ele é definido.

Exemplo

O código a seguir ilustra o uso da sobrecarga de operador para implementar um tipo de fração. Uma fração é representada por um numerador e um denominador. A função hcf é usada para determinar o fator comum mais alto, que é usado para reduzir frações.

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

Saída:

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

Operadores no nível global

Você também pode definir operadores no nível global. O código a seguir define um operador +?.

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

A saída do código acima é 12.

Você pode redefinir os operadores aritméticos regulares dessa maneira porque as regras de escopo para F# determinam que os operadores que acabam de ser definidos têm precedência sobre os operadores internos.

A palavra-chave inline geralmente é usada com operadores globais, que costumam ser pequenas funções mais bem integradas ao código de chamada. Tornar as funções do operador embutidas também permite que elas trabalhem com parâmetros de tipo estaticamente resolvidos para produzir código genérico estaticamente resolvido. Para mais informações, confira Funções embutidas e parâmetros de tipo estaticamente resolvidos.

Confira também