24

What is the right way to use Nullable in F#?

Currently I'm using this, but it seems awefully messy.

let test (left : Nullable<int>) = if left.HasValue then left.Value else 0

Console.WriteLine(test (new System.Nullable<int>()))
Console.WriteLine(test (new Nullable<int>(100)))

let x = 100
Console.WriteLine(test (new Nullable<int>(x)))
Jonathan Allen
  • 68,373
  • 70
  • 259
  • 447

2 Answers2

23

I'm afraid there's no syntactical sugar for nullable types in F# (unlike in C# where you simply append a ? to the type). So yeah, the code you show there does look terribly verbose, but it's the only way to use the System.Nullable<T> type in F#.

However, I suspect what you really want to be using are option types. There's a few decent examples on the MSDN page:

let keepIfPositive (a : int) = if a > 0 then Some(a) else None

and

open System.IO
let openFile filename =
   try
       let file = File.Open (filename, FileMode.Create)
       Some(file)
   with
       | exc -> eprintf "An exception occurred with message %s" exc.Message; None 

Clearly a lot nicer to use!

Options essentially fulfill the role of nullable types in F#, and I should think you really want to be using them rather than nullable types (unless you're doing interop with C#). The difference in implementation is that option types are formed by a discriminated union of Some(x) and None, whereas Nullable<T> is a normal class in the BCL, with a bit of syntactical sugar in C#.

Noldorin
  • 144,213
  • 56
  • 264
  • 302
  • Ok, so how do you convert a scalar like 100 into an "int option"? – Jonathan Allen Jun 03 '09 at 20:31
  • The first example in my post shows that. For the example you gave, it's just `Some(100)` – Noldorin Jun 03 '09 at 20:51
  • 2
    There is now plenty of syntactic support. See for instance [all operators that can take a nullable](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/symbol-and-operator-reference/nullable-operators), as well as functions like `Option.ofNullable`, `Option.toNullable` and many others. – Abel Sep 20 '17 at 04:24
16

You can let F# infer most of the types there:

let test (left : _ Nullable) = if left.HasValue then left.Value else 0

Console.WriteLine(test (Nullable()))
Console.WriteLine(test (Nullable(100)))

let x = 100
Console.WriteLine(test (Nullable(x)))

You can also use an active pattern to apply pattern matching on nullable types:

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

let test = // this does exactly the same as the 'test' function above
    function
    | Value v -> v
    | _ -> 0

I blogged some time ago about nullable types in F# [/shameless_plug]

Mauricio Scheffer
  • 98,863
  • 23
  • 192
  • 275
  • I just happened on this. You might combine this approach with Eric Lippert's series on nullable micro-optimizations (http://ericlippert.com/2012/12/20/nullable-micro-optimizations-part-one/) and use `if n.HasValue then n.GetValueOrDefault() else 0` instead. – phoog Jun 11 '14 at 15:14
  • @phoog Whenever you write "if n.HasValue" you risk writing a partial function, so it's best to wrap it in an exhaustive active pattern and always use that instead. – Mauricio Scheffer Jun 11 '14 at 15:21
  • I don't quite understand. Are you saying that it's better *not* to define the active pattern as `let (|Null|Value|) (x: _ Nullable) = if x.HasValue then Value (x.GetValueOrDefault()) else Null`? – phoog Jun 11 '14 at 15:29
  • No, I just meant to say that "user code" should use the exhaustive active pattern instead of using an "if". However you define the active pattern itself is fine. – Mauricio Scheffer Jun 11 '14 at 17:24
  • I see, thanks. But why does `if n.HasValue` risk writing a partial function? – phoog Jun 11 '14 at 20:29
  • 1
    HasValue/Value forces you to check and then separately extract the value instead of doing it in a single operation. If you happen to extract without checking you get an exception. With an exhaustive pattern match that cannot happen. – Mauricio Scheffer Jun 11 '14 at 21:12
  • 1
    Swift worked around this nicely by automatically extracting the value with an "[if let](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_432)" a sort of ad-hoc matching sugar, though it also offers the "[forced unwrapping](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_430)" (equivalent to the "if" here); the docs appropriately warn about it. – Mauricio Scheffer Jun 11 '14 at 21:13