F#中的强类型ID?

sdg*_*sdh 2 f#

我的应用程序中有两种实体:客户和产品。它们每个都由UUID在数据库级别标识。

在我的F#代码中,可以用表示System.Guid

为了提高可读性,我添加了以下一些类型:

open System

type CustomerId = Guid

type ProductId = Guid
Run Code Online (Sandbox Code Playgroud)

但是,这并不妨碍我将a ProductId用作a CustomerId,反之亦然。

我想出了一个包装器的想法来防止这种情况:

open System

[<Struct>]
type ProductId = 
  {
    Product : Guid
  }

[<Struct>]
type CustomerId = 
  {
    Customer : Guid
  }
Run Code Online (Sandbox Code Playgroud)

这使初始化更加冗长,并且可能不那么直观:

let productId = { Product = Guid.NewGuid () }
Run Code Online (Sandbox Code Playgroud)

但这增加了类型安全性:

// let customerId : CustomerId = productId // Type error
Run Code Online (Sandbox Code Playgroud)

我想知道还有什么其他方法。

Ngh*_*Bui 7

您可以使用单格联合类型:

open System

[<Struct>]
type ProductId = ProductId of Guid

[<Struct>]
type CustomerId = CustomerId of Guid

let productId = ProductId (Guid.NewGuid())
Run Code Online (Sandbox Code Playgroud)

通常,我们直接在类型中添加一些方便的辅助方法/属性:

[<Struct>]
type ProductId = private ProductId of Guid with
    static member Create () = ProductId (Guid.NewGuid())
    member this.Value = let (ProductId i) = this in i

[<Struct>]
type CustomerId = private CustomerId of Guid with
    static member Create () = CustomerId (Guid.NewGuid())
    member this.Value = let (CustomerId i) = this in i

let productId = ProductId.Create ()
productId.Value |> printfn "%A"
Run Code Online (Sandbox Code Playgroud)


Tom*_*cek 5

另一种不太常见但值得一提的方法是使用所谓的幻像类型。这个想法是,您将拥有一个通用包装器ID<'T>,然后使用不同的类型'T来表示不同类型的ID。这些类型从未真正实例化,这就是为什么将它们称为幻像类型的原因。

[<Struct>]
type ID<'T> = ID of System.Guid

type CustomerID = interface end
type ProductID = interface end
Run Code Online (Sandbox Code Playgroud)

现在,您可以创建ID<CustomerID>ID<ProductID>代表两种ID的值:

let newCustomerID () : ID<CustomerID> = ID(System.Guid.NewGuid())
let newProductID () : ID<ProductID> = ID(System.Guid.NewGuid())
Run Code Online (Sandbox Code Playgroud)

这样做的好处是,您可以轻松编写可与任何ID配合使用的函数:

let printID (ID g) = printfn "%s" (g.ToString())
Run Code Online (Sandbox Code Playgroud)

例如,我现在可以创建一个客户ID,一个产品ID并同时打印两者,但是我无法对这些ID进行相等性测试,因为它们的类型不匹配:

let ci = newCustomerID ()
let pi = newProductID ()
printID ci
printID pi
ci = pi // Type mismatch. Expecting a 'ID<CustomerID>' but given a 'ID<ProductID>'    
Run Code Online (Sandbox Code Playgroud)

这是一个巧妙的技巧,但是比为每个ID使用新类型要复杂得多。特别是,您可能会在各个地方需要更多的类型注释来使这项工作有效,并且类型错误可能不太清楚,尤其是当涉及通用代码时。但是,值得一提的是它是替代方法。