Options
Sometimes something exists or doesn't exist. This can be useful to model explicitly. FSharp For Fun And Profit has a nice discussion of option types and how to use them. You can also find information on the tour of F#, F# language reference and the F# core documentation. Finally, Jane Street's tech blog has a good discussion as well.
The main purpose is to model situations where you could have "something" or "nothing" explicitly.
For example, this testMap
does not contain a "c" key.
let testMap = Map [("a", 1); ("b", 1)]
If we try to find the value indexed by "c" we will get an exception.
Map.find "c" testMap
The preferred way to do this is to try to get the key, and then if there is no value for that key return nothing. Options are either Some x
or None
, where x
is the data that you want. This is what the "..try" functions are about.
Map.tryFind "a" testMap
Map.tryFind "c" testMap
Other option examples
let xx = Some 4.0
let yy = None
xx |> Option.map(fun x -> x + 1.0)
|
yy |> Option.map (fun x -> x + 1.0)
|
let add100ToOption x = x |> Option.map(fun x -> x + 100.0)
let xxyy = [xx; yy; xx; yy; add100ToOption xx ]
xxyy
|
now add another 100 to every element
xxyy |> List.map add100ToOption
|
let divideBy2 x = x / 2.0
xxyy
|> List.map(fun x ->
x |> Option.map divideBy2
)
|
Choose is like *.map but it discards
the None
results and unwraps the Some
results.
xxyy
|> List.choose(fun x ->
x |> Option.map divideBy2
)
|
Question 1
Create a value named a
and assign Some 4
to it.
answerlet a = Some 4
val a: int option = Some 4
Question 2
Create a value name b
and assign None
to it.
answerlet b = None
val b: 'a option
Question 3
Create a tuple named c
and assign (Some 4, None)
to it.
answerlet c = Some 4, None
val c: int option * 'a option
Question 4
Write a function named d that takes x: float
as an input and outputs
Some x
if x < 0 and `None` if x >= 0. Test it by mapping each element of
[0.0; 1.4; -7.0]
by function d.
answer or, we don't actually have to tell it that let d (x: float) = if x < 0.0 then Some x else None
[0.0; 1.4;-7.0] |> List.map d
val d: x: float -> float option
val it: float option list = [None; None; Some -7.0]
x
is a float
because type inference can tell that x
must be a float
because the function does x < 0.0
and 0.0
is a float
.let d2 x = if x < 0.0 then Some x else None
[0.0; 1.4;-7.0] |> List.map d2
val d2: x: float -> float option
val it: float option list = [None; None; Some -7.0]
Question 5
Consider this list of trading days for a stock and it's price and dividends:
type StockDays =
{
Day : int
Price : decimal
Dividend : decimal Option
}
let stockDays =
[ for day = 0 to 5 do
let dividend = if day % 2 = 0 then None else Some 1m
{ Day = day
Price = 100m + decimal day
Dividend = dividend } ]
-
create a new list called
stockDaysWithDividends
that is a filtered version ofstockDays
that only contains days with dividends. -
Then create an list called
stockDaysWithoutDividends
that is a filtered version ofstockDays
that only contains days that do not have dividends.
answerlet stockDaysWithDivideds =
stockDays
|> List.filter(fun day ->
// variable names are arbitrary.
// We could have written `fun x` or `fun y` or ...
// But it's helpful to use
// meaningful names like "day" if the record that our
// function is operating on represents a day.
day.Dividend.IsSome)
val stockDaysWithDivideds: StockDays list =
[{ Day = 1
Price = 101M
Dividend = Some 1M }; { Day = 3
Price = 103M
Dividend = Some 1M }; { Day = 5
Price = 105M
Dividend = Some 1M }]
let stockDaysWithoutDividends =
stockDays
|> List.filter(fun day ->
day.Dividend.IsNone)
val stockDaysWithoutDividends: StockDays list =
[{ Day = 0
Price = 100M
Dividend = None }; { Day = 2
Price = 102M
Dividend = None }; { Day = 4
Price = 104M
Dividend = None }]
Question 6
Consider the value let nestedOption = (Some (Some 4))
. Pipe
it to Option.flatten
so that you are left with Some 4
.
answerlet nestedOption = (Some (Some 4))
nestedOption |> Option.flatten
// this would also work, but doesn't use a pipe
Option.flatten nestedOption
val nestedOption: int option option = Some (Some 4)
val it: int option = Some 4
Question 7
Consider this list let listOfNestedOptions = [(Some (Some 4)); Some (None); None]
.
Show how to transform it into [Some 4; None; None]
by mapping a function to each
element of the list.
answerlet listOfNestedOptions = [(Some (Some 4)); Some (None); None]
// map the function Option.flatten to each element of the list
listOfNestedOptions |> List.map Option.flatten
val listOfNestedOptions: int option option list =
[Some (Some 4); Some None; None]
val it: int option list = [Some 4; None; None]
module Map from Microsoft.FSharp.Collections
--------------------
type Map<'Key,'Value (requires comparison)> = interface IReadOnlyDictionary<'Key,'Value> interface IReadOnlyCollection<KeyValuePair<'Key,'Value>> interface IEnumerable interface IStructuralEquatable interface IComparable interface IEnumerable<KeyValuePair<'Key,'Value>> interface ICollection<KeyValuePair<'Key,'Value>> interface IDictionary<'Key,'Value> new: elements: ('Key * 'Value) seq -> Map<'Key,'Value> member Add: key: 'Key * value: 'Value -> Map<'Key,'Value> ...
--------------------
new: elements: ('Key * 'Value) seq -> Map<'Key,'Value>
module List from Microsoft.FSharp.Collections
--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
val float: value: 'T -> float (requires member op_Explicit)
--------------------
type float = System.Double
--------------------
type float<'Measure> = float
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> = int
val decimal: value: 'T -> decimal (requires member op_Explicit)
--------------------
type decimal = System.Decimal
--------------------
type decimal<'Measure> = decimal