2017-05-04 78 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'. ...