F#函数类型注释用于多个类型

4
我正在尝试定义一个“准通用”函数。与其完全通用,我希望它仅适用于“整数”类型(即byte、sbyte、int16、uint16、int、uint32、int64、uint64、bigint)。
如何将此放入函数定义的类型注释中?为了澄清,我应该如何重写以下代码以使其实际工作(只使用3种类型,不会损失通用性):
let square (x: int|int64|bigint) =
    x * x

3
可能可以通过约束条件实现,但是否容易/实际可行我不知道。 - Joe Clay
3个回答

8

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

F#允许您通过在编译时解决这种约束并将正确的函数调用嵌入到代码中来表达有限形式的约束。这利用了静态解析类型参数

对于您所描述的情况非常简单,您可以只需编写:

let inline square x = x * x

这将适用于任何具有定义了*运算符的类型'T
您还可以明确应用特定的静态/成员约束,但这需要更多丑陋的语法,例如:
let inline id item =
    ( ^T : (member Id : int) (item))

这个示例函数将操作任何暴露出一个类型为 intId 属性的类型。


更新:根据您所描述的具体用例,您确实需要类型类。在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

注意,这使您可以指定要允许功能的确切类型。

我实际上并不是在尝试平方数字,我只是认为这将是一个简单的例子。我正在尝试创建一些执行素性测试和特定因数分解技术的函数。我不想让这些函数适用于浮点数,因为因数分解没有任何意义,而且一些算法会失控。 - Talmage
@Talmage,更新后的答案更符合您的要求吗? - TheInnerLight
是的,这实际上是我在吃午饭时考虑的方法。我会尽量在今晚完全消化这些回答,然后标记问题已解答。 - Talmage

2
基本上有三种方法可以解决您的问题:
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)

以上会让您收到错误信息。

c) 您无法完全控制您的类型
也就是说,您不能在类型内定义方法,而必须使用扩展方法。扩展方法的问题是它们不能与使用通用类型约束的内联函数一起使用。在这种情况下,最好回退到我在此处描述的参数方法

type MultParam =
    | MyInt16Param of System.Int16
    | MyInt32Param of System.Int32
with 
    static member op_Implicit(x: System.Int16) = MyInt16Param x
    static member op_Implicit(x: System.Int32) = MyInt32Param x

然后再次定义一个带泛型约束条件的内联函数,将您的输入类型转换为包装器类型。

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 f = mult' (int16(2)) (int32(3))

您IP地址为146.190.162.183,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。

1

约束是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'. ...

如果在进入专业领域时附加限制不方便,也可以在离开时进行。

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'. ...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接