Сильно набранные идентификаторы в F #? - PullRequest
2 голосов
/ 21 мая 2019

В моем приложении есть два вида сущностей: клиенты и продукты. Каждый из них идентифицируется на уровне базы данных с помощью UUID.

В моем коде F # это может быть представлено System.Guid.

Для удобства чтения я добавил несколько таких типов:

open System

type CustomerId = Guid

type ProductId = Guid

Однако это не мешает мне использовать ProductId в качестве CustomerId и наоборот.

Мне пришла в голову идея обёртки, чтобы предотвратить это:

open System

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

[<Struct>]
type CustomerId = 
  {
    Customer : Guid
  }

Это делает инициализацию более многословной и, возможно, менее интуитивной:

let productId = { Product = Guid.NewGuid () }

Но это добавляет безопасность типов:

// let customerId : CustomerId = productId // Type error

Мне было интересно, какие есть другие подходы.

Ответы [ 2 ]

6 голосов
/ 21 мая 2019

Вы можете использовать односторонний союз типы:

open System

[<Struct>]
type ProductId = ProductId of Guid

[<Struct>]
type CustomerId = CustomerId of Guid

let productId = ProductId (Guid.NewGuid())

Обычно мы добавляем несколько удобных вспомогательных методов / свойств непосредственно к типам:

[<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"
5 голосов
/ 21 мая 2019

Другой, менее распространенный, но заслуживающий упоминания подход заключается в использовании так называемых фантомных типов . Идея заключается в том, что у вас будет общая оболочка ID<'T>, а затем вы будете использовать разные типы для 'T для представления разных типов идентификаторов. Эти типы фактически никогда не создаются, поэтому они называются фантомными типами.

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

type CustomerID = interface end
type ProductID = interface end

Теперь вы можете создавать значения ID<CustomerID> и ID<ProductID> для представления двух типов идентификаторов:

let newCustomerID () : ID<CustomerID> = ID(System.Guid.NewGuid())
let newProductID () : ID<ProductID> = ID(System.Guid.NewGuid())

Приятно то, что вы можете легко писать функции, работающие с любым идентификатором:

let printID (ID g) = printfn "%s" (g.ToString())

Например, теперь я могу создать один идентификатор клиента, один идентификатор продукта и распечатать оба, но я не могу выполнить тест на равенство этих идентификаторов, поскольку их типы не совпадают:

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

Это хитрый трюк, но он немного сложнее, чем просто использование нового типа для каждого идентификатора. В частности, вам, вероятно, понадобится больше аннотаций типов в различных местах, чтобы сделать эту работу, и ошибки типов могут быть менее очевидными, особенно когда используется общий код. Тем не менее, стоит упомянуть об этом в качестве альтернативы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...