3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

F#Advent Calendar 2017

Day 24

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

Posted at

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

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オブジェクトが位置する側に?をつけて表します。

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に比べるとかなり劣勢と言わざるを得ません。

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の位置を右辺側に移動させるだけで定義できます。

左辺が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に対応する演算子を以下のように定義します。

両辺が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のときに対応する演算子も定義してみます。

左辺か右辺のどちらかが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に対応した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 で利用可能です *)

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

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?