【问题标题】:Defining generic conversion to and from measurement units in F# to their underlying types定义 F# 中的度量单位与其基础类型之间的通用转换
【发布时间】:2016-01-31 14:48:01
【问题描述】:

从度量单位转换为基础值类型并返回很容易:

[<Measure>]
type meter

module Meter =
    let from m = m * 1</meter>
    let too m = m * 1<meter>

但后来我想,一般来说,从一种基础类型到测量单位再转换回来不是很好吗?当然,我可以在每个实例上具体说明它,但我认为使用 cast-to 和 cast-from 运算符会很方便,同时保持类型安全。

我的第一次尝试:

[<AutoOpen>]
module Convert = 
    let inline (!->) x = x * 1<_>    // (often) magically works
    let inline (!-<) x = x * 1</_>   // syntax error on the "/_"

!-&gt; 运算符依靠类型推断工作。如果第二个运算符也可以工作,那就太好了,但不支持 /_ 语法。我尝试了一些类型注释的变体,但无法提出直接的通用解决方案,部分原因是我们无法编写 'T&lt;'M&gt; 或其某些变体。

然后我想到了使用用于隐式转换的技巧,但随后应用于显式转换,因为 'T&lt;'M&gt; 的基础类型如果 'T 和显式转换始终存在:

[<AutoOpen>]
module Convert = 
    let inline explicit_convert (x:^a) : ^b = ((^a or ^b) : (static member op_Explicit : ^a -> ^b) x) 
    let inline (::>) x = explicit_convert x       // works both ways

这有一个额外的好处,它适用于任何具有显式转换运算符的类型。不幸的是,编译器抱怨这样做可能不安全:

名称为“op_Explicit”的成员约束由 F# 编译器赋予特殊状态,因为某些 .NET 类型通过该成员隐式扩充。如果您尝试从自己的代码中调用成员约束,这可能会导致运行时失败。

我不确定我是否走在正确的轨道上,或者是否有一种更安全的方法来创建可以从任何测量到基础类型并返回的运算符(包括复杂的测量,例如 int&lt;meter ^ 4&gt;int&lt;meter / sec ^ 2&gt;)

【问题讨论】:

    标签: generics f# type-conversion units-of-measurement


    【解决方案1】:

    您实际上已经编写了删除度量单位的函数 - 请参见以下示例:

    > let t:int = !->1<m>;;
    
    val t : int = 1
    

    【讨论】:

    • 两者都适用——你只需要对类型进行正确的注释
    【解决方案2】:

    LanguagePrimitives.FloatWithMeasureInt32WithMeasure 等方法在运行时提供,它们通常会“统一”一个值。

    类似地,floatint 等函数(通常用于转换)将剥离通用单元。

    棘手的一点是在编译时以一般方式无缝绑定到正确的函数。下面是我认为你想要的,遵循here 所示的方法,我相信这是这类东西的标准技巧。

    [<AutoOpen>]
    module Convert = 
    
      type AddUnits = AddUnits with
          static member ($) (AddUnits, x: float) = LanguagePrimitives.FloatWithMeasure<_>(x) 
          static member ($) (AddUnits, x: int) = LanguagePrimitives.Int32WithMeasure<_>(x)
    
      let inline (!->) x = AddUnits $ x
    
      type StripUnits = StripUnits with
          static member ($) (StripUnits, x:float<_>) = float x
          static member ($) (StripUnits, x:int<_>) = int x
    
      let inline (!-<) x = StripUnits $ x
    
    open FSharp.Data.UnitSystems.SI.UnitSymbols
    
    !-< 22<m>       // 22
    !-< 9.8<m/s^2>  // 9.8
    
    let x : int<m> = !-> 22         // 22<m>
    let y : float<m/s^2> = !-> 9.8  // 9.8<m/s^2>
    

    【讨论】:

    • 谢谢,这看起来很有希望。我不熟悉$ 语法,你能详细说明一下吗?那是 Du 的自动映射吗?
    • @Abel $ 只是 latkin 为他的自定义运算符所做的选择(如果他愿意,他可以选择 (+) 代替)。他做出这个选择是为了模仿 Haskell 中的“应用”运算符(参见here)
    • 第一部分已经在previous answer中。您建议的代码完全相同的事实让我认为它绝对应该是 F# 库的一部分;)
    猜你喜欢
    • 2011-04-17
    • 1970-01-01
    • 2010-12-26
    • 2011-06-29
    • 1970-01-01
    • 2015-04-28
    • 2016-11-30
    • 2010-10-01
    • 2010-10-02
    相关资源
    最近更新 更多