演算子のオーバーロード #3

さて、?<-はいったん置いておいて、演算子オーバーロード解決を勉強した。

F# の演算子定義は2通りのパターンがある。

1つは(C#と同様に)クラスなどのスタティックメンバーとして書くもの。

type MyRecord = { x: int; y: int } with
    static member (+) (left: MyRecord, right: MyRecord) =
        { x = left.x + right.x;
          y = left.y + right.y }

メソッドとして書くので、引数はタプルで受け取る。しかし実際には中置演算子として使える。

もう1つは、グローバルレベルで定義するもの。

let (+) (left: MyRecord) (right: MyRecord) =
    { x = left.x + right.x;
      y = left.y + right.y }

こちらはF#の関数として書くので、引数はタプルにしない。こちらも中置演算子として使える。

これらの違いは何かというと、演算子オーバーロード(多重定義)されるかどうかが違う。

グローバルレベルで定義された演算子は、演算子オーバーロードを隠してしまう。

let (+) (left: MyRecord) (right: MyRecord) =
    { x = left.x + right.x;
      y = left.y + right.y }

let a = 1 + 2 // コンパイルエラー。MyRecord型の引数が期待されるところにint型の引数を渡している。

演算子オーバーロードが期待通りに動作するためには、演算子はグローバルレベルで定義するのではなく、引数の型のスタティックメンバーとして定義しておかなければならない。

演算子オーバーロード解決は、まずグローバルレベルの定義を探し、それが見つからなかった場合だけ、引数の型のスタティックメンバーから適切な演算子定義を探す、という順番になっているからだ。

(続く)