【问题标题】:Use of compile time derivable types in F# templates在 F# 模板中使用编译时可派生类型
【发布时间】:2016-11-08 20:59:55
【问题描述】:

在下面的代码中,我想使用一个变量的类型来实例化一个模板类,最终作为一个泛型变量传递给一个函数,但即使这种更简单的形式也不起作用:

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 的建议,这是可行的,如果您将类模板化,然后使用 &lt;_&gt; 实例化它就可以了:

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  

但是为什么我需要模板类类型,就像我上面建议的那样?当我使用&lt;_&gt; 时,编译器似乎在推断类型,所以为什么我不能简单地使用(就像我对函数一样):

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 

【问题讨论】:

  • 不,你不能像这样指定类型参数,虽然你已经静态知道x2y2的类型,所以你的用例不清楚。
  • 不是很清楚你到底想做什么:你是否试图构建代码,用在编译时未知的通用参数实例化你的类,或者您是否只想说“x2 是什么类型,请使用它”,然后让编译器弄清楚?
  • 它是:只是想说“无论 x2 是什么类型,请使用它”,并让编译器弄清楚? 案例 - 在上面的示例中,我想saver3 用浮点模板类型实例化,因为x2 是浮点数。在实际代码中,x2 将是泛型类型的函数参数,但该函数使用不同的编译时类型实例化

标签: class templates generics f#


【解决方案1】:

如果你只是想让编译器推断你可以使用的泛型参数的类型:

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

let saver3 = saveXY(x2, y2)

【讨论】:

  • 第二种形式无效,但第一种形式有效 - 请参阅更新
  • @user1857742 - 您使用的是哪个版本的 F#?第二种形式可能需要 F# 4
  • 我认为它的 3.1 - 在 Visual Studio 2013 中
【解决方案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&lt;T&gt; 的实例,其中typeof&lt;T&gt; 是给定的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()

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

【讨论】:

  • 感谢您的详细回答 - 我不想使用仅在运行时知道的类型来实例化该类,而是使用作为函数参数的通用类型。
猜你喜欢
  • 2021-11-22
  • 1970-01-01
  • 1970-01-01
  • 2017-09-28
  • 1970-01-01
  • 1970-01-01
  • 2011-11-19
相关资源
最近更新 更多