Search code examples
f#type-providers

Weird None behaviour in type providers


have a type provider with three properties 'a', 'b', and 'c' of type 'string', 'string option' and 'int option', respectively.

When I have an instance with "", None, and Some 1 in those properties, this fails:

(row1.a, row1.b, row1.c) |> should equal ("", None, Some 1)

But all of these work fine:

row1.a |> should equal ""

row1.b |> should equal None

row1.c |> should equal (Some 1)

("", None, Some 1) |> should equal ("", None, Some 1)

How is this possible? What can make the None in b be different from any other None? After compilation, None is just a null, can two null values be different in .Net?

Tuples have structural equality, like most F# types, so it should work. I get an NUnit.Framework.AssertionException with Message:

Expected: <(, , Some(1))>
But was:  <(, , Some(1))>

NUnit just calls .Equals, so that's where the problem is.

This also fails:

(row1.a, row1.b, row1.c).Equals(("", None, Some 1)) |> should equal true

The runtime type of row1 is System.Tuple<string,Microsoft.FSharp.Core.FSharpOption<string>,Microsoft.FSharp.Core.FSharpOption<int>>, so even this should work in theory:

row1 |> should equal ("", None, Some 1)

And in fact it does when there's no None in the tuple.

I can't reproduce this behaviour with anything else but type providers.


Solution

  • We have been bitten by this several times, so we create a specific note on this:

    FsUnit uses type test to implement its DSL. Type inference doesn't work on this DSL, so make sure that two compared values belong to the same type.

    For example, for some generic values such as True, False, etc, you need to specify their types (as formula<fol>.True, formula<fol>.False, etc) otherwise these values will be compared as being of type obj.

    If you take a look at how FsUnit is implemented, it isn't really type-safe. I believe Jack P.'s pull request is a step towards making FsUnit more type-safe. It seems to be an area to improve FsUnit.