如何对F#记录进行参数验证

时间:2013-08-30 19:04:32

标签: constructor f# record

F#可以轻松定义

等类型
type coords = { X : float; Y : float }

但是如何在不进入更详细的类定义语法的情况下为构造函数定义约束/检查参数?例如。如果我想从(0,0)开始coords或抛出异常。

此外,如果我将我的定义更改为类,我需要实现Equals()等所有我不想要的样板代码(以及我在C#中我试图摆脱的那些)。

4 个答案:

答案 0 :(得分:17)

您可以将实施设为私有。您仍然可以获得结构上的平等,但是会丢失直接的字段访问和模式匹您可以使用活动模式恢复该功能。

//file1.fs

type Coords = 
  private { 
    X: float
    Y: float 
  }

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Coords =
  ///The ONLY way to create Coords
  let create x y =
    check x
    check y
    {X=x; Y=y}

  let (|Coords|) {X=x; Y=y} = (x, y)

//file2.fs

open Coords
let coords = create 1.0 1.0
let (Coords(x, y)) = coords
printfn "%f, %f" x y

答案 1 :(得分:9)

Designing with Types上有一个名为F# for fun and profit的系列。在“强制使用构造函数”一节中,它建议使用构造函数 - 这是在实例化类型之前验证的位置。为了防止人们直接实例化类型,它建议使用命名约定或签名文件。

您可以通过Google搜索“域驱动设计f#”找到更多相关文章和示例。

请注意,我来自C#/没有将F#应用到我们的域层(但是;)我无法确定任何推荐的方法如何在更大的项目中运行。在这个勇敢的新世界中,有些事情看起来确实不同。

答案 2 :(得分:3)

您必须使用类定义语法:

type coords(x: float, y: float) =
  do
    if x < 0.0 then
      invalidArg "x" "Cannot be negative"
    if y < 0.0 then
      invalidArg "y" "Cannot be negative"

  member this.X =
    x
  member this.Y =
    y

答案 3 :(得分:1)

Daniel的答案似乎是最接近“ FP”方法的,但缺点是我们失去了利用记录提供的其他收益(如复制和更新)的能力。由于我们现在有了匿名记录,因此似乎可以使用这些记录以透明的方式处理封装的对象。

更新:Abel建议使用匿名记录有一些弊端(例如丧失模式匹配的能力等),因此我将这种方法与私有单例DU和公共记录结合使用以解决该问题。关注。

// file1.fs

type Coords' =
    { X : float
      Y : float }


type Coords = private Coords of Coords'

module Coords =
    
    let private checkCoord (value : float) =
        if value < 0.0 || value > 32.0 then invalidOp "Invalid coordinate"

    let create (newcoord : Coords') =
        checkCoord newcoord.X
        checkCoord newcoord.Y
        newcoord |> Coords

    let value (Coords c) = c

// file2.fs
open File1

module Tests =

    [<Test>]
    let Test0 () =
        let firstcoord = Coords.create {X = 5.0; Y = 6.0}
        let secondcoord = Coords.create {(firstcoord |> Coords.value) with X = 10.0}
        let thirdcoord = Coords.value secondcoord

        Assert.IsTrue (thirdcoord.X = 10.0)
        Assert.IsTrue (thirdcoord.Y = 6.0)
        Assert.Pass ()

    [<Test>]
    let Test1 () =
        {X = 0.0; Y = 0.0} |> Coords   //Doesn't compile
        ()