次の方法で共有


演算子のオーバーロード

このトピックでは、クラスまたはレコード型の算術演算子をグローバル レベルでオーバーロードする方法について説明します。

構文

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

注釈

前の構文では、 演算子記号 は、 +-*/=などのいずれかです。 パラメーター リストは、その演算子の通常の構文に表示される順序でオペランドを指定します。 メソッド本体は、結果の値を構築します。

演算子の演算子オーバーロードは静的である必要があります。 +-などの単項演算子の演算子オーバーロードでは、次の宣言に示すように、演算子シンボルでチルダ (~) を使用して、演算子が単項演算子であり、二項演算子ではないことを示す必要があります。

static member (~-) (v : Vector)

次のコードは、単項マイナス演算子とスカラー乗算演算子の 2 つの演算子のみを持つベクター クラスを示しています。 この例では、ベクトルとスカラーの順序に関係なく演算子が機能する必要があるため、スカラー乗算に 2 つのオーバーロードが必要です。

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

アウトプット:

1 2
2 4
2 4
-2 -4

新しい演算子の作成

すべての標準演算子をオーバーロードできますが、特定の文字のシーケンスから新しい演算子を作成することもできます。 使用できる演算子文字は、 !$%&*+-./<=>?@^|、および ~です。 ~文字は、演算子を単項にする特別な意味を持ち、演算子文字シーケンスの一部ではありません。 すべての演算子を単項にできるわけではありません。

使用する正確な文字シーケンスに応じて、演算子には特定の優先順位と結合性があります。 結合規則は、左から右、または右から左のいずれかにすることができ、同じレベルの優先順位の演算子がかっこなしで順番に出現するたびに使用されます。

演算子文字 . は優先順位に影響しないため、たとえば、通常の乗算と同じ優先順位と結合性を持つ独自のバージョンの乗算を定義する場合は、 .*などの演算子を作成できます。

$演算子は、単独で、追加の記号を使用せずに使用する必要があります。

F# のすべての演算子の優先順位を示す表は、 Symbol と Operator Reference にあります。

オーバーロードされた演算子名

F# コンパイラは、演算子式をコンパイルするときに、その演算子のコンパイラによって生成された名前を持つメソッドを生成します。 これは、メソッドの共通中間言語 (CIL) およびリフレクションと 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

F# の not 演算子はシンボリック演算子ではないため、 op_Inequality を出力しないことに注意してください。 これは、ブール式を否定する IL を出力する関数です。

ここに記載されていない演算子文字の他の組み合わせは、演算子として使用でき、次の表の個々の文字の名前を連結して構成される名前を持つことができます。 たとえば、+!op_PlusBangになります。

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

プレフィックス演算子とインフィックス演算子

プレフィックス 演算子は、関数と同様に、オペランドまたはオペランドの前に配置されることが想定されています。 インフィックス 演算子は、2 つのオペランドの間に配置される必要があります。

プレフィックス演算子として使用できるのは、特定の演算子だけです。 一部の演算子は常にプレフィックス演算子であり、他の演算子はインフィックスまたはプレフィックスで、残りは常にインフィックス演算子です。 !=を除き、!で始まる演算子と、演算子~、または~の繰り返しシーケンスは、常にプレフィックス演算子です。 演算子 +-+.-.&&&%、および %% には、プレフィックス演算子またはインフィックス演算子を指定できます。 これらの演算子のプレフィックス バージョンは、定義時にプレフィックス演算子の先頭に ~ を追加することによって、インフィックス バージョンと区別します。 ~は、演算子を使用する場合は使用されず、定義されている場合にのみ使用されます。

次のコードは、演算子オーバーロードを使用して分数型を実装する方法を示しています。 分数は分子と分母で表されます。 関数 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())

アウトプット:

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

グローバル レベルの演算子

グローバル レベルで演算子を定義することもできます。 次のコードでは、演算子 +?を定義します。

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

上記のコードの出力は 12

F# のスコープ規則によって、新しく定義された演算子が組み込みの演算子よりも優先されるため、この方法で通常の算術演算子を再定義できます。

キーワード inline は、多くの場合、グローバル演算子と共に使用されます。これは、多くの場合、呼び出し元のコードに最適に統合される小さな関数です。 また、演算子関数をインラインにすることで、静的に解決された型パラメーターを操作して、静的に解決されたジェネリック コードを生成することもできます。 詳細については、「 インライン関数静的に解決される型パラメーター」を参照してください。

こちらも参照ください