f# - 什么是错误“类型实例化涉及 byref 类型。” 什么是 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
我想做的是创建一个通用函数,我可以在其中传递 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
但我得到的错误是:
[FS0412] 类型实例化涉及 byref 类型。Common IL 的规则不允许这样做。
有解决方法吗?我不熟悉这个问题。
解决方案
简短回答:将您的mutable
参数替换为ref
.
TA-Lib 有一个非常不幸的 API:那些讨厌的out
参数(在 F# 中称为byref
),它们总是很麻烦。在这种情况下,它们不能是泛型类型实例化的一部分。
这是一个更短的例子。考虑好旧的list<T>
。我们可以做一个空的list<int>
:
let noInts = [] : list<int>
但是如果那些int
s 是 sbyref
呢?
let noRefs = [] : list< byref<int> >
不能做 - 编译器说。类型实例化涉及 byref 类型。Common IL 的规则不允许这样做。对不起。
在您的情况下,of 的最后一个参数myGenericFunction
是 F# 函数。在 F# 中,函数由类型表示FSharpFunc<T, R>
(其中T
是参数和R
结果)。所以你最后一个参数的类型是这样的:
FSharpFunc< int * int * float array * int * byref<int> * byref<int> * float array, int >
看到里面的那两个byref<int>
s了吗?那些是&outStartIndex
和&outNbElement
。并且它们在通用实例化中是被禁止的。倒霉。
但有希望!
mutable
关键字只是在 F# 中制作可变单元格的两种方法之一。另一种方法是ref
:
let x = ref 0 // Initialization
printfn "%d" !x // Prints "0"
x := 42 // Mutation
printfn "%d" !x // Prints "42"
这是一个老派的东西,早于mutable
,被实现为一个库(而不是语言结构),并且在大多数情况下mutable
更好。但这不是其中之一!
事实证明:
- 与真正的 .NET CIL
out
参数不同,ref
单元格可以作为通用实例化的一部分。因为,从 .NET 的角度来看,它们并没有什么特别之处——只是另一个类。 - F# 编译器对它们有特殊的作用:当预期的类型是 a
ref
,但你试图传递一个带有out
-parameter 的函数时,编译器会自动为你生成一些包装代码。
因此,有了这些知识,您可以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)
...
然后消费者可以这样称呼它:
myGenericFunction 42 [|1; 2; 3|] Core.Sma // Wrapping code gets generated here