我一遍又一遍地阅读official Microsoft docs on type constraints,但我无法理解为什么这段代码无法编译:
let inline transform<'A, 'a when 'A : (member Item : int -> float)> (a: 'A) : 'a =
a.[0]
错误FS0752:运算符'expr。[idx]'已根据此程序点之前的信息用于不确定类型的对象。考虑添加更多类型约束
并且:
let inline transform<'A, 'a when 'A : (member f : int -> float)> (a: 'A) : 'a =
a.f(0)
错误FS0072:根据此程序点之前的信息查找不确定类型的对象。在此程序点之前可能需要类型注释来约束对象的类型。这可能允许解析查找。
显然我不明白如何在f#中使用泛型成员约束。
我面临的一般问题是,我希望在“类似矢量”的类型上制作泛型函数,例如标准float[]
或Vector<float>
来自MathNet.Numerics
甚至DV
来自DiffSharp包。
现在我必须为每种类型获得一个特殊功能,如(完整代码):
#I ".paket/load"
#load "mathnet.numerics.fsharp.fsx"
#load "diffsharp.fsx"
open DiffSharp.AD.Float64
open MathNet.Numerics
open MathNet.Numerics.LinearAlgebra
open MathNet.Numerics.LinearAlgebra.Double
let l1 = 4.5
let l2 = 2.5
let a0 = [1.1; -0.9]
let inline transformVec (a:Vector<float>) =
let x1, y1 = l1 * cos a.[0], l1 * sin a.[0]
let x2, y2 = x1 + l2 * cos (a.[0] + a.[1]), y1 + l2 * sin (a.[0] + a.[1])
vector [x1; y1; x2; y2]
let inline transformDV (a:DV) =
let x1, y1 = l1 * cos a.[0], l1 * sin a.[0]
let x2, y2 = x1 + l2 * cos (a.[0] + a.[1]), y1 + l2 * sin (a.[0] + a.[1])
toDV [x1; y1; x2; y2]
正如您所看到的,这些功能可以完全相同,但可以在不同的类型上运行。
我想得到一个像(不是工作代码)的通用函数:
let inline transform<'A, 'a when 'A : (member Item : int -> 'a)> (toExt : 'a list -> 'A) (a: 'A) : 'A =
let x1, y1 = l1 * cos a.[0], l1 * sin a.[0]
let x2, y2 = x1 + l2 * cos (a.[0] + a.[1]), y1 + l2 * sin (a.[0] + a.[1])
toExt [x1; y1; x2; y2]
let transformVec = transform vector
let transformDV = transform toDV
我错过了什么?
编辑:我已将其与Mathnet.Numerics
let inline transform (toExt : 'a list -> 'A) (a: 'A) : 'A =
let inline get i : 'a = (^A : (member get_Item: int -> 'a) a,i)
let x1, y1 = l1 * cos (get(0)), l1 * sin (get(0))
let x2, y2 = x1 + l2 * cos (get(0) + get(1)), y1 + l2 * sin (get(0) + get(1))
toExt [x1; y1; x2; y2]
(transform vector) (vector a0)
因为它强制'a
(警告FS0064
)为float
,我不想要......(来自DV
的{{1}}返回{ {1}}在DiffSharp
上输入,而不是D
。)
用
代替声明get_Item
让编译器破解:
错误FS0001:由于在编译时无法解析类型参数,因此无法在此处使用声明的类型参数“a”
答案 0 :(得分:4)
您需要像这样呼叫成员Item
:
let inline transform (a: 'A) : 'a = (^A : (member get_Item: _ -> _) a, 0)
但是你会收到警告,
~vs72B.fsx(2,5): warning FS0077: Member constraints with the name 'get_Item' are given special status by the F# compiler as certain .NET types are implicitly augmented with this member. This may result in runtime failures if you attempt to invoke the member constraint from your own code.
因为一些原始类型使用“模拟成员”。因此,对于列表,它将起作用:
transform ["element"]
// val it : string = "element"
但不适用于数组
transform [|"element"|]
System.NotSupportedException: Specified method is not supported.
at <StartupCode$FSI_0009>.$FSI_0009.main@()
Stopped due to error
这是因为F#编译器假装数组有该成员,但事实上它们没有。
如果这是一个问题,您可以使用更复杂的重载解决方案为特定类型添加特殊实现,这不是直截了当但我可以告诉您如何,或者您可以考虑使用F#+ Indexable abstraction适用于通常具有Item
属性的类型。
当然你可以使用#nowarn "77"
来忽略该警告,但正如您所见,编译器无法检查是否有人会使用数组调用您的函数并在运行时失败。
<强>更新强>
由于您询问后续问题如何使用它,这是一个例子:
#r "MathNet.Numerics.dll"
#r "MathNet.Numerics.FSharp.dll"
#r "FSharpPlus.dll"
open FSharpPlus
open MathNet.Numerics.LinearAlgebra
let x = item 1 [0..10]
let y = item 1 [|0..10|]
let z = item 1 (vector [0.;1.;2.])
// val x : int = 1
// val y : int = 1
// val z : float = 1.0
我不确定它是否适用于DiffSharp,我不知道你从那个库中使用了哪种类型,我多次找到DV
。
<强> UPDATE2 强>
关于您的通用transform
函数的后续问题,使用简单的成员约束是不够的,您还需要一般性地解决从列表到目标类型的转换,并创建不同的通用乘法一般工作,但你的类型
需要处理。您可以使用与成员约束相结合的重载来获得所需的功能:
let inline item (i:int) (a: 'A) : 'a = (^A : (member get_Item: _ -> _) a, i)
type T = T with
static member ($) (T, _:Vector<float>) = fun (x:float list) -> vector x
static member ($) (T, _:Matrix<float>) = fun (x:float list) -> matrix [x]
static member ($) (T, _:DV ) = fun (x: D list ) -> toDV (List.toArray x)
let inline toDestType (x:'t list) :'D = (T $ Unchecked.defaultof<'D>) x
type V = V with
static member ($) (V, x:float ) = fun (y: float) -> x * y : float
static member ($) (V, x:D ) = fun (y: float) -> x * y : D
let inline mult (y:float) (x:'t) :'t = (V $ x) y
let inline transform (a:'T) :'T =
let x1, y1 = mult l1 (cos (item 0 a)), mult l1 (sin (item 0 a))
let x2, y2 = x1 + mult l2 (cos ((item 0 a) + (item 1 a))), y1 + mult l2 (sin ((item 0 a) + (item 1 a)))
let g = toDestType [x1; y1; x2; y2]
g
let b = transform (DV [| 1. ; 2.|])
let a = transform (vector [1. ; 2.])
每次引用DiffSharp时我仍然会收到运行时错误,但intellisense会显示推断出的正确类型。