2016-07-06 46 views
1

在下面的代碼中,我想使用變量的類型實例化一個模板類,最終作爲傳遞給函數的泛型變量,但即使這個更簡單的形式也不起作用:在F#模板中使用編譯時可派生類型

type saveXY<'a when 'a:comparison> (x:'a,y:'a) = 
    member this.X = x 
    member this.Y = y 
    member this.lessThan() = this.X < this.Y 

[<EntryPoint>] 
let main argv = 

    let x1 = 3 
    let y1 = 7 
    let saver1 = new saveXY<int>(x1,y1)   // Good 
    printfn "%A" (saver1.lessThan()) 

    let x2 = 3.0 
    let y2 = 7.0 
    let saver2 = new saveXY<float>(x2,y2)   // Good 
    printfn "%A" (saver2.lessThan()) 

    let saver3 = new saveXY<x2.GetType()> (x2,y2) // No Good, see errors 

    0 

然而,對於saver3下面我獲得(和我不能找到FS1241信息):

...\Program.fs(23,39): error FS0010: Unexpected symbol '(' in type arguments. Expected ',' or other token. 
...\CompareProblem\Program.fs(23,39): error FS1241: Expected type argument or static argument 

如果去除saveXY模板,然後saver2是錯誤的,因爲saver1導致saveXY類參數被限制爲整數。

我還嘗試將x和y聲明爲obj,但這不起作用。我懷疑問題在於,這只是這不可能, 也就是說,如果類參數類型是通用類型的,那麼它們從第一次使用開始就會派生一次。另一方面,也許我錯過了一些東西。

有沒有辦法使用基於變量的類型,這可以在編譯時確定爲F#中的類型模板參數?是否有另一種方法來創建一個能夠處理/存儲通用值的類型?

UPDATE:從李先生的建議,這個工作,如果模板類,然後只用<_>初始化它會工作:

type saveXY<'a when 'a:comparison> (x:'a, y:'a) = 
    member this.X = x 
    member this.Y = y 
    member this.lessThan() = this.X < this.Y 

[<EntryPoint>] 
let main argv = 

    let x1 = 3 
    let y1 = 7 
    let saver3 = new saveXY<_> (x1,y1) // works, 'a is int 
    printfn "%A" (saver3.lessThan()) 

    let x2 = 3.0 
    let y2 = 7.0 
    let saver3 = new saveXY<_> (x2,y2) // works, 'a is float 
    printfn "%A" (saver3.lessThan()) 

    System.Console.ReadKey() |> ignore // wait for a key 
    0 

但爲什麼我需要模板類的類型,如我上面建議?這似乎是編譯器反正演繹的類型,當我使用<_>那麼爲什麼我不能簡單地使用(因爲我會爲一個函數):

type saveXY(x, y) = // x and y are generic, no? They only require comparison, yes? 
    member this.X = x 
    member this.Y = y 
    member this.lessThan() = this.X < this.Y 

[<EntryPoint>] 
let main argv = 

    let x1 = 3 
    let y1 = 7 
    let saver3 = new saveXY (x1,y1) // works 
    printfn "%A" (saver3.lessThan()) 

    let x2 = 3.0 
    let y2 = 7.0 
    let saver3 = new saveXY (x2,y2) // But this FAILS with error FS0001 
    printfn "%A" (saver3.lessThan()) 

    System.Console.ReadKey() |> ignore // wait for a key 
    0 
+1

不,您不能像這樣指定類型參數,但您已經知道靜態類型'x2'和'y2',所以您的用例不清楚。 – Lee

+2

不是很清楚你到底想要做什麼:你是在試圖構建一些代碼來實例化你的類,這個類的泛型參數是_is在編譯時不知道的,或者你只是想說「_whatever type'x2',請使用that_「,並讓編譯器找出它? –

+0

它的:_simply想說「任何類型的x2,請使用它」,並讓編譯器找出它?_ case - 在上面的例子中,我想'saver3'用float模板類型實例化,因爲'x2'是一個浮點數。在實際的代碼中,x2將是泛型類型的函數參數,但函數實例化的地方是不同的編譯時類型 – user1857742

回答

2

如果你只是想編譯器推斷類型泛型參數可以使用:

let saver3 = new saveXY<_>(x2, y2) 

let saver3 = saveXY(x2, y2) 
+0

第二種形式不起作用,但第一種形式不起作用 - 請參閱UPDATE – user1857742

+0

@ user1857742 - 哪個版本的F#是你在用嗎?第二種形式可能需要F#4 – Lee

+0

我認爲它的3.1 - 在Visual Studio 2013中 – user1857742

2

F#沒有爲實例通用代碼在運行時已知類型的任何內置的方法。你總是可以通過反射來做到這一點,但它並不經常使用 - 如果你使用反射來創建它,你實際上無法用這個值做很多事情。

對於示例的目的,可以移動LessThan到一個單獨的接口:

type ILessThan = 
    abstract LessThan : unit -> bool 

type SaveXY<'T when 'T:comparison> (x:'T,y:'T) = 
    member this.X = x 
    member this.Y = y 
    interface ILessThan with 
    member this.LessThan() = 
     printfn "Comparing values of type: %s" (typeof<'T>.Name) 
     this.X < this.Y 

鑑於表示參數的System.Type和兩個obj值,可以使用反射與在運行時創建的SaveXY<T>一個實例typeof<T>是給定System.Type

let createSaveXY typ x y = 
    let typ = typedefof<SaveXY<_>>.MakeGenericType [| typ |] 
    typ.GetConstructors().[0].Invoke([| x; y |]) :?> ILessThan 

這裏是如何工作的一個例子:

let lt = createSaveXY (typeof<float>) (box 3.0) (box 7.0) 
lt.LessThan() 

但正如我剛纔所說,這是很少有用的,它不是超級高效的 - 因此而不是複製此,試着描述你正在試圖解決的問題 - 有可能是一個更好的解決方案。

+0

感謝您的詳細解答 - 我不想用只在運行時才知道的類實例化類,而是一個通用的類,它是一個函數。 – user1857742