Перегрузка операторов (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#).