Jump to content
Main menu
Main menu
move to sidebar
hide
Navigation
Haskell
Wiki community
Recent changes
Random page
HaskellWiki
Search
Search
Create account
Log in
Personal tools
Create account
Log in
Pages for logged out editors
learn more
Contributions
Talk
Editing
Ru/IO Inside
(section)
Page
Discussion
English
Read
Edit
View history
Tools
Tools
move to sidebar
hide
Actions
Read
Edit
View history
General
What links here
Related changes
Special pages
Page information
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
=== Example: emulating OOP with record types === Let's implement the classical OOP example: drawing figures. There are figures of different types: circles, rectangles and so on. The task is to create a heterogeneous list of figures. All figures in this list should support the same set of operations: draw, move and so on. We will represent these operations as IO procedures. Instead of a "class" let's define a structure containing implementations of all the procedures required: <haskell> data Figure = Figure { draw :: IO (), move :: Displacement -> IO () } type Displacement = (Int, Int) -- horizontal and vertical displacement in points </haskell> The constructor of each figure's type should just return a Figure record: <haskell> circle :: Point -> Radius -> IO Figure rectangle :: Point -> Point -> IO Figure type Point = (Int, Int) -- point coordinates type Radius = Int -- circle radius in points </haskell> We will "draw" figures by just printing their current parameters. Let's start with a simplified implementation of the 'circle' and 'rectangle' constructors, without actual 'move' support: <haskell> circle center radius = do let description = " Circle at "++show center++" with radius "++show radius return $ Figure { draw = putStrLn description } rectangle from to = do let description = " Rectangle "++show from++"-"++show to) return $ Figure { draw = putStrLn description } </haskell> As you see, each constructor just returns a fixed 'draw' procedure that prints parameters with which the concrete figure was created. Let's test it: <haskell> drawAll :: [Figure] -> IO () drawAll figures = do putStrLn "Drawing figures:" mapM_ draw figures main = do figures <- sequence [circle (10,10) 5, circle (20,20) 3, rectangle (10,10) (20,20), rectangle (15,15) (40,40)] drawAll figures </haskell> Now let's define "full-featured" figures that can actually be moved around. In order to achieve this, we should provide each figure with a mutable variable that holds each figure's current screen location. The type of this variable will be "IORef Point". This variable should be created in the figure constructor and manipulated in IO procedures (closures) enclosed in the Figure record: <haskell> circle center radius = do centerVar <- newIORef center let drawF = do center <- readIORef centerVar putStrLn (" Circle at "++show center ++" with radius "++show radius) let moveF (addX,addY) = do (x,y) <- readIORef centerVar writeIORef centerVar (x+addX, y+addY) return $ Figure { draw=drawF, move=moveF } rectangle from to = do fromVar <- newIORef from toVar <- newIORef to let drawF = do from <- readIORef fromVar to <- readIORef toVar putStrLn (" Rectangle "++show from++"-"++show to) let moveF (addX,addY) = do (fromX,fromY) <- readIORef fromVar (toX,toY) <- readIORef toVar writeIORef fromVar (fromX+addX, fromY+addY) writeIORef toVar (toX+addX, toY+addY) return $ Figure { draw=drawF, move=moveF } </haskell> Now we can test the code which moves figures around: <haskell> main = do figures <- sequence [circle (10,10) 5, rectangle (10,10) (20,20)] drawAll figures mapM_ (\fig -> move fig (10,10)) figures drawAll figures </haskell> It's important to realize that we are not limited to including only IO actions in a record that's intended to simulate a C++/Java-style interface. The record can also include values, IORefs, pure functions - in short, any type of data. For example, we can easily add to the Figure interface fields for area and origin: <haskell> data Figure = Figure { draw :: IO (), move :: Displacement -> IO (), area :: Double, origin :: IORef Point } </haskell>
Summary:
Please note that all contributions to HaskellWiki are considered to be released under simple permissive license (see
HaskellWiki:Copyrights
for details). If you don't want your writing to be edited mercilessly and redistributed at will, then don't submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
DO NOT SUBMIT COPYRIGHTED WORK WITHOUT PERMISSION!
Cancel
Editing help
(opens in new window)
Toggle limited content width