data Plane = Plane { point :: Point, normal :: Vector Double }
data Sphere = Sphere { center :: Point, radius :: Double }
class Shape s where
intersect :: s -&g
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 Shape
s, because it is a monomorphic type. Because Haskell does not support downcasting, no information is lost by removing the representational distinction between Plane
s and Sphere
s. The specific data types become functions that construct Shape
s:
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).