什么是错误“类型实例化涉及 byref 类型。” F# 中的解决方法是什么

Tho*_*mas 6 f#

我有一些代码包装 TA-Lib,很多包装器非常相似:

let sma (timePeriod: int) (data: float[]) =
    let mutable outStartIndex = 0
    let mutable outNbElement = 0

    let mutable smaData : float array = Array.zeroCreate (data.Length - timePeriod + 1)

    let retCode = Core.Sma(0, (data.Length - 1), data, timePeriod, &outStartIndex, &outNbElement, smaData)

    if retCode <> Core.RetCode.Success then
        invalidOp (sprintf "AssertRetCodeSuccess")

    let padding = Array.create (timePeriod - 1) System.Double.NaN
    Array.append padding smaData



let ema (timePeriod: int) (data: float[]) =
    let mutable outStartIndex = 0
    let mutable outNbElement = 0
    let mutable emaData : float array = Array.zeroCreate (data.Length - timePeriod + 1)

    let retCode = Core.Ema(0, (data.Length - 1), data, timePeriod, &outStartIndex, &outNbElement, emaData)

    if retCode <> Core.RetCode.Success then
        invalidOp (sprintf "AssertRetCodeSuccess")

    let padding = Array.create (timePeriod - 1) System.Double.NaN
    Array.append padding emaData
Run Code Online (Sandbox Code Playgroud)

我想做的是创建一个通用函数,我可以在其中传递 TA-Lib 函数来调用。就像是:

let myGenericFunction (timePeriod: int) (data: float[]) TALibFunc =
    let mutable outStartIndex = 0
    let mutable outNbElement = 0

    let mutable smaData : float array = Array.zeroCreate (data.Length - timePeriod + 1)

    let retCode = TALibFunc(0, (data.Length - 1), data, timePeriod, &outStartIndex, &outNbElement, smaData)

    if retCode <> Core.RetCode.Success then
        invalidOp (sprintf "AssertRetCodeSuccess")

    let padding = Array.create (timePeriod - 1) System.Double.NaN
    Array.append padding smaData
Run Code Online (Sandbox Code Playgroud)

但我得到的错误是:

[FS0412] 类型实例化涉及 byref 类型。Common IL 规则不允许这样做。

有解决方法吗?我对这个问题不太熟悉。

Fyo*_*kin 8

简短的回答:将您的mutable参数替换为ref.


TA-Lib 有一个非常不幸的 API:那些讨厌的out参数(在 F# 中称为byref),它们总是带来麻烦。在这种情况下,它们不能是泛型类型实例化的一部分。

这是一个更短的例子。考虑好老了list<T>。我们可以做一个空的list<int>

let noInts = [] : list<int>
Run Code Online (Sandbox Code Playgroud)

但如果这些int是呢byref

let noRefs = [] : list< byref<int> >
Run Code Online (Sandbox Code Playgroud)

编译器说,无能为力。类型实例化涉及 byref 类型。Common IL 规则不允许这样做。对不起。


在您的例子中, 的最后一个参数myGenericFunction是 F# 函数。在 F# 中,函数由类型表示FSharpFunc<T, R>(其中T是参数,R是结果)。所以最后一个参数的类型是这样的:

FSharpFunc< int * int * float array * int * byref<int> * byref<int> * float array, int >
Run Code Online (Sandbox Code Playgroud)

看到byref<int>里面那两个了吗?这些是&outStartIndex&outNbElement。并且它们在通用实例化中是被禁止的。倒霉。


但还有希望!

关键字mutable只是在 F# 中创建可变单元格的两种方法之一。另一种方法是ref

let x = ref 0     // Initialization
printfn "%d" !x   // Prints "0"
x := 42           // Mutation
printfn "%d" !x   // Prints "42"
Run Code Online (Sandbox Code Playgroud)

这是一个老派的东西,早于mutable,作为一个库实现(而不是语言构造),并且在大多数情况下mutable更好。但这不是其中之一!

事实证明:

  1. 与真正的 .NET CILout参数不同,ref单元格可以成为通用实例化的一部分。因为,从 .NET 的角度来看,它们没有什么特别的——只是另一个类。
  2. F# 编译器对它们有特殊的用途:当预期类型是 a 时ref,但您试图传递一个带有out- 参数的函数,编译器将自动为您生成一些包装代码。

因此,有了这些知识,您可以myGenericFunction像这样修改:

let myGenericFunction (timePeriod: int) (data: float[]) TALibFunc =
    let outStartIndex = ref 0
    let outNbElement = ref 0

    let mutable smaData : float array = Array.zeroCreate (data.Length - timePeriod + 1)

    let retCode = TALibFunc(0, (data.Length - 1), data, timePeriod, outStartIndex, outNbElement, smaData)

    ...
Run Code Online (Sandbox Code Playgroud)

然后消费者可以这样称呼它:

myGenericFunction 42 [|1; 2; 3|] Core.Sma // Wrapping code gets generated here
Run Code Online (Sandbox Code Playgroud)