Generics F#多类型的函数类型注释

Generics F#多类型的函数类型注释,generics,f#,functional-programming,Generics,F#,Functional Programming,我试图定义一个“准泛型”函数。与其让它完全通用,我希望它只适用于“整数”类型(即byte、sbyte、int16、uint16、int、uint32、int64、uint64、bigint) 如何将其放入函数定义的类型注释中?为了澄清,我将如何重写以下代码以实际工作(仅使用3种类型,大概不会丢失泛化): 首先,在运行时使用标准的.NET泛型无法解决此类类型约束 F#确实允许您通过在编译时解析这些约束并在内联插入适当的函数调用来表达这些约束的有限形式。这就利用了 对于您所描述的案例,这非常简单,您

我试图定义一个“准泛型”函数。与其让它完全通用,我希望它只适用于“整数”类型(即byte、sbyte、int16、uint16、int、uint32、int64、uint64、bigint)

如何将其放入函数定义的类型注释中?为了澄清,我将如何重写以下代码以实际工作(仅使用3种类型,大概不会丢失泛化):


首先,在运行时使用标准的.NET泛型无法解决此类类型约束

F#确实允许您通过在编译时解析这些约束并在内联插入适当的函数调用来表达这些约束的有限形式。这就利用了

对于您所描述的案例,这非常简单,您可以写:

let inline square x = x * x
这将适用于定义了
*
运算符的任何类型
'T

您还可以显式应用特定的静态/成员约束,但这需要更难看的语法,例如

let inline id item =
    ( ^T : (member Id : int) (item))
此示例函数将对公开类型为
int
Id
属性的任何类型进行操作


更新:根据您描述的特定用例,您可以真正键入类。这些在F#中并不存在(除了一些硬编码的示例),但您可以使用标记类型和成员约束来模拟它们,下面是一个示例:

type Marker =
    |Marker

    static member Multiply (marker : Marker, numX : int, numY : int) =
        numX * numY
    static member Multiply (marker : Marker, numX : int64, numY : int64) =
        numX * numY

let inline multiply x y =
    ((^T or ^U) : (static member Multiply : ^T * ^U * ^U -> ^S) (Marker, x, y))

multiply 5 7
multiply 5L 7L

请注意,这允许您指定允许功能的确切类型。

解决问题的方法基本上有三种

a)您使用的类型已经支持要应用于它们的运算符/方法
在这种情况下,只需在函数前面添加
inline
,就可以了

b)您可以完全控制正在使用的类型
也就是说,您可以在函数定义中定义新成员,而无需使用扩展方法。在本例中,您在每个类上定义了一个方法来实现所需的功能

type MyInt16 = MyInt16 of int
    with
    static member Mult(x, y) = 
        match x,y with
        | MyInt16 x', MyInt16 y' -> MyInt16 (x' * y')

type MyInt32 = MyInt32 of int
    with
    static member Mult(x, y) = 
        match x,y with
        | MyInt32 x', MyInt32 y' -> MyInt32 (x' * y')
还有一个使用泛型类型约束的内联函数,这种语法看起来很奇怪

let inline mult (x:^T) (y:^T) = (^T : (static member Mult: ^T -> ^T -> ^T) (x, y))
然后你测试

let a = MyInt16 2
let b = MyInt16 3

let c = mult a b
这很有效。让我们看看当我们使用不同的类型时会发生什么

let d = mult a (MyInt32 3)
let f = mult' (int16(2)) (int32(3))
以上内容会给您带来一个错误

c)您无法完全控制您的类型 也就是说,您不能在类型中定义方法,但必须使用扩展方法。扩展方法的问题是它们不能被使用 使用泛型类型约束的内联函数。在这种情况下,你最好回到

然后再次定义一个带有泛型约束的内联函数,将传入类型转换为包装类型

let inline (!>) (x:^a) : ^b = ((^a or ^b) : (static member op_Implicit : ^a -> ^b) x)
并添加您的实现。这一次,由于我们需要使用模式匹配,所以有点冗长

let inline mult' (x: ^T) (y: ^T) : ^T =
    let x' = !> x
    let y' = !> y 
    let r = 
        match x', y' with
        | MyInt16Param x'', MyInt16Param y'' -> x'' * y'' |> box
        | MyInt32Param x'', MyInt32Param y'' -> x'' * y'' |> box
        | _ -> failwith "Not possible"
    r :?> _
现在让我们再次测试

let e = mult' (int16(2)) (int16(3))
这很有效。让我们看看当我们使用不同的类型时会发生什么

let d = mult a (MyInt32 3)
let f = mult' (int16(2)) (int32(3))
按原样再出错一次

选项b)基本上是模拟Haskell类型类功能,其中as

选项c)更接近于OCamls多态性变体约束,简化F型系统(和成员)两个特征的组合效应可能会有所帮助。它们的使用允许在编译时排除不兼容的类型,尽管存在多个详细的错误消息

module MyInt =
    type MyInt<'T> = private MyInt of 'T
    type Wrap = Wrap with
        static member ($) (Wrap, value : int   ) = MyInt value
        static member ($) (Wrap, value : int64 ) = MyInt value
        static member ($) (Wrap, value : bigint) = MyInt value
    let inline create value : MyInt<_> = Wrap $ value

let x = MyInt.create 1 // MyInt.MyInt<int>
let y = MyInt.create 1I // MyInt.MyInt<bigint>
let z = MyInt.create 1.0 // Error  No overloads match for method 'op_Dollar'. ...
模块MyInt=

键入MyInt这可能通过-是否简单/实用实现,但我不知道。我实际上并不是在尝试平方数,我只是认为这将是一个简单的示例。我正在尝试创建一些执行素性测试和特定因子分解技术的函数。我不想让这些函数在浮点数上工作,因为因子分解没有任何意义,有些算法会出问题。@Talmage是更新后的答案,更符合您的要求吗?是的,这实际上是我吃午饭时考虑的方法。今晚,我将尽力充分消化这些回答,然后标记所回答的问题。
module MyInt' =
    type MyInt<'T> = private MyInt of 'T
    type Unwrap = Unwrap with
        static member ($) (Unwrap, MyInt(value : int   )) = value
        static member ($) (Unwrap, MyInt(value : int64 )) = value
        static member ($) (Unwrap, MyInt(value : bigint)) = value
    let inline myInt value = Unwrap $ value
    let x, y, z = MyInt 1, MyInt 1I, MyInt 1.0

let a = MyInt'.myInt MyInt'.x // int
let b = MyInt'.myInt MyInt'.y // bigint
let c = MyInt'.myInt MyInt'.z // Error  No overloads match for method 'op_Dollar'. ...