Header menu logo Teaching

BinderScriptNotebook

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)
Some 5.0
yy |> Option.map (fun x -> x + 1.0)
<null>
let add100ToOption x = x |> Option.map(fun x -> x + 100.0)
let xxyy = [xx; yy; xx; yy; add100ToOption xx ] 
xxyy
[Some 4.0; None; Some 4.0; None; Some 104.0]

now add another 100 to every element

xxyy |> List.map add100ToOption
[Some 104.0; None; Some 104.0; None; Some 204.0]
let divideBy2 x = x / 2.0
xxyy 
|> List.map(fun x -> 
    x |> Option.map divideBy2
)
[Some 2.0; None; Some 2.0; None; Some 52.0]

Choose is like *.map but it discards the None results and unwraps the Some results.

xxyy 
|> List.choose(fun x -> 
    x |> Option.map divideBy2
)
[2.0; 2.0; 52.0]

Question 1

Create a value named a and assign Some 4 to it.

answer

let a = Some 4
val a: int option = Some 4

Question 2

Create a value name b and assign None to it.

answer

let b = None
val b: 'a option

Question 3

Create a tuple named c and assign (Some 4, None) to it.

answer

let 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

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]

or, we don't actually have to tell it that 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 } ]   
  1. create a new list called stockDaysWithDividends that is a filtered version of stockDays that only contains days with dividends.
  2. Then create an list called stockDaysWithoutDividends that is a filtered version of stockDays that only contains days that do not have dividends.

answer

let 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.

answer

let 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.

answer

let 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]

val testMap: Map<string,int>
Multiple items
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>
val find: key: 'Key -> table: Map<'Key,'T> -> 'T (requires comparison)
val tryFind: key: 'Key -> table: Map<'Key,'T> -> 'T option (requires comparison)
val xx: float option
union case Option.Some: Value: 'T -> Option<'T>
val yy: 'a option
union case Option.None: Option<'T>
module Option from Microsoft.FSharp.Core
val map: mapping: ('T -> 'U) -> option: 'T option -> 'U option
val x: float
val add100ToOption: x: float option -> float option
val x: float option
val xxyy: float option list
Multiple items
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 map: mapping: ('T -> 'U) -> list: 'T list -> 'U list
val divideBy2: x: float -> float
val choose: chooser: ('T -> 'U option) -> list: 'T list -> 'U list
val a: int option
val b: 'a option
val c: int option * 'a option
val d: x: float -> float option
Multiple items
val float: value: 'T -> float (requires member op_Explicit)

--------------------
type float = System.Double

--------------------
type float<'Measure> = float
val d2: x: float -> float option
type StockDays = { Day: int Price: decimal Dividend: Option<decimal> }
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
Multiple items
val decimal: value: 'T -> decimal (requires member op_Explicit)

--------------------
type decimal = System.Decimal

--------------------
type decimal<'Measure> = decimal
val stockDays: StockDays list
val day: int
val dividend: decimal option
val stockDaysWithDivideds: StockDays list
val filter: predicate: ('T -> bool) -> list: 'T list -> 'T list
val day: StockDays
StockDays.Dividend: Option<decimal>
property Option.IsSome: bool with get
val stockDaysWithoutDividends: StockDays list
property Option.IsNone: bool with get
val nestedOption: int option option
val flatten: option: 'T option option -> 'T option
val listOfNestedOptions: int option option list

Type something to start searching.