在 F# 中定义度量单位与其基础类型之间的通用转换

Defining generic conversion to and from measurement units in F# to their underlying types

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

[<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> 的基础类型 if '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

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

Member constraints with the name 'op_Explicit' are given special status by the F# compiler as certain .NET types are implicitly augmented with this member. This may result in runtime failures if you attempt to invoke the member constraint from your own code.

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

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

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

val t : int = 1

LanguagePrimitives.FloatWithMeasureInt32WithMeasure等方法在运行时提供,一般会"unitize"一个值。

类似地,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>