2016-07-06 81 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