Options in F#

In the last post I compared the new Nullable reference types that were introduced in C# 8 with using Option from language-ext, which tries to mimic the functional paradigm approach to representing the abscence of a value. One of the major drawbacks of Option in C# is that there is no built in language-support, instead you have to rely on third party packages, or roll your own.

In F# however, as well as in other functional languages, Option is built in from the start and is the default way to represent a missing value. I usually don’t write code in F# but I wanted to take a look on how the scenario I used in the previous post would look like in a functional-first language so I made a test implementation in F#. Now this is just dummy code to test the scenario, in a real world application the functions would look a lot different:

open System

type UserId = UserId of uint32

type Customer =
    { Id: UserId }

type Email = Email of string

let tryParse (s: string) : UserId option =
    match UInt32.TryParse s with
    | true, num -> Some (UserId num)
    | _ -> None

let getCustomerById (id: UserId) : Customer option =
    match id with
    | UserId 1u -> Some { Id = UserId 1u }
    | UserId 2u -> Some { Id = UserId 2u }
    | _ -> None 

let getEmailForCustomer (customer: Customer) : Email option =
    match customer.Id with
    | UserId 1u -> Some (Email "apan@bepan.se")
    | _  -> None

let sendPromotionalEmail (email: Email) : unit =
    ()

[<EntryPoint>]
let main argv =
    if argv.Length <> 1 then
        failwith "Usage: program <user_id>"

    let result = 
        tryParse argv.[0]
        |> Option.bind getCustomerById 
        |> Option.bind getEmailForCustomer
        |> Option.map sendPromotionalEmail

    match result with
    | Some _ -> printfn "Email sent successfully"
    | None -> printfn "No email sent"
    0 

Note that even though F# is a statically typed language it is not required to explicitly define types for all input parameters and return values, most of the time the compiler can resolve the types so you don’t have to. In the code above I have however defined the types anyway to make it clearer how to work with Option, bind and map.

As you can see there are three functions that returns Options, tryParse that returns Option<CustomerId>, getCustomerById that returns Option<Customer>, and getEmailForCustomer that returns Option<Email>. Note that even though none of the functions takes an Option as input we are able to pipe the returned value through the functions in the main function using Option.bind and Option.map. Just like we were able to do in C# using language-ext.

Conclusion

In this simple scenario it was quite simple to port the C# implemenation to F#, so much that I would like to explore other scenarios as well and how to implement them using F#.

If using F# is an option it might be worth considering it, and get the full experience, over trying to copy the behavior by using language-ext. I will continue exploring F# and how it compares to C# in different scenarios. I believe it will be an interesting journey.

Lämna en kommentar

E-postadressen publiceras inte. Obligatoriska fält är märkta *