定义F#中测量单位与其基础类型

时间:2015-10-31 05:46:00

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

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

[<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 "/_"

!->运算符通过类型推断工作。如果第二个运算符也起作用会很好,但/_不支持语法。我尝试了一些带类型注释的变体,但无法提出直接的通用解决方案,部分原因是我们无法编写'T<'M>或其中的某些变体。

然后我考虑使用用于隐式转换的技巧,但随后应用于显式转换,因为'T<'M>的基础类型'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类型使用此成员隐式增加。如果您尝试从自己的代码中调用成员约束,这可能会导致运行时失败。

我不确定我是否在正确的轨道上,或者是否有一种更安全的方式来创建一个可以从任何测量到底层类型和返回的运算符(包括复杂的测量,如{{1 }或int<meter ^ 4>

2 个答案:

答案 0 :(得分:3)

在运行时中提供了

LanguagePrimitives.FloatWithMeasureInt32WithMeasure等方法,这些方法通常会&#34;统一&#34;价值。

同样地,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>

答案 1 :(得分:0)

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

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

val t : int = 1