我的应用程序中有两种实体:客户和产品。它们每个都由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)
我想知道还有什么其他方法。
您可以使用单格联合类型:
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)
另一种不太常见但值得一提的方法是使用所谓的幻像类型。这个想法是,您将拥有一个通用包装器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使用新类型要复杂得多。特别是,您可能会在各个地方需要更多的类型注释来使这项工作有效,并且类型错误可能不太清楚,尤其是当涉及通用代码时。但是,值得一提的是它是替代方法。