Search code examples
f#comparisondiscriminated-union

How to compare deeply nested discriminated unions?


I want to unit test a function that returns a Result (see below).

My question is: How can I easily check if the result is numerically equal to the expected value?

Here's the version with exact matching.

type QuadraticResult =
    | ComplexResult of  Complex * Complex
    | DoubleResult of float
    | TwoResults of float * float


type Result=
    | QuadraticResult of QuadraticResult
    | LinearResult of LinearFormulaSolver.Result

/// Solves a x² + bx + c = 0
let Compute (a,b,c) : Result =



[<Fact>]
member test.``the solution for x² = 0.0 is a double 0.0`` ()=
    let result = Compute (1.0, 0.0, 0.0)
    let expected = Result.QuadraticResult (DoubleResult 0.0)

    // only exact match, I'd like to test if difference is below a certain threshold
    Assert.Equal (result, expected)

Here's the solution I use so far. It's based on Andreys solution but extended for the allowed distance, permutations of results and the linear case. :

let ComplexEquality distance (x : Complex) (y : Complex )= 
        let dx = x.Real - y.Real
        let dy = x.Imaginary - y.Imaginary
        abs (dx) < distance && abs(dy) < distance


let QuadraticEquality distance x y = match (x,y) with
                        | (ComplexResult (a,b),ComplexResult(c,d)) -> (ComplexEquality distance  a c && ComplexEquality distance b d) || (ComplexEquality distance  a d && ComplexEquality distance b c)
                        | (DoubleResult a,DoubleResult b) -> abs (a - b) < distance
                        | (TwoResults (a,b),TwoResults(c,d)) -> (abs(a - c) < distance && (b - d) < distance) || (abs(a - d) < distance && (b - c) < distance)
                        | _ -> false

let LinearEquality distance x y = match (x , y) with
                        | (SingleResult a, SingleResult b) -> abs (a-b) < distance
                        | (NoResults, NoResults) | (InfiniteResults, InfiniteResults) -> true
                        | _ -> false


let ResultEquality distance x y = match (x,y) with
                        | (QuadraticResult a,QuadraticResult b) -> QuadraticEquality distance a b
                        | (LinearResult a,LinearResult b) -> LinearEquality distance a b
                        | _ -> false

[<Fact>]
member test.``the solution for x² = 0 is a double 0`` ()=
    let result = QuadraticFormulaSolver.Compute (1.0, 0.0, 0.0)
    let expected = Result.QuadraticResult (QuadraticFormulaSolver.DoubleResult 0.00001)

    Assert.True( ResultEquality 0.001 result expected)

Solution

  • I think you just need to write helper functions. For example:

    open System.Numerics
    
    
    type QuadraticResult =
        | ComplexResult of  Complex * Complex
        | DoubleResult of float
        | TwoResults of float * float
    
    type Result=
        | QuadraticResult of QuadraticResult
        | LinearResult of int
    
    let QuadraticEquality x y = match (x,y) with
                                | (ComplexResult (a,b),ComplexResult(c,d)) -> (a.Equals c) && (b.Equals d)
                                | (DoubleResult a,DoubleResult b) -> a = b
                                | (TwoResults (a,b),TwoResults(c,d)) -> (a = b) && (c = d)
                                | _ -> false
    
    let ResultEquality x y = match (x,y) with
                             | (QuadraticResult a,QuadraticResult b) -> QuadraticEquality a b
                             | (LinearResult a,LinearResult b) -> a = b
                             | _ -> false
    

    And in the tests easy to write

    Assert.IsTrue(ResultEquality result expected);