
I'm bashing together a simple command line geometry thing that let's you create shapes, move them around them, delete and edit them, test for intersections, etc. The motivation is to get a grip on Haskell IO and how it interacts with user defined datatypes. I'm using GHC. The problem, which I've tried to show as simply as possible in the abstracted code below, is that something seems to go horribly wrong with un-boxing (is this the correct terminology?) user defined type X's from IO(X)'s. In the example below: - enterCircle makes a Shape using the Circle cstr and boxes it in a return. And the type checker is happy with the function. - But when the Shape is un-boxed in mainLoop then Haskell's type system (or GHC's?) has lost track of its Shapeness. Instead it sees it as a Pt2->Float. Which makes some sort of sense, but is useless (or at least needs ugly hacks to overcome) if you want to put your Circle in a List of Shapes. - Even worse then the loss of Shapeness is that Haskell is letting me type enterCircle as returning an IO(Shape) - indeed that's what :t gives me in GCHI - but then disagrees with this type in mainLoop! This isn't what I was expecting from the vaunted Haskell type system; can someone explain this contradiction? So. Does return fail to wrap up type info about user defined types in general? Is this correct Haskell behaviour? Or have I done something wrong? If I haven't done something wrong, is there a standard idiom for overcoming this? I suppose I could use a show to convert the Circle to a string and then a read to convert it back in mainLoop's where, but this will mean uglier branching than I planned (the idea was to have a Map of fns that return shapes and just one branch in mainLoop to handle every kind of shape.) And it seems to make something of a nonsense of Haskell's type system as the entire Shape hierarchy will be reduced to IO(String) for these operations. import Data.Map as Map data Pt2 = Pt2 {x::Float, y::Float} deriving (Show, Read) data Shape = Circle {origin::Pt2, radius::Float} | Square {origin::Pt2, side ::Float} | Rect {origin::Pt2, other ::Pt2} deriving (Show, Read) enterCircle :: IO(Shape) enterCircle = return (r) -- needs to rtn io (shape) because will take input here where r = Circle{origin=Pt2{x=1,y=2}, radius=4} mainLoop :: [Shape] -> IO (String) mainLoop shapes = do putStrLn "?" cmd <- getLine if cmd=="q" then return ("done") else do y <- enterCircle mainLoop shapes' where shapes' = y : shapes main = mainLoop []

It has to do with the scope of "where" in your mainloop. See comments inline. On Thu, Apr 12, 2012 at 04:08:10PM +0100, umptious wrote:
import Data.Map as Map
data Pt2 = Pt2 {x::Float, y::Float} deriving (Show, Read)
data Shape = Circle {origin::Pt2, radius::Float} | Square {origin::Pt2, side ::Float} | Rect {origin::Pt2, other ::Pt2} deriving (Show, Read)
enterCircle :: IO(Shape) enterCircle = return (r) -- needs to rtn io (shape) because will take input here where r = Circle{origin=Pt2{x=1,y=2}, radius=4}
mainLoop :: [Shape] -> IO (String) mainLoop shapes = do putStrLn "?" cmd <- getLine if cmd=="q" then return ("done") else do y <- enterCircle
This "y" shades the Pt2 accessor "y".
mainLoop shapes' where shapes' = y : shapes
This where thinks that "y" is actually the Pt2 accessor "y". An easy fix would be to rewrite this line as:
mainLoop $ y: shapes
Or mess with tabs to get the right scope for "where"... hth, L. -- Lorenzo Bolla http://lbolla.info
participants (2)
-
Lorenzo Bolla
-
umptious