简体   繁体   English

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

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

It's easy enough to convert from a measurement unit to the underlying value type and back: 从度量单位转换为基础值类型然后再转换很容易:

[<Measure>]
type meter

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

But then I thought, wouldn't it be lovely to have a conversion from one underlying type to the measurement unit and back, generically? 但是后来我想,从一种基础类型转换为度量单位,然后再转换回来,这不是很好吗? Sure, I can on each instance be specific about it but I figured it'd be convenient to have a cast-to and cast-from operator, while retaining type safety. 当然,我可以在每个实例上都具体说明它,但是我认为在保留类型安全性的同时拥有强制转换和强制转换运算符会很方便。

My first attempt: 我的第一次尝试:

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

The !-> operator works by virtue of type inference. !->运算符通过类型推断来工作。 It would be nice if the second operator also worked, but /_ is not supported syntax. 如果第二个运算符也可以,但是/_不支持语法,那会很好。 I tried some type-annotated variants but couldn't come up with a straight generic solution, in part because we cannot write 'T<'M> or some variant thereof. 我尝试了一些带类型注释的变体,但无法提出直接的通用解决方案,部分原因是我们无法编写'T<'M>或其变体。

I then thought of using the trick used for implicit conversions, but then applied to explicit conversions, since the underlying type of 'T<'M> if 'T and an explicit cast always exists: 然后,我想到使用用于隐式转换的技巧,然后将其应用于显式转换,因为如果'T和显式强制转换始终存在,则'T<'M>的基础类型:

[<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

This has the added benefit that it works on any type that has an explicit conversion operator. 这具有额外的好处,它可以在任何具有显式转换运算符的类型上使用。 Unfortunately, the compiler complains that it may not be safe to do so: 不幸的是,编译器抱怨这样做可能并不安全:

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. F#编译器将名称为“ op_Explicit”的成员约束赋予特殊状态,因为某些.NET类型将隐式地扩充为此成员。 This may result in runtime failures if you attempt to invoke the member constraint from your own code. 如果您尝试从自己的代码中调用成员约束,则可能导致运行时失败。

I'm not sure if I'm on the right track or whether there's a more type-safe way of creating an operator that can go from any measurement to the underlying type and back (including complex measurements, like int<meter ^ 4> or int<meter / sec ^ 2> ) 我不确定是否在正确的轨道上,还是不确定是否存在创建类型运算符的更类型安全的方法,该运算符可以从任何度量转换为基础类型然后返回(包括复杂度量,例如int<meter ^ 4>int<meter / sec ^ 2>

LanguagePrimitives.FloatWithMeasure , Int32WithMeasure , etc methods are provided in the runtime, which will generically "unitize" a value. 运行时中提供了LanguagePrimitives.FloatWithMeasureInt32WithMeasure等方法,这些方法通常会“统一”一个值。

Similarly, float , int , etc functions (normally used for casting) will strip off a generic unit. 同样, floatint等函数(通常用于转换)将剥离通用单元。

The tricky bit is seamlessly binding to the right functions at compile time in a general way. 棘手的一点是在编译时以一般方式无缝绑定到正确的函数。 Below does what I think you want, following the approach shown here , which I believe is the standard trick for this kind of stuff. 下面按照此处显示的方法执行我认为想要的操作,我认为这是此类操作的标准技巧。

[<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>

You have actually already written the function to remove the units of measure - see this example: 您实际上已经编写了删除度量单位的函数-请参见以下示例:

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

val t : int = 1

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM