Sök…
Definition av alternativ
Ett Option
är en diskriminerad union med två fall, None
eller Some
.
type Option<'T> = Some of 'T | None
Använd alternativ <'T> över nollvärden
I funktionella programmeringsspråk som F#
null
värden anses vara potentiellt skadlig och dålig stil (icke-idiomatisk).
Tänk på denna C#
-kod:
string x = SomeFunction ();
int l = x.Length;
x.Length
kommer att kasta om x
är null
låt oss lägga till skydd:
string x = SomeFunction ();
int l = x != null ? x.Length : 0;
Eller:
string x = SomeFunction () ?? "";
int l = x.Length;
Eller:
string x = SomeFunction ();
int l = x?.Length;
I idiomatiska F#
null
används inte så vår kod ser så här ut:
let x = SomeFunction ()
let l = x.Length
Ibland finns det emellertid ett behov av att representera tomma eller ogiltiga värden. Då kan vi använda Option<'T>
:
let SomeFunction () : string option = ...
SomeFunction
returnerar antingen Some
string
eller None
. Vi extraherar string
med mönstermatchning
let v =
match SomeFunction () with
| Some x -> x.Length
| None -> 0
Anledningen till att denna kod är mindre ömtålig än:
string x = SomeFunction ();
int l = x.Length;
Det beror på att vi inte kan kalla Length
på ett string option
. Vi måste extrahera string
hjälp av mönstermatchning och genom att göra det är vi garanterade att string
är säkert att använda.
Alternativmodul möjliggör järnvägsorienterad programmering
Felhantering är viktigt men kan göra en elegant algoritm till en röra. Järnvägsorienterad programmering ( ROP
) används för att göra felhantering elegant och komponerbar.
Tänk på den enkla funktionen 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
Syftet med f
är att tolka den ingående string
värde (om det finns Some
) i en int
. Om int
är större än 0
kastar vi det i en float
. I alla andra fall räddar vi ut med None
.
Även en extremt enkel funktion minskar den kapslade match
läsbarheten avsevärt.
ROP
konstaterar att vi har två slags exekveringsvägar i vårt program
- Lycklig väg - Beräknar så småningom
Some
värde - Felväg - Alla andra sökvägar producerar
None
Eftersom felvägarna är vanligare tenderar de att ta över koden. Vi vill att den lyckliga sökkoden är den mest synliga kodvägen.
En ekvivalent funktion g
med ROP
kan se ut så här:
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
Det ser mycket ut som hur vi tenderar att bearbeta listor och sekvenser i F#
.
Man kan se ett Option<'T>
som en List<'T>
som bara kan innehålla 0
eller 1
element där Option.bind
beter sig som List.pick
(begreppsmässigt Option.bind
bättre till List.collect
men List.pick
kan vara lättare att förstå).
bind
, filter
och map
hanterar felvägarna och g
innehåller bara den lyckliga sökkoden.
Alla funktioner som direkt accepterar Option<_>
och returnerar Option<_>
är direkt komponerbara med |>
och >>
.
ROP
ökar därför läsbarheten och kompositionen.
Använda alternativstyper från C #
Det är inte bra att utsätta alternativstyper för C # -kod, eftersom C # inte har ett sätt att hantera dem. Alternativen är antingen att introducera FSharp.Core
som ett beroende i ditt C # -projekt (vilket du måste göra om du konsumerar ett F # -bibliotek som inte är designat för interop med C #), eller att ändra None
värden till null
.
Pre-F # 4.0
Så här gör du är att skapa en egen konverteringsfunktion:
let OptionToObject opt =
match opt with
| Some x -> x
| None -> null
För värdetyper måste du använda boxning eller använda System.Nullable
.
let OptionToNullable x =
match x with
| Some i -> System.Nullable i
| None -> System.Nullable ()
F # 4.0
I F # 4.0 ofObj
funktionerna för ofObj
, toObj
, ofNullable
och toNullable
till Option
modulen. I F # interaktiva kan de användas enligt följande:
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
Observera att None
kompilerar till null
internt. Men när det gäller F # är det 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