在F#模板中使用编译时可派生类型

时间:2016-07-06 17:36:33

标签: class templates generics 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#中的类型模板参数?是否有另一种方法来创建一个能够处理/存储通用值的类型?

更新:根据Lee的建议,如果您对该类进行模板设置,然后使用<_>进行实例化,则该方法有效:

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 

2 个答案:

答案 0 :(得分:2)

如果您只是希望编译器推断出可以使用的泛型参数的类型:

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

let saver3 = saveXY(x2, y2)

答案 1 :(得分: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()

但正如我之前所说,这很少有用而且效率不高 - 所以不要复制它,试着描述你想要解决的问题 - 可能有更好的解决方案。