F#:找不到与此覆盖相对应的抽象属性

Mar*_*ohl 2 f# interface higher-order-functions

你好伙伴Overflowers.我正在开发一个组项目,用于创建绘制3D场景的2D渲染的光线跟踪器.我目前正在进行的任务涉及对象(形状)的矩阵变换,需要移动,镜像,剪切等.

在处理形状时,我们选择实现一个定义命中函数类型的接口.此命中函数在每个形状中定义,例如球体,方框,平面等.当变换形状时,我需要变换撞击形状的光线,并且这样做的方式似乎是具有更高阶函数,改变原始形状命中功能.

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

未找到与此覆盖对应的抽象属性

这对我没有任何意义,因为它适用于同类型的其他命中功能.谁能发现什么是错的?

我试图剥离与此问题无关的所有模块,命名空间和代码.

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
Run Code Online (Sandbox Code Playgroud)

Van*_*oiy 8

简短的回答

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

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

答案稍微长一些

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

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

F#使用非常相似的两种语法:采用参数列表(并且还支持重载和可选参数)的"经典"CLI方法与F#自己喜欢的函数类型相比FSharpFunc,它总是需要一个参数但支持currying并且可以通过元组.但这两者的语义可能不同.

问题的最后一行尝试传递一个带有tupled输入的函数来实现一个方法,该方法接受两个参数,就像C#或VB.NET中的方法一样:CLI方法的参数列表.直接分配一个F#风格的第一类函数在这里不起作用,并且nether将是一个单元组参数; 编译器坚持要明确地获取每个参数.如果使用完整的方法参数列表编写实现,它将起作用.例如:

    member this.hit (arg1, arg2) = transformHitFunction sh.hit t (arg1, arg2)
Run Code Online (Sandbox Code Playgroud)

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

    abstract member hit: (Point * Vector -> (Texture*float*Vector) option)
Run Code Online (Sandbox Code Playgroud)

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

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

    member this.hit a = transformHitFunction sh.hit t a // error
Run Code Online (Sandbox Code Playgroud)

不管用.更确切地说,编译器将拒绝将其a视为元组.同样的问题适用于

     member this.hit ((arg1, arg2)) = transformHitFunction sh.hit t (arg1, arg2) // error
Run Code Online (Sandbox Code Playgroud)

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