【问题标题】:Constraints on Records in F#?F# 中记录的约束?
【发布时间】:2018-06-14 12:23:24
【问题描述】:

我更喜欢记录而不是 F# 中的类,因为我不需要实现相等和哈希码。但是,它们似乎没有提供一种工具来确保记录值之间的不变量。

例如,假设我正在为数独板建模。我可能会写一个这样的记录类型:

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

let empty = { Values = Map.empty }

现在的问题是我对Values的地图有一些要求:

  • 每个键 (x, y) 必须有 xy 介于 0 和 8 之间,包括 0 和 8
  • 每个值都必须介于 1 和 9 之间,包括 1 和 9
  • 每行中的每个值都必须是唯一的
  • 列中的每个值都必须是唯一的
  • 每个值在 3x3 单元格中都必须是唯一的

使用类型,我可以在构造函数中检查这些,如果不满足则引发异常。这样,任何使用Sudoku 的代码都由构造函数保证满足不变量。

我如何通过唱片实现这一目标?

【问题讨论】:

    标签: oop f# record


    【解决方案1】:

    我会通过为键和值创建单例联合类型来做到这一点,并为这些类型提供私有构造函数:

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

    然后,我将为单元格和板创建记录类型,再次为这些类型提供私有构造函数:

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

    定义了数据类型后,我将根据您概述的不同失败案例创建有区别的联合:

    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>
    

    接下来,我将创建用于创建键的模块,并且执行验证的值将返回 Result,其中包含键/值或相应的错误。这些模块将使用私有构造函数来实例化单例联合类型:

    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
    

    然后我会为SudokoCell 创建一个类似的模块,它使用SudokoKey.create 函数应用于xy 整数,以及单元格类型的私有构造函数,以创建一个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 }))
    

    最后,您只需要一个 SudokoBoard 模块来创建板本身,并在地图上应用剩余的验证规则。我已经解决了这个问题,但将实现留作练习:

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

    有了这个,您就有了一个 Sudoko 板的描述性模型,它可以验证所有值,防止创建非法键、值、单元格或板,如果使用无效值,则始终返回信息性错误.

    【讨论】:

    • 不错!我真的很喜欢你的 DDD 相关帖子。特别是,因为我自己真的在为这些事情苦苦挣扎。
    • @sebhofer 如果你还没有读过,请查看Domain Modeling Made Functional这本书。它涵盖了我在 DDD 答案中使用的大部分技术。
    【解决方案2】:

    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 }
    

    请注意,我自己没有对此进行测试,因此如果您发现这不起作用,请发表评论并说明您所学的内容。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多