Поделиться через


Перегрузка операторов (F#)

В этом разделе описывается перегрузка арифметических операторов в классе или типе записи, а также на глобальном уровне.

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

Заметки

В предыдущей синтаксической конструкции operator-symbol представляет собой один из операторов: +, -, *, /, = и т. д. parameter-list задает операнды в том порядке, в котором они следуют в обычном синтаксисе для данного оператора. method-body создает результирующее значение.

Перегрузки операторов для операторов должны быть статическими. В перегрузках операторов для унарных операторов, таких как + и -, необходимо использовать тильду (~) в символе оператора (operator-symbol), чтобы указать, что оператор является унарным, а не бинарным, как показано в следующем объявлении.

static member (~-) (v : Vector)

Следующий код иллюстрирует векторный класс, имеющий только два оператора: один для унарного отрицания и другой для умножения на скаляр. В примере необходимы две перегрузки для скалярного умножения, поскольку оператор должен работать независимо от порядка, в котором следуют вектор и скаляр.

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

Создание новых операторов

Можно перегружать все стандартные операторы, а также создавать новые операторы из последовательностей определенных символов. Допустимые операторные символы — !, %, &, *, +, -, ., /, <, =, >, ?, @, ^, |и ~. Символ ~ имеет специальное значение: он делает оператор унарным и не является частью последовательности символов оператора. Не каждый оператор можно сделать унарным, как это описано в подразделе Префиксные и инфиксные операторы далее в этом разделе.

В зависимости от того, какая именно последовательность символов используется, оператор будет иметь определенные приоритет и ассоциативность. Ассоциативность может левой (вычисление происходит слева направо) или правой (справа налево) и используется всякий раз, когда в последовательности присутствуют операторы с одинаковым приоритетом без скобок.

Операторный символ . не влияет на приоритет, поэтому, например, если требуется определить собственную версию умножения, имеющую такие же приоритет и ассоциативность, что и обычное умножение, можно создать такие операторы, как .*.

Таблицу, в которой приведен приоритет всех операторов в языке F#, можно найти в разделе Справочник символов и операторов (F#).

Имена перегруженных операторов

Когда компилятор F# компилирует выражение с оператором, он формирует метод с генерируемым компилятором именем для этого оператора. Это имя указывается в тексте на языке MSIL для метода, а также в отражении и в IntelliSense. Обычно использовать эти имена в коде F# не требуется.

В следующей таблице приведены стандартные операторы и соответствующие им генерируемые имена.

Оператор

Генерируемое имя

[]

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

Другие комбинации операторных символов, не приведенные здесь, могут использоваться в качестве операторов и иметь имена, образуемые объединением имен отдельных символов из следующей таблицы. Например, +! преобразуется в op_PlusBang.

Операторный символ

Имя

>

Greater

<

Less

+

Plus

-

Minus

*

Multiply

/

Divide

=

Equals

~

Twiddle

%

Percent

.

Dot

&

Amp

|

Bar

@

At

^

Hat

!

Bang

?

Qmark

(

LParen

,

Comma

)

RParen

[

LBrack

]

RBrack

Префиксные и инфиксные операторы

Префиксные операторы — это операторы, которые должны размещаться перед операндом или операндами, подобно функции. Инфиксные операторы — это операторы, которые должны размещаться между двумя операндами.

Только некоторые операторы могут использоваться как префиксные операторы. Некоторые операторы всегда префиксные операторы, другие могут быть инфиксной или префиксными, а остальные всегда будут инфиксными операторами. Операторы ! и ~, а также повторяющиеся последовательности из них всегда являются операторами префиксов. Операторы +, -, +., -., &, &&, % и %% могут быть префиксными или инфиксными. Чтобы отличить префиксную версию этих операторов от инфиксной версии, добавьте ~ в начале оператора префикса при определении. ~ не используется при использовании оператора, а только при его определении.

Пример

Следующий код иллюстрирует использование перегрузки операторов для реализации дробного типа. Дробь представляется числителем и знаменателем. Функция hcf используется для определения наибольшего общего делителя, используемого для сокращения дробей.

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

Операторы на глобальном уровне

Определять операторы можно также на глобальном уровне. В следующем коде определяется оператор +?.

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

Результат выполнения приведенного выше кода — 12.

Таким образом можно переопределять регулярные арифметические операторы, поскольку правила определения области видимости для F# подразумевают, что новые определенные операторы имеют приоритет над встроенными операторами.

С глобальными операторами, нередко представляющими собой небольшие функции, которые лучше всего интегрируются в вызывающий код, часто используется ключевое слово inline. Оформление функций-операторов как встроенных позволяет им работать со статически разрешаемыми параметрами типов для получения статически разрешаемого универсального кода. Дополнительные сведения см. в разделах Встроенные функции (F#) и Статически разрешаемые параметры типа (F#).

См. также

Основные понятия

Члены (F#)