サーチ…
オプションの定義
Option
は、 None
またはSome
2つのケースで区別されたユニオンです。
type Option<'T> = Some of 'T | None
NULL値に対してOption <'T>を使用する
F#
ような関数型プログラミング言語では、値は潜在的に有害で貧弱なスタイル(非慣用的)とみなされnull
。
このC#
コードを考えてみましょう:
string x = SomeFunction ();
int l = x.Length;
x
がnull
場合はx.Length
がスローされnull
保護を追加しましょう:
string x = SomeFunction ();
int l = x != null ? x.Length : 0;
または:
string x = SomeFunction () ?? "";
int l = x.Length;
または:
string x = SomeFunction ();
int l = x?.Length;
慣用句F#
null
値は使用されないので、コードは次のようになります。
let x = SomeFunction ()
let l = x.Length
ただし、空の値または無効な値を表す必要があることがあります。それから、 Option<'T>
使うことができます:
let SomeFunction () : string option = ...
SomeFunction
は、 Some
string
値またはNone
返します。パターンマッチングを用いてstring
値を抽出する
let v =
match SomeFunction () with
| Some x -> x.Length
| None -> 0
このコードが以下の理由より脆弱でない理由は:
string x = SomeFunction ();
int l = x.Length;
string option
Length
を呼び出すことができないからです。パターンマッチングを使用してstring
値を抽出する必要があります。そうすることで、 string
値が安全に使用できることが保証されます。
オプションモジュールにより鉄道指向プログラミングが可能
エラー処理は重要ですが、優雅なアルゴリズムを混乱させる可能性があります。 鉄道指向プログラミング ( ROP
)は、エラー処理をエレガントで構成可能にするために使用されます。
簡単な関数f
考えてみましょう。
let tryParse s =
let b, v = System.Int32.TryParse s
if b then Some v else None
let f (g : string option) : float option =
match g with
| None -> None
| Some s ->
match tryParse s with // Parses string to int
| None -> None
| Some v when v < 0 -> None // Checks that int is greater than 0
| Some v -> v |> float |> Some // Maps int to float
目的f
、入力解析するstring
(存在する場合は値をSome
)にint
。 int
が0
より大きい場合は、 float
キャストします。それ以外の場合は、「 None
」で救済する。
ただし、ネストされたmatch
が非常に単純な関数では、可読性が大幅に低下します。
ROP
はプログラムで2種類の実行パスがあることを確認しています
- ハッピーパス-最終的に計算します
Some
値を - エラーパス-他のすべてのパスは発生しない
None
エラー・パスは頻繁に発生するため、コードを引き継ぐ傾向があります。ハッピーなパスコードが最も目に見えるコードパスであることを望みます。
ROP
を使った同等の関数g
、次のようになります。
let g (v : string option) : float option =
v
|> Option.bind tryParse // Parses string to int
|> Option.filter ((<) 0) // Checks that int is greater than 0
|> Option.map float // Maps int to float
F#
でリストやシーケンスを処理する傾向によく似ています。
一つは、見ることができるOption<'T>
のようなList<'T>
のみ含まれていてもよい0
または1
の要素Option.bind
同じように動作しList.pick
(概念的Option.bind
より良いにマップをList.collect
が、 List.pick
あるかもしれません理解しやすい)。
bind
、 filter
、およびmap
はエラー・パスを処理し、 g
はハッピー・パス・コードのみを処理します。
直接受け入れるすべての機能Option<_>
と返すOption<_>
との直接構成可能です|>
と>>
。
したがってROP
は可読性と合成性を向上させます。
C#のオプションタイプを使用する
Option型をC#コードに公開するのは良い考えではありません.C#にはそれらを処理する方法がないからです。オプションは、C#プロジェクトの依存関係としてFSharp.Core
を導入するか(C#との相互運用用に設計されていない F#ライブラリを使用している場合に必要な作業です)、またはNone
値をnull
に変更しnull
。
Pre-F#4.0
これを行う方法は、あなた自身の変換関数を作成することです:
let OptionToObject opt =
match opt with
| Some x -> x
| None -> null
値の型は、ボクシングやSystem.Nullable
を使用する必要があります。
let OptionToNullable x =
match x with
| Some i -> System.Nullable i
| None -> System.Nullable ()
F#4.0
F#4.0では、 Option
モジュールに導入されたofObj
、 toObj
、 ofNullable
、およびtoNullable
が使用されています。 F#インタラクティブでは、次のように使用できます。
let l1 = [ Some 1 ; None ; Some 2]
let l2 = l1 |> List.map Option.toNullable;;
// val l1 : int option list = [Some 1; null; Some 2]
// val l2 : System.Nullable<int> list = [1; null; 2]
let l3 = l2 |> List.map Option.ofNullable;;
// val l3 : int option list = [Some 1; null; Some 2]
// Equality
l1 = l2 // fails to compile: different types
l1 = l3 // true
None
内部的にnull
コンパイルされることに注意してください。しかし、F#に関する限り、それはNone
です。
let lA = [Some "a"; None; Some "b"]
let lB = lA |> List.map Option.toObj
// val lA : string option list = [Some "a"; null; Some "b"]
// val lB : string list = ["a"; null; "b"]
let lC = lB |> List.map Option.ofObj
// val lC : string option list = [Some "a"; null; Some "b"]
// Equality
lA = lB // fails to compile: different types
lA = lC // true