2

When I step through the following code, report on the second line is null. However, the third line generates a NullReferenceException.

member this.setTaggedResearchReportList (index : int) (taggedResearchReport : TaggedResearchReportUIVO option) =
    let report = Option.get(taggedResearchReport)
    if not(report.Equals(null)) then
        // do some stuff here

Why is that, and what can I do to avoid it? Thanks!

Added later:

Here's the line that calls this.setTaggedResearchReportList:

getMostRecentTaggedResearchReportForSecurityId (item.id) (new Action<_>(this.setTaggedResearchReportList 0))

Here's the getMostRecentTaggedResearchReportForSecurityId method:

let getMostRecentTaggedResearchReportForSecurityId (securityId : int) (callbackUI : Action<_>) =
    getSingleRPCResult<JSONSingleResult<TaggedResearchReportUIVO>, TaggedResearchReportUIVO>
        "TaggedResearchReportRPC"  
        "getMostRecentResearchReportForSecurityId" 
        (sprintf "%i" securityId)
        callbackUI
        (fun (x : option<JSONSingleResult<TaggedResearchReportUIVO>>) ->
            match x.IsSome with
                | true -> Some(Option.get(x).result)
                | false -> None 
        )
pnuts
  • 58,317
  • 11
  • 87
  • 139
Mike Cialowicz
  • 9,892
  • 9
  • 47
  • 76

5 Answers5

7

This isn't an answer per se, but to add to the discussion of null handling, particularly when interop-ing with C# code: I like to avoid using the [<AllowNullLiteral>] attribute and define a module such as the following to isolate the use of null in F# code.

[<AutoOpen>]
module Interop =

    let inline isNull value = System.Object.ReferenceEquals(value, null)
    let inline nil<'T> = Unchecked.defaultof<'T>
    let inline safeUnbox value = if isNull value then nil else unbox value
    let (|Null|_|) value = if isNull value then Some() else None

type Foo() = class end

type Test() =
    member this.AcceptFoo(foo:Foo) = //passed from C#
        if isNull foo then nullArg "foo"
        else ...

    member this.AcceptFoo2(foo:Foo) = //passed from C#
        match foo with
        | Null -> nullArg "foo"
        | _ -> ...

    member this.AcceptBoxedFoo(boxedFoo:obj) =
        let foo : Foo = safeUnbox boxedFoo
        ...

    member this.ReturnFoo() : Foo = //returning to C#
        if (test) then new Foo()
        else nil

In general, keep these checks as close to the interface of your API as possible and you can typically forget about null within F#, due to preserving the compiler's null checks.

Daniel
  • 47,404
  • 11
  • 101
  • 179
4

Since taggedResearchReport is an option type, you want to use pattern matching for your logic here:

member this.setTaggedResearchReportList (index : int) (taggedResearchReport : TaggedResearchReportUIVO option) =
   match taggedResearchReport with
   | Some(report) -> //do some stuff when we have "something"
   | None -> //do some different stuff when we have "nothing"

Update

I'm a bit lost in the additional code you added, but there is definitely some funky stuff going on with the way you are using option types. Use pattern matching instead of IsSome and Option.get. e.g. in your lambda expression should look more like this:

(fun (x : option<JSONSingleResult<TaggedResearchReportUIVO>>) ->
            match x with
                | Some(value) -> Some(value.result)
                | None -> None 
        )

I'm not sure if that will solve your problems, but it's a start.

Stephen Swensen
  • 22,107
  • 9
  • 81
  • 136
  • So I've tried that... and the problem is that `taggedResearchReport` is a `Some(null)`, and so it never gets to the `None` part. I've also tried putting a `| Some(null) -> // stuff` match in at the top, and I get the `The type 'TaggedResearchReportUIVO' does not have 'null' as a proper value.` Murgh. – Mike Cialowicz Mar 28 '11 at 17:20
  • @Mike - can you show the code where you call `setTaggedResearchReportList`? Something funky with your `taggedResearchReport` argument must be going on there. – Stephen Swensen Mar 28 '11 at 17:24
  • It's an RPC call: `getMostRecentTaggedResearchReportForSecurityId (item.id) (new Action<_>(this.setTaggedResearchReportList 0))`, which goes to... – Mike Cialowicz Mar 28 '11 at 17:24
  • Ah... I'll just put it in the original post. – Mike Cialowicz Mar 28 '11 at 17:26
  • 1
    Yikes -- try applying the `AllowNullLiteral` attribute to the definition of `TaggedResearchReportUIVO` and not wrapping values of the type in `option` (I don't know much about RPC, but it sounds like you will need to deal with null). – Stephen Swensen Mar 28 '11 at 17:28
  • I tried adding `AllowNullLiteral` to my `TaggedResearchReportUIVO` definition, but I get an error: `Records, union, abbreviations and struct types cannot have the 'AllowNullLiteral' attribute.` What a pain in the ass. I think I'm going to have to change my Java RPC method to return an empty `TaggedResearchReportUIVO` instead of a `null` when it can't find a report. This is quite a ridiculous pain in the ass... all the different null types in F# are incredibly annoying to deal with. – Mike Cialowicz Mar 28 '11 at 17:38
  • 2
    @Mike - please don't be discouraged. In practice I find availability of both `null` and `option` types in F# to pretty much never be an issue. The reason it's bad here is because you are trying to mix the two together. Of the three kinds of types mentioned, what kind of type is `TaggedResearchReportUIVO`? – Stephen Swensen Mar 28 '11 at 17:42
  • 1
    (if it is a record type or struct type, then you should be able to turn it into a class type (http://msdn.microsoft.com/en-us/library/dd233205.aspx) which can accept the NullLiteral attribute. If it is a union type, then you will need to "return an empty `TaggedResearchReportUIVO`" as you suggested.) – Stephen Swensen Mar 28 '11 at 17:56
  • Thanks Stephen. I'm new to F#, and the pattern that I'm using was developed by some others here who are also new to F#... so I'm sure there's lots of room for improvement. I ended up accepting kvb's answer below, since his `Unchecked.defaultof` was very helpful and allows me to get a bug fix out for this quickly. Your answers were very helpful as well, so I voted you up. Thanks! – Mike Cialowicz Mar 28 '11 at 17:58
4

Your TaggedResearchReportUIVO type is evidently defined in F#, and doesn't allow null as a proper value. Therefore, the compiler will prevent you from using the literal null as a value of that type; however, some other code is going behind the compiler's back and sticking a null value in there. To work around the immediate issue, you can try comparing against Unchecked.defaultof<TaggedResearchReportUIVO> instead of null.

However, it's probably worth assessing whether you should be making some more significant changes to avoid this type of issue in the first place. For instance, if it makes sense to have null as a proper value of type TaggedResearchReportUIVO, then you could add the [<AllowNullLiteral>] attribute to the definition of that type. Alternatively, if it really doesn't make sense to use null as a proper value, then you need to investigate the code which is generating the problematic value.

As an aside, there are other parts of your code that can be cleaned up considerably. For example, consider changing

fun (x : option<JSONSingleResult<TaggedResearchReportUIVO>>) -> 
    match x.IsSome with
    | true -> Some(Option.get(x).result)
    | false -> None

to

Option.map (fun (jsr : JSONSingleResult<_>) -> jsr.result)
kvb
  • 54,864
  • 2
  • 91
  • 133
  • Thank you! The `Unchecked.defaultof` is a good solution right now so that I can get a fix out for this bug quickly. I also tried the `[]` attribute, which unfortunately didn't work (seem my comments to one of the other questions). I'll look into the code refactoring that you mentioned. I'm fairly new to F#, and am using this pattern, which was implemented by others who were also new to F#... so I assume that there's plenty of room for improvement. – Mike Cialowicz Mar 28 '11 at 17:56
2

As others pointed out, the problem is that the deserializer may return null, but since you're working with F# types, you cannot directly check whether the value is null!

I think the best approach is to get rid of the null value coming from some .NET libraries as early as possible, so that you can keep the rest of the code clean. Here is how I think it could be fixed:

let getMostRecentTaggedResearchReportForSecurityId 
        (securityId : int) (callbackUI : Action<_>) =
    getSingleRPCResult< JSONSingleResult<TaggedResearchReportUIVO>, 
                        TaggedResearchReportUIVO >
        "TaggedResearchReportRPC"  
        "getMostRecentResearchReportForSecurityId" 
        (sprintf "%i" securityId)
        callbackUI
        (fun (x : option<JSONSingleResult<TaggedResearchReportUIVO>>) ->
            // Note: Return 'Some' only when the result is 'Some' and the 'result'
            // value carried inside the discriminated union is not 'null'
            match x with
            | Some(res) when 
                  // Make sure that there are no dangerous values (I suppose 
                  // JSONSingleResult is imported from .NET and 
                  // TaggedResearchReportUIVO is your F# type.
                  res <> null && res.result <> Unchecked.defaultOf<_> -> 
                Some(res.result)
            | _ -> None )

Then you can safely use pattern matching to work with the value, because it will never be null:

member this.setTaggedResearchReportList 
        (index : int) (taggedResearchReport : TaggedResearchReportUIVO option) =
    match taggedResearchReport with 
    | Some(report) ->
        // do some stuff here
    | _ -> () // do nothing here

I wouldn't probably use AllowNullLiteral because the only place where you get this problem is when getting the result from JSON library. Once you solve the problem there, you can safely use the value everywhere else in F# code (without checking for null).

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
1

If report is null, you cannot call report.Equals, so the code is useless. Change it to report = null. Also, defining taggedResearchReport as option and checking it for null seems to be a wrong use-case.

khachik
  • 28,112
  • 9
  • 59
  • 94
  • I've tried that, but I get a `The type 'TaggedResearchReportUIVO' does not have 'null' as a proper value` compile error. It's a very strange problem... I've tried checking for null before I do the `Option.get`, but that's no good either. – Mike Cialowicz Mar 28 '11 at 17:09
  • @Mike Cialowicz: types created in F# do not allow "null as a proper value" by default (preferring `option` types to explicitly represent "nothing" instead), but that is only enforced at compile time (e.g. comparison with `=`) while `Option.get` is giving you the `null` value you are observing at runtime. Using `Option.get` is something your pretty much never want to do, always pattern match on `option` types. – Stephen Swensen Mar 28 '11 at 17:14
  • @Mike so you should use pattern matching instead of null checking. – khachik Mar 28 '11 at 17:18