2017-05-04 81 views
2

我试图定义一个“准通用”函数。我希望它只能用于“整数”类型(即byte,sbyte,int16,uint16,int,uint32,int64,uint64,bigint),而不是完全通用。多种类型的F#函数类型注释

我该如何把它放到函数定义的类型注释中?为了澄清,我将如何改写下面的代码实际工作(仅使用3种可能没有泛化的损失):

let square (x: int|int64|bigint) = 
    x * x 
+3

这可能是与实现[约束](https://docs.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/generics/constraints) - 它是否会然而,容易/实际,我不知道。 –

回答

6

首先,有没有办法使用标准.NET解决这样一个类型约束运行时的泛型。

F#确实允许您在编译时通过解析它们并在内嵌插入适当的函数调用来表示限制形式的此类约束。这使用statically resolved type parameters

这对你所描述的情况很简单,你可以这样写:

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 

注意这可以让你指定的您想要允许功能的类型。

+0

我实际上并没有试图计算平方数,我只是认为这将是一个简单的例子。我试图创建一些执行素性测试和特定分解技术的函数。我不想让这些函数在浮点数上工作,因为因式分解没有任何意义,某些算法会失控。 – Talmage

+0

@Talmage更新的答案更多你在找什么? – TheInnerLight

+0

是的,这实际上是我在吃午饭时想到的方法。我会尽量完全消化今晚的反应,然后标记回答的问题。 – Talmage

2

基本上有三种途径,您的问题

一)使用的是已经支持要应用到它们的操作符/方法的类型
在这种情况下,只需在函数前加inline而开心

b)您必须在类型完全控制您使用
也就是说你可以定义在函数定义新的成员,而无需使用扩展方法。在这种情况下,你定义在每类中的方法实现你所使用泛型类型的限制与此相当怪异的语法

let inline mult (x:^T) (y:^T) = (^T : (static member Mult: ^T -> ^T -> ^T) (x, y)) 

需要

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 a = MyInt16 2 
let b = MyInt16 3 

let c = mult a b 

This works。让我们看看当我们使用不同类型时会发生什么

let d = mult a (MyInt32 3) 

上面会给你一个错误。

c)您没有对你的类型
那是你无法定义的类型中的方法,但你将不得不使用扩展方法完全控制。扩展方法的问题在于,它们不能与使用泛型类型约束的内联函数一起使用 。在这种情况下,你最好回落到parameter approach I described here

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) 

,并添加您的实现。这一次有点wordier因为我们需要使用模式匹配

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)) 

错误再次发生,因为它应该是。

选项b)是基本上Haskells型类特征的仿真,其中作为
选项c)是接近OCamls多态性变体

1

约束,简写的F#类型系统的两个特征的组合效果(Statically Resolved Type Parameters和会员Constraints),可能会有所帮助。他们的工作允许在编译时排除不兼容的类型,虽然有多条冗长的错误消息。

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