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

时间:2016-04-21 23:55:55

标签: generics casting f#

我尝试编写一个为算术类型执行通用转换的函数,例如接收类型为uint64的参数的函数,然后转换为与类型参数相同的类型。我的想法是:

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

但是这段代码没有编译,我在尝试了几种方法之后就陷入了困境:

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

那么我如何在F#中编写这样的通用算术运算? (我刚开始学习,所以我的问题可能很愚蠢,请放轻松。)

3 个答案:

答案 0 :(得分:5)

:?类型检查仅允许(至少这是我的理解)测试您匹配的表达式类型的子类型。由于'T可以是任何类型,编译器无法判断uint32是否是该类型的子类型,因此无法进行类型测试。

要检查匹配表达式中的“任意”类型,首先需要box值,基本上将其转换为obj。由于所有其他类型都是obj(C#中的Object和整个CLR)的子类型,因此您可以测试您想要的任何类型。

正如您所注意到的那样,仅凭这一点是不够的,因为匹配表达式的所有分支都需要返回相同的类型。因为所有数字类型(我知道)的唯一常见超类型再次为obj,您需要再次对每个转换进行包装,然后将匹配结果向下转发到'T。从理论上讲,这不是100%类型安全的,但在这种情况下,您知道转换将保持。

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

哦,在性能关键的现实世界代码(紧密循环等,大量调用)中使用这样的东西可能不是一个好主意,因为数字类型是在堆栈上分配的值类型,而每个数字的拳击都会在堆上分配一个必须进行垃圾收集的对象(iirc,装箱一个4字节的整数会创建一个16字节的对象,所以差别很大)。

答案 1 :(得分:5)

我并不认为这是一个好主意,但如果你想进一步采用@TeaDrivenDev的想法,你可以使用通用的unbox<'T>方法绕过返回类型限制。

所有这些的性能开销当然可能很重要......

示例代码:

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

这解决了所有分支必须返回相同类型的限制,因为每个分支 为任何特定的泛型参数返回相同的类型。只有一个分支将为特定参数返回的事实既不在这里也不在于编译器。

答案 2 :(得分:2)

您可以使用静态成员约束,这里是&#34;短&#34;例如:

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

如评论中所述,您可以使用F#+中的explicit函数,该函数涵盖了存在显式运算符时的所有情况。这是sample code

现在,如果你看一下在另一个项目(FsControl)中定义的那个函数的source code,你会发现一个更复杂的解决方法。

你可能想知道为什么,所以这里的答案很长:

理论上应该可以使用single call来调用成员op_Explicit,但这只有当该成员确实存在时才有效,而本机数字类型则不然。

对于这些情况,F#编译器使用通常称为&#34;模拟成员的功能&#34;这是使用静态优化实现的,但在F#编译器源代码之外不可用。

所以F#+使用了一个不同的特性:重载解析,就像我给你看的示例代码一样,但是对于那些真正包含op_Explicit静态成员的成员来说,它会使用额外的重载作为一般情况。 / p>