[prev in list] [next in list] [prev in thread] [next in thread]
List: haskell-beginners
Subject: Re: [Haskell-beginners] Program reliability and multiple data constructors; polymorphism
From: Tim Perry <tim.v2.0 () gmail ! com>
Date: 2012-04-19 16:04:23
Message-ID: CAFVgASWanFt=eZQizLoRRBtcJ_g-AoM+NKhL=yKM=T7BXBaSrw () mail ! gmail ! com
[Download RAW message or body]
[Attachment #2 (multipart/alternative)]
On Wed, Apr 18, 2012 at 8:10 AM, umptious <umptious@gmail.com> wrote:
> One of the programming exercises I keep evolving as I learn Haskell is a
> toy 2D shape editor with four primitives:
>
> data Shape = Circle {origin::Pt2, radius::Float}
> | Square {origin::Pt2, side ::Float}
> | Rect {origin::Pt2, other ::Pt2}
> | Composite {shapes::[Shape]}
> deriving (Show, Read)
>
> The intent is Composites can contain Shapes of any kind, including other
> Composites so that you can apply transformations to a Composite and these
> will be applied to the contained Shapes recursively. So an arm might
> contain a hand which constains a dozen or so Rects. Transform the arm and
> the hand and rects should transform; transform the hand and its rects
> should transform but the not arm. Big deal.
>
> And the above makes that really easy when you know you're talking to a
> Composite. But now I've hit an intellectual stumbling point and the books
> and source I have don't seem to address it: I can apply the destructuring
> command "shapes" defined in the cstr "Composite" to ANY Shape. And if I do
> that to say a circle, BLAM! Or if I apply "radius" to Rect, BLAM! At
> runtime. No type checking support (because yes, they're the same type.)
>
Well, if you have a Shape, you do not know what data type you have and
neither does the compiler. However, you can code a function, say shapeList,
which always gives you a list of Shapes regardless of what type of Shape
gets past in:
shapeList :: Shape -> [Shape]
shapeList (Composite shapes) = shapes
shapeList s = [s]
Lesson: don't use record syntax on a heterogeneous collection. I'm
surprised the compiler doesn't complain when record syntax
isn't guaranteed to succeed.
As a general comment, it looks like you are trying to code C++ or Java
style OO code in Haskell. I can say from experience, it doesn't work well.
Generally, envision your functions to work on a class of abstract data
types (ATDs). Generalize this class of ATDs into a typeclass. Write an
instance of the function to operate on each ADT you want to be a member of
a typeclass. So, if I was going to write some code to handle shapes I might
do it like the following. Be warned, I'm far from a Haskell Guru, but I
think this is a better approach. Hopefully we'll get an improved bit of
code....
import Data.List
data Pt2 = Pt2 { x :: Float , y :: Float } deriving (Show, Read)
data Circle = Circle { originCircle :: Pt2 , radius :: Float } deriving
(Show, Read)
data Square = Square { originSquare ::Pt2 , side :: Float } deriving
(Show, Read)
data Rect = Rect {originRect ::Pt2, other :: Pt2} deriving (Show, Read)
data Composite = Composite { circles :: [Circle]
, squares :: [Square]
, rects :: [Rect]
}
class Shape a where
area :: a -> Float
minx :: a -> Float
miny :: a -> Float
instance Shape Circle where
area (Circle _ r) = r * r * pi
minx (Circle (Pt2 x _) r) = x - r
miny (Circle (Pt2 _ y) r) = y - r
instance Shape Square where
area (Square _ side) = side*side
minx (Square (Pt2 x y) side) = if side < 0
then x + side
else x
miny (Square (Pt2 x y) side) = if side < 0
then y + side
else y
instance Shape Rect where
area (Rect (Pt2 x1 y1) (Pt2 x2 y2)) = abs((x2 - x1) * (y2 - y1))
minx (Rect (Pt2 x1 y1) (Pt2 x2 y2)) = min x1 x2
miny (Rect (Pt2 x1 y1) (Pt2 x2 y2)) = min y1 y2
instance Shape Composite where
area (Composite cs ss rs) = (sum $ map area cs) + (sum $ map area ss) +
(sum $ map area rs)
minx (Composite cs ss rs) = Data.List.minimum(map minx cs ++ map minx ss
++ map minx rs)
miny (Composite cs ss rs) = Data.List.minimum(map miny cs ++ map miny ss
++ map miny rs)
[Attachment #5 (text/html)]
<br><br><div class="gmail_quote">On Wed, Apr 18, 2012 at 8:10 AM, umptious <span \
dir="ltr"><<a href="mailto:umptious@gmail.com">umptious@gmail.com</a>></span> \
wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px \
#ccc solid;padding-left:1ex"> One of the programming exercises I keep evolving as I \
learn Haskell is a toy 2D shape editor with four primitives:<br><br>data Shape = \
Circle {origin::Pt2, radius::Float}<br> | Square \
{origin::Pt2, side ::Float}<br>
| Rect {origin::Pt2, other ::Pt2}<br> \
| Composite {shapes::[Shape]}<br> deriving (Show, \
Read)<br><br>The intent is Composites can contain Shapes of any kind, including \
other Composites so that you can apply transformations to a Composite and these will \
be applied to the contained Shapes recursively. So an arm might contain a hand which \
constains a dozen or so Rects. Transform the arm and the hand and rects should \
transform; transform the hand and its rects should transform but the not arm. Big \
deal.<br>
<br>And the above makes that really easy when you know you're talking to a \
Composite. But now I've hit an intellectual stumbling point and the books and \
source I have don't seem to address it: I can apply the destructuring command \
"shapes" defined in the cstr "Composite" to ANY Shape. And if I \
do that to say a circle, BLAM! Or if I apply "radius" to Rect, BLAM! At \
runtime. No type checking support (because yes, they're the same type.)<br> \
</blockquote><div><br></div><div>Well, if you have a Shape, you do not know what data \
type you have and neither does the compiler. However, you can code a function, say \
shapeList, which always gives you a list of Shapes regardless of what type of Shape \
gets past in:</div> <div><br></div><div><div>shapeList :: Shape -> \
[Shape]</div><div>shapeList (Composite shapes) = shapes</div><div>shapeList s = \
[s]</div></div><div><br></div><div>Lesson: don't use record syntax on a \
heterogeneous collection. I'm surprised the compiler doesn't complain when \
record syntax isn't guaranteed to succeed.</div> <div><br></div><div>As a general \
comment, it looks like you are trying to code C++ or Java style OO code in Haskell. I \
can say from experience, it doesn't work well. \
</div><div><br></div><div>Generally, envision your functions to work on a class of \
abstract data types (ATDs). Generalize this class of ATDs into a typeclass. Write an \
instance of the function to operate on each ADT you want to be a member of a \
typeclass. So, if I was going to write some code to handle shapes I might do it like \
the following. Be warned, I'm far from a Haskell Guru, but I think this is a \
better approach. Hopefully we'll get an improved bit of code....</div> \
<div><br></div><div><div>import Data.List</div><div><br></div><div>data Pt2 = Pt2 { x \
:: Float , y :: Float } deriving (Show, Read)</div><div><br></div><div>data Circle = \
Circle { originCircle :: Pt2 , radius :: Float } deriving (Show, Read)</div> \
<div><br></div><div>data Square = Square { originSquare ::Pt2 , side :: Float } \
deriving (Show, Read)</div><div><br></div><div>data Rect = Rect {originRect \
::Pt2, other :: Pt2} deriving (Show, Read)</div><div><br> </div><div>data Composite = \
Composite { circles :: [Circle]</div><div> , squares :: \
[Square]</div><div> , rects :: [Rect]</div><div> \
}</div><div> <br></div><div>class Shape a where</div><div> area :: a -> \
Float</div><div> minx :: a -> Float</div><div> miny :: a -> \
Float</div><div><br></div><div>instance Shape Circle where</div><div> area (Circle \
_ r) = r * r * pi</div> <div> minx (Circle (Pt2 x _) r) = x - r</div><div> miny \
(Circle (Pt2 _ y) r) = y - r</div><div><br></div><div>instance Shape Square \
where</div><div> area (Square _ side) = side*side</div><div> minx (Square (Pt2 x \
y) side) = if side < 0</div> <div> then x + \
side</div><div> else x</div><div> miny (Square \
(Pt2 x y) side) = if side < 0</div><div> then \
y + side</div> <div> else \
y</div><div><br></div><div>instance Shape Rect where</div><div> area (Rect (Pt2 x1 \
y1) (Pt2 x2 y2)) = abs((x2 - x1) * (y2 - y1))</div><div> minx (Rect (Pt2 x1 y1) \
(Pt2 x2 y2)) = min x1 x2</div> <div> miny (Rect (Pt2 x1 y1) (Pt2 x2 y2)) = min y1 \
y2</div><div><br></div><div>instance Shape Composite where</div><div> area \
(Composite cs ss rs) = (sum $ map area cs) + (sum $ map area ss) + (sum $ map area \
rs)</div> <div> minx (Composite cs ss rs) = Data.List.minimum(map minx cs ++ map \
minx ss ++ map minx rs)</div><div> miny (Composite cs ss rs) = \
Data.List.minimum(map miny cs ++ map miny ss ++ map miny \
rs)</div></div><div><br></div> <div><br></div><div><br></div></div>
_______________________________________________
Beginners mailing list
Beginners@haskell.org
http://www.haskell.org/mailman/listinfo/beginners
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic