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
Somewartość - Ś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