Null許容演算子があるなら、「Option許容演算子」があってもいいと思う。だから勝手に定義してみた。

> 定義した演算子の一覧は「[まとめ] ここで定義した演算子一覧」にあります

F#はMicrosoftの[Don Syme](https://twitter.com/dsyme "https://twitter.com/dsyme")氏が開発したプログラミング言語で、命令型、オブジェクト指向、関数型の要素を併せ持つマルチパラダイム言語と[位置付けられています](https://msdn.microsoft.com/visualfsharpdocs/conceptual/visual-fsharp "https://msdn.microsoft.com/visualfsharpdocs/conceptual/visual-fsharp")。実装はオープンソースの[MITライセンス](https://github.com/fsharp/fsharp/blob/master/License.txt "https://github.com/fsharp/fsharp/blob/master/License.txt")のもと、[githubのリポジトリ](https://github.com/fsharp/fsharp "https://github.com/fsharp/fsharp")で公開されています。

実行環境は.NET Frameworkおよび.NET CoreやMonoといったマルチプラットフォームに対応していますが、サポート状況については各プラットフォームごとの情報を参照してください(「[F# の概要](https://docs.microsoft.com/ja-jp/dotnet/fsharp/get-started/index "https://docs.microsoft.com/ja-jp/dotnet/fsharp/get-started/index")」など)。

なお、ここで対象とするF#のバージョンは4.1([FSharp.Core](https://github.com/fsharp/fsharp/tree/master/src/fsharp/FSharp.Core "https://github.com/fsharp/fsharp/tree/master/src/fsharp/FSharp.Core") 4.4.1.0)とします。

# Null許容演算子があるのに...
F#には[Null許容演算子](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/symbol-and-operator-reference/nullable-operators "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/symbol-and-operator-reference/nullable-operators")が定義されています。これは算術演算と比較演算において、左辺か右辺のどちらか、もしくは両辺とも`Nullable<'T>`オブジェクトに対応する演算子です。通常の演算子のほか、`Nullable`オブジェクトが位置する側に`?`をつけて表します。

```fsharp:Null許容演算子の例
open System
open Microsoft.FSharp.Linq.NullableOperators

(* 算術演算 *)
(Nullable 1) ?+ 2              // Nullable<int> 3
(Nullable 1) ?+? (Nullable 2)  // Nullable<int> 3

(* 比較演算 *)
3 >? (Nullable 1)              // true
(Nullable 3) ?<? (Nullable 1)  // false
```

では、`Option`はどんな演算子が対応しているかといえば、両辺ともに`Option`の比較演算のみという状況です。`Nullable`に比べるとかなり劣勢と言わざるを得ません。

```fsharp:Optionに対応する演算子の例
(Some 3) =  (Some 3)  // true
(Some 3) <> (Some 1)  // false
(Some 1) >  (Some 3)  // false
```

# 「Option許容演算子」を考えてみる
ならば、Null許容演算子のように四則演算に対応したり、左辺や右辺のどちらかのみが`Option`のときにも対応できる「Option許容演算子」を考えてみたいと思うに至りました。そこで`Option`で演算を行う際に問題となるポイントを整理しておきます。

## 演算子の記号はどうする?
Null許容演算子は`?>`, `+?`, `?*?`など、Nullableオブジェクトの位置によって`?`のつく位置が決まります。そこで「Option許容演算子」では`.>`, `+.`, `.+.`のように.(ピリオド)で`Option`型の値の位置を表すようにしようと思います。

> +.など、一部はF#の内部実装で使われているものがありますが、ここでは独立したモジュールで定義し、影響が出ないようにします。

## Noneの扱いをどうするか?
`Option`で演算を行うにあたり、避けて通れないのは`None`の扱いです。これはNull許容演算子での`Nullable<'T>()`の扱いに準じて考えてみようと思います。

### 比較演算でのNoneの扱い
比較演算では両辺が`Option`のときは既に対応済みなので、どちらかのみが`Option`のときもそれに準じた動作をさせることにします。そのため、`Option`ではないほうの型に`Some`をつけて演算させることで実現します。

左辺が`Option`のときの演算子は以下のように定義してみました。右辺が`Option`のときの演算子は.とSomeの位置を右辺側に移動させるだけで定義できます。

```fsharp:左辺がOptionのときに対応する比較演算子を定義
let (.=)  (x: 'T option) (y: 'T) = x =  Some y
let (.<>) (x: 'T option) (y: 'T) = x <> Some y
let (.>)  (x: 'T option) (y: 'T) = x >  Some y
let (.<)  (x: 'T option) (y: 'T) = x <  Some y
let (.>=) (x: 'T option) (y: 'T) = x >= Some y
let (.<=) (x: 'T option) (y: 'T) = x <= Some y
```

### 算術演算でのNoneの扱い
算術演算ではNullable許容演算子での扱いに準じて、左辺か右辺のどちらかに`None`があるときは、結果が`None`になるようにします。

まず両辺が`Option`に対応する演算子を以下のように定義します。

```fsharp:両辺がOptionのときに対応する算術演算子を定義
let inline (.+.) (x: 'T option) (y: 'T option) =
  match x, y with
  | Some a, Some b -> Some (a + b)
  | _ -> None

(Some 1) .+. (Some 2)  // Some 3
(Some 1) .+. None      // None
None     .+. (Some 2)  // None
```

これをもとに左辺か右辺のどちらかが`Option`のときに対応する演算子も定義してみます。

```fsharp:左辺か右辺のどちらかがOptionのときに対応する算術演算子を定義
let inline (.+) (x: 'T option) (y: 'T) = x .+. Some y
let inline (+.)   (x: 'T) (y: 'T option) = Some x .+. y

(Some 1) .+ 2         // Some 3
None     .+ 1         // None
1        +. (Some 2)  // Some 3
1        +. None      // None
```

# [おまけ] 「Someはずし演算子」も考えてみた
最後に、算術演算の結果から`Some`が持つ値を抽出する「Someはずし演算子」も定義してみました。結果が`None`のときは`System.ArgumentException`という例外が発生するようになっています。これは内部で[Option.get](https://msdn.microsoft.com/library/803e9fcb-6edd-4910-808c-25f08cbc55ea "https://msdn.microsoft.com/library/803e9fcb-6edd-4910-808c-25f08cbc55ea")という関数を実行していることによるものです。記号は`|+|`, `|+`, `+|`などとしました。

```fsharp:「Optionはずし演算子」の定義
(* 両辺がOptionに対応したOptionはずし演算子 *)
let inline (|+|) (x: 'T option) (y: 'T option) = Option.get (x .+. y)
(Some 1) |+| (Some 2)  // 3
(Some 1) |+| None      // 例外発生「System.ArgumentException: オプション値は None でした」

(* 左辺がOptionに対応したOptionはずし演算子 *)
let inline (|+)   (x: 'T option) (y: 'T) = Option.get (x .+ y)
(Some 1) |+ 2  // 3
None     |+ 2  // 例外発生「System.ArgumentException: オプション値は None でした」
```

# [まとめ] ここで定義した演算子一覧

```fsharp:ここで定義した演算子一覧
(* Optionの引数に対応する「Option許容演算子」を定義してみた *)
module OptionableOperators =

  (*
    比較演算
  *)
  (* 左辺がOptionに対応する演算子 *)
  let (.=)  (x: 'T option) (y: 'T) = x =  Some y
  let (.<>) (x: 'T option) (y: 'T) = x <> Some y
  let (.>)  (x: 'T option) (y: 'T) = x >  Some y
  let (.<)  (x: 'T option) (y: 'T) = x <  Some y
  let (.>=) (x: 'T option) (y: 'T) = x >= Some y
  let (.<=) (x: 'T option) (y: 'T) = x <= Some y

  (* 右辺がOptionに対応する演算子 *)
  let (=.)  (x: 'T) (y: 'T option) = Some x =  y
  let (<>.) (x: 'T) (y: 'T option) = Some x <> y
  let (>.)  (x: 'T) (y: 'T option) = Some x >  y
  let (<.)  (x: 'T) (y: 'T option) = Some x <  y
  let (>=.) (x: 'T) (y: 'T option) = Some x >= y
  let (<=.) (x: 'T) (y: 'T option) = Some x <= y

  (*
    算術演算
  *)
  (* 両辺がOptionに対応する演算子 *)
  let inline (.+.) (x: 'T option) (y: 'T option) =
    match x, y with
    | Some a, Some b -> Some (a + b)
    | _ -> None

  let inline (.-.) (x: 'T option) (y: 'T option) =
    match x, y with
    | Some a, Some b -> Some (a - b)
    | _ -> None

  let inline (.*.) (x: 'T option) (y: 'T option) =
    match x, y with
    | Some a, Some b -> Some (a * b)
    | _ -> None

  let inline (./.) (x: 'T option) (y: 'T option) =
    match x, y with
    | Some a, Some b -> Some (a / b)
    | _ -> None

  let inline (.%.) (x: 'T option) (y: 'T option) =
    match x, y with
    | Some a, Some b -> Some (a % b)
    | _ -> None

  (* 左辺がOptionに対応する演算子 *)
  let inline (.+) (x: 'T option) (y: 'T) = x .+. Some y
  let inline (.-) (x: 'T option) (y: 'T) = x .-. Some y
  let inline (.*) (x: 'T option) (y: 'T) = x .*. Some y
  let inline (./) (x: 'T option) (y: 'T) = x ./. Some y
  let inline (.%) (x: 'T option) (y: 'T) = x .%. Some y

  (* 右辺がOptionに対応する演算子 *)
  let inline (+.)   (x: 'T) (y: 'T option) = Some x .+. y
  let inline (-.)   (x: 'T) (y: 'T option) = Some x .-. y
  let inline ( *. ) (x: 'T) (y: 'T option) = Some x .*. y
  let inline (/.)   (x: 'T) (y: 'T option) = Some x ./. y
  let inline (%.)   (x: 'T) (y: 'T option) = Some x .%. y

  (*
    結果からSomeをはずす算術演算子
  *)
  (* 両辺がOptionに対応する演算子 *)
  let inline (|+|) (x: 'T option) (y: 'T option) = Option.get (x .+. y)
  let inline (|-|) (x: 'T option) (y: 'T option) = Option.get (x .-. y)
  let inline (|*|) (x: 'T option) (y: 'T option) = Option.get (x .*. y)
  let inline (|/|) (x: 'T option) (y: 'T option) = Option.get (x ./. y)
  let inline (|%|) (x: 'T option) (y: 'T option) = Option.get (x .%. y)

  (* 左辺がOptionに対応する演算子 *)
  let inline (|+)   (x: 'T option) (y: 'T) = Option.get (x .+ y)
  let inline (|-)   (x: 'T option) (y: 'T) = Option.get (x .- y)
  let inline ( |* ) (x: 'T option) (y: 'T) = Option.get (x .* y)
  let inline (|/)   (x: 'T option) (y: 'T) = Option.get (x ./ y)
  let inline (|%)   (x: 'T option) (y: 'T) = Option.get (x .% y)

  (* 右辺がOptionに対応する演算子 *)
  let inline (+|)   (x: 'T) (y: 'T option) = Option.get (x +. y)
  let inline (-|)   (x: 'T) (y: 'T option) = Option.get (x -. y)
  let inline ( *| ) (x: 'T) (y: 'T option) = Option.get (x *. y)
  let inline (/|)   (x: 'T) (y: 'T option) = Option.get (x /. y)
  let inline (%|)   (x: 'T) (y: 'T option) = Option.get (x %. y)

(* open OptionableOperators で利用可能です *)
```

---
ここをご覧いただいた方の参考になればと思います。