1

我一遍又一遍地阅读有关类型约束的官方 Microsoft 文档,但我不明白为什么这段代码无法编译:

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# 中将成员约束与泛型一起使用。我面临的一般问题是我想通过“类矢量”类型(如 standard float[],甚至Vector<float>来自DiffSharp包)创建通用函数。现在我必须为每种类型获取一个临时函数,例如(完整代码):MathNet.NumericsDV

#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(warning FS0064) 是float,我不希望...(DVDiffSharp返回D类型 on get_Item,不是float。)

将声明替换为

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

使编译器发出嘶哑的声音:

错误 FS0001:此处不能使用声明的类型参数“a”,因为在编译时无法解析类型参数

4

1 回答 1

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抽象通常具有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多次发现。

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

于 2017-03-27T08:51:42.457 回答