Optionals · David Raab

Optionals

In this post I want to talk about Optionals more deeply. I already wrote about null, but I noticed that it is still not immediately clear on why Optionals are better. Instead of focusing why null is bad, this time I want to focus why Optionals are good. For this purpose I also wrote a small application that I will cover. But first, let's go over Optionals and see which benefits they have.

null is not the problem

At first, I will state that null itself is not even the problem.

Let's assume we write a application and we need to read some Product entries from a database. We just have a simple id as an int to identify a product.

Now let's assume we get some user input that tells us to show a Product with the id 12345. Sure, as long we only get request to products that exists, we don't run into any kind of problems. But let's imagine we write a function that returns a Product, and the requested Product doesn't exists. What do we do in such a case?

The usual approach, at least in OO programming, is to either return null, or throwing an Exception. But i don't think that Exceptions are better compared to null.

So, why is returning null bad? Because a client calling our function is not forced to check for null. And if he works with null as if he had a Product, the program will crash. What happens if we throw an Exception? Well, our program still crashes!

The thing is. No matter if we return null or we throw exceptions. We must handle the case when we don't get a Product. But null or exceptions don't force you to handle both cases. Neither null or exceptions forces you to add checks. Both concepts just crashes your program at runtime. The only hope you have is that you hopefully have a test-suite that hopefully handles those cases. For me, that is too much of hope.

Yes, throwing an Exception is a little bit better. But think on why it is a little bit better. We see it as an advantage as we hopefully (gosh!) already see any kind of error during development, so we can add the needed try/catch statements.

But wouldn't it be better if we are forced to add the checks because the language forces us to do it? Because we otherwise get a compile-time error? If we somehow could get this kind of behaviour it would mean we never can forgot to add a null check or a try/catch statements. It will become impossible to write programs that unexpectedly crashes at runtime.

Our program either compiles fine, and we handled all places where a no value could be returned, or if we forgot to handle such a place we just get a compile-time error that gives us the exact place and line where we forgot to handle such a case.

It seems like a dream. But this is exactly what Optionals gives us!

Optionals

Optionals fixes that problem because it makes the idea of No value it's own type. The option type in F# is defined as followed:

1: 
2: 
3: 
type option<'a> =
| Some of 'a
| None

What does that mean? You can compare it to a bool or an enum type. We have two cases. Like a bool that either can be true or false. Here we have an option that either can be Some or None. The difference is that the Some case can carry an additional value. Something that an enum or a bool cannot do.

The Some or None are basically constructors. In the same sense that true or false are constructors that creates a bool. As None don't carry any value, you just can write None, the same you just can write null. But, if a function has a path that returns None, you must ensure that all code paths return an option type. As we cannot create functions with mixed return types. So you cannot write something like this:

1: 
2: 
3: 
4: 
let containsE (str:string) =
    if str.Contains("E")
    then str  // string
    else None // option

This would either return a string or an option. So what you must do is wrap your value in a Some.

1: 
2: 
3: 
4: 
let containsE (str:string) =
    if str.Contains("E")
    then Some str // option<string>
    else None     // option<string>

This has some implications:

  1. Now you have a function that returns an option containing a string.
  2. A user can see that containsE not always returns a value.
  3. A user that wants to work with the result of containsE first must check which case he got.

The last implication means you must check if you either got Some or None

1: 
2: 
3: 
4: 
let opt = containsE "foo"
match opt with
| None     -> printfn "No E"
| Some str -> printfn "%s contains an e" x

But we also have a lot of helper functions in the Option module like Option.map, Option.iter, Option.bind and so on that helps us working with option types.

For example it is quite common that we want to check if we have Some value and call a function with value. But what do we do if we have None? Then we can't call our function. This kind of stuff is what a Option.map does. So instead of

1: 
2: 
3: 
4: 
5: 
let x = Some 10
let y =
    match x with
    | None   -> None
    | Some x -> Some (x + 5)

we also can just write

1: 
let y = Option.map (fun x -> x + 5) x

Now, we are forced to handle option values. But option itself is it's own type and we have a lot of functions that helps us working with option values.

The Application

The best way to show the benefits and the difference is to go through a small example. For that purpose I created a small in-memory database and a CLI program with basic CRUD operations. You can see the full source code at the end. But i don't want to cover every detail. I want to focus on the Optional part. First, let's see how we can use the program

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
Command: show
  Id |                      Name | Price
   1 |                        TV | 499.99
   2 |                    A Book | 29.99
   3 |              Game Console | 349.99
Command: asdasdfkhjb
Error: invalid command -- [asdasdfkhjb]
Command: delete 2
Command: show 2
Product with id 2 doesn't exists.
Command: insert "Zelda: Skyward Swords" 49,99
Command: show
  Id |                      Name | Price
   1 |                        TV | 499.99
   3 |              Game Console | 349.99
   4 |     Zelda: Skyward Swords | 49.99
Command: name 1 "Television"
Command: price 3 299,99
Command: show
  Id |                      Name | Price
   1 |                Television | 499.99
   3 |              Game Console | 299.99
   4 |     Zelda: Skyward Swords | 49.99
Command: exit

The program just contains the commands show, insert, name, price, delete and exit as valid commands. As you can imagine we need to handle a lot of failures.

As usual, all kind of user input can be invalid. A command that doesn't exists. Parsing of a number failed, in general a user just enters garbage. Or a user tries to change or show a specific entry that doesn't exists.

As i don't want to cover the whole program, just let's look first at the modules and functions and their signatures to get a overview of the code.

So let's just go through some of the interesting parts. At first, i added a Option.IfNone function. It either returns a value if present or the provided value. It is just a call to defaultArg. I created ifNone because the default argument order of defaultArg doesn't work nicely with piping.

1: 
2: 
module Option =
    let ifNone x opt = defaultArg opt x

Here is a brief overview of the modules and functions i created.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
module Product = begin
  val create    : int -> string -> decimal -> Product
  val withName  : string -> Product -> Product
  val withPrice : decimal -> Product -> Product
end

module DB = begin
  val getById    : 'a -> Map<'a,'b> -> 'b option
  val getAll     : Map<'a,'b> -> 'b list
  val containsId : 'a -> Map<'a,'b> -> bool
  val nextId     : Map<int,'a> -> int
  val insert     : 'a -> 'b -> Map<'a,'b> -> Map<'a,'b>
  val delete     : 'a -> Map<'a,'b> -> Map<'a,'b>
  val update     : 'a -> 'b -> Map<'a,'b> -> Map<'a,'b>
  val updateId   : ('a -> 'a) -> 'b -> Map<'b,'a> -> Map<'b,'a>
end

module CLI = begin
  val parseCommand   : string -> Command
  val printProduct   : Product -> unit
  val show           : Map<'a,Product> -> Map<'a,Product>
  val showProduct    : Map<int,Product> -> int -> Map<int,Product>
  val insert         : Map<int,Product> -> string -> decimal -> Map<int,Product>
  val updateName     : Map<'a,Product> -> 'a -> string -> Map<'a,Product>
  val updatePrice    : Map<'a,Product> -> 'a -> decimal -> Map<'a,Product>
  val executeCommand : Map<int,Product> -> Command -> Map<int,Product> option
  val eval           : Map<int,Product> -> string -> Map<int,Product> option
end

For the in-memory database i just used a Map without creating a new type, that's why you see a lot of Map types. But overall you still see that we don't have so few functions returning option.

At first we have the Product module. It just provides some helper functions to create and modify the following Product Record type.

1: 
2: 
3: 
4: 
5: 
type Product = {
    Id:    int
    Name:  string
    Price: decimal
}

The Product type should contain all our Business logic, validation and so on. It should only contain pure functions, usually this modules should contain the most code, but in this example Product doesn't really do much, so it is the smallest module.

DB is basically an Application layer or Service layer. It just provides the code to save things to a database or read things from a database. It is just a simple Key/Value interface. As you can see from the types. It is fully generic and could work with any type.

From the types, only getById returns an option. But that doesn't mean it is the only function that has some option handling in it.

Finally, on top we have the CLI module. The purpose of it is to provide the logic for the CLI. We parse our input string, interpret the commands and update the database.

DB

Map itself is an immutable type. So the general idea is that operations like insert, delete, update return the new state of Map.

1: 
2: 
    let getById id db =
        Map.tryFind id db

Our getbyid function is fairly simple, as it is just a call to tryFind. But it makes sense to talk about tryFind and compare it with a Dictionary. If you use a Dictionary in C# and you try to fetch an entry you usually have two different behaviours.

Either you choose that retrieving an entry could throw an exception, or you use the TryGetValue method. It doesn't throw an exception, but instead it returns a bool that you must check, to get the value you have to pass an out parameter to TryGetValue. I don't really like both ways. We don't want exceptions and wrap the code in try/catch. But also TryGetValue is annoying as we first create a (mutable) value and pass it to TryGetValue.

In F# on the other hand a function like tryFind just returns an option, we either have Some value or None. Here we just return the option as-is. That is also the reason why getById returns an option. We don't must immediately check the option. We also can just pass it as a value around. We only must check it if we need the value inside Some.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
    let update key value db =
        Map.add key value db

    let updateId f key db =
        db
        |> getById key
        |> Option.map (fun value -> update key (f value) db)
        |> Option.ifNone db

Inside DB i added a updateId function. Usually i wouldn't add such a function, but on the other hand, it is useful and at the same time interesting. The purpose of this function is that we can fetch and update an entry at the same time. The interesting is: updateId don't return an option.

At first we fetch the specified entry with getById that returns an option. When we got a result we want to transform the value. That's why we have (f value) in-there. The result of this is passed to update, that then returns a new updated Map. But what happens if getById returns a None instead of a value? The Option.map part will also directly return None instead of executing the update call. But we always want to ensure to return a Map, so we either return a new updated Map or in the case we had None. Option.ifNone returns the Map we started with, without any change applied.

Assume you work in a language without option and you get a null. You theoretically could write.

1: 
2: 
3: 
let updateId f key db =
    let value = db |> getById key
    update key (f value) db

But you will only notice that this is error-prone. As this code can just throw a NullReferenceException if you try to update a non-existent value. Sure you then could add the typical null checks to get it safe.

1: 
2: 
3: 
4: 
5: 
let updateId f key db =
    let value = db |> getById key
    if value <> null
    then update key (f value) db
    else db

The difference is, with an option you are forced to handle that case. You cannot write error-prone code in the first-place! But in general it also shows that updateId even the fact that an id could not be presented, still don't return an option. Actually we have an operation that cannot fail here. Either it updates an entry, or it does nothing. And you get that behaviour ensured by the type-system at compilation-time!

CLI

The CLI handling is in general split into two parts. First I parse the input by the user and I use a Discriminated Union to save the different input commands. The type is just named Command. parseCommand do the transformation of converting the input string to such a Command. The interesting thing is. Parsing could fail, but you don't see a Command option for this function.

This is a general idea. Sure option is nice, but if you anyway build a custom type, you can make the "None", "NotExistence" or "Invalid" a part of your type. Here I just have a Command that contains an Invalid case.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
    // The CLI Commands
    type Command =
        | Invalid     of string
        | Show
        | ShowProduct of int
        | NewName     of int * string
        | NewPrice    of int * decimal
        | Insert      of name:string * price:decimal
        | Delete      of int
        | Exit

In general you can see that the Discriminated Union just contains a case for every CLI command, it also contains the parsed input as int, string or decimal. In my code I just use Pattern Matching, but with Active Patterns I can specify transformation functions on top of it.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
    // Parsing string to Command
    let (|Int|_|) input =
        match Int32.TryParse input with
        | false,_ -> None
        | true,x  -> Some x

    let (|Decimal|_|) input =
        match Decimal.TryParse input with
        | false,_ -> None
        | true,x  -> Some x

    let (|LC|) (str:string) = str.ToLowerInvariant()

LC in that example is a Complete Active Pattern. As it will always succeed. LC just takes a string and turns it into a lower-case string. I use it like that

1: 
| [| LC "show" |] -> Show

This not only a Pattern Match that checks if we have an array with "show" as the only entry. It first transform the entry to a lower-case string and then compares it with "show".

Transforming a string to a lower-case string always succeed, but we also can use Partial Active Patterns for operation that could fail. That is what (|Int|_|) and (|Decimal|_|) stands for. Both operation try to parse a string as either Int or Decimal. In a success case we return Some, otherwise None. But the handling of those option is done for us. You also see something like that in other functions.

For example a List.choose is basically a map and then a filter operation in one step. You not only can transform an entry to a new value. By returning Some or None you also can filter. The choose operation only take the Some elements. Here we have the same. Using Options for a success/failure or filter case is quite common.

So parsing and validation can be done in one-step. For example parsing the price case looks like this.

1: 
| [| LC "price"; Int id; Decimal price |] ->

We Pattern Match and only if we have an Array with three elements and the second element can successfully transformed into an int and the third element can be turned into a Decimal, only then the case successfully matches. id and price are also int and decimal not option.

1: 
2: 
3: 
4: 
5: 
    let showProduct db id =
        match db |> DB.getById id with
        | None   -> printfn "Product with id %d doesn't exists." id
        | Some p -> printProduct p
        db

Also the CLI functions have the idea that they just return the new Map. showProduct is a operation that could fail, as the specified entry cannot exists. That's why we handle both cases here. We either print a message that the Product didn't exists, or we print the returned product. Because we expect the new Map state as a return value, but showProduct never changes Map, we always just return db (the input Map).

1: 
2: 
3: 
4: 
5: 
    let updateName db id newName =
        DB.updateId (Product.withName newName) id db

    let updatePrice db id newPrice =
        DB.updateId (Product.withPrice newPrice) id db

Our DB.updateId already handled the option for us, that means we just can write the updateName and updatePrice functions without thinking about option. And still everything works as expected without any failure!

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
    // Execution of CLI Commands
    let executeCommand db command =
        match command with
        | Show               -> Some <| show db
        | ShowProduct x      -> Some <| showProduct db x
        | Insert(name,price) -> Some <| insert db name price
        | NewName(id,name)   -> Some <| updateName db id name
        | NewPrice(id,price) -> Some <| updatePrice db id price
        | Delete x           -> Some <| DB.delete x db
        | Invalid input      ->
            printfn "Error: invalid command -- [%s]" input
            Some db
        | Exit -> None

In executeCommand I use the option type for signalling if we still continue or want to stop. The idea is once again that we just return a new Map after every command. That Map contains the new state. But once I return None it marks an end. Here you also see the mapping from the Command to the actual functions. The Invalid case for example doesn't abort the program. We just print an error message and just return db unchanged. Only the Exit command ends the program.

1: 
2: 
    let eval db str =
        parseCommand str |> executeCommand db

Near the end I simplified the whole program into two function. Parsing a string to a Command, and executing a Command that returns us the next Map. At this level we just compose both operations into a single function. Now we have eval.

eval takes a Map and an input string, and will just return a new updated Map for us. We already achieved a higher-level beyond error or option checking.

1: 
2: 
3: 
4: 
5: 
6: 
let main db =
    let rec loop db =
        printf "Command: "
        stdin.ReadLine() |> CLI.eval db |> Option.iter loop
    loop db
    ()

The only thing needed is the main program loop. We just print "Command: " to the terminal. We read a string. Pass it to Cli.eval db. This will Parse our string, do all kind of checking and just returns us a eventually a new Map. As long we get a new Map. we just call loop again that recurs with the new Map.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
// Start with some default entries
let storage =
    Map.ofList [
        1, Product.create 1 "TV" 499.99m
        2, Product.create 2 "A Book" 29.99m
        3, Product.create 3 "Game Console" 349.99m
    ]

// Start program loop
main storage

We create an immutable Map as our starting database. With main storage we finally start our whole application loop.

Further Reading

Full Code

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
117: 
118: 
119: 
120: 
121: 
122: 
123: 
124: 
125: 
126: 
127: 
128: 
129: 
130: 
131: 
132: 
133: 
134: 
135: 
136: 
137: 
138: 
139: 
140: 
141: 
142: 
143: 
144: 
145: 
146: 
147: 
148: 
149: 
150: 
151: 
152: 
153: 
154: 
155: 
156: 
157: 
158: 
159: 
160: 
161: 
162: 
// The "module App =" including the intention is not needed, here i added it
// because of the tools i use for this blog. But you can Copy & Paste the code
// to a "fsx" file without "module App =" and execute the code with F# Interactive
module App =
    open System
    open System.Text.RegularExpressions

    module Option =
        let ifNone x opt = defaultArg opt x

    type Product = {
        Id:    int
        Name:  string
        Price: decimal
    }

    [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
    module Product =
        let create id name price =
            {Id=id; Name=name; Price=price}

        let withName newName product =
            {product with Name=newName}

        let withPrice newPrice product =
            {product with Price=newPrice}

    module DB =
        let getById id db =
            Map.tryFind id db

        let getAll db =
            db |> Map.toList |> List.sortBy fst |> List.map snd

        let containsId id db =
            Map.containsKey id db

        let nextId db =
            (Map.fold (fun acc k v -> max acc k) 0 db) + 1

        let insert key value db =
            // Check if we have that key
            if   db |> containsId key
            then db                      // yes -- return "db" unchanged
            else db |> Map.add key value // no  -- add product

        let delete id db =
            db |> Map.remove id

        let update key value db =
            Map.add key value db

        let updateId f key db =
            db
            |> getById key
            |> Option.map (fun value -> update key (f value) db)
            |> Option.ifNone db

    module CLI =
        // The CLI Commands
        type Command =
            | Invalid     of string
            | Show
            | ShowProduct of int
            | NewName     of int * string
            | NewPrice    of int * decimal
            | Insert      of name:string * price:decimal
            | Delete      of int
            | Exit

        // Parsing string to Command
        let (|Int|_|) input =
            match Int32.TryParse input with
            | false,_ -> None
            | true,x  -> Some x

        let (|Decimal|_|) input =
            match Decimal.TryParse input with
            | false,_ -> None
            | true,x  -> Some x

        let (|LC|) (str:string) = str.ToLowerInvariant()

        let parseCommand (input:string) =
            let args =
                Regex.Matches(input, "(?:\"(?<str>[^\"]+)\"|(?<str>\S+))")
                |> Seq.cast<Match>
                |> Seq.map (fun m -> m.Groups.["str"].Value)
                |> Seq.toArray
            match args with
            | [| LC "show" |]        -> Show
            | [| LC "show"; Int x |] -> ShowProduct x
            | [| LC "insert"; name; Decimal price |] ->
                Insert (name,price)
            | [| LC "name"; Int id; name |] -> NewName (id,name)
            | [| LC "price"; Int id; Decimal price |] ->
                NewPrice (id,price)
            | [| LC "delete"; Int x |]      -> Delete x
            | [| LC "exit" |] -> Exit
            | _               -> Invalid input

        // Helper functions
        let printProduct product =
            printfn "%4d | %25s | %4.2f" product.Id product.Name product.Price

        // CLI Commands
        let show db =
            printfn "%4s | %25s | %s" "Id" "Name" "Price"
            db |> DB.getAll |> List.iter printProduct
            db

        let showProduct db id =
            match db |> DB.getById id with
            | None   -> printfn "Product with id %d doesn't exists." id
            | Some p -> printProduct p
            db

        let insert db name price =
            let id      = DB.nextId db
            let product = Product.create id name price
            DB.insert id product db

        let updateName db id newName =
            DB.updateId (Product.withName newName) id db

        let updatePrice db id newPrice =
            DB.updateId (Product.withPrice newPrice) id db

        // Execution of CLI Commands
        let executeCommand db command =
            match command with
            | Show               -> Some <| show db
            | ShowProduct x      -> Some <| showProduct db x
            | Insert(name,price) -> Some <| insert db name price
            | NewName(id,name)   -> Some <| updateName db id name
            | NewPrice(id,price) -> Some <| updatePrice db id price
            | Delete x           -> Some <| DB.delete x db
            | Invalid input      ->
                printfn "Error: invalid command -- [%s]" input
                Some db
            | Exit -> None

        let eval db str =
            parseCommand str |> executeCommand db

    let main db =
        let rec loop db =
            printf "Command: "
            stdin.ReadLine() |> CLI.eval db |> Option.iter loop
        loop db
        ()

    // Start with some default entries
    let storage =
        Map.ofList [
            1, Product.create 1 "TV" 499.99m
            2, Product.create 2 "A Book" 29.99m
            3, Product.create 3 "Game Console" 349.99m
        ]

    // Start program loop
    main storage
type option<'a> =
  | Some of 'a
  | None

Full name: optionals.option<_>
union case option.Some: 'a -> option<'a>
union case option.None: option<'a>
val containsE : str:string -> string

Full name: optionals.containsE
val str : string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
System.String.Contains(value: string) : bool
val containsE : str:string -> option<string>

Full name: optionals.containsE
val opt : option<string>

Full name: optionals.opt
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
val x : option<int>

Full name: optionals.x
val y : option<int>

Full name: optionals.y
val x : int
val y : int option

Full name: optionals.y
module Option

from Microsoft.FSharp.Core
val map : mapping:('T -> 'U) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.map
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

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

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
Multiple items
val decimal : value:'T -> decimal (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.decimal

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

Full name: Microsoft.FSharp.Core.decimal

--------------------
type decimal<'Measure> = decimal

Full name: Microsoft.FSharp.Core.decimal<_>
Multiple items
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  override Equals : obj -> bool
  member Remove : key:'Key -> Map<'Key,'Value>
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
type bool = System.Boolean

Full name: Microsoft.FSharp.Core.bool
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
val updateId : f:'a -> key:'b -> db:'c -> 'd

Full name: optionals.updateId
val f : 'a
val key : 'b
val db : 'c
val value : obj
val updateId : f:'a -> key:'b -> db:'c -> 'c

Full name: optionals.updateId
val id : x:'T -> 'T

Full name: Microsoft.FSharp.Core.Operators.id
namespace System
namespace System.Text
namespace System.Text.RegularExpressions
val ifNone : x:'a -> opt:'a option -> 'a

Full name: 2016-04-11-optionals.App.Option.ifNone
val x : 'a
val opt : 'a option
val defaultArg : arg:'T option -> defaultValue:'T -> 'T

Full name: Microsoft.FSharp.Core.Operators.defaultArg
type Product =
  {Id: int;
   Name: string;
   Price: decimal;}

Full name: 2016-04-11-optionals.App.Product
Product.Id: int
Product.Name: string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
Product.Price: decimal
Multiple items
val decimal : value:'T -> decimal (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.decimal

--------------------
type decimal = Decimal

Full name: Microsoft.FSharp.Core.decimal

--------------------
type decimal<'Measure> = decimal

Full name: Microsoft.FSharp.Core.decimal<_>
Multiple items
type CompilationRepresentationAttribute =
  inherit Attribute
  new : flags:CompilationRepresentationFlags -> CompilationRepresentationAttribute
  member Flags : CompilationRepresentationFlags

Full name: Microsoft.FSharp.Core.CompilationRepresentationAttribute

--------------------
new : flags:CompilationRepresentationFlags -> CompilationRepresentationAttribute
type CompilationRepresentationFlags =
  | None = 0
  | Static = 1
  | Instance = 2
  | ModuleSuffix = 4
  | UseNullAsTrueValue = 8
  | Event = 16

Full name: Microsoft.FSharp.Core.CompilationRepresentationFlags
CompilationRepresentationFlags.ModuleSuffix: CompilationRepresentationFlags = 4
val create : id:int -> name:string -> price:decimal -> Product

Full name: 2016-04-11-optionals.App.ProductModule.create
val id : int
val name : string
val price : decimal
val withName : newName:string -> product:Product -> Product

Full name: 2016-04-11-optionals.App.ProductModule.withName
val newName : string
val product : Product
val withPrice : newPrice:decimal -> product:Product -> Product

Full name: 2016-04-11-optionals.App.ProductModule.withPrice
val newPrice : decimal
val getById : id:'a -> db:Map<'a,'b> -> 'b option (requires comparison)

Full name: 2016-04-11-optionals.App.DB.getById
val id : 'a (requires comparison)
val db : Map<'a,'b> (requires comparison)
val tryFind : key:'Key -> table:Map<'Key,'T> -> 'T option (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.tryFind
val getAll : db:Map<'a,'b> -> 'b list (requires comparison)

Full name: 2016-04-11-optionals.App.DB.getAll
val toList : table:Map<'Key,'T> -> ('Key * 'T) list (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.toList
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val sortBy : projection:('T -> 'Key) -> list:'T list -> 'T list (requires comparison)

Full name: Microsoft.FSharp.Collections.List.sortBy
val fst : tuple:('T1 * 'T2) -> 'T1

Full name: Microsoft.FSharp.Core.Operators.fst
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val snd : tuple:('T1 * 'T2) -> 'T2

Full name: Microsoft.FSharp.Core.Operators.snd
val containsId : id:'a -> db:Map<'a,'b> -> bool (requires comparison)

Full name: 2016-04-11-optionals.App.DB.containsId
val containsKey : key:'Key -> table:Map<'Key,'T> -> bool (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.containsKey
val nextId : db:Map<int,'a> -> int

Full name: 2016-04-11-optionals.App.DB.nextId
val db : Map<int,'a>
val fold : folder:('State -> 'Key -> 'T -> 'State) -> state:'State -> table:Map<'Key,'T> -> 'State (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.fold
val acc : int
val k : int
val v : 'a
val max : e1:'T -> e2:'T -> 'T (requires comparison)

Full name: Microsoft.FSharp.Core.Operators.max
val insert : key:'a -> value:'b -> db:Map<'a,'b> -> Map<'a,'b> (requires comparison)

Full name: 2016-04-11-optionals.App.DB.insert
val key : 'a (requires comparison)
val value : 'b
val add : key:'Key -> value:'T -> table:Map<'Key,'T> -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.add
val delete : id:'a -> db:Map<'a,'b> -> Map<'a,'b> (requires comparison)

Full name: 2016-04-11-optionals.App.DB.delete
val remove : key:'Key -> table:Map<'Key,'T> -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.remove
val update : key:'a -> value:'b -> db:Map<'a,'b> -> Map<'a,'b> (requires comparison)

Full name: 2016-04-11-optionals.App.DB.update
val updateId : f:('a -> 'a) -> key:'b -> db:Map<'b,'a> -> Map<'b,'a> (requires comparison)

Full name: 2016-04-11-optionals.App.DB.updateId
val f : ('a -> 'a)
val key : 'b (requires comparison)
val db : Map<'b,'a> (requires comparison)
Multiple items
module Option

from 2016-04-11-optionals.App

--------------------
module Option

from Microsoft.FSharp.Core
val value : 'a
type Command =
  | Invalid of string
  | Show
  | ShowProduct of int
  | NewName of int * string
  | NewPrice of int * decimal
  | Insert of name: string * price: decimal
  | Delete of int
  | Exit

Full name: 2016-04-11-optionals.App.CLI.Command
union case Command.Invalid: string -> Command
union case Command.Show: Command
union case Command.ShowProduct: int -> Command
union case Command.NewName: int * string -> Command
union case Command.NewPrice: int * decimal -> Command
union case Command.Insert: name: string * price: decimal -> Command
union case Command.Delete: int -> Command
union case Command.Exit: Command
val input : string
type Int32 =
  struct
    member CompareTo : value:obj -> int + 1 overload
    member Equals : obj:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member GetTypeCode : unit -> TypeCode
    member ToString : unit -> string + 3 overloads
    static val MaxValue : int
    static val MinValue : int
    static member Parse : s:string -> int + 3 overloads
    static member TryParse : s:string * result:int -> bool + 1 overload
  end

Full name: System.Int32
Int32.TryParse(s: string, result: byref<int>) : bool
Int32.TryParse(s: string, style: Globalization.NumberStyles, provider: IFormatProvider, result: byref<int>) : bool
union case Option.None: Option<'T>
union case Option.Some: Value: 'T -> Option<'T>
Multiple items
type Decimal =
  struct
    new : value:int -> decimal + 7 overloads
    member CompareTo : value:obj -> int + 1 overload
    member Equals : value:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member GetTypeCode : unit -> TypeCode
    member ToString : unit -> string + 3 overloads
    static val Zero : decimal
    static val One : decimal
    static val MinusOne : decimal
    static val MaxValue : decimal
    ...
  end

Full name: System.Decimal

--------------------
Decimal()
Decimal(value: int) : unit
Decimal(value: uint32) : unit
Decimal(value: int64) : unit
Decimal(value: uint64) : unit
Decimal(value: float32) : unit
Decimal(value: float) : unit
Decimal(bits: int []) : unit
Decimal(lo: int, mid: int, hi: int, isNegative: bool, scale: byte) : unit
Decimal.TryParse(s: string, result: byref<decimal>) : bool
Decimal.TryParse(s: string, style: Globalization.NumberStyles, provider: IFormatProvider, result: byref<decimal>) : bool
val x : decimal
String.ToLowerInvariant() : string
val parseCommand : input:string -> Command

Full name: 2016-04-11-optionals.App.CLI.parseCommand
val args : string []
Multiple items
type Regex =
  new : pattern:string -> Regex + 1 overload
  member GetGroupNames : unit -> string[]
  member GetGroupNumbers : unit -> int[]
  member GroupNameFromNumber : i:int -> string
  member GroupNumberFromName : name:string -> int
  member IsMatch : input:string -> bool + 1 overload
  member Match : input:string -> Match + 2 overloads
  member Matches : input:string -> MatchCollection + 1 overload
  member Options : RegexOptions
  member Replace : input:string * replacement:string -> string + 5 overloads
  ...

Full name: System.Text.RegularExpressions.Regex

--------------------
Regex(pattern: string) : unit
Regex(pattern: string, options: RegexOptions) : unit
Regex.Matches(input: string, pattern: string) : MatchCollection
Regex.Matches(input: string, pattern: string, options: RegexOptions) : MatchCollection
module Seq

from Microsoft.FSharp.Collections
val cast : source:Collections.IEnumerable -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.cast
type Match =
  inherit Group
  member Groups : GroupCollection
  member NextMatch : unit -> Match
  member Result : replacement:string -> string
  static member Empty : Match
  static member Synchronized : inner:Match -> Match

Full name: System.Text.RegularExpressions.Match
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
val m : Match
property Match.Groups: GroupCollection
val toArray : source:seq<'T> -> 'T []

Full name: Microsoft.FSharp.Collections.Seq.toArray
active recognizer LC: string -> string

Full name: 2016-04-11-optionals.App.CLI.( |LC| )
active recognizer Int: string -> int option

Full name: 2016-04-11-optionals.App.CLI.( |Int|_| )
Multiple items
active recognizer Decimal: string -> decimal option

Full name: 2016-04-11-optionals.App.CLI.( |Decimal|_| )

--------------------
type Decimal =
  struct
    new : value:int -> decimal + 7 overloads
    member CompareTo : value:obj -> int + 1 overload
    member Equals : value:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member GetTypeCode : unit -> TypeCode
    member ToString : unit -> string + 3 overloads
    static val Zero : decimal
    static val One : decimal
    static val MinusOne : decimal
    static val MaxValue : decimal
    ...
  end

Full name: System.Decimal

--------------------
Decimal()
Decimal(value: int) : unit
Decimal(value: uint32) : unit
Decimal(value: int64) : unit
Decimal(value: uint64) : unit
Decimal(value: float32) : unit
Decimal(value: float) : unit
Decimal(bits: int []) : unit
Decimal(lo: int, mid: int, hi: int, isNegative: bool, scale: byte) : unit
val printProduct : product:Product -> unit

Full name: 2016-04-11-optionals.App.CLI.printProduct
val show : db:Map<'a,Product> -> Map<'a,Product> (requires comparison)

Full name: 2016-04-11-optionals.App.CLI.show
val db : Map<'a,Product> (requires comparison)
module DB

from 2016-04-11-optionals.App
val iter : action:('T -> unit) -> list:'T list -> unit

Full name: Microsoft.FSharp.Collections.List.iter
val showProduct : db:Map<int,Product> -> id:int -> Map<int,Product>

Full name: 2016-04-11-optionals.App.CLI.showProduct
val db : Map<int,Product>
val p : Product
val insert : db:Map<int,Product> -> name:string -> price:decimal -> Map<int,Product>

Full name: 2016-04-11-optionals.App.CLI.insert
Multiple items
module Product

from 2016-04-11-optionals.App

--------------------
type Product =
  {Id: int;
   Name: string;
   Price: decimal;}

Full name: 2016-04-11-optionals.App.Product
val updateName : db:Map<'a,Product> -> id:'a -> newName:string -> Map<'a,Product> (requires comparison)

Full name: 2016-04-11-optionals.App.CLI.updateName
val updatePrice : db:Map<'a,Product> -> id:'a -> newPrice:decimal -> Map<'a,Product> (requires comparison)

Full name: 2016-04-11-optionals.App.CLI.updatePrice
val executeCommand : db:Map<int,Product> -> command:Command -> Map<int,Product> option

Full name: 2016-04-11-optionals.App.CLI.executeCommand
val command : Command
val eval : db:Map<int,Product> -> str:string -> Map<int,Product> option

Full name: 2016-04-11-optionals.App.CLI.eval
val main : db:Map<int,Product> -> unit

Full name: 2016-04-11-optionals.App.main
val loop : (Map<int,Product> -> unit)
val printf : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printf
val stdin<'T> : IO.TextReader

Full name: Microsoft.FSharp.Core.Operators.stdin
module CLI

from 2016-04-11-optionals.App
val iter : action:('T -> unit) -> option:'T option -> unit

Full name: Microsoft.FSharp.Core.Option.iter
val storage : Map<int,Product>

Full name: 2016-04-11-optionals.App.storage
val ofList : elements:('Key * 'T) list -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.ofList
val ifNone : x:'a -> opt:'a option -> 'a

Full name: 2016-04-11-optionals.Option.ifNone
type Product =
  {Id: int;
   Name: string;
   Price: decimal;}

Full name: 2016-04-11-optionals.Product
val create : id:int -> name:string -> price:decimal -> Product

Full name: 2016-04-11-optionals.ProductModule.create
val withName : newName:string -> product:Product -> Product

Full name: 2016-04-11-optionals.ProductModule.withName
val withPrice : newPrice:decimal -> product:Product -> Product

Full name: 2016-04-11-optionals.ProductModule.withPrice
val getById : id:'a -> db:Map<'a,'b> -> 'b option (requires comparison)

Full name: 2016-04-11-optionals.DB.getById
val getAll : db:Map<'a,'b> -> 'b list (requires comparison)

Full name: 2016-04-11-optionals.DB.getAll
val containsId : id:'a -> db:Map<'a,'b> -> bool (requires comparison)

Full name: 2016-04-11-optionals.DB.containsId
val nextId : db:Map<int,'a> -> int

Full name: 2016-04-11-optionals.DB.nextId
val insert : key:'a -> value:'b -> db:Map<'a,'b> -> Map<'a,'b> (requires comparison)

Full name: 2016-04-11-optionals.DB.insert
val delete : id:'a -> db:Map<'a,'b> -> Map<'a,'b> (requires comparison)

Full name: 2016-04-11-optionals.DB.delete
val update : key:'a -> value:'b -> db:Map<'a,'b> -> Map<'a,'b> (requires comparison)

Full name: 2016-04-11-optionals.DB.update
val updateId : f:('a -> 'a) -> key:'b -> db:Map<'b,'a> -> Map<'b,'a> (requires comparison)

Full name: 2016-04-11-optionals.DB.updateId
Multiple items
module Option

from 2016-04-11-optionals

--------------------
module Option

from Microsoft.FSharp.Core
type Command =
  | Invalid of string
  | Show
  | ShowProduct of int
  | NewName of int * string
  | NewPrice of int * decimal
  | Insert of name: string * price: decimal
  | Delete of int
  | Exit

Full name: 2016-04-11-optionals.CLI.Command
val parseCommand : input:string -> Command

Full name: 2016-04-11-optionals.CLI.parseCommand
active recognizer LC: string -> string

Full name: 2016-04-11-optionals.CLI.( |LC| )
active recognizer Int: string -> int option

Full name: 2016-04-11-optionals.CLI.( |Int|_| )
Multiple items
active recognizer Decimal: string -> decimal option

Full name: 2016-04-11-optionals.CLI.( |Decimal|_| )

--------------------
type Decimal =
  struct
    new : value:int -> decimal + 7 overloads
    member CompareTo : value:obj -> int + 1 overload
    member Equals : value:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member GetTypeCode : unit -> TypeCode
    member ToString : unit -> string + 3 overloads
    static val Zero : decimal
    static val One : decimal
    static val MinusOne : decimal
    static val MaxValue : decimal
    ...
  end

Full name: System.Decimal

--------------------
Decimal()
Decimal(value: int) : unit
Decimal(value: uint32) : unit
Decimal(value: int64) : unit
Decimal(value: uint64) : unit
Decimal(value: float32) : unit
Decimal(value: float) : unit
Decimal(bits: int []) : unit
Decimal(lo: int, mid: int, hi: int, isNegative: bool, scale: byte) : unit
val printProduct : product:Product -> unit

Full name: 2016-04-11-optionals.CLI.printProduct
val show : db:Map<'a,Product> -> Map<'a,Product> (requires comparison)

Full name: 2016-04-11-optionals.CLI.show
module DB

from 2016-04-11-optionals
val showProduct : db:Map<int,Product> -> id:int -> Map<int,Product>

Full name: 2016-04-11-optionals.CLI.showProduct
val insert : db:Map<int,Product> -> name:string -> price:decimal -> Map<int,Product>

Full name: 2016-04-11-optionals.CLI.insert
Multiple items
module Product

from 2016-04-11-optionals

--------------------
type Product =
  {Id: int;
   Name: string;
   Price: decimal;}

Full name: 2016-04-11-optionals.Product
val updateName : db:Map<'a,Product> -> id:'a -> newName:string -> Map<'a,Product> (requires comparison)

Full name: 2016-04-11-optionals.CLI.updateName
val updatePrice : db:Map<'a,Product> -> id:'a -> newPrice:decimal -> Map<'a,Product> (requires comparison)

Full name: 2016-04-11-optionals.CLI.updatePrice
val executeCommand : db:Map<int,Product> -> command:Command -> Map<int,Product> option

Full name: 2016-04-11-optionals.CLI.executeCommand
val eval : db:Map<int,Product> -> str:string -> Map<int,Product> option

Full name: 2016-04-11-optionals.CLI.eval
val main : db:Map<int,Product> -> unit

Full name: 2016-04-11-optionals.main
module CLI

from 2016-04-11-optionals
val storage : Map<int,Product>

Full name: 2016-04-11-optionals.storage