如何比较深层嵌套的歧视联盟?

时间:2016-07-20 15:23:27

标签: f# comparison discriminated-union

我想对一个返回Result的函数进行单元测试(见下文)。

我的问题是:如何轻松检查结果在数值上是否等于预期值?

这里是完全匹配的版本。

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)

这是我到目前为止使用的解决方案。 它基于安德烈的解决方案,但扩展了允许的距离,结果的排列和线性情况。 :

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)

2 个答案:

答案 0 :(得分:6)

我认为没有任何“魔术”可以让你自动执行此操作。我认为你有三种选择:

  1. 编写自定义函数以执行适用于现有类型的相等性测试,并对所有嵌套的float值执行特殊的比较

  2. 在实现自定义比较的float上写一个包装器,然后在有区别的联盟中使用此类型

  3. 写一些基于反射的魔术来执行自定义相等测试。

  4. 其中,我认为(1)可能是最简单的选择 - 即使它意味着更多的打字。如果您想在程序中的任何位置使用此自定义比较,则选项(2)可能会很有趣。最后(3)如果你有很多不同的嵌套类型可能会有意义,但它也是最容易出错的选项。

    我写了一个(2)的最小演示,但我仍然认为(1)可能是更好的方法:

    [<Struct; CustomComparison; CustomEquality>] 
    type ApproxFloat(f:float) = 
      member x.Value = f
      override x.GetHashCode() = f.GetHashCode()
      override x.Equals(another) =
        match another with
        | :? ApproxFloat as y -> abs (x.Value - y.Value) <= 0.001
        | _ -> false
      interface System.IComparable with
        member x.CompareTo(another) = 
          match another with
          | :? ApproxFloat as y -> compare x.Value y.Value
          | _ -> failwith "Cannot compare"
    
    type Complex = 
      | Complex of ApproxFloat * ApproxFloat
    
    type Result = 
      | Result of Complex
    
    Result(Complex(ApproxFloat(1.0), ApproxFloat(1.0))) =
      Result(Complex(ApproxFloat(1.0001), ApproxFloat(1.0001))) 
    

答案 1 :(得分:1)

我认为你只需要编写辅助函数。例如:

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

并且在易于编写的测试中

Assert.IsTrue(ResultEquality result expected);