将成员约束添加到内联函数参数会导致数组访问器出现 FS0752

Adding member constraint to an inlined function's paramater causes FS0752 on array accessor

我反复阅读 official Microsoft docs on type constraints,但我不明白为什么这段代码无法编译:

let inline transform<'A, 'a when 'A : (member Item : int -> float)> (a: 'A) : 'a =
    a.[0]

error FS0752: The operator 'expr.[idx]' has been used on an object of indeterminate type based on information prior to this program point. Consider adding further type constraints

还有:

let inline transform<'A, 'a when 'A : (member f : int -> float)> (a: 'A) : 'a =
    a.f(0)

error FS0072: Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved.

显然我不明白如何在 f# 中使用泛型的成员约束。 我面临的一般问题是我想在 "vector-like" 类型上创建通用函数,例如标准 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 来自 DiffSharp returns D 输入 get_Item,而不是 float。)

将声明替换为

let inline transform<'a> (toExt : 'a list -> 'A) (a: 'A) : 'A = 

使编译器崩溃:

error FS0001: The declared type parameter 'a' cannot be used here since the type parameter cannot be resolved at compile time

您需要这样呼叫成员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.

因为一些原始类型使用 "simulated members"。所以对于列表它会起作用:

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#+对于通常具有 Item 属性.

的类型具有 Indexable abstraction

当然你可以忽略那个警告,用 #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 多次。

更新2

关于通用 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 时仍然会遇到运行时错误,但是智能感知会显示正确的推断类型。