简体   繁体   English

在F#中算术转换为泛型类型

[英]Arithmetic casting to generic type in F#

I try to write a function which does a generic casting for arithmetic types, for example a function which receives an argument of type uint64 , then converts to a type being the same as the type parameter. 我尝试编写一个为算术类型执行通用转换的函数,例如接收类型为uint64的参数的uint64 ,然后转换为与type参数相同的类型。 My idea is: 我的想法是:

let convert<'T> (x:uint64) = 'T x

But this code does not compile, and I stuck here after trying several approaches like: 但是这段代码没有编译,我在尝试了几种方法之后就陷入了困境:

let convert<'T> (x:uint64) = 
  match Unchecked.defaultof<'T> with 
    | :? uint32 -> uint32 x
....

So how could I write such a generic arithmetic casting in F#? 那么我如何在F#中编写这样的通用算术转换? (I just start learning so my question is maybe stupid, please take it easy). (我刚开始学习,所以我的问题可能是愚蠢的,请放轻松)。

:? type checks only allow (at least that is my understanding) testing for subtypes of the type of the expression that you match on. 类型检查仅允许(至少这是我的理解)测试您匹配的表达式类型的子类型。 As 'T can be any type, the compiler can't tell if uint32 is a subtype of that, so that type test is not possible. 由于'T可以是任何类型,编译器无法判断uint32是否是该类型的子类型,因此无法进行类型测试。

To check for "arbitrary" types in match expressions, you need to box the value first, essentially cast it to obj . 要检查匹配表达式“任意”类型,则需要box第一的价值,基本上是投它obj As all other types are subtypes of obj ( Object in C# and the CLR at large), you can then test for whatever types you want. 由于所有其他类型都是obj子类型(C#中的Object和大型CLR),因此您可以测试所需的任何类型。

As you noticed correctly, that alone is not enough, because all branches of the match expression need to return the same type. 正如您所注意到的那样,仅凭这一点是不够的,因为匹配表达式的所有分支都需要返回相同的类型。 Because the only common supertype of all number types (that I know of) is again obj , you need to box each conversion again, and then downcast the result of the match to 'T . 因为所有数字类型(我知道)的唯一常见超类型再次是obj ,您需要再次对每个转换进行包装,然后将匹配结果向下转换为'T In theory, that is not 100% type safe, but in this case you know that the conversion will hold. 理论上,这不是100%类型安全,但在这种情况下,您知道转换将成立。

let convert<'T> (x:uint64) = 
  match box Unchecked.defaultof<'T> with 
    | :? uint32 -> uint32 x |> box
    | :? int -> int x |> box
    :?> 'T

Oh, and it probably wouldn't be a good idea to use something like this in performance critical real world code (tight loops etc., large numbers of calls), because number types are value types allocated on the stack, while each boxing of a number allocates an object on the heap that will have to be garbage collected (iirc, boxing a 4-byte integer creates a 16-byte object, so the difference is quite substantial). 哦,在性能关键的现实世界代码(紧密循环等,大量调用)中使用这样的东西可能不是一个好主意,因为数字类型是在堆栈上分配的值类型,而每个拳击一个数字在堆上分配一个必须被垃圾收集的对象(iirc,装箱一个4字节的整数创建一个16字节的对象,所以差别非常大)。

I'm not claiming this is a good idea, but if you want to take @TeaDrivenDev 's idea one step further you can get around the return type limitation using the generic unbox<'T> method. 我并不是说这是一个好主意,但是如果你想进一步采用@TeaDrivenDev的想法,你可以使用通用的unbox<'T>方法绕过返回类型限制。

The performance overhead on all of this might be significant of course... 所有这些的性能开销当然可能很重要......

Example code: 示例代码:

let convert<'T> (x:uint64) : 'T =
  match box Unchecked.defaultof<'T> with
  | :? uint32 -> uint32 x |> unbox<'T>
  | :? uint16 -> uint16 x |> unbox<'T>
  | :? string -> string x |> unbox<'T>
  | _ -> failwith "I give up"


1u + (12 |> uint64 |> convert) // val it : uint32 = 13u
1us + (uint64 22 |> convert)   // val it : uint16 = 23us

This gets around the limitation of all branches having to return the same type, as each branch does return the same type for any specific generic parameter. 这绕过了必须返回相同类型的所有分支的限制,因为每个分支确实为任何特定的通用参数返回相同的类型。 The fact that only one branch will ever return for a specific parameter is neither here nor there to the compiler. 对于特定参数只有一个分支将返回的事实既不在这里也不在于编译器。

You can use static member constraints, here's a "short" example: 您可以使用静态成员约束,这是一个“简短”示例:

type Explicit =
    static member inline ($) (_:byte , _:Explicit) = byte            
    static member inline ($) (_:sbyte, _:Explicit) = sbyte           
    static member inline ($) (_:int16, _:Explicit) = int16           
    static member inline ($) (_:int32, _:Explicit) = int
    // more overloads

let inline convert value: 'T = 
    (Unchecked.defaultof<'T> $ Unchecked.defaultof<Explicit>) value

// Specialized to uint64
let inline fromUint64 (value: uint64) :'T = convert value

// Usage
let x:int = fromUint64 7UL

As said in the comments you can use the function explicit from F#+ which covers all cases when there is an explicit operator. 如评论中所述,您可以使用F#+中的 explicit函数,它涵盖了存在显式运算符时的所有情况。 Here's a sample code . 这是一个示例代码

Now if you look at the source code of that function which is defined in another project (FsControl) you will find an even more complicated workaround. 现在,如果你看一下在另一个项目(FsControl)中定义的那个函数的源代码 ,你会发现一个更复杂的解决方法。

You may wonder why, so here's the long answer: 你可能想知道为什么,所以这里的答案很长:

In theory it should be possible to use a single call invoking the member op_Explicit , but that would only work when that member really exists which is not the case with the native number types. 从理论上讲,应该可以使用一个调用成员op_Explicit单个调用 ,但这只能在该成员确实存在时才有效,而本机数字类型则不然。

For those cases the F# compiler uses a feature usually referred to as "simulated members" which is implemented using static optimizations but that's not available outside the F# compiler source code. 对于这些情况,F#编译器使用通常称为“模拟成员”的功能,该功能使用静态优化实现,但在F#编译器源代码之外不可用。

So F#+ uses a different feature instead: overload resolution, as in the sample code I showed you but it uses an additional overload as a general case for those members which really contain an op_Explicit static member. 所以F#+使用了一个不同的特性:重载解析,就像我给你看的示例代码一样,但是对于那些真正包含op_Explicit静态成员的成员来说,它使用了一个额外的重载。

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

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