Thursday, September 9, 2010

Nullable in F#

In F#, unlike VB.NET and C#, Nullable<T> is not a language-supported construct. That is not to say that you can't use nullable types in F#. But in F#, Nullable<T> is just another type in the BCL, it doesn't get any special treatment from the compiler. Concretely, in C# you get:

  • Special type syntax: C# has some syntax sugar for nullable types, e.g. int? x = 4 is shorthand for Nullable<int> x = 4
  • Implicit conversions: int? a = null and int? a = 4 are both implicit conversions. In the first case because Nullable<int> is a value type so it can't really be null. In the second case there's an implicit conversion from int to Nullable<int>.
  • Overloaded operators: arithmetic, comparison, boolean operators are overloaded for nullable types. The null coalescing (??) operator is also overloaded.

Not having this in F# is not that much of a problem actually, since you usually use an Option type instead of Nullable. Option is widely supported in F# and it has the advantage of working with any underlying type, not just value types.

However this lack of support can become annoying when interoping with code that makes extensive use of Nullables. So let's see what we can do in F# to improve this situation.

Special type syntax

Not much we can do here... then again, it's not so bad either. The type parameter of a Nullable constructor is inferred by the F# compiler so instead of:

let x = Nullable<int>(4)

we can just say:

let x = Nullable 4

If we want a null Nullable, we do have to specify the type:

let x = Nullable<int>()

except for those cases where the compiler knows the type a priori, e.g.:

let x: Nullable<int> = Nullable()

we'll see some more of these later.

Also, we could create a shorter type alias:

type N<'a when 'a: (new: unit -> 'a) and 'a: struct and 'a :> ValueType> = Nullable<'a>
let x = N<int>()

but the few bytes saved are probably not worth the loss of readability.

Implicit conversions

F# does not allow implicit conversions. Just as in OCaml, you need to be explicit about the types you want (barring type inference). Sometimes this can be annoying if you're working with an API that assumes implicit conversions are part of the language, such as LINQ to XML. For these particular cases you can define an operator or shorthand to avoid calling op_Implicit constantly.

However, for Nullable types I'd avoid this. Being explicit about types (modulo type inference) is in the nature of F#.

Mapping to Option

So far we haven't done anything but complaining. So let's start writing some code for a change!

I mentioned before that Nullable is similar to Option. Indeed, mapping one to another is quite easy:

module Option =
   let fromNullable (n: _ Nullable) =
       if n.HasValue
           then Some n.Value
           else None
   let toNullable =
       function
       | None -> Nullable()
       | Some x -> Nullable(x)

This is not really an isomorphism though, like I said the domain of Nullable is smaller than the domain of Option.

Pattern matching

A very useful feature of Options is their ability to be pattern-matched. We can define partial active patterns over Nullable to achieve the same effect:

let (|Null|_|) (x: _ Nullable) =
   if x.HasValue
       then None
       else Some()

let (|Value|_|) = Option.fromNullable

Only problem is that the compiler can't statically assert that these partial active patterns cover all possible cases, so every time you use it you get a warning "Incomplete pattern matches on this expression". You can turn this off with a #nowarn "25" at the beginning of the file. EDIT: you can define the active pattern as a choice instead, to make it exhaustive. See kvb's comment below.

Comparison operators

Next, we define the comparison operators. Equality already works as expected so we don't need to do anything about it. For the other operators, we'll use a convention of appending '?' as a suffix for all operators. For example, '>' becomes '>?'. We'll also use a little helper function to allow us to express Nullable comparisons in terms of their underlying type's comparison functions:

let mapBoolOp op (a: _ Nullable) (b: _ Nullable) =
   if a.HasValue && b.HasValue
       then op a.Value b.Value
       else false

We can also define this using pattern matching, which allows us to take advantage of type inference and makes the code a bit more concise:

let mapBoolOp op a b =
   match a,b with
   | Value x, Value y -> op x y
   | _ -> false

Now the definition of the operators themselves:

let inline (>?) a b = (mapBoolOp (>)) a b
let inline (<?) a b = (mapBoolOp (<)) a b
let inline (>=?) a b = a >? b || a = b
let inline (<=?) a b = a <? b || a = b

And that's it for comparison operators.

Boolean operators

These apply only to Nullable<bool>:

Negation:

let inline notn (a: bool Nullable) =
   if a.HasValue
       then Nullable(not a.Value)
       else Nullable()

C# doesn't have a && (short-circuit and) operator for Nullable<bool>, but it does have a & (non-short-circuit and) operator. This is probably because the right part of the expression has to be evaluated anyway if the left part is null, so it's not much of a short-circuit evaluation. VB.NET has AndAlso and OrElse (short-circuit) for Nullable<bool>, but the documentation warns about this.

let inline (&?) a b =
   let rec and' a b =
       match a,b with
       | Null, Value y when not y -> Nullable(false)
       | Null, Value y when y -> Nullable()
       | Null, Null -> Nullable()
       | Value x, Value y -> Nullable(x && y)
       | _ -> and' b a
   and' a b

Or operator:

let inline (|?) a b = notn ((notn a) &? (notn b))

Arithmetic operators

To define arithmetic operators, we'll use another helper function similar to mapBoolOp:

let liftNullable op (a: _ Nullable) (b: _ Nullable) =
   if a.HasValue && b.HasValue
       then Nullable(op a.Value b.Value)
       else Nullable()
let inline (+?) a b = (liftNullable (+)) a b
let inline (-?) a b = (liftNullable (-)) a b
let inline ( *?) a b = (liftNullable ( *)) a b
let inline (/?) a b = (liftNullable (/)) a b

I stole the idea of lifting operators from this hubfs thread.

By the way, you might wonder why I didn't call mapBoolOp a lift. Well, at first I did, but then I read this article and found out I was using the term "lifting" the wrong way.

Null-coalescing operator

Let's see a basic example of the null coalescing operator applied to a nullable type in C#:

int? c = null;
int d = c ?? -1;

This is just like F#'s defaultArg, except it's for Nullable. If we have to express this in F# :

let c = Nullable<int>()
let d = if c.HasValue then c.Value else -1

If you find that too verbose you can hide it behind an infix operator:

let inline (|??) (a: 'a Nullable) (b: 'a) = if a.HasValue then a.Value else b
let d = c |?? -1

However this same operator can't be applied when chaining multiple '??', e.g.:

int? e = null;
int? f = null;
int g = e ?? f ?? -1;

It's possible to define another operator for this, and combine it with a Lazy to achieve the same effect of laziness and composability (see this article, which does it for Option types), but the end result looks weird in my opinion and not worth the added complexity. Instead, we can use pattern matching:

let e = Nullable<int>()
let f = Nullable<int>()
let g = match e,f with Value x,_ -> x | _,Value y -> y | _ -> -1

It's not as concise as C#, but at least it's pretty clear.

Functional behavior

Since Nullable is so similar to Option, we can also define some composable functions for Nullable just like the ones in the Option module:

module Nullable =
   let create x = Nullable x
   let getOrDefault n v = match n with Value x -> x | _ -> v
   let getOrElse (n: 'a Nullable) (v: 'a Lazy) = match n with Value x -> x | _ -> v.Force()
   let get (x: _ Nullable) = x.Value
   let fromOption = Option.toNullable
   let toOption = Option.fromNullable
   let bind f x =
       match x with
       | Null -> Nullable()
       | Value v -> f v
   let hasValue (x: _ Nullable) = x.HasValue
   let isNull (x: _ Nullable) = not x.HasValue
   let count (x: _ Nullable) = if x.HasValue then 1 else 0
   ...

You might wonder what value Nullable.create or Nullable.get could have. The reason behind them is that constructors and properties are not really first-class functions. You can't compose or pipe them. For example, to create a nullable you have to do let x = Nullable 5 , you can't write let x = 5 |> Nullable.

Conclusions

Even though nullable types are not as nice in F# as in other .NET languages, they're fully usable and the helper functions and operators defined here make it easier to do so. Some times, though, the best bet is to map them to Option types, do your thing, and then map back to Nullables.

Full source code is here.

5 comments:

  1. Regarding active patterns, you could make the patterns exhaustive:

    let (|Null|Value|) (x:_ Nullable) =
      if x.HasValue then Value x.Value
      else Null

    ReplyDelete
  2. @kvb: thanks, didn't know choices could take parameters.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. type t() =
    member x.a = 1

    let v = Nullable()

    Error 1 A generic construct requires that the type 't' is a CLI or F# struct type

    Ideas?

    ReplyDelete
  5. @Roman Lisper : works for me, but blogspot may have eaten some angled brackets there. Please use stackoverflow or the MSDN forums for general questions about F#.

    ReplyDelete