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

Ale*_* 75 1 f#

按照此处的建议F# GreaterThanZero 传递 int 或decimal,我尝试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"
Run Code Online (Sandbox Code Playgroud)

Fyo*_*kin 5

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

解决此问题的一种方法是显式指定 的类型value

type NumberIsPositiveCheck (property:string, value: int) =
  ...
Run Code Online (Sandbox Code Playgroud)

但这将使整个班级变得非通用,据我了解,这不是您想要的。

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

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


但不要绝望,并非一切都失去了!如果仔细观察您的类,您会发现 的结果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)
Run Code Online (Sandbox Code Playgroud)

或者您可能想要传递整个过程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)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

从这里开始,下一步就变得显而易见了:你为什么要写一个类?它的唯一目的似乎是实现接口,并且您可以完全不需要类:

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
Run Code Online (Sandbox Code Playgroud)

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


这个故事的寓意是:更少的类,更多的功能。类的使用频率远低于函数。