List of different types?

前端 未结 2 815
小鲜肉
小鲜肉 2020-11-28 07:27
data Plane = Plane { point :: Point, normal :: Vector Double }
data Sphere = Sphere { center :: Point, radius :: Double }

class Shape s where
    intersect :: s -&g         


        
相关标签:
2条回答
  • 2020-11-28 08:01

    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.

    0 讨论(0)
  • 2020-11-28 08:02

    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 讨论(0)
提交回复
热议问题