Tillmann, Thank you for your detailed reply. It was a real eye opener. I hadn't seen anything like that before.
It seems that your ShapeClass is very similar to, and plays the same role as, the Class ShapeC from my example. I wonder if that was how haskellers implemented shared functions before type classes were invented. One advantage that I see in your approach is that you only need one function, "call", that can be used to dereference any method in ShapeClass. In my example, I needed to define ShapeC ShapeD instances for both draw and copyTo. I suppose one nice aspect of using a type class is that the copyTo method can be applied to a Rectangle to give another Rectangle, or to a Circle, or to a generic ShapeD to give a generic ShapeD. The copyTo function in your example produces a generic shape. Thanks again for your help. Tad On Wed, Mar 30, 2011 at 2:57 AM, Tillmann Rendel <ren...@informatik.uni-marburg.de> wrote: > Hi, > > Steffen Schuldenzucker wrote: >> >> data Shape = Shape { >> draw :: String >> copyTo :: Double -> Double -> Shape >> } > > Tad Doxsee wrote: >> >> Suppose that the shape class has 100 methods and that 1000 fully >> evaluated shapes are placed in a list. > > The above solution would store the full method table with each object. > Instead, we could share the method tables between objects. An object would > then uniformly contain two pointers: One pointer to the method table, and > one poiner to the internal state. > > {-# LANGUAGE ExistentialQuantification, Rank2Types #-} > > data Object methods = forall state . Object { > methods :: methods state, > state :: state > } > > Calling a method requires dereferencing both pointers. > > call :: (forall state . methods state -> state -> a) -> > (Object methods -> a) > call method (Object methods state) = method methods state > > > Using this machinery, we can encode the interface for shapes. > > data ShapeClass state = ShapeClass { > draw :: state -> String, > copyTo :: state -> Double -> Double -> Shape > } > > type Shape = Object ShapeClass > > > An implementation of the interface consists of three parts: A datatype or > the internal state, a method table, and a constructor. > > data RectangleState = RectangleState {rx, ry, rw, rh :: Double} > > rectangleClass :: ShapeClass RectangleState > rectangleClass = ShapeClass { > draw = \r -> > "Rect (" ++ show (rx r) ++ ", " ++ show (ry r) ++ ") -- " > ++ show (rw r) ++ " x " ++ show (rh r), > copyTo = \r x y -> rectangle x y (rw r) (rh r) > } > > rectangle :: Double -> Double -> Double -> Double -> Shape > rectangle x y w h > = Object rectangleClass (RectangleState x y w h) > > > The analogous code for circles. > > data CircleState = CircleState {cx, cy, cr :: Double} > > circleClass :: ShapeClass CircleState > circleClass = ShapeClass { > draw = \c -> > "Circ (" ++ show (cx c) ++ ", " ++ show (cy c)++ ") -- " > ++ show (cr c), > copyTo = \c x y -> circle x y (cr c) > } > > circle :: Double -> Double -> Double -> Shape > circle x y r > = Object circleClass (CircleState x y r) > > > Rectangles and circles can be stored together in usual Haskell lists, > because they are not statically distinguished at all. > > -- test > r1 = rectangle 0 0 3 2 > r2 = rectangle 1 1 4 5 > c1 = circle 0 0 5 > c2 = circle 2 0 7 > > shapes = [r1, r2, c1, c2] > > main = mapM_ (putStrLn . call draw) shapes > > > While this does not nearly implement all of OO (no inheritance, no late > binding, ...), it might meet your requirements. > > Tillmann > > PS. You could probably use a type class instead of the algebraic data type > ShapeClass, but I don't see a benefit. Indeed, I like how the code above is > very explicit about what is stored where. For example, in the code of the > rectangle function, it is clearly visible that all shapes created with that > function will share a method table. > _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe