M.Hiroi's Home Page

お気楽 F# プログラミング超入門

演算子のオーバーロード


Copyright (C) 2022 Makoto Hiroi
All rights reserved.

はじめに

F# は OCaml と違って演算子をオーバーロード (多重定義) することができます。

●オーバーロードの構文

オーバーロードの構文を以下に示します。

static member (op) (args1: type1, args2: type2) = 式

クラスやレコードなどで演算子を多重定義します。static は必ず付けてください。演算子 op が二項演算子の場合、引数は 2 つ必要になります。単項演算子の場合、op の前に ~ を付けて、引数は 1 つだけになります。

●レコードの場合

レコードは { ... } の後ろで member を定義することができます。

type record_name =
  {
    フィールド名 : 型式
    ...
  }
  member ...
  ...
  static member ...
  ...

簡単な例として、平面座標を表すレコード point を定義し、それをベクトルとみなして和 (+)、差 (-)、スカラー倍 (*)、内積 (*) を計算する演算子を多重定義してみましょう。プログラムは次のようになります。

リスト : レコード point (point.fsx)

type point =
  {
    x: float
    y: float
  }
  // ベクトルの和と差
  static member ( + ) (p1: point, p2: point) =
    {x = p1.x + p2.x; y = p1.y + p2.y }
  static member ( - ) (p1: point, p2: point) =
    {x = p1.x - p2.x; y = p1.y - p2.y }
  // スカラー倍
  static member ( * ) (k: float, p: point) =
    {x = k * p.x; y = k * p.y }
  static member ( * ) (p: point, k: float) =
    {x = k * p.x; y = k * p.y }
  // 内積
  static member ( * ) (p1: point, p2: point) =
    p1.x * p2.x + p1.y * p2.y

簡単な実行例を示します。

> #load "point.fsx";;
... 略 ...

> open Point;;
> let p1 = {x = 1.0; y = 1.0};;
val p1: point = { x = 1.0
                  y = 1.0 }

> let p2 = {x = 2.0; y = 3.0};;
val p2: point = { x = 2.0
                  y = 3.0 }

> p1 + p2;;
val it: point = { x = 3.0
                  y = 4.0 }

> p2 - p1;;
val it: point = { x = 1.0
                  y = 2.0 }

> p1 - p2;;
val it: point = { x = -1.0
                  y = -2.0 }

> 2.0 * p2;;
val it: point = { x = 4.0
                  y = 6.0 }

> p2 * 3.0;;
val it: point = { x = 6.0
                  y = 9.0 }

> p1 * p2;;
val it: float = 5.0

> p2 * p2;;
val it: float = 13.0

●クラスの場合

クラスで定義する場合は次のようになります。

リスト : クラス Point

type Point(x0: float, y0: float) =
  let x = x0
  let y = y0
  member this.X with get() = x
  member this.Y with get() = y
  // ベクトル演算
  static member ( + ) (p1: Point, p2: Point) =
    new Point(p1.X + p2.X, p1.Y + p2.Y)
  static member ( - ) (p1: Point, p2: Point) =
    new Point(p1.X - p2.X, p1.Y - p2.Y)
  // スカラー倍
  static member ( * ) (k: float, p: Point) =
    new Point(k * p.X, k * p.Y)
  static member ( * ) (p: Point, k: float) =
    new Point(p.X * k, p.Y * k)
  // 内積
  static member ( * ) (p1: Point, p2: Point) =
    p1.X * p2.X + p1.Y * p2.Y
> #load "point.fsx";;
... 略 ...

> open Point;;
> let p1 = new Point(1.0, 1.0);;
val p1: Point

> let p2 = new Point(2.0, 3.0);;
val p2: Point

> p1 + p2;;
val it: Point = FSI_0002.Point+Point {X = 3.0;
                                      Y = 4.0;}

> p1 - p2;;
val it: Point = FSI_0002.Point+Point {X = -1.0;
                                      Y = -2.0;}

> p2 - p1;;
val it: Point = FSI_0002.Point+Point {X = 1.0;
                                      Y = 2.0;}

> 2.0 * p2;;
val it: Point = FSI_0002.Point+Point {X = 4.0;
                                      Y = 6.0;}

> p2 * 3.0;;
val it: Point = FSI_0002.Point+Point {X = 6.0;
                                      Y = 9.0;}
> p1 * p2;;
val it: float = 5.0

> p2 * p2;;
val it: float = 13.0

●グローバルな演算子の定義

F# は let でグローバルな演算子を定義することができます。

let [inline] (op) (args1: type1) (args2: type2) = 式

演算子 op が二項演算子の場合、引数は 2 つ必要になります。オーバーロードとは違って、カリー化関数として定義することに注意してください。単項演算子の場合、op の前に ~ を付けて、引数は 1 つだけになります。

なお、この方法で既存の演算子を多重定義することはできません。let の定義が優先されるので、既存の演算子を再定義する、もしくは新しい演算子を定義するときに使用してください。

簡単な例を示しましょう。

> type point = {x: float; y: float};;
type point =
  {
    x: float
    y: float
  }

> let (+/) (p1: point) (p2: point) = {x = p1.x + p2.x; y = p1.y + p2.y};;
val (+/) : p1: point -> p2: point -> point

> let (-/) (p1: point) (p2: point) = {x = p1.x - p2.x; y = p1.y - p2.y};;
val (-/) : p1: point -> p2: point -> point

> let p1 = {x = 1.0; y = 1.0};;
val p1: point = { x = 1.0
                  y = 1.0 }

> let p2 = {x = 2.0; y = 3.0};;
val p2: point = { x = 2.0
                  y = 3.0 }

> p1 +/ p2;;
val it: point = { x = 3.0
                  y = 4.0 }

> p1 -/ p2;;
val it: point = { x = -1.0
                  y = -2.0 }

> p2 -/ p1;;
val it: point = { x = 1.0
                  y = 2.0 }

初版 2022 年 4 月 16 日