簡體   English   中英

多種類型的F#功能類型注釋

[英]F# Function Type Annotation for Multiple Types

我正在嘗試定義一個“准通用”函數。 我不希望它完全是通用的,我希望它只適用於“整數”類型(即byte,sbyte,int16,uint16,int,uint32,int64,uint64,bigint)。

如何將它放入函數定義的類型注釋中? 為了澄清,我將如何重寫以下代碼以實際工作(僅使用3種類型,可能不會失去泛化):

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

首先,在運行時使用標准.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

請注意,這允許您指定要允許該功能的確切類型。

基本上有3種方法可以解決您的問題

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

應該再次出錯。

選項b)基本上是Haskells類型類特征的仿真,其中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'. ...

如果在進入專用域時附加約束是不方便的,那么離開時也可以這樣做。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM