是否可以强制记录尊重某些不变量?

Mat*_*ias 24 f# record refinement-type

假设我想创建一个表示可接受的最小/最大边界的记录类型:

type Bounds = { Min: float; Max: float }
Run Code Online (Sandbox Code Playgroud)

有没有办法强制执行Min <Max?编写validateBounds函数很容易,我只是想知道是否有更好的方法来执行此操作.

编辑:我意识到,对于这个具体的例子,我可能会放弃暴露两个属性并重新排序参数,所以让我们说我们试图做

type Person = { Name: string }
Run Code Online (Sandbox Code Playgroud)

和名称需要至少有一个字符.

byt*_*ter 14

这是基于保护级别的另一种解决方案:

module MyModule =
    type Bounds = private { _min: float; _max: float } with
        // define accessors, a bit overhead
        member public this.Min = this._min
        member public this.Max = this._max
        static member public Make(min, max) =
            if min > max then raise (ArgumentException("bad values"))
            {_min=min; _max=max}

    // The following line compiles fine,
    // e.g. within your module you can do "unsafe" initialization
    let myBadBounds = {_min=10.0; _max=5.0}

open MyModule
let b1 = Bounds.Make(10.0, 20.0) // compiles fine
let b1Min = b1.Min
let b2 = Bounds.Make(10.0, 5.0) // throws an exception
// The following line does not compile: the union cases of the type 'Bounds'
// are not accessible from this code location
let b3 = {_min=10.0; _max=20.0}
// The following line takes the "bad" value from the module
let b4 = MyModule.myBadBounds
Run Code Online (Sandbox Code Playgroud)

  • @Daniel:你至少可以保持自由比较.投入一个"Bounds"活动模式,就像我为我的课程解决方案所展示的那样,这可能是最好的方法. (3认同)
  • 这很聪明 - 我不知道你可以将“构造函数”设为私有。 (2认同)
  • @Mathias:请记住,当字段为私有时,您将失去记录的大部分好处.不妨使用一堂课. (2认同)

Ste*_*sen 7

我认为你最好的选择是静态成员:

type Bounds = { Min: float; Max: float }
    with
        static member Create(min: float, max:float) =
            if min >= max then
                invalidArg "min" "min must be less than max"

            {Min=min; Max=max}
Run Code Online (Sandbox Code Playgroud)

并使用它

> Bounds.Create(3.1, 2.1);;
System.ArgumentException: min must be less than max
Parameter name: min
   at FSI_0003.Bounds.Create(Double min, Double max) in C:\Users\Stephen\Documents\Visual Studio 2010\Projects\FsOverflow\FsOverflow\Script2.fsx:line 5
   at <StartupCode$FSI_0005>.$FSI_0005.main@()
Stopped due to error
> Bounds.Create(1.1, 2.1);;
val it : Bounds = {Min = 1.1;
                   Max = 2.1;}
Run Code Online (Sandbox Code Playgroud)

但是,正如您所指出的,这种方法的重要一点是没有任何东西可以直接阻止构建"无效"记录.如果这是一个主要问题,请考虑使用类类型来保证不变量:

type Bounds(min:float, max:float) = 
    do
        if min >= max then
            invalidArg "min" "min must be less than max"

    with
        member __.Min = min
        member __.Max = max
Run Code Online (Sandbox Code Playgroud)

与方便的活动模式一起使用类似于记录获得的模式(特别是关于模式匹配):

let (|Bounds|) (x:Bounds) =
    (x.Min, x.Max)
Run Code Online (Sandbox Code Playgroud)

全部一起:

> let bounds = Bounds(2.3, 1.3);;
System.ArgumentException: min must be less than max
Parameter name: min
   at FSI_0002.Bounds..ctor(Double min, Double max) in C:\Users\Stephen\Documents\Visual Studio 2010\Projects\FsOverflow\FsOverflow\Script2.fsx:line 4
   at <StartupCode$FSI_0003>.$FSI_0003.main@()
Stopped due to error
> let bounds = Bounds(1.3, 2.3);;

val bounds : Bounds

> let isMatch = match bounds with Bounds(1.3, 2.3) -> "yes!" | _ -> "no";;

val isMatch : string = "yes!"

> let isMatch = match bounds with Bounds(0.3, 2.3) -> "yes!" | _ -> "no";;

val isMatch : string = "no"
Run Code Online (Sandbox Code Playgroud)