在 F# 中创建类型的非可比较版本



我使用 Guid 和字符串作为数据结构中的键。在 C# 中,我花了很多(累积的)小时想知道为什么当 id 是 OrderId 并且我将其与 ContractId 匹配时,我正在寻找的 id 没有事件通过。我想做的是防止这整个类别的错误。

假设我有一个具有以下基础数据类型的协定:

type Contract = { Schedule : Guid; TickTable : Guid; Price : float; Quantity : float }

现在我有两个问题:

let contract =
    { Schedule = Guid.Empty; TickTable = Guid.Empty; Price = 0.; Quantity = 0. }
contract.Schedule = contract.TickTable;; // true - ugh
contract.Price = contract.Quantity;; // true - ugh

我可以解决这样的一个问题:

[<Measure>] type dollars
[<Measure>] type volume
type Contract =
    { Schedule : Guid; TickTable : Guid;
      Price : float<dollars>; Quantity : float<volume> }

现在我们有:

let contract =
    { Schedule = Guid.Empty; TickTable = Guid.Empty;
      Price = 0.<dollars>; Quantity = 0.<volume> }
contract.Schedule = contract.TickTable;; // true - ugh
contract.Price = contract.Quantity;; // type mismatch - yay

有没有办法装饰 Guids 以便我得到类型不匹配?我只想影响编译时间 - 理想情况下,编译的代码将是相同的,就像度量单位一样。

我知道我可以执行以下操作,但它看起来很丑陋,我希望它会导致运行时影响:

[<Measure>] type dollars
[<Measure>] type volume
type ScheduleId = ScheduleKey of Guid
type TickTableId = TickTableKey of Guid
type Contract =
    { Schedule : ScheduleId; TickTable : TickTableId;
      Price : float<dollars>; Quantity : float<volume> }
let contract =
    { Schedule = ScheduleKey Guid.Empty; TickTable = TickTableKey Guid.Empty;
      Price = 0.<dollars>; Quantity = 0.<volume> }
contract.Schedule = contract.TickTable;; // type error - yay
contract.Price = contract.Quantity;; // type mismatch - yay

您可以通过编写具有 [<Measure>] 类型参数的类型来包装任何类型以包含单位,甚至是一般的单位。此外,正如 latkin 在注释中暗示的那样,使用结构体(就地分配,而不是作为新对象)将节省额外的分配和间接性。

通用度量单位感知包装器:

type [<Struct>] UnitAware<'T, [<Measure>] 'u> =
    val Raw : 'T
    new (raw) = { Raw = raw }
let withUnit<[<Measure>] 'u> a = UnitAware<_, 'u>(a)

这样,可以为任意类型提供一个度量单位感知值类型包装器,只需通过withUnit<myUnit>包装并使用.Raw解包:

let a = 146L |> withUnit<dollars>
let b = 146L |> withUnit<volume>
a = b // Type mismatch.

由于结构比较,具有相同单元和相同内容的两个结构包装器也将相等。与其他度量单位用法一样,附加类型安全性在运行时丢失:box a = box b为真,就像box 1.<dollars> = box 1.<volumes>一样。