5

I was wondering if you can add something like a "null-safe"-operator in F#. I know that there might be plans for such an operator in C# for the next bigger release.

If there is no way to achieve such a behaviour, is there a way to wrap a statement that possibly could throw a NullReferenceException into a block that catches the exception and just returns null.

I thought of something like:

let sth = %% <@ someobject.method().another().andAnother() @>

where %% is a custom operator that executes the expression and checks for the exception.

One Attempt I made was:

open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Linq.QuotationEvaluation

let (~%%) (right:Expr) =
  try 
    right.CompileUntyped()
    right()
  with
  | ex -> null

But this does not do what I wanted (in fact it doesn't even compile :)

I read through:

Does anyone have ideas how to elegantly create such a one-line try-catch for chained methods?

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
Peter Ittner
  • 551
  • 2
  • 13
  • 3
    I've got to say, while this may seem like a useful operator, I find that in practice it is not (Groovy has such an operator, and I am exposed to it alot). For starts, it's ripe for abuse: rather than thinking through the contract of a method, many programmers will blindly use the operator everywhere. This leads to propagating nulls when in fact a null pointer exception would have been appropriate. It's then difficult to debug exactly where the errant contract violating null input came from. – Stephen Swensen Apr 29 '14 at 16:37
  • Very Good Point! Laurent Le Brun also wrote an article about that: http://laurent.le-brun.eu/site/index.php/2008/03/26/32-design-by-contract-with-fsharp – Peter Ittner Apr 30 '14 at 07:49

3 Answers3

6

I do not think you can implement something like the .? operator proposed for C# in a practically useful and syntactically convenient way for F# as a library feature. Although, I think it would be a very useful language extension, so I'd submit it to the F# user voice and perhaps submit a pull request :-).

Exception handling. Your code sample is not quite doing the same as C#, because you are just handling arbitrary exceptions. The point of the C# feature is that it inserts null checks after each . - you could do that by transforming the quotation and then compiling it, but the compilation is going to be slow. Your function could really just be:

let orNull f = try f () with :? NullReferenceException -> null

And then you can write

orNull <| fun () -> x.Foo().Bar()

.. but as mentioned earlier, this is just wrapping code in a standard try block and it handles exceptions, so it is not going to be doing the same thing as the C# code.

Computation builder. If you wanted a fancier F# solution, you could come up with a computation expression builder that lets you write something like this:

safe { let! a = x.Foo()
       let! b = a.Bar()
       return b }

This can do the null checks in the same way as the C# .? operator, but you need a separate binding for each part of the method call chain, so it is a bit more typing. The computation builder inserts a hidden null check in the Bind member like this:

type NullBuilder() = 
  member x.Return(v) = v
  member x.Bind(v, f) = if (box v) = null then null else f v 

let safe = NullBuilder()
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
3

This is an interesting question. Fortunately, code that lives solely within the F# world (i.e. doesn't use .NET libraries authored in other languages) doesn't have the widespread problem that .? is intended to solve because nullability must be explicit (using [<AllowNullLiteral>]). However, there is a functional counterpart to this concept: the option type and bind function (bind is the foundation for workflows, as demonstrated by Tomas).

You could take something like this:

type T() =
    member this.M(b) = if b then Some this else None

T().M(true)
|> Option.bind (fun t -> t.M(true))
|> Option.bind (fun t -> t.M(false)) 
|> Option.bind (fun t -> t.M(true))

and define a bind operator

let (?>) o f = Option.bind f o

and shorten it to this:

T().M(true)
?> fun t -> t.M(true)
?> fun t -> t.M(false)
?> fun t -> t.M(true)

but, there's very little value in doing so. It's neither more concise nor clearer. However, I think it demonstrates:

  • the general concept you're dealing with
  • that it isn't inherently tied to null
  • how this is typically dealt with in a functional language
Daniel
  • 47,404
  • 11
  • 101
  • 179
  • thank you for your answer. I understoud Options and Binding more clearer by looking at: http://msdn.microsoft.com/de-de/library/ee353901.aspx But as you mentioned, I don't see much benefit in using this construct when dealing with my problem. The reason I use F# is that I want to write more explicit code and less boilder plate code. Using Option + Bind would not be clear to all readers in the first place, so I will probably stay with simple `try .. catch .. with` – Peter Ittner Apr 30 '14 at 07:41
1

Maybe use F# worflows, extending the idea of the Maybe monad, and be able to write this:

let sth = maybe { someobject.method().another().another() }

See http://santialbo.com/blog/2013/03/27/monads-in-f-sharp/

Mau
  • 14,234
  • 2
  • 31
  • 52