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

时间:2017-03-27 08:41:32

标签: generics f# type-constraints arrayaccess

我一遍又一遍地阅读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”

1 个答案:

答案 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会显示推断出的正确类型。