Search code examples
f#equalitydiscriminated-union

testing equality with Dicriminated union in a list


I have defined different types :

type TypeNull() = class end

type MyType1 = {
    a:int;
    b:int
}

type MyType2 = {
    a:string;
    b:int
}

type MyType3 = {
    a:string;
    b:DateTime
}

and different disciminated union that use them :

type myDU =
    | A of int
    | B of string
    | C of string

type myDU2 =
    | D of MyType1
    | E of MyType2
    | F of TypeNull

I have on function that map myDU to myDU2 :

let applyArray = function
    | A x -> [E({a="1"; b=2})]
    | B x -> [D({a=1; b=2});E({a="1"; b=2});E({a="5"; b=24})]
    | C x -> [D({a=1; b=2});E({a="1"; b=2});F(TypeNull())]

and then two tests to test equality :

let arrayValueEquals =
    let expected = [D({a=1; b=2});E({a="1"; b=2});E({a="5"; b=24})]
    let actual = applyArray <| B("xxx")
    actual = expected

let arrayValueNullEquals =
    let expected = [D({a=1; b=2});E({a="1"; b=2});F(TypeNull())]
    let actual = applyArray <| C("xxx")
    actual = expected

Which in fsi gives :

val applyArray : _arg1:myDU -> myDU2 list
val arrayValueEquals : bool = true
val arrayValueNullEquals : bool = false

My question is as follows, why does the first test succeeds and not the second one?

here is the complete gist :

// Learn more about F# at http://fsharp.net. See the 'F# Tutorial' project
// for more guidance on F# programming.

#load "Library1.fs"
open test2
open System

type TypeNull() = class end

type MyType1 = {
    a:int;
    b:int
}

type MyType2 = {
    a:string;
    b:int
}

type MyType3 = {
    a:string;
    b:DateTime
}

type myDU =
    | A of int
    | B of string
    | C of string

type myDU2 =
    | D of MyType1
    | E of MyType2
    | F of TypeNull

let applyArray = function
    | A x -> [E({a="1"; b=2})]
    | B x -> [D({a=1; b=2});E({a="1"; b=2});E({a="5"; b=24})]
    | C x -> [D({a=1; b=2});E({a="1"; b=2});F(TypeNull())]

let arrayValueEquals =
    let expected = [D({a=1; b=2});E({a="1"; b=2});E({a="5"; b=24})]
    let actual = applyArray <| B("xxx")
    actual = expected

let arrayValueNullEquals =
    let expected = [D({a=1; b=2});E({a="1"; b=2});F(TypeNull())]
    let actual = applyArray <| C("xxx")
    actual = expected

Solution

  • In F# there is something called Structural Equality.

    In short: lists, arrays and Discriminated Unions support equality if their elements do support equality. For lists it will be an element by element comparison.

    Basic Discriminated Unions support Equality out-of-the-box but objects don't and that's why once you add TypeNull to the list the comparison fails.

    Try simply this:

    type TypeNull() = class end
    TypeNull() = TypeNull() // false
    

    then

    let actual = TypeNull()
    let expected = TypeNull()
    actual = expected // false
    

    so, unless you explicitly define equality for your object, the default behaviour is that it will yield true only if the two instances are the same:

    type TypeNull() = class end
    let a = TypeNull()
    let actual = a
    let expected = a
    actual = expected // true
    

    but with DUs it works automatically:

    type TypeNull = TypeNull
    TypeNull = TypeNull // true
    

    then

    let actual = TypeNull
    let expected = TypeNull
    actual = expected // True