首页 文章

如何定义具有度量单位的区分联合的映射函数

提问于
浏览
4

我创建了两个度量单位,美元和欧元 . 我想在运行时区分它们,所以我定义了以下区分联合 . 然后,我想像这样定义map函数:

[<Measure>] type USD
[<Measure>] type EUR
type CurrencyKind =
    | CurrencyUsd of decimal<USD>
    | CurrencyEur of decimal<EUR>

    member this.Map<[<Measure>] 'currency> (f : decimal<'currency> -> decimal<'currency>) =
        match this with
        | CurrencyUsd x -> f x
        | CurrencyEur x -> f x

但是,当我这样做时,我在 'currency 下收到以下错误:

FS0660 F#此代码的通用性低于其注释所需的代码,因为显式类型变量无法进行泛化 . 它被限制为“美元” .

CurrencyEur x -> f xx

FS0001 F#计量单位'USD'与计量单位'EUR'不符

有没有什么好方法可以构建这个版本?

1 回答

  • 3

    定义泛型函数的方式, map 函数的调用者定义了单位是什么,但这不正确 - 您希望 map 函数本身根据值定义单位 . 这可以使用接口完成,但它变得很丑陋 .

    在这种情况下,我认为你可以通过定义一个新的单元类型来解决这个问题,比如 Money

    [<Measure>] type Money
    

    您作为参数传递给 map 的函数需要一些金额并返还一些其他金额,因此您可以给它一个 decimal<Money> -> decimal<Money> 类型 . 无论是美元还是欧元,这都是 map 函数可以决定的 . 在实现中,我们只需在调用 f 之前将USD或EUR转换为Money,然后转换回:

    let map (f : decimal<Money> -> decimal<Money>) input =
      match input with
      | CurrencyUsd x -> CurrencyUsd((f (x * 1M<Money/USD>)) * 1M<USD/Money>)
      | CurrencyEur x -> CurrencyEur((f (x * 1M<Money/EUR>)) * 1M<EUR/Money>)
    

    这可能会为您提供所需的安全性 . 如果我们计算 m + m ,那可行,因为单位仍然是钱,但如果你计算 m * m ,你会得到一个编译时错误:

    CurrencyUsd(10.M<_>) |> map (fun m -> m + m)
    CurrencyUsd(10.M<_>) |> map (fun m -> m * m) // Type error!
    

    对于记录,使用接口的丑陋版本将如下所示:

    type Mapper = 
      abstract Apply<[<Measure>] 'm> : decimal<'m> -> decimal<'m>
    
    let map (f : Mapper) input =
      match input with
      | CurrencyUsd x -> CurrencyUsd(f.Apply x)
      | CurrencyEur x -> CurrencyEur(f.Apply x)
    
    CurrencyUsd(10.M<_>) |> map { new Mapper with member x.Apply m = m + m } 
    CurrencyUsd(10.M<_>) |> map { new Mapper with member x.Apply m = m * m }
    

相关问题