Let's Draw Us a Purty Picture
Ok, so I promised you some pretty pictures in this tutorial... Now, finally, the time has come for this! We're going to draw pictures in a really cool way: We're going to write our own SVG files from scratch!
SVG files are a simple file format for specifying images that you'll probably hear a lot more of in the future- It's completely resolution independent and already used by many drawing programs such as Inkscape, which I highly recommend.(Note to internet explorer users: If the pictures below don't show up right in your web browser, you probably need to download an SVG viewer... everyone else should be fine...)
Here's the code that'll take a list of colored polygons and write it to an svg file:
let writePoint :: Point -> String writePoint (x,y) = (show x)++","++(show y)++" " let writePolygon :: (Color,Polygon) -> String writePolygon ((r,g,b),p) = "<polygon points=\""++(concatMap writePoint p)++"\" style=\"fill:#cccccc;stroke:rgb("++(show r)++","++(show g)++","++(show b)++");stroke-width:2\"/>" let writePolygons :: [(Color,Polygon)] -> String writePolygons p = "<svg xmlns=\"http://www.w3.org/2000/svg\">"++(concatMap writePolygon p)++"</svg>" let colorize :: Color -> [Polygon] -> [(Color,Polygon)] colorize = zip.repeat let rainbow@[red,green,blue,yellow,purple,teal] = map colorize [(255,0,0),(0,255,0),(0,0,255),(255,255,0),(255,0,255),(0,255,255)] writeFile "tut0.svg" $ writePolygons (blue [[(100,100),(200,100),(200,200),(100,200)],[(200,200),(300,200),(300,300),(200,300)]])
After you run this code, you'll see the following picture drawn in the file tut0.svg that you can open in your web browser:
This code is a quick n' dirty SVG file writer. In order to draw a list of polygons, it breaks the task into subtasks- First, into writing the separate polygons, then into the task of writing each individual point.Since the Polygons are just a list of individual Polygons and those are just a list of points, we express this in Haskell using the map function- This function takes a list of things and a function that operates on an individual item in the list, and grinds through the list with this function- Haskellers never use loops- Instead, they either use recursion to do looping, or they use functions like map that take other functions as parameters. Functions that do this are called higher order functions. In the writePolygons and writePolygon functions, you can see the mapping function being used: In this example, we're using a clever variant called concatMap that also concatenates the result of the mapping together, saving us a step.
An important thing to note in the writePoint function is that something funky is happening on the left side of the equals sign- Instead of taking a variable name for the point we're passing in, we're instead pulling in the point using (x,y), so that we can tease the point apart as separate x and y values- Haskell can do this for us because it supports pattern matching: We can put an arbitrary structure where other languages may ask for a simple variable name- The compiler then figures out if the value "coming in" to the function can meet your structure, and if so, it will destructure the parameter and give you whatever pieces you want with the names you gave it.
The way we handle colors in this example uses some more clever Haskell tricks... It has enough complicated ideas that it deserves it's own section in this tutorial: If you can understand what colorize = zip.repeat means, you'll understand probably most of what Haskell has to offer in just three words!
The Insane coolness of
Earlier I told you that you should look at the last arrow in a type definition and that all things in front of that arrow are parameters- While this is a good way to think about Haskell functions, in reality, the arrow -> is actually right associative... that means this function actually reads as follows:
So in reality, strangely enough, the colorize function only takes one parameter, but it actually returns another function (of type [Polygon] -> [(Color,Polygon)]) as a result! So if we ever write colorize my_favorite_color my_favorite_polygon what's really happening is that the color is passed in first and a new function is created- Next, the polygon is passed into this new function to generate the result.
OK, so how does this fact help us simplify our definition of colorize? Well, remember that Haskell supports pattern matching- This means, we can just drop the p (polygon) parameter from both sides and the compiler cna still make sense of it:
Next, we can use a the haskell sequencing operator (represented by the dollar sign $) to get rid of the parenthesis- When you see this, it just means there's an imaginary "matching parenthesis" to the dollar sign at the end of the code expression:
The dollar sign is really just syntactic sugar but it's really useful because if we are just feeding single values into functions, as we usually are, then it forms kind of an assembly line- So basically, we're taking the value c, running it through repeat, then running it through zip- This works exactly the same way as a Unix pipe, if you've ever worked with that clever concoction before...
When we see this kind of assembly line, we can usually simplify it even more by using function composition, which Haskell represents with a period (You may have learned this in precalculus- Maybe you remember your teacher talking about "f of g of x" in front of the class and putting a little dot between the f and the g- This was exactly the same idea :-)
Using function composition, we can now compose the the zip and repeat functions like so:
Can you figure out why we want to do this? You may notice we have another value dangling off the back of our function again- We can snip it off of both sides, just like before, leaving us with the final, most elegant form:
You've gotta admit, a language that can do that is pretty cool! Writing functions with the "dangling values" removed is called writing in a point-free style
Most of the time, a general-purpose function like colorize is too much trouble- What we really want is functions for coloring things red, green, blue, etc. The last thing we defined in the code fragment above were some simple functions with obvious names that are just versions of colorize that have the color value prefilled- We do this by just mapping a list of rgb values against the colorize to create a list of color functions that we just use to define a slew of new functions all at once- Having a language that treats functions just like any other values makes stuff like this easy. As a bonus, this line of code also defines a variable rainbow that contains all the colors, and which we'll use later to paint with lots of colors, all at one.
One thing that's really good about it is that it does an immense amount of stuff using just a miniscule amount of code- Our colorize function is the ultimate in brevity (Remember that all the type definition lines, which have a double colon, are just optional niceties and aren't actually needed for the program to work...)
Why do we care so much about brevity? Well, since Haskell also has ridiculously strict type checking, it means we can sandwich all our bugs between the type checker on one side (many bugs involve mismatching types) and code brevity on the other end (less code, means less room for bugs to hide)
Arguably, Haskell is the one language that has the toughest type checking and allows for the briefest code- That means Haskell allows you write code that's more bug free than almost any other language. I would estimate that I spent only about 3% of my development time debugging this program, which I could never accomplish that in another language. (Now, mind you, it can be a b*** to get your program to compile properly in Haskell in the first place, but once you get to that point, you're home free :-)
Well, we're creating raw xml data as text in this example- It would be much better to use an XML processing library, like HaXml, to make this even less error prone. However, we want to just stick with the basic GHC compiler here and not spend time installing tons of stuff for a simple little tutorial.