我正在尝试在Haskell中编写一个简单的光线跟踪器。我想定义一个类型类,表示可用的各种表面,并有一个函数来确定光线与它们相交的位置:
{-# LANGUAGE RankNTypes #-}
data Vector = Vector Double Double Double
data Ray = Ray Vector Vector
class Surface s where
intersections :: s -> Ray -> [Vector]
-- Obviously there would be some concrete surface implementations here...
data Renderable = Renderable
{ surface :: (Surface s) => s
, otherStuff :: Int
}
getRenderableIntersections :: Renderable -> Ray -> [Vector]
getRenderableIntersections re ra = intersections (surface re) ra
然而,这给了我错误:
Ambiguous type variable 's' in the constraint:
'Surface'
arising from a use of 'surface'
(实际代码更复杂,但我试图将其提炼为更简单的东西,同时保持我想要实现的目标)。
我该如何解决这个问题?或者,鉴于我来自标准的OO背景,我从根本上做错了什么?
答案 0 :(得分:10)
请不要使用存在类型!你可以,但没有意义。
从功能的角度来看,你可以完全放弃Surface的类型类概念。 Surface
是将Ray映射到向量列表的东西,不是吗?所以:
type Surface = Ray -> [Vector]
data Renderable = Renderable
{ surface :: Surface
, otherStuff :: Int
}
现在,如果你真的想要,你可以拥有一个ToSurface
类型,基本上就像你给出的那样:
class ToSurface a where
toSurface :: a -> Surface
但这仅仅是为了方便和特殊的多态性。您的模型中没有任何内容需要它。
一般来说,存在的用例非常少,但至少有90%的时间可以用存在的方式代替它所代表的功能,并获得更清晰,更容易推理的东西。
此外,尽管你可能会过多地接受这些问题并且这些问题并不完全匹配,但你可能会发现Conal在指称性设计方面的一些内容很有用:http://conal.net/blog/posts/thoughts-on-semantics-for-3d-graphics/
答案 1 :(得分:3)
在getRenderableIntersections
功能中,您拨打surface
。解释器无法确定要使用的类Surface
的实例。如果您有两个这样的实例:
instance Surface SurfaceA where
-- ...
instance Surface SurfaceB where
-- ...
解释器如何确定surface
的类型?
您定义Renderable
的方式意味着有一个函数surface :: Surface s => Renderable -> s
。
尝试创建实例Surface SurfaceA
并询问以下类型查询(给定一个简单的构造函数SurfaceA
):
> :t surface (Renderable SurfaceA 0) -- What's the type of the expression?
那么,这个表达式是什么类型的?我打赌你期待SurfaceA
。错误。采用surface
的类型。它需要一个Renderable
参数,我们传递一个Renderable
参数。剩下的是什么? Surface s => s
。这就是那种表达的类型。我们仍然不知道s
代表什么类型。
如果您希望类型为SurfaceA
,则需要更改代码,使其变为surface :: Surface s => Renderable s -> s
。这样可以确定s
是什么,因为它与s
中使用的Renderable
相同。
编辑:正如@mokus所建议的那样,你也可以试试ExistentialTypes
扩展名。它允许在类型声明的右侧“隐藏”类型参数。
data Renderable = forall s. Surface s => Renderable
{ surface :: s
, otherStuff :: Int
}
我上面链接的HaskellWiki页面的an example非常类似于你想要做的事情。
编辑:(通过@stusmith) - 为了记录,我在下面包含了基于这些建议进行编译的代码。但是我已经接受了答案,我认为这是一种更好的接近方式。
{-# LANGUAGE ExistentialQuantification #-}
data Vector = Vector Double Double Double
data Ray = Ray Vector Vector
class Surface_ s where
intersections :: s -> Ray -> [Vector]
data Surface = forall s. Surface_ s => Surface s
instance Surface_ Surface where
intersections (Surface s) ra = intersections s ra
data Renderable = Renderable
{ surface :: Surface
}
getRenderableIntersections :: Renderable -> Ray -> [Vector]
getRenderableIntersections re ra = intersections (surface re) ra