Actually, after thinking it back, I found out one other method. The key idea is to split what is common to every shape with what is not:<br><br><span style="font-family: courier new,monospace;">data Circle = Circle { cr :: Double }<br>
data Rectangle = Rectangle { rw, rh :: Double }<br><br>class Shapeful s where<br> name :: s -> String<br> fields :: s -> String<br><br>instance Shapeful Circle where<br> name _ = "Circle"<br> fields (Circle cr) = show cr<br>
<br>instance Shapeful Rectangle where<br> name _ = "Rectangle"<br> fields (Rectangle rw rh) = show rw ++ ", " ++ show rh<br><br>data Shape = forall s. (Shapeful s)<br> => Shape { sx, sy :: Double,<br>
inner :: a }<br><br>drawShape :: Shape -> String<br>drawShape (Shape sx sy inner) = name inner ++ " (" ++ show sx ++ ", " ++<br> show sy ++ ", " ++ fields inner ++ ")"<br>
</span><br><br><font face="courier new,monospace">list :: [Shape]<br>list = [Shape 10 10 $ Circle 5, Shape 40 40 $ Rectangle 12 10]<br><br><br></font>Since you loose the exact type of what contains Shape, your class "Shapeful" must provide all the necessary information (but that is kind of usual in Haskell).<br>
The advantage here is that you generalize the position (sx and sy fields) which are no longer duplicated within Rectange and Circle.<br><br><br>2011/3/29 Yves Parès <span dir="ltr"><<a href="mailto:limestrael@gmail.com">limestrael@gmail.com</a>></span><br>
<div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">Actually, Tako:<br><br><div><div><font face="'courier new', monospace"><div class="im">
data Shape = forall a. Drawable a => Shape a<br><br></div>Can also be done with GADTs:<br><br><font face="courier new,monospace"> data Shape where<br>
Shape :: Drawable a => a -> Shape<br></font></font></div></div><br>If wouldn't know if one approach is preferable to the other or if is just a matter of taste.<br><br>Your problem, Tad, is kind of common. I ran against it several times. I know of two ways to solve it :<br>
<br>- "The open way" (this is your method, with a class ShapeC and datatype ShapeD which wraps instances of ShapeC)<br><br>- "The closed way", which can be broken in two alternatives:<br><br>* Using a plain Haskell98 ADT:<br>
<font face="courier new,monospace"> data Shape = Circle .... | Rectangle ....<br> draw :: Shape -> String<br> draw (Circle ...) = ...<br> draw (Rectangle ...) = ...<br><br><font face="arial,helvetica,sans-serif">Flexible and simple, but not safe, since you have no way to type-diferenciate Circles from Rectangles.<br>
<br>* Using a GADT and empty data declarations:<br><font face="courier new,monospace"> data Circle<br> data Rectangle<br> data Shape a where<br> Circle :: Double -> Double -> Double -> Shape Circle<br>
Rectangle :: Double -> Double -> Double -> Double -> Shape Rectangle<br><br><font face="arial,helvetica,sans-serif">And then you can both use "Shape a" or "Shape Circle/Shape Rectangle", which enables you either to make lists of Shapes or to specifically use Circles or Rectangles.<br>
<br>The drawback of it is that since you have a closed type (the GADT Shape), you cannot add a new shape without altering it.<br></font></font><br></font></font><div><div></div><div class="h5"><br><div class="gmail_quote">
2011/3/29 Steffen Schuldenzucker <span dir="ltr"><<a href="mailto:sschuldenzucker@uni-bonn.de" target="_blank">sschuldenzucker@uni-bonn.de</a>></span><br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><br>
Tad,<br>
<br>
It doesn't look bad, but depending on what you want to do with the<br>
[ShapeD] aftewards you might not need this level of generality.<br>
<br>
Remember that the content of a ShapeD has type (forall a. ShapeC a =><br>
a), so all you can do with it is call class methods from ShapeC. So if<br>
all you do is construct some ShapeD and pass that around, the following<br>
solution is equivalent:<br>
<br>
data Shape = Shape {<br>
draw :: String<br>
copyTo :: Double -> Double -> Shape<br>
-- ^ We loose some information here. The original method of ShapeC<br>
-- stated that copyTo of a Rectangle will be a rectangle again<br>
-- etc. Feel free to add a proxy type parameter to Shape if this<br>
-- information is necessary.<br>
}<br>
<br>
circle :: Double -> Double -> Double -> Shape<br>
circle x y r = Shape dc $ \x y -> circle x y r<br>
where dc = "Circ (" ++ show x ++ ", " ++ show y ++ ") -- "" ++ show r<br>
<br>
rectangle :: Double -> Double -> Double -> Double -> Shape<br>
rectangle x y w h = ... (analogous)<br>
<br>
shapes = [rectangle 1 2 3 4, circle 4 3 2, circle 1 1 1]<br><font color="#888888">
<br>
-- Steffen</font><div><div></div><div><br>
<br>
On 03/29/2011 07:49 AM, Tad Doxsee wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
I've been trying to learn Haskell for a while now, and recently<br>
wanted to do something that's very common in the object oriented<br>
world, subtype polymorphism with a heterogeneous collection. It took<br>
me a while, but I found a solution that meets my needs. It's a<br>
combination of solutions that I saw on the web, but I've never seen<br>
it presented in a way that combines both in a short note. (I'm sure<br>
it's out there somewhere, but it's off the beaten path that I've been<br>
struggling along.) The related solutions are<br>
<br>
1. section 3.6 of <a href="http://homepages.cwi.nl/%7Eralf/OOHaskell/paper.pdf" target="_blank">http://homepages.cwi.nl/~ralf/OOHaskell/paper.pdf</a><br>
<br>
2. The GADT comment at the end of section 4 of<br>
<a href="http://www.haskell.org/haskellwiki/Heterogenous_collections" target="_blank">http://www.haskell.org/haskellwiki/Heterogenous_collections</a><br>
<br>
I'm looking for comments on the practicality of the solution, and<br>
references to better explanations of, extensions to, or simpler<br>
alternatives for what I'm trying to achieve.<br>
<br>
Using the standard example, here's the code:<br>
<br>
<br>
data Rectangle = Rectangle { rx, ry, rw, rh :: Double } deriving (Eq,<br>
Show)<br>
<br>
drawRect :: Rectangle -> String drawRect r = "Rect (" ++ show (rx r)<br>
++ ", " ++ show (ry r) ++ ") -- " ++ show (rw r) ++ " x " ++ show<br>
(rh r)<br>
<br>
<br>
data Circle = Circle {cx, cy, cr :: Double} deriving (Eq, Show)<br>
<br>
drawCirc :: Circle -> String drawCirc c = "Circ (" ++ show (cx c) ++<br>
", " ++ show (cy c)++ ") -- " ++ show (cr c)<br>
<br>
r1 = Rectangle 0 0 3 2 r2 = Rectangle 1 1 4 5 c1 = Circle 0 0 5 c2 =<br>
Circle 2 0 7<br>
<br>
<br>
rs = [r1, r2] cs = [c1, c2]<br>
<br>
rDrawing = map drawRect rs cDrawing = map drawCirc cs<br>
<br>
-- shapes = rs ++ cs<br>
<br>
Of course, the last line won't compile because the standard Haskell<br>
list may contain only homogeneous types. What I wanted to do is<br>
create a list of circles and rectangles, put them in a list, and draw<br>
them. It was easy for me to find on the web and in books how to do<br>
that if I controlled all of the code. What wasn't immediately obvious<br>
to me was how to do that in a library that could be extended by<br>
others. The references noted previously suggest this solution:<br>
<br>
<br>
class ShapeC s where draw :: s -> String copyTo :: s -> Double -><br>
Double -> s<br>
<br>
-- needs {-# LANGUAGE GADTs #-} data ShapeD where ShapeD :: ShapeC s<br>
=> s -> ShapeD<br>
<br>
instance ShapeC ShapeD where draw (ShapeD s) = draw s copyTo (ShapeD<br>
s) x y = ShapeD (copyTo s x y)<br>
<br>
mkShape :: ShapeC s => s -> ShapeD mkShape s = ShapeD s<br>
<br>
<br>
<br>
instance ShapeC Rectangle where draw = drawRect copyTo (Rectangle _ _<br>
rw rh) x y = Rectangle x y rw rh<br>
<br>
instance ShapeC Circle where draw = drawCirc copyTo (Circle _ _ r) x<br>
y = Circle x y r<br>
<br>
<br>
r1s = ShapeD r1 r2s = ShapeD r2 c1s = ShapeD c1 c2s = ShapeD c2<br>
<br>
shapes1 = [r1s, r2s, c1s, c2s] drawing1 = map draw shapes1<br>
<br>
shapes2 = map mkShape rs ++ map mkShape cs drawing2 = map draw<br>
shapes2<br>
<br>
-- copy the shapes to the origin then draw them shapes3 = map (\s -><br>
copyTo s 0 0) shapes2 drawing3 = map draw shapes3<br>
<br>
<br>
Another user could create a list of shapes that included triangles by<br>
creating a ShapeC instance for his triangle and using mkShape to add<br>
it to a list of ShapeDs.<br>
<br>
Is the above the standard method in Haskell for creating an<br>
extensible heterogeneous list of "objects" that share a common<br>
interface? Are there better approaches? (I ran into a possible<br>
limitation to this approach that I plan to ask about later if I can't<br>
figure it out myself.)<br>
<br>
- Tad<br>
<br>
_______________________________________________ Haskell-Cafe mailing<br>
list <a href="mailto:Haskell-Cafe@haskell.org" target="_blank">Haskell-Cafe@haskell.org</a><br>
<a href="http://www.haskell.org/mailman/listinfo/haskell-cafe" target="_blank">http://www.haskell.org/mailman/listinfo/haskell-cafe</a><br>
</blockquote>
<br>
<br>
_______________________________________________<br>
Haskell-Cafe mailing list<br>
<a href="mailto:Haskell-Cafe@haskell.org" target="_blank">Haskell-Cafe@haskell.org</a><br>
<a href="http://www.haskell.org/mailman/listinfo/haskell-cafe" target="_blank">http://www.haskell.org/mailman/listinfo/haskell-cafe</a><br>
</div></div></blockquote></div><br>
</div></div></blockquote></div><br>