2011-10-16 37 views
37
data Plane = Plane { point :: Point, normal :: Vector Double } 
data Sphere = Sphere { center :: Point, radius :: Double } 

class Shape s where 
    intersect :: s -> Ray -> Maybe Point 
    surfaceNormal :: s -> Point -> Vector Double 

También hice instancias de Plane y Sphere de Shape.¿Lista de diferentes tipos?

Estoy tratando de almacenar esferas y planos en la misma lista, pero no funciona. Entiendo que no debería funcionar porque Sphere y Plane son dos tipos diferentes, pero ambas son instancias de Shape, entonces ¿no debería funcionar? ¿Cómo puedo almacenar formas y planos en una lista?

shapes :: (Shape t) => [t] 
shapes = [ Sphere { center = Point [0, 0, 0], radius = 2.0 }, 
     Plane { point = Point [1, 2, 1], normal = 3 |> [0.5, 0.6, 0.2] } 
     ] 
+1

http://www.haskell.org/haskellwiki/Heterogenous_collections –

+2

Conocía colecciones heterogéneas, pero es algo que quería evitar. – Arlen

Respuesta

47

Este problema representa un punto de inflexión entre el pensamiento orientado a objetos y funcional. A veces, incluso los Haskellers sofisticados siguen en esta transición mental, y sus diseños a menudo entran en el patrón existential typeclass, mencionado en la respuesta de Thomas.

Una solución funcional a este problema consiste en cosificar la clase de tipos en un tipo de datos (por lo general una vez hecho esto, la necesidad de la clase de tipos se desvanece):

data Shape = Shape { 
    intersect :: Ray -> Maybe Point, 
    surfaceNormal :: Point -> Vector Double 
} 

Ahora se puede construir fácilmente una lista de Shape s , porque es un tipo monomórfico. Debido a que Haskell no admite downcasting, no se pierde información al eliminar la distinción de representación entre Plane sy Sphere s. Los tipos de datos específicos se convierten en funciones que construyen Shape s:

plane :: Point -> Vector Double -> Shape 
sphere :: Point -> Double -> Shape 

Si no puede capturar todo lo que necesita saber acerca de una forma en el tipo de datos Shape, puede enumerar los casos con un tipo de datos algebraica, como sugiere Thomas . Pero recomendaría contra eso si es posible; en su lugar, intente encontrar las características esenciales de la forma que necesita en lugar de solo enumerar ejemplos.

+4

Esto muy bien puesto que no deberíamos pensar en clases de tipos como herencia OO o interfaces y pensar en ellos subtipo. Son solo un conjunto de funciones que un tipo puede admitir – Ankur

+0

¿Qué pasaría si necesitaras tipear si algo era un avión o una esfera? Entonces, ¿sugerirías el estilo ADT? – CMCDragonkai

+0

@CMCDragonkai, probablemente haga una 'esfera de datos' que tenga las propiedades necesarias de una esfera,' plano de datos' que tenga las propiedades necesarias de un plano, y luego funciones de inyección como 'toShape :: Sphere -> Shape' . Tal vez ponerlo en una 'clase ToShape' (como una idea de último momento) – luqui

24

Usted está buscando una lista heterogénea, que la mayoría no lo hacen Haskellers gusta particularmente a pesar de que ellos mismos han pedido esta misma pregunta cuando primero aprender Haskell.

escribe:

shapes :: (Shape t) => [t] 

Esto dice la lista tiene el tipo t, todos los cuales son los mismos y pasar a ser una forma (la misma forma!). En otras palabras, no, no debería funcionar como lo tienes.

Dos formas comunes para manejarlo (una forma Haskell 98 primero, y luego una manera más elegante que yo no recomiendo segundo) son:

Utilice un nuevo tipo de unión estáticamente los subtipos de interés:

data Foo = F deriving Show 
data Bar = B deriving Show 

data Contain = CFoo Foo | CBar Bar deriving Show 
stuffExplicit :: [Contain] 
stuffExplicit = [CFoo F, CBar B] 

main = print stuffExplicit 

Esto es bueno ya que es sencillo y no pierde ninguna información sobre lo que está en la lista. Puede determinar que el primer elemento es un Foo y el segundo elemento es un Bar. El inconveniente, como probablemente ya se haya dado cuenta, es que debe agregar explícitamente cada tipo de componente creando un nuevo constructor de tipo Contain. Si esto no es deseable, sigue leyendo.

Usar tipos existenciales: Otra solución implica perder información sobre los elementos; solo conserva, por ejemplo, el conocimiento de que los elementos están en una clase particular. Como resultado, solo puede usar operaciones de esa clase en los elementos de la lista. Por ejemplo, el siguiente sólo se recordarán los elementos son de la clase Show, por lo que la única cosa que puede hacer para los elementos es utilizar las funciones que son polimórficos en Show:

data AnyShow = forall s. Show s => AS s 

showIt (AS s) = show s 

stuffAnyShow :: [AnyShow] 
stuffAnyShow = [AS F, AS B] 

main = print (map showIt stuffAnyShow) 

Esto requiere algunas extensiones al lenguaje Haskell , es decir, ExplicitForAll y ExistentialQuantification. Tuvimos que definir showIt explícitamente (usando la coincidencia de patrones para deconstruir el tipo AnyShow) porque no puede usar nombres de campo para tipos de datos que usan cuantificación existencial.

Hay más soluciones (con suerte, otra respuesta usará Data.Dynamic - si nadie lo hace y usted está interesado, entonces lea sobre él y no dude en publicar cualquier pregunta que la lectura genere).

+0

El primer método es lo primero que intenté, y no funcionó bien. ¡No estoy seguro si me gusta el segundo método! También me gustaría evitar el uso de extensiones. – Arlen

+0

No me gusta, parece más sintaxis foránea cuando los conceptos ya son (probablemente) lo suficientemente extraños para el lector. Quizás en otra respuesta? –

Cuestiones relacionadas