F# 如何比较深度嵌套的歧视联合?

F# 如何比较深度嵌套的歧视联合?,f#,comparison,discriminated-union,F#,Comparison,Discriminated Union,我想对返回结果的函数进行单元测试(见下文) 我的问题是:如何轻松地检查结果在数值上是否等于预期值 这是完全匹配的版本 type QuadraticResult = | ComplexResult of Complex * Complex | DoubleResult of float | TwoResults of float * float type Result= | QuadraticResult of QuadraticResult | Li

我想对返回
结果的函数进行单元测试(见下文)

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

这是完全匹配的版本

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)
type quardicresult=
|complexcomplex*Complex的结果
|浮点数的双重结果
|float*float的两个结果
类型结果=
|二次结果的二次结果
|LinearFormulaSolver.Result的LinearResult
///解算x²+bx+c=0
让我们计算(a,b,c):结果=
[]
成员测试。“x²=0.0的解决方案是双0.0”()=
让结果=计算(1.0,0.0,0.0)
让期望值=结果。二次结果(双结果0.0)
//只有精确匹配,我想测试差异是否低于某个阈值
Assert.Equal(结果,预期)
这是我目前使用的解决方案。 它基于Andreys解,但扩展到允许的距离、结果的排列和线性情况。 :

let ComplexEquality距离(x:Complex)(y:Complex)=
设dx=x.Real-y.Real
设dy=x.虚-y.虚
abs(dx)<距离&abs(dy)<距离
让平方质量距离x y=与(x,y)匹配
|(ComplexResult(a,b),ComplexResult(c,d))->(ComplexEquality距离a c和ComplexEquality距离b d)| |(ComplexEquality距离a d和ComplexEquality距离b c)
|(双结果a,双结果b)->abs(a-b)<距离
|(两个结果(a,b),两个结果(c,d))->(abs(a-c)<距离和(b-d)<距离)| |(abs(a-d)<距离和(b-c)<距离)
|_u->false
让LinearQuality距离x y=与(x,y)匹配
|(单结果a、单结果b)->abs(a-b)<距离
|(NoResults,NoResults)|(无限结果,无限结果)->true
|_u->false
让ResultEquality距离x y=与(x,y)匹配
|(正交结果a,正交结果b)->正交质量距离a b
|(线性结果a,线性结果b)->线性质量距离a b
|_u->false
[]
成员测试。`x²=0的解决方案是双0`()=
让结果=QuadraticFormulaSolver.Compute(1.0,0.0,0.0)
让期望值=结果.quardicResult(quardicFormulaSolver.DoubleResult 0.00001)
Assert.True(预期结果为ResultEquality 0.001)

我认为没有什么“魔术”能让你自动做到这一点。我认为你有三个选择:

  • 编写自定义函数对现有类型执行相等性测试,并对所有嵌套的
    浮点值执行特殊类型的比较

  • 在实现自定义比较的
    float
    上编写一个包装器,然后在区分的联合中使用此类型

  • 编写一些基于反射的魔术来执行自定义相等性测试

  • 在这些选项中,我认为(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))) 
    
    []
    类型ApproxFloat(f:浮动)=
    成员x.值=f
    重写x.GetHashCode()=f.GetHashCode()
    覆盖x.Equals(另一个)=
    相配
    | :? 近似为y->abs(x.Value-y.Value)假
    接口系统.i可与
    成员x.与(另一)相比=
    相配
    | :? 近似为y->比较x.值y.值
    |->failwith“无法比较”
    类型复合体=
    |ApproxFloat*ApproxFloat复合体
    类型结果=
    |复杂结果
    结果(复杂度(近似值(1.0)、近似值(1.0)))=
    结果(复合物(近似值(1.0001)、近似值(1.0001)))
    
    我认为您只需要编写助手函数。例如:

    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);
    

    谢谢你详细的回答。我只需要在简单的单元测试中使用它,所以我不想更改我的类型(顺便说一句,这些类型并不总是“我的”类型)。是否有一种简单的方法可以获得
    浮点值
    值,即使它保存在深层?然后我可以比较浮点值本身。对于
    Complex
    案例(它是.NET库的一部分,因此不容易更改),我认为
    大致相等的
    函数将是一个选项-但是我需要一个快速的方法来获得
    Complex
    值。我现在使用的是问题中包含的解决方案的增强版本。
    Assert.IsTrue(ResultEquality result expected);