Categories
Tech

F# Friday – The Option Type, Part 3

One more word on the Option type. While Option allows you a more type-safe alternative to nulls, it does not itself handle nulls. In other words, this …

let s = Some null

… gets you a Some containing a null. That doesn’t help so much if our aim is to get away from null, not kick it down the road.

The Java Way

In version 8, Java introduced its own Optional type, no doubt inspired by the Option and Maybe types available in the functional languages. It does handle nulls. Take the following classes as examples:

public class User {
  private String username;
  private String name;
  
  public User(String username) { this(username, null); }
  public User(String username, String name) {
    this.username = username;
    this.name = name;
  }
  
  public String getUsername() { return username; }
  public String getName() { return name; }
}

public class Session {
  private User user;
  
  public Session() { this(null); }
  public Session(User u) { this.user = u; }
  
  public User getUser() { return user; }
}

public class Application {
  private Session session;

  public Application() { this(null); }
  public Application(Session s) { this.session = s; }
  
  public Session getSession() { return session; }
}

Because Optional handles null in its map operation, this works:

Application app = new Application(new Session());
String name = Optional.of(app)
                .map(Application::getSession)
                .map(Session::getUser)
                .map(User::getName)
                .orElse("n/a");
// name = "n/a"

In our example, the Session has a null User property. But that’s OK, because the map operation on line 4 (highlighted above) takes that null and converts it to a None, or more accurately, an Empty per Java parlance.

Back in .NET Land

We cannot do quite the same thing in F#. Now we, as enlightened F# developers, would never code up something that uses nulls all over the place like the Java classes above. But the C# world is more comfortable with nulls, so let’s say that you are working with a C# library that contains the analogs to the Java classes above:

public class User
{
    public string Username { get; private set; }
    public string Name { get; private set; }

    public User(string username, string name = null)
    {
        Username = username;
        Name = name;
    }
}

public class Session
{
    public User User { get; private set; }

    public Session(User u = null) { User = u; }
}

public class Application
{
    public Session Session { get; private set; }

    public Application(Session s = null) { Session = s; }
}

You pull that library into your F# project and write something like this:

let app = Application(Session())
let name =
    Some app
    |> Option.map (fun a -> a.Session)
    |> Option.map (fun s -> s.User)
    |> Option.map (fun u -> u.Name)
    |> defaultArg <| "n/a"
// NullReferenceException!!!

But that breaks down pretty quickly. Line 5 (highlighted above) yields an exception because s.User is null. Option.map in F# does not convert a null to a None. So then, what do we do?

Well, if you’re using F# 4.0, there is something new to the Option module: Option.ofObj. It is essentially a named constructor for Option. It takes an object reference and returns a None if it is a null, or a Some if it is not. Also, remember that Option.bind takes an operation that returns an Option and flattens it so that it does not return a nested Option, but just an Option. Therefore, you can change your code to this:

let app = Application(Session())
let name = 
    Some app
    |> Option.bind (fun a -> Option.ofObj a.Session)
    |> Option.bind (fun s -> Option.ofObj s.User)
    |> Option.bind (fun u -> Option.ofObj u.Name)
    |> defaultArg <| "n/a"
// name : string = "n/a"

It’s a bit more headache than you’d like, but that’s what you get for using flaky C# code, right?

If you’re not using F# 4.0 yet, then you can write a little utility function to do the same job:

let nullToOption = function 
                   | null -> None
                   | x -> Some x

let app = Application(Session())
let name = 
    Some app
    |> Option.bind (fun a -> nullToOption a.Session)
    |> Option.bind (fun s -> nullToOption s.User)
    |> Option.bind (fun u -> nullToOption u.Name)
    |> defaultArg <| "n/a"
// name : string = "n/a"

One reply on “F# Friday – The Option Type, Part 3”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.