6

我正在尝试在 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 背景,我从根本上做错了什么?

4

2 回答 2

10

请不要为此使用存在类型!你可以,但没有意义。

从功能的角度来看,您可以完全放弃 Surface 的这种类型类概念。ASurface是将 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/对 3d 图形语义的思考/

于 2011-02-15T15:39:37.397 回答
3

getRenderableIntersections你调用的函数surface中。解释器无法确定Surface您要使用的类的哪个实例。如果您有两个这样的实例:

instance Surface SurfaceA where
  -- ...
instance Surface SurfaceB where
  -- ...

解释器如何确定 的类型surface

您定义的Renderable方式意味着有一个 function 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可以确定什么,因为它sRenderable.

编辑:正如@mokus 所建议的,您也可以尝试ExistentialTypes扩展。它允许在类型声明的右侧“隐藏”类型参数。

data Renderable = forall s. Surface s => Renderable
  { surface    :: s
  , otherStuff :: Int
  }

我在上面链接到的 HaskellWiki 页面甚至有一个与您想要做的非常相似的示例。

编辑:(由@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
于 2011-02-15T14:37:41.440 回答