One more word on the Option
type. While Option
allows you a more type-safe alternative to null
s, it does not itself handle null
s. 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 null
s. 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 null
s all over the place like the Java classes above. But the C# world is more comfortable with null
s, 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”
[…] F# Friday – The Option Type, Part 3 – Brad Collins […]