How to do argument validation of F# records

2019-02-16 08:18发布

问题:

F# makes it easy to define types such as

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

but how do I define constraints/check arguments for the constructor without going into the more verbose class definition syntax? E.g. if I want coords to start from (0,0) or throw an exception.

Moreover, if I change my definition to a class I need to implement Equals() etc. all the boiler plate code I don't want (and which I have in C# that I'm trying to get away from).

回答1:

You can make the implementation private. You still get structural equality but you lose direct field access and pattern matching. You can restore that ability using active patterns.

//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


回答2:

There's a series called Designing with Types on F# for fun and profit. In section "Forcing use of the constructor" it recommends the use of constructor functions - that's where the validations go before the type is instantiated. To keep people from directly instantiating types it recommends either naming conventions or signature files.

You can find several more relevant articles and examples by googling "domain driven design f#".

Note that I'm coming from C# / not having applied F# to our domain layer (yet ;) I cannot really tell how either of the recommended methods would work out in a bigger project. Some things sure seem.. different in this brave new world.



回答3:

You have to use the class definition syntax:

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