Mike Ward
Mike Ward

Reputation: 3321

Testing for null reference in F#

Given the following:

[<DataContract>]
type TweetUser = {
    [<field:DataMember(Name="followers_count")>] Followers:int
    [<field:DataMember(Name="screen_name")>] Name:string
    [<field:DataMember(Name="id_str")>] Id:int
    [<field:DataMember(Name="location")>] Location:string}

[<DataContract>]
type Tweet = {
    [<field:DataMember(Name="id_str")>] Id:string
    [<field:DataMember(Name="text")>] Text:string
    [<field:DataMember(Name="retweeted")>] IsRetweeted:bool
    [<field:DataMember(Name="created_at")>] DateStr:string
    [<field:DataMember(Name="user", IsRequired=false)>] User:TweetUser
    [<field:DataMember(Name="sender", IsRequired=false)>] Sender:TweetUser
    [<field:DataMember(Name="source")>] Source:string}

Deserializing with DataContractJsonSerializer(typeof<Tweet[]>) will result in either the User or Sender field being null (at least that's what the debugger is telling me).

If I try to write the following:

    let name = if tweet.User <> null 
                  then tweet.User.Name
                  else tweet.Sender.Name

the compiler emits the error: "The type 'TweetUser' does not have 'null' as a proper value"

How do I test null values in this case?

Upvotes: 15

Views: 5573

Answers (3)

turkinator
turkinator

Reputation: 925

Though this question is old, I didn't see any examples of boxing to solve the problem. In cases where my presenter does not allow null literals, but may be set from a view, I prefer to use boxing.

isNull <| box obj

or

let isMyObjNull = isNull <| box obj

or

match box obj with
| null -> (* obj is null *)
| _ -> (* obj is not null *)

Upvotes: 3

ildjarn
ildjarn

Reputation: 62975

To cyclically expand on @Tomas' answer ;-]

let name = if not <| obj.ReferenceEquals (tweet.User, null)
              then tweet.User.Name
              else tweet.Sender.Name

or

let inline isNull (x:^T when ^T : not struct) = obj.ReferenceEquals (x, null)

Unchecked.defaultof<_> is doing the right thing and producing nulls for your record types; the issue is that the default equality operator uses generic structural comparison, which expects you to always play by F#'s rules when using F# types. In any case, a null-check really only warrants referential comparison in the first place.

Upvotes: 20

Tomas Petricek
Tomas Petricek

Reputation: 243051

To add some details to the comment by @ildjarn, you are getting the error message, because F# does not allow using null as a value of types that are declared in F#. The motivation for this is that F# tries to eliminate null values (and NullReferenceException) from pure F# programs.

However, if you're using types that are not defined in F#, you are still allowed to use null (e.g. when calling a function that takes System.Random as an argument, you can give it null). This is needed for interoperability, because you may need to pass null to a .NET library or accept it as a result.

In your example, TweetUser is a (record) type declared in F#, so the language does not allow treating null as a value of type TweetUser. However, you can still get null value through i.e. Reflection or from C# code, so F# provides an "unsafe" function that creates a null value of any type - including F# records, which should not normally have null value. This is the Unchecked.defaultOf<_> function and you can use it to implement a helper like this:

let inline isNull x = x = Unchecked.defaultof<_>

Alternatively, if you mark a type with the AllowNullLiteral attribute, then you're saying to the F# compiler that it should allow null as a value for that specific type, even if it is a type declared in F# (and it would not normally allow null).

Upvotes: 15

Related Questions