Szukaj…
Definicja opcji
Option
jest dyskryminowanym związkiem z dwoma przypadkami: None
lub Some
.
type Option<'T> = Some of 'T | None
Użyj opcji <'T> ponad wartościami zerowymi
W funkcjonalnych językach programowania, takich jak F#
wartości null
są uważane za potencjalnie szkodliwe i kiepskie (nie idiomatyczne).
Rozważ ten kod C#
:
string x = SomeFunction ();
int l = x.Length;
x.Length
wyrzuci, jeśli x
jest null
, dodajmy ochronę:
string x = SomeFunction ();
int l = x != null ? x.Length : 0;
Lub:
string x = SomeFunction () ?? "";
int l = x.Length;
Lub:
string x = SomeFunction ();
int l = x?.Length;
W idiomatic wartości F#
null
nie są używane, więc nasz kod wygląda następująco:
let x = SomeFunction ()
let l = x.Length
Czasami jednak konieczne jest przedstawienie pustych lub niepoprawnych wartości. Następnie możemy użyć Option<'T>
:
let SomeFunction () : string option = ...
SomeFunction
albo powraca Some
string
wartości lub None
. Wyciągamy wartość string
za pomocą dopasowania wzorca
let v =
match SomeFunction () with
| Some x -> x.Length
| None -> 0
Powód, dla którego ten kod jest mniej delikatny niż:
string x = SomeFunction ();
int l = x.Length;
Jest tak, ponieważ nie możemy wywołać opcji Length
w string option
. Musimy wyodrębnić wartość string
za pomocą dopasowania wzorca i dzięki temu mamy pewność, że wartość string
jest bezpieczna w użyciu.
Moduł opcjonalny umożliwia programowanie zorientowane na kolej
Obsługa błędów jest ważna, ale może zmienić elegancki algorytm w bałagan. Programowanie zorientowane na kolej ( ROP
) służy do tego, aby obsługa błędów była elegancka i możliwa do skomponowania.
Rozważ prostą funkcję 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
Celem f
jest przetwarzanie wejściowego string
wartości (jeśli jest Some
) w jego int
. Jeśli int
jest większe niż 0
, rzucamy go na float
. We wszystkich innych przypadkach ratujemy się z None
.
Chociaż niezwykle prosta funkcja zagnieżdżonego match
znacznie zmniejsza czytelność.
ROP
zauważa, że mamy dwa rodzaje ścieżek wykonania w naszym programie
- Szczęśliwa ścieżka - w końcu obliczy
Some
wartość - Ścieżka błędów - wszystkie inne ścieżki powodują
None
Ponieważ ścieżki błędów są częstsze, mają tendencję do przejmowania kodu. Chcielibyśmy, aby szczęśliwy kod ścieżki był najbardziej widoczną ścieżką kodu.
Równoważna funkcja g
korzystająca z ROP
może wyglądać następująco:
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
Wygląda bardzo podobnie do tego, w jaki sposób przetwarzamy listy i sekwencje w F#
.
Można zobaczyć Option<'T>
jak List<'T>
która może zawierać tylko 0
lub 1
element, w którym Option.bind
zachowuje się jak List.pick
(koncepcyjnie Option.bind
odwzorowuje lepiej na List.collect
ale List.pick
może być łatwiejsze do zrozumienia).
bind
, filter
i map
obsługuje ścieżki błędów g
zawiera tylko kod szczęśliwej ścieżki.
Wszystkie funkcje, które bezpośrednio akceptują Option<_>
i zwracają Option<_>
można bezpośrednio łączyć z |>
i >>
.
ROP
zwiększa zatem czytelność i składalność.
Korzystanie z typów opcji od C #
Nie jest dobrym pomysłem eksponowanie typów Opcji na kod C #, ponieważ C # nie ma sposobu na ich obsługę. Opcje są albo przedstawić FSharp.Core
jako zależność w projekcie C # (czyli to, co trzeba było zrobić, jeśli zużywa się F # biblioteki nie przeznaczonego dla współdziałanie z C #), lub zmienić None
wartości do null
.
Pre-F # 4.0
Sposobem na to jest utworzenie własnej funkcji konwersji:
let OptionToObject opt =
match opt with
| Some x -> x
| None -> null
W przypadku typów wartości należy użyć ich boksu lub użycia System.Nullable
.
let OptionToNullable x =
match x with
| Some i -> System.Nullable i
| None -> System.Nullable ()
F # 4.0
W F # 4.0 ofObj
, toObj
, ofNullable
i toNullable
wprowadzone do karty Option
. W interaktywnym F # można ich używać w następujący sposób:
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
Pamiętaj, że None
wewnętrznie kompiluje się do null
. Jednak jeśli chodzi o F #, jest to 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