F#: 未找到与此覆盖对应的摘要 属性

F#: No abstract property was found that corresponds to this override

各位Overflowers大家好。我正在从事一个小组项目,以创建一个绘制 3D 场景的 2D 渲染的光线追踪器。我目前的任务涉及对象(形状)的矩阵变换,需要四处移动、镜像、剪切等

在处理形状时,我们选择实现一个定义命中函数类型的接口。这个命中函数是在每个形状中定义的,例如球体、盒子、平面等。在转换形状时,我需要转换击中形状的光线,而这样做的方法似乎是使用更高阶的函数来改变原来的命中函数。

为了做到这一点,我实现了函数 transformHitFunction,它似乎可以工作,但是实现 Shape 接口的新类型 transformedShape 给我错误

No abstract property was found that corresponds to this override

这对我来说没有任何意义,因为它与其他相同类型的命中函数一起使用。谁能发现问题所在?

我已尝试删除与此问题无关的所有模块、名称空间和代码。

type Transformation = Matrix of float [,]

type Vector =
    | V of float * float * float

let mkVector x y z = V(x, y, z)
let vgetX (V(x,_,_)) = x
let vgetY (V(_,y,_)) = y
let vgetZ (V(_,_,z)) = z

type Point =
    | P of float * float * float

let mkPoint x y z = P(x, y, z)
let pgetX (P(x,_,_)) = x
let pgetY (P(_,y,_)) = y
let pgetZ (P(_,_,z)) = z

type Material = Material
    type Texture =
    | T of (float -> float -> Material)

type Shape =
    abstract member hit: Point * Vector -> (Texture*float*Vector) option

let transformPoint (p:Point) t =
    match t with
    | Matrix m -> mkPoint ((pgetX(p))*m.[0,0] + (pgetY(p))*m.[0,1] + (pgetZ(p))*m.[0,2] + m.[0,3])
                          ((pgetX(p))*m.[1,0] + (pgetY(p))*m.[1,1] + (pgetZ(p))*m.[1,2] + m.[1,3])
                          ((pgetX(p))*m.[2,0] + (pgetY(p))*m.[2,1] + (pgetZ(p))*m.[2,2] + m.[2,3])

let transformVector (v:Vector) t =
    match t with
    | Matrix m -> mkVector ((vgetX(v))*m.[0,0] + (vgetY(v))*m.[0,1] + (vgetZ(v))*m.[0,2] + m.[0,3])
                           ((vgetX(v))*m.[1,0] + (vgetY(v))*m.[1,1] + (vgetZ(v))*m.[1,2] + m.[1,3])
                           ((vgetX(v))*m.[2,0] + (vgetY(v))*m.[2,1] + (vgetZ(v))*m.[2,2] + m.[2,3])

let transformHitFunction fn (t:Transformation) =
    fun (p:Point,v:Vector) -> 
        let tp = transformPoint p t
        let tv = transformVector v t
        match fn(tp,tv) with
        | None -> None
        | Some (tex:Texture, d:float, n) -> let tn = transformVector n t
                                            Some (tex, d, tn)

type transformedShape (sh:Shape, t:Transformation) =
     interface Shape with
         member this.hit = transformHitFunction sh.hit t

简答

当在实现或覆盖成员时遇到问题,请提供参数列表完全,如抽象或虚拟成员的定义。 (另外,请注意括号,因为额外的括号会以微妙的方式改变成员的类型。)

例如在这种情况下:member this.hit (arg1, arg2) = ...

稍微长一点的回答

您遇到的情况是 F# 的第一个 class 函数与其对面向对象样式方法的支持之间的差异相关。

为了与公共语言基础结构 (CLI) 的面向对象语言(以及 F# 程序中的面向对象编程风格)兼容,F# 有时不仅区分函数和值,甚至区分面向对象和功能风格。

F# 对两件事使用非常相似的语法:采用参数列表(并且还支持重载和可选参数)的 "classical" CLI 方法与 F# 自己最喜欢的函数类型 FSharpFunc,后者始​​终采用一个参数但支持柯里化,并且可以通过元组采用多个参数。但是这两者的语义可以不同。

问题的最后一行尝试传递一个带有元组输入的函数来实现一个采用两个参数的方法,就像 C# 中的方法或 VB.NET 采用它们一样:CLI 方法的参数列表。直接分配 F# 风格的 first-class 函数在这里不起作用,单个元组参数也不会起作用;编译器坚持明确获取每个参数。如果您使用完整的方法参数列表编写实现,它将起作用。例如:

    member this.hit (arg1, arg2) = transformHitFunction sh.hit t (arg1, arg2)

另一种解决方案是将 hit 声明为:

    abstract member hit: (Point * Vector -> (Texture*float*Vector) option)

(注意括号!)现在它是一个包含第一个class函数的属性;你可以通过返回这样一个函数来实现它,但是成员的类型巧妙地改变了。

后者是为什么甚至将原始接口实现为单参数函数的原因,例如像这样:

    member this.hit a = transformHitFunction sh.hit t a // error

将不起作用。更准确地说,编译器将拒绝将 a 视为元组。同样的问题适用于

     member this.hit ((arg1, arg2)) = transformHitFunction sh.hit t (arg1, arg2) // error

现在怎么了?外括号定义参数列表,但内括号使用元组模式来分解单个参数!所以参数列表仍然只有一个参数,编译失败。编写方法时最外层的括号和逗号与其他地方使用的元组不同,尽管编译器在某些情况下会在两者之间进行转换。

目前,您的 transformedShape.hit 是未编入索引的 属性。调用时,它 returns 一个函数,您需要提供一个 Point*Vector 元组,您将得到想要的结果。如果您添加辅助绑定,您将能够更好地看到它:将鼠标悬停在 f 此处:

type transformedShape (sh:Shape, t:Transformation) =
     interface Shape with
         member this.hit = 
            let f = transformHitFunction sh.hit t
            f

正如其他人已经指出的那样,您需要做的就是明确说明论点,这样就很好了:

type transformedShape2 (sh:Shape, t:Transformation) =
        interface Shape with
            member this.hit(p, v) = transformHitFunction sh.hit t (p, v)