定義した演算子の一覧は「[まとめ] ここで定義した演算子一覧」にあります
F#はMicrosoftのDon Syme氏が開発したプログラミング言語で、命令型、オブジェクト指向、関数型の要素を併せ持つマルチパラダイム言語と位置付けられています。実装はオープンソースのMITライセンスのもと、githubのリポジトリで公開されています。
実行環境は.NET Frameworkおよび.NET CoreやMonoといったマルチプラットフォームに対応していますが、サポート状況については各プラットフォームごとの情報を参照してください(「F# の概要」など)。
なお、ここで対象とするF#のバージョンは4.1(FSharp.Core 4.4.1.0)とします。
Null許容演算子があるのに...
F#にはNull許容演算子が定義されています。これは算術演算と比較演算において、左辺か右辺のどちらか、もしくは両辺ともNullable<'T>
オブジェクトに対応する演算子です。通常の演算子のほか、Nullable
オブジェクトが位置する側に?
をつけて表します。
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
に比べるとかなり劣勢と言わざるを得ません。
(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の位置を右辺側に移動させるだけで定義できます。
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
に対応する演算子を以下のように定義します。
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
のときに対応する演算子も定義してみます。
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という関数を実行していることによるものです。記号は|+|
, |+
, +|
などとしました。
(* 両辺が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 でした」
[まとめ] ここで定義した演算子一覧
(* 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 で利用可能です *)
ここをご覧いただいた方の参考になればと思います。