开发者

List of different types?

开发者 https://www.devze.com 2023-04-13 04:39 出处:网络
data Plane = Plane { point :: Point, normal :: Vector Double } data Sphere = Sphere { center :: Point, radius :: Double }
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

I have also made both Plane and Sphere instances of Shape.

I'm trying to store spheres and planes in the same list, but it doesn't work. I understand that it shouldn't work because Sphere and Plane are two different types, but they are both ins开发者_StackOverflow社区tances of Shape, so shouldn't it work? How would I store shapes and planes in a list?

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] }
         ]


This problem represents a turning point between object-oriented and functional thinking. Sometimes even sophisticated Haskellers are still in this mental transition, and their designs often fall into the existential typeclass pattern, mentioned in Thomas's answer.

A functional solution to this problem involves reifying the typeclass into a data type (usually once this is done, the need for the typeclass vanishes):

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

Now you can easily construct a list of Shapes, because it is a monomorphic type. Because Haskell does not support downcasting, no information is lost by removing the representational distinction between Planes and Spheres. The specific data types become functions that construct Shapes:

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

If you cannot capture everything you need to know about a shape in the Shape data type, you can enumerate the cases with an algebraic data type, as Thomas suggested. But I would recommend against that if possible; instead, try to find the essential characteristics of a shape that you need rather than just listing off examples.


You are looking for a heterogeneous list, which most Haskellers don't particularly like even though they've asked themselves this same question when first learning Haskell.

You write:

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

This says the list has type t, all of which are the same and happen to be a Shape (the same shape!). In other words - no, it shouldn't work how you have it.

Two common ways to handle it (a Haskell 98 way first, then a fancier way that I don't recommend second) are:

Use a new type to statically union the subtypes of interest:

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

This is nice seeing as it's straight forward and you don't lose any information about what is contained in the list. You can determine the first element is a Foo and the second element is a Bar. The drawback, as you probably already realize, is that you must explicitly add each component type by making a new Contain type constructor. If this is undesirable then keep reading.

Use Existential Types: Another solution involves losing information about the elements - you just retain, say, knowledge that the elements are in a particular class. As a result you can only use operations from that class on the list elements. For example, the below will only remember the elements are of the Show class, so the only thing you can do to the elements is use functions that are polymorphic in 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)

This requires some extensions to the Haskell language, namely ExplicitForAll and ExistentialQuantification. We had to define showIt explicitly (using pattern matching to deconstruct the AnyShow type) because you can't use field names for data types that use existential quantification.

There are more solutions (hopefully another answer will use Data.Dynamic - if no one does and you are interested then read up on it and feel free to post any questions that reading generates).

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号