简体   繁体   English

对F#记录的限制?

[英]Constraints on Records in F#?

I prefer records to classes in F# because I do not need to implement equality and hash-codes. 我更喜欢F#中的类记录,因为我不需要实现相等和哈希码。 However, they do not seem to provide a facility for ensuring invariants across the records values. 但是,它们似乎没有提供确保记录值不变量的工具。

For example, suppose I am modelling a sudoku board. 例如,假设我正在为数独板建模。 I might write a record type like this: 我可能写一个像这样的记录类型:

type Sudoku = { Values : Map<(int * int), int> }

let empty = { Values = Map.empty }

Now the problem is that I have some requirements on the Values map: 现在的问题是我对Values map有一些要求:

  • Every key (x, y) must have x and y be between 0 and 8, inclusive 每个键(x, y)必须有xy介于0和8之间(包括0和8)
  • Every value must be between 1 and 9, inclusive 每个值必须介于1和9之间(包括1和9)
  • Every value must be unique across a row 每一个值在一行中必须是唯一的
  • Every value must be unique across a column 每列的值必须是唯一的
  • Every value must be unique inside a 3x3 cell 每个值在3x3单元格内必须是唯一的

With a type, I could check these in the constructor and raise an exception if they are not met. 使用类型,我可以在构造函数中检查这些,如果不满足则引发异常。 That way, any code that uses a Sudoku is guaranteeed by the constructor that the invariants are met. 这样,任何使用Sudoku代码都由构造函数保证满足不变量。

How might I achieve this with a record? 我如何通过记录实现这一目标?

I would do this by creating single-case union types for the keys and values, giving those types private constructors: 我会通过为键和值创建单例联合类型来实现此目的,为这些类型提供私有构造函数:

[<Struct>] type SudokoKey = private SudokoKey of int
[<Struct>] type SudokoValue = private SudokoValue of int

Then, I would create record types for the cells and the board, again, giving the types private constructors: 然后,我将为单元格和板创建记录类型,再次给出类型私有构造函数:

type SudokoCell =
    private {
        X: SudokoKey
        Y: SudokoKey
    }

type SudokoBoard =
    private {
        Cells: Map<SudokoCell, SudokoValue>
    }

With the data types defined, I would create discriminated union with the different failure cases you've outlined: 在定义了数据类型的情况下,我将根据您概述的不同故障情况创建区别性联合:

type SudokoError =
| KeyOutOfRange of int
| ValueOutOfRange of int
| ValuesNotUniqueAcrossRow of Map<(int * int), int>
| ValuesNotUniqueAcrossColumn of Map<(int * int), int>
| ValuesNotUniqueIn3x3Cell of Map<(int * int), int>

Next, I would create modules for creating the keys and the values that perform the validations return a Result with either the key/value or the appropriate error. 接下来,我将创建用于创建密钥的模块,执行验证的值将返回带有键/值或相应错误的Result These modules would use the private constructors to instantiate the single-case union type: 这些模块将使用私有构造函数来实例化单例联合类型:

module SudokoKey =
    let create key =
        if key < 0 || key > 8
        then Error <| KeyOutOfRange key
        else Ok <| SudokoKey key

    let value (SudokoKey key) = key

module SudokoValue =
    let create value =
        if value < 1 || value > 9
        then Error <| ValueOutOfRange value
        else Ok <| SudokoValue value

    let value (SudokoValue value) = value

Then I'd create a similar module for SudokoCell that uses the SudokoKey.create function applied over x and y integers, and the private constructor of the cell type, to create a Result with either the cell or the appropriate error: 然后我为SudokoCell创建一个类似的模块,它使用在xy整数上应用的SudokoKey.create函数,以及单元格类型的私有构造函数,用单元格或相应的错误创建一个Result

module SudokoCell =
    let create (x,y) =
        x |> SudokoKey.create 
          |> Result.bind (fun xKey -> 
            y |> SudokoKey.create
              |> Result.bind (fun yKey -> 
                Ok { X = xKey; Y = yKey }))

Finally, you'd just need a SudokoBoard module to create the board itself, applying the remaining validation rules over the map. 最后,您只需要一个SudokoBoard模块来创建电路板本身,并在地图上应用剩余的验证规则。 I've stubbed this out, but left the implementation as an exercise: 我已将其删除,但将实现作为练习:

module SudokoBoard =
    // Map<(int * int), int> -> SudokoBoard
    let create cells =
        // Implementation of validations for board left as exercise

With this in place, you have a descriptive model of the Sudoko board that validates all of the values, prevents creation of an illegal key, value, cell, or board, and always returns an informative error if an invalid value is used. 有了这个,您就可以使用Sudoko板的描述模型来验证所有值,防止创建非法密钥,值,单元格或电路板,并且如果使用了无效值,则始终返回信息性错误。

https://stackoverflow.com/a/13925632/2314532 may have an answer to this: you can write something like the following: https://stackoverflow.com/a/13925632/2314532可能有这样的答案:您可以编写如下内容:

type Sudoku = private { _values : Map<(int * int), int> } with
    member public this.Values = this._values
    static member public Create (values : Map<(int * int), int>) =
        // Do your constraint checking here
        { _values = values }

Note that I have not tested this myself, so if you find that this doesn't work, please post a comment with what you learn. 请注意,我自己没有对此进行过测试,因此如果您发现这不起作用,请发表您所学内容的评论。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM