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

Ta *_*inh 5 generics f# casting

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

let convert<'T> (x:uint64) = 'T x
Run Code Online (Sandbox Code Playgroud)

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

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

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

Tea*_*Dev 5

:?类型检查仅允许(至少这是我的理解)测试您匹配的表达式类型的子类型.由于'T可以是任何类型,编译器不能告诉如果uint32是一个亚型,让型式试验是不可能的.

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

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

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

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


mav*_*vnn 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
Run Code Online (Sandbox Code Playgroud)

这绕过了必须返回相同类型的所有分支的限制,因为每个分支确实为任何特定的通用参数返回相同的类型.对于特定参数只有一个分支将返回的事实既不在这里也不在于编译器.


Gus*_*Gus 2

您可以使用静态成员约束,这是一个“简短”示例:

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
Run Code Online (Sandbox Code Playgroud)

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

现在,如果您查看在另一个项目(FsControl)中定义的该函数的源代码,您会发现一个更复杂的解决方法。

您可能想知道为什么,这是长答案:

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

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

因此,F#+ 使用不同的功能:重载解析,如我向您展示的示例代码中所示,但它使用额外的重载作为那些真正包含静态成员的成员的一般情况op_Explicit