2011-02-15 8 views
6

Estoy tratando de escribir un rastreador de rayos simple en Haskell. Quería definir una clase de tipos que representan los diferentes tipos de superficies disponibles, con una función para determinar donde un rayo de ellos se cruza:Variable de tipo ambiguo 'blah' en la restricción ... ¿cómo solucionarlo?

{-# 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 

Sin embargo, esto me da el error:

Ambiguous type variable 's' in the constraint: 
    'Surface' 
    arising from a use of 'surface' 

(El real el código es más complejo, pero he intentado destilarlo en algo más simple, manteniendo la esencia de lo que estoy tratando de lograr).

¿Cómo puedo solucionar esto? O, como alternativa, dado que procedo de un entorno OO estándar, ¿qué estoy haciendo fundamentalmente mal?

Respuesta

10

Por favor, no use tipos existenciales para esto! Podrías, pero no tendría sentido.

Desde un punto de vista funcional, puede eliminar por completo esta noción de clase de clase de Surface.Un Surface es algo que asigna un Ray a una lista de Vectores, ¿no? Por lo tanto:

type Surface = Ray -> [Vector] 

data Renderable = Renderable 
{ surface :: Surface 
, otherStuff :: Int 
} 

Ahora bien, si usted realmente desea, puede tener una clase de tipos ToSurface esencialmente como usted dio:

class ToSurface a where 
    toSurface :: a -> Surface 

Pero eso es sólo para la comodidad y el polimorfismo ad hoc. Nada en su modelo lo requiere.

En general, hay muy pocos casos de uso para existenciales, pero al menos el 90% del tiempo puede sustituir un existencial con las funciones que representa y obtener algo más limpio y fácil de razonar.

Además, a pesar de que puede ser un poco demasiado para que usted tome, y los problemas no son exactamente iguales, es posible encontrar útiles algunos de los escritos de Conal en el diseño denotacional: http://conal.net/blog/posts/thoughts-on-semantics-for-3d-graphics/

3

En su función getRenderableIntersections llama al surface. No hay forma de que el intérprete descubra qué instancia de la clase Surface desea usar. Si tiene dos instancias: tales

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

¿Cómo puede el intérprete determinar el tipo de surface?

La forma en que definió Renderable significa que hay una función surface :: Surface s => Renderable -> s.

intente crear una instancia Surface SurfaceA y pidiendo la consulta siguiente tipo (dado un constructor sencilla SurfaceA):

> :t surface (Renderable SurfaceA 0) -- What's the type of the expression? 

Entonces, ¿qué tipo es esta expresión? Apuesto a que estás esperando SurfaceA. Incorrecto. Tome el tipo de surface. Toma un argumento Renderable y le estamos pasando un argumento Renderable. ¿Qué queda después de eso? Surface s => s. Ese es el tipo de esa expresión. Todavía no sabemos qué tipo representa s.

Si desea que el tipo sea SurfaceA, necesita cambiar su código para que se convierta en algo así como surface :: Surface s => Renderable s -> s. De esta manera, se puede determinar qué es s, porque es el mismo s usado en Renderable.

EDITAR: Según lo sugerido por @mokus, también puede probar la extensión ExistentialTypes. Permite "ocultar" los parámetros de tipo en el lado derecho de una declaración de tipo.

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

La página HaskellWiki he vinculado anteriormente incluso tiene an example muy similar a lo que quiere hacer.

EDIT: (Por @stusmith) - código Para que conste, estoy incluyendo debajo del cual compila basa en estas sugerencias aquí. Sin embargo, he aceptado la respuesta, que creo que muestra una mejor manera de abordar las cosas.

{-# 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 
+0

... pero dado que todas las superficies implementan el método de 'intersecciones', ¿por qué no es suficiente? Obviamente, con mi experiencia, veo 'Surface' como una clase abstracta e 'intersections' como un método virtual. – stusmith

+0

@stusmith: ver mi actualización. No puede tener parámetros de tipo abierto en medio de expresiones. O bien toda la expresión es paramétrica, o todos los tipos pueden determinarse estáticamente. (Tal vez hay una extensión GHC o algo que permite eso, pero no lo sé) –

+0

¿Significa esto que Haskell no tiene el equivalente de funciones "virtuales"? Quería tener una lista de Renderables, y poder trabajar en ellos como tipos abstractos. Por lo tanto, ¿necesito tener '[Renderable s]' en su lugar? ¿Y puede esa lista contener instancias heterogéneas de 'Renderable' con diferentes instancias' Surface'? – stusmith

Cuestiones relacionadas