你好伙伴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
答案 0 :(得分:8)
简短回答
如果在实现或覆盖成员时遇到问题,请在抽象或虚拟成员的定义中提供参数列表完全。 (另外,请注意您的括号,因为额外的括号可以以微妙的方式更改成员的类型。)
E.g。在这种情况下: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)
另一种解决方案是将hit
声明为:
abstract member hit: (Point * Vector -> (Texture*float*Vector) option)
(注意括号!)现在它是一个包含第一类函数的属性;你可以通过返回这样一个函数来实现它,但是成员的类型巧妙地改变了。
后者是为什么甚至将原始接口实现为单参数函数,例如像这样:
member this.hit a = transformHitFunction sh.hit t a // error
不起作用。更确切地说,编译器将拒绝将a
视为元组。同样的问题适用于
member this.hit ((arg1, arg2)) = transformHitFunction sh.hit t (arg1, arg2) // error
现在怎么了?外括号定义参数列表,但内括号使用元组模式来分解单个参数!因此参数列表仍然只有一个参数,编译失败。编写方法时最外面的括号和逗号与其他地方使用的元组不同,即使编译器在某些情况下在两者之间进行转换。
答案 1 :(得分:4)
目前,您的transformedShape.hit
是非索引属性。调用时,它会返回一个您需要提供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)