20

F# 使定义类型变得容易,例如

type coords = { X : float; Y : float }

但是如何在不涉及更冗长的类定义语法的情况下为构造函数定义约束/检查参数?例如,如果我希望坐标从 (0,0) 开始或抛出异常。

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

4

4 回答 4

20

您可以将实现设为私有。您仍然获得结构平等,但您失去了直接的字段访问和模式匹配。您可以使用主动模式恢复该能力。

//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
于 2013-08-30T20:35:20.307 回答
9

有一个名为Designing with Types on F# 的系列,既有趣又赚钱。在“强制使用构造函数”一节中,它建议使用构造函数——这是在实例化类型之前进行验证的地方。为了防止人们直接实例化类型,它建议使用命名约定或签名文件。

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

请注意,我来自 C# / 尚未将 F# 应用到我们的域层(尚未;)我无法真正说出任何一种推荐的方法在更大的项目中如何发挥作用。在这个勇敢的新世界中,有些事情确实看起来……不同。

于 2013-08-30T23:35:57.513 回答
4

您必须使用类定义语法:

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
于 2013-08-30T19:35:50.537 回答
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
        ()
于 2020-10-02T18:56:37.350 回答