I'm trying to write a simple ray-tracer in Haskell. I wanted to define a typeclass representing the various kinds of surfaces available, with a function to determine where a ray intersects them:
{-# 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
However this gives me the error:
Ambiguous type variable 's' in the constraint:
'Surface'
arising from a use of 'surface'
(The actual code is more complex but I've tried to distill it to something simpler, while keeping the gist of what I'm trying to achieve).
How do I fix this? Or alternatively, given that I come from a standard OO background, what am I fundamentally doing wrong?
Please don't use existential types for this! You could, but there would be no point.
From a functional standpoint you can drop this typeclass notion of Surface entirely. A Surface
is something that maps a Ray to a list of Vectors, no? So:
type Surface = Ray -> [Vector]
data Renderable = Renderable
{ surface :: Surface
, otherStuff :: Int
}
Now if you really want, you can have a ToSurface
typeclass essentially as you gave:
class ToSurface a where
toSurface :: a -> Surface
But that's just for convenience and ad-hoc polymorphism. Nothing in your model requires it.
In general, there are a very few use cases for existentials, but at least 90% of the time you can substitute an existential with the functions it represents and obtain something cleaner and easier to reason about.
Also, even though it may be a tad too much for you to take in, and the issues don't exactly match, you might find useful some of Conal's writing on denotational design: http://conal.net/blog/posts/thoughts-on-semantics-for-3d-graphics/
In your getRenderableIntersections
function you call surface
. There is no way for the interpreter to figure out what instance of the class Surface
you want to use. If you have two such instances:
instance Surface SurfaceA where
-- ...
instance Surface SurfaceB where
-- ...
How can the interpreter determine the type of surface
?
The way you defined Renderable
means there is a function surface :: Surface s => Renderable -> s
.
Try creating an instance Surface SurfaceA
and asking the following type query (given a simple constructor SurfaceA
):
> :t surface (Renderable SurfaceA 0) -- What's the type of the expression?
So, what type is this expression? I bet you're expecting SurfaceA
. Wrong. Take the type of surface
. It takes a Renderable
argument and we're passing it a Renderable
argument. What is left after that? Surface s => s
. That's the type of that expression. We still don't know what type does s
represent.
If you want the type to be SurfaceA
you need to change your code so it becomes something like surface :: Surface s => Renderable s -> s
. This way what s
is can be determined, because it is the same s
used in Renderable
.
EDIT: As suggested by @mokus, you could also try the ExistentialTypes
extension. It allows "hiding" away type parameters on the right side of a type declaration.
data Renderable = forall s. Surface s => Renderable
{ surface :: s
, otherStuff :: Int
}
The HaskellWiki page I linked to above even has an example very similar to what you want to do.
EDIT: (By @stusmith) - For the record, I'm including code below which compiles based on these suggestions here. However I've accepted the answer which I think shows a better way of approaching things.
{-# 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With