F# - 将 LanguagePrimitives.GenericZero 与传递给 class 构造函数的值进行比较

F# - Compare LanguagePrimitives.GenericZero with a value passed on the class contructor

按照此处的建议,,我试图在 class 成员函数中使用 value > LanguagePrimitives.GenericZero,但我找不到使用方法。
问题可能是值是在类型构造函数中传递的,而不是传递给函数的。
请查看代码注释中的错误。

type IValidationCheck =   
    abstract member Validate: unit -> Result<unit, string>

type NumberIsPositiveCheck (property:string, value) =
    interface IValidationCheck with
        member (*inline*) this.Validate () =  //# does not allow me to use "inline"
            if value > LanguagePrimitives.GenericZero then Ok()  //# fail to compile: the type IComparable does not have a get_Zero operator
            else Error $"{property} must be greater than zero"

问题是编译器无法确定 value 的类型,它需要确定 LanguagePrimitives.GenericZero 的正确重载。编译器对 value 的唯一了解是您使用 > 运算符对其进行比较,因此它必须实现 IComparable。这就是 IComparable 出现在错误消息中的原因。

修复它的一种方法是显式指定 value:

的类型
type NumberIsPositiveCheck (property:string, value: int) =
  ...

但这会使整个classnon-generic,据我了解,这不是你想要的。

在这里你运气不好:你不能声明一个泛型 class 其中一个类型参数是 SRTP。这是因为底层 .NET 虚拟机根本不支持此类高级类型约束。所以没有办法编码这样的class.

(好吧,完全准确地说,classes 上的 SRTP 实际上受到一些限制,但绝对不存在接口)


但不要绝望,并非一无所有!如果仔细观察 class,您会注意到 Validate() 的结果在对象构造时完全确定。一旦调用构造函数,Validate() 的结果就已经知道了。

因此,您可以做的最简单的事情就是传入布尔结果而不是值:

type NumberIsPositiveCheck (property:string, result) =
    interface IValidationCheck with
        member this.Validate () =
            if result then Ok()
            else Error $"{property} must be greater than zero"

let validator = NumberIsPositiveCheck("foo", 42 > 0)

或者您可能想要通过整个 Result<_,_>,完全切断中间人。


但是为了涵盖我的所有基础,让我们假设出于某种未知原因您确实想要传递值本身,而不是布尔结果。

如果是这样,您还可以将泛型零与值本身一起传递,然后在 Validate() 中进行比较:

type NumberIsPositiveCheck (property:string, value, zero) =
    interface IValidationCheck with
        member this.Validate () =
            if value > zero then Ok()
            else Error $"{property} must be greater than zero"

let validator = NumberIsPositiveCheck("foo", 42, 0)

或者,您也可以传递一个比较函数而不是专门为零:

type NumberIsPositiveCheck (property:string, value, compare) =
    interface IValidationCheck with
        member this.Validate () =
            if compare value then Ok()
            else Error $"{property} must be greater than zero"

let validator = NumberIsPositiveCheck("foo", 42, fun x -> x > 0)

从这里开始,下一步就很明显了:你为什么要先写一个 class?它的唯一目的似乎是实现接口,你可以完全没有 class:

let inline numberIsPositiveCheck (property:string) value =
    { new IValidationCheck with
        member this.Validate () =
            if value > LanguagePrimitives.GenericZero then Ok()
            else Error $"{property} must be greater than zero"
    }

let validator = numberIsPositiveCheck "foo" 42

轰!现在您不必将零作为参数传递,因为现在它是一个函数,而不是 class,因此它可以具有 SRTP。


故事的寓意:classes 更少,功能更多。 类 比函数有用的频率低得多。