Baby Steps In Haskell

August 30th, 2009


closeThis post was published 2 years 5 months 9 days ago and as such probably does not reflect my current opinions, knowledge or ability.

Alright! So today I progressed a little further with my endeavours to learn Haskell, and I finally feel like things are coming together.

Previously, I’d worked my way through several tutorials. While I had remembered a lot of the small things they said, learned the syntax, written the couple of lines of code that were posed as example exercises, the moment I tried to take on any sizeable problem my brain just reverted back to splitting everything into objects.

(If you’re using Internet Explorer, you might need an SVG viewer to see the image above)

Last night I found this tutorial, which works through building a piece of software to create a park map of sorts as an SVG. I wouldn’t necessarily recommend it as the only or even first thing you read – there were areas where I had to refer to other guides, and felt I was spending a lot of time just figuring out why we were trying to achieve certain things rather than focussing on the Haskell that was behind them.

What’s good however is that the tutorial goes through a significant amount of code so you can see how everything comes together. It also lists good and bad points for each step – I found it beneficial to into some of the bad points and work to correct them.

After working through several of the tutorial, I felt ready to start working on a separate project: drawing bar graphs. Sure it’s primitive, and I think there are a lot of improvements that could be made, but I feel this really cleared up a lot of key concepts that just hadn’t quite “clicked” after my previous attempts.

There is a whole lot about the code that I’m worried about: areas that I know I can improve, areas that I think I will be able to improve as I learn more and, almost certainly, things I’m doing horribly wrong that would make experience programmers scream, without me even realising it. I’m hoping just dealing with writing to the .svg file in a more managed way (possibly with an existing XML library) will make a big difference to readability, as that’s where a lot of the mess seems to be.

Here’s the code (indentation will probably turn out a little dodgy):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
-- Import from standard libraries
import Data.List
 
-- Define some data types
type Point     = (Float,Float)
type Color     = (Int,Int,Int)
type LineColor = Color
type FillColor = Color
type Polygon   = [Point]
type Sample    = (String,Float)
 
-- Types of SVG Primitives and Elements
data Shape     = LinePoly LineColor Polygon
		    | FilledPoly FillColor LineColor Polygon
		    | Line LineColor (Point,Point)
	            | Text Color Int Point String
 
-- Name some colours			   
rainbow@[red,green,blue,yellow,purple,teal,white,black] =  [(255,0,0),(0,255,0),(0,0,255),(255,255,0),(255,0,255),(0,255,255),(255,255,255),(0,0,0)]
 
-- Format coordinates for output
writePoint :: Point -> String 				-- x,y
writePoint (x,y) = (show x)++","++(show y)++" "
writeLinePoint :: (Point,Point) -> String 	-- x1="" y1="" x2="" y2=""
writeLinePoint ((x1,y1),(x2,y2)) = "x1=\""++(show x1)++"\" y1=\""++(show y1)++"\" x2=\""++(show x2)++"\" y2=\""++(show y2)++"\""
writeXY :: Point -> String					-- x="" y=""
writeXY (x,y) = "x=\""++(show x)++"\" y=\""++(show y)++"\""
 
-- Draw primitives and shapes
writeShape :: Shape -> String
writeShape (LinePoly (r,g,b) p) = "<polygon points=\""++(concatMap writePoint p)++"\" style=\"fill:transparent;stroke:rgb("++(show r)++","++(show g)++","++(show b)++");stroke-width:2\"/>\n"
writeShape (FilledPoly (fr,fg,fb) (lr,lg,lb) p) = "<polygon points=\""++(concatMap writePoint p)++"\" style=\"fill:rgb("++(show fr)++","++(show fg)++","++(show fb)++");stroke:rgb("++(show lr)++","++(show lg)++","++(show lb)++");stroke-width:2\"/>\n"
writeShape (Line (r,g,b) p) = "<line " ++ writeLinePoint p ++" style = \"stroke:rgb("++(show r)++","++(show g)++","++(show b)++");stroke-width:2\"/>\n"
writeShape (Text (r,g,b) i p s) = "<text "++writeXY p++" font-family=\"Verdana\" font-size=\""++(show i)++"\" fill=\"rgb("++(show r)++","++(show g)++","++(show b)++")\">"++s++"</text>"
 
-- Draw SVG header elements and contents
writeShapes :: [Shape] -> String 
writeShapes p = "<svg xmlns=\"http://www.w3.org/2000/svg\">\n"++(concatMap writeShape p)++"</svg>"
 
-- Draw and position graph bars and labels
drawSample :: (Sample,Float) -> [Shape]
drawSample ((s,x),i) = 
    FilledPoly (round (x), round (x/2), round i * 10) black [(100, i * 60.0),(x + 100 , i * 60.0),(x + 100, i * 60.0 + 50),(100 ,i * 60.0 + 50)]
	: [Text black 16 (x + 120, i * 60 + 30) s]
drawSamples :: [Sample] -> [Shape]
drawSamples s = concatMap drawSample (zip s [1..])
 
-- Draw and position axis and titles
drawAxis :: Float -> [String] -> [Shape]
drawAxis x s = 
    Line black ((100,50),(100,x * 60.0 + 60)) 
    : Line black ((100, x * 60.0 + 60),(500, x * 60.0 + 60))
    : Text black 28 (100, 40) (s !! 0)
	: Text black 20 (250, x * 60.0 + 100) (s !! 1)
	: [Text black 20 (20, x * 30.0 + 50) (s !! 2)]
 
main = do 
	-- Load graph data from file
    sample_text <- readFile "samples.txt"
    let file :: ([String],[Sample])
        file = read sample_text
 
	-- Create the new image
    writeFile "graph.svg" $ writeShapes (drawSamples (snd file) ++ drawAxis (fromIntegral (length (snd file))) (fst file))
 
    -- Output to show success
    putStr "Image graph.svg created. \n"

Anyway, if you’ve read that and have any advice, feel free to be brutal and let me know what to change. As I said, there are a lot that I feel is still on the wrong tracks, but I wanted to get some code up here earlier rather than later in case anyone had feedback.



Popular Posts


Comments

  1. Bryan says:

    Hey,

    I haven’t had a chance to look over the code very thoroughly, but I’ve got few suggestions from a quick run through of the code:

    1: Look at creating instances of show for your defined data types. This will make your code much cleaner as anytime you print or call a function that uses show with one of those types the function will be called implicitly instead of needing to call the various write functions you have defined.

    2: I would also create a polymorphic data type to represent your xml elements so they can be created programmatically. This way you could shorten those long string creations. (You can look for xml libraries on hackage, and use cabal to download them….let me know if you’re not familiar with cabal and I can point you to some sources)

    3: Could you add the sample input so I could run the program and get a better understanding of its operation….I’ve never worked with svg files before.

    4: This purely preference, but what I like to do is define which functions I’m using when importing a module.
    Ex. import Data.List (sort,nub)
    Though looking through your code it doesn’t appear that Data.List is required for any of the functions. Using this convention would make it easy to see scenarios like this where imports are extraneous.

    5: Using tabs for formatting is Evil! :P

    Keep in mind that these are merely suggestions and are no means hard and fast rules (except number 5 :P ), and I would by no means consider myself a haskell expert, although I’m working on that. You are definitely off to a good start, keep up the practice!

    Oh and one more thing, in the Object Mentor Blog, Bob Martin and Michael Feathers recently wrote several posts about writing clean code in functional languages. These are definitely worth checking out if your interested: http://blog.objectmentor.com/ (Maybe one day I’ll gather enough courage to start my own blog!)

    -Bryan

  2. Hazel says:

    Hey Bryan, thanks as ever for your awesome feedback.

    1. I see what you mean there, will definitely do that.
    2. Again, I think I understand so I’ll give that a go. I seem to have Cabal working correctly – I was hoping using someone else’s libraries would give me some more experience there.
    3.

    ([
    "What should I eat?",
    "Deliciousness",
    "Food"
    ],
    [
    ("Cupcakes" , 400),
    ("Cookies", 300),
    ("Pie", 150),
    ("Ice-Cream", 250),
    ("Squid", 10)
    ])
    

    As you can possibly guess, I was reading in lists of values and labels and then realised I probably needed a header and axis labels as well – and ended up with this mess. I can think of better ways to do this but again, my aim was just to start by getting things working.
    4. Good point. This came from working from the tutorial I linked to to start off – I realised I didn’t need the other modules but I wasn’t sure on that one, guess I probably should have checked :/
    5. The “tabs” should be spaces (although I was using a different text editor from normal which wasn’t set up to replace tabs with spaces, so I may have made a mistake with that somewhere).

    Its so strange being completely back at the beginning of learning to program again – I think I have far more sympathy for new first years coming to university now!

    Thanks again for your help and for the link. It would be great if you started a blog – you clearly have a lot of great advice to share. I find time is an issue, and getting over the fact that I hate anything that I wrote more than a couple of weeks ago is hard, but it has certainly helped my programming come on a whole lot.

  3. Bryan says:

    Hey,

    So I got curious and looked up an xml library on hackage called HXT which thus far appears to be an excellent xml package. You can install it by doing ‘cabal install hxt’. It has to install a haskell curl library as a dependency which ended up being a problem for me. This was because it needed a particular package that I didn’t have installed, that included the file curl.h. I downloaded these packages and it remedied the problem for Ubuntu….hopefully it will help you:

    http://packages.ubuntu.com/search?searchon=contents&keywords=curl.h&mode=exactfilename&suite=hardy&arch=any

    Well, I went ahead and applied it to your example and replaced the meat of the xml creation with this:

    —————————————
    – Create XML Representation of SVG File Based On Shapes
    createXml :: [Shape] -> IOStateArrow s XmlTree XmlTree
    createXml shapes = mkelem “svg” [sattr "xmlns" "http://www.w3.org/2000/svg"] $ map createElement shapes

    – Converts a shape into an xml element
    createElement :: (ArrowXml a) => Shape -> a XmlTree XmlTree
    createElement (LinePoly (r,g,b) p) = polygonElement (concatMap writePoint p) $ linePolyStyle r g b
    createElement (FilledPoly (fr,fg,fb) (lr,lg,lb) p) = polygonElement (concatMap writePoint p) $ filledPolyStyle fr fg gb lr lg lb
    createElement (Line (r,g,b) (p1,p2)) = lineElement p1 p2 $ lineStyle r g b
    createElement (Text (r,g,b) i p s) = textElement p “Verdana” i (rgb r g b) s

    – Creates a polygon xml element
    polygonElement :: (ArrowXml a) => String -> String -> a XmlTree XmlTree
    polygonElement points style = mkelem “polygon” [sattr "points" points, sattr "style" style] []

    – Creates a line xml element
    lineElement :: (ArrowXml a) => Point -> Point -> String -> a XmlTree XmlTree
    lineElement (x1,y1) (x2,y2) style =
    mkelem “line”
    [ sattr "x1" (show x1), sattr "y1" (show y1), sattr "x2" (show x2), sattr "y2" (show y2), sattr "style" style] []

    – creates a text xml element
    textElement :: (ArrowXml a) => Point -> String -> Int -> String -> String -> a XmlTree XmlTree
    textElement (x,y) fontFam fontSize fill text =
    mkelem “text”
    [ sattr "x" (show x), sattr "y" (show y), sattr "font-family" fontFam, sattr "font-size" (show fontSize), sattr "fill" fill ]
    [ txt text ]

    – Style attrubutes for each type of element
    lineStyle r g b = “stroke:” ++ (rgb r g b) ++ “stroke-width:2″
    filledPolyStyle fr fg fb lr lg lb = “fill:” ++ (rgb fr fg fb) ++ “stroke:” ++ (rgb lr lg lb) ++ “stroke-width:2″
    linePolyStyle r g b = “fill:transparent;stroke:” ++ (rgb r g b) ++ “stroke-width:2″

    – Creates rgb “thing” based on rgb int values
    rgb :: Int -> Int -> Int -> String
    rgb r g b = “rgb(“++(show r)++”,”++(show g)++”,”++(show b)++”);”
    ————————————

    and replaced the writeFile with:

    ————————————-
    – Create the xml representation
    let svgXml = createXml (drawSamples (snd file) ++ drawAxis (fromIntegral (length (snd file))) (fst file))

    – Use arrow to write the XML Representation to file
    runX (root [] [svgXml] >>> writeDocument [(a_indent, v_1)] “graph2.svg”)
    ————————————–

    The library is pretty simple to use. The primary function is ‘mkelem’ which takes a string name and two lists, a list of attribues and a lits of sub elements. Well thats my initial pass at using an xml library, I hope it helps you out….I gotta get back to work now :D

    http://www.haskell.org/haskellwiki/HXT#Document_construction_examples

    -Bryan

  4. Bryan says:

    Sorry to post such a nightmare in a reply…it certainly didn’t look that way when I was writing it!

  5. Hazel says:

    Hey, I was expecting to have another post up on the topic before now but since I haven’t I just wanted to let you know I haven’t given up!
    I’ve been looking through the HXT documentation and examples here and there is a lot of new information for me to understand, but I’m working through it. As you may have seen, I’ve also been using C# for a one week game project – I’m seeing a difference in the way I look at programming already. Hopefully I’ll have something cool to share soon :)

  6. Bryan says:

    Yea, from your recent posts it definitely seems as though your pretty busy. I know what you mean by programming differently…I was recently working on a Ruby program and found myself adding a foldr function to the Array class :P . Well, if you find you need any help let me know!

    - Bryan

  7. Alex says:

    Hazel, while you’re jamming out the C# for your game project, take a look at F#. Functional programming in .NET. It’s primarily architected by Simon Peyton Jones (The guy who did so much awesome work with GHC the Glasgow Haskell Compiler), and bares a lot of resemblance to Haskell. It will be one of the core languages in the 2010 release of Visual Studio, so very much worth taking a look at. Plus, you’ll have access to all the .NET libraries you’re use to.

    -Alex

  8. Hazel says:

    I’ve heard of F# and certainly been curious but have never really looked into it. I’ll definitely try and take some time to check it out.
    Thanks!

Leave a Reply