Functional programming came up a lot lately in conversations with mutually unrelated friends of mine. There seems to be increasing interest in the topic.
So for the past weekend I decided I’d start learning Haskell. Don’t ask why this particular language. I just happen to like the name.
That, and a memory that I have from a programming contest I participated in the early 00’s. I can’t remember the contest’s name, but it had to do with programming simulated ant colonies. You submitted a script that would play the role of an ant’s brain and your colony would battle against the other participants’. There were several hundred teams in the leaderboards and the name “Haskell” next to the team name and score stood out a lot. Especially close to the top of the leaderboard.
Prior to this weekend project I had some superficial experience with Scheme and OCaml (another name that stood out a lot in the same leaderboards), but this time I wanted to create something while learning, so that I’d be forced to gain an understanding instead of merely following a tutorial.
I consider Tetris to be a good enough benchmark for an ok-ish understanding of a programming language. In fact, I have coded quite a few Tetrises in various programming languages. I did one in QBASIC, another one in Angelscript, that was later ported in C#/XNA and finally in C++/OpenGL.
So I decided to make a Tetris. In Haskell. In a weekend. It took me a bit more, but it was completely worth it. This first look at Haskell was fun and rewarding, and I’m already thinking up excuses to do more Haskell-related stuff in the near future.
The following summarizes my thoughts that may be relevant to someone starting out with Haskell, in a more or less random order.
- You make standalone exe’s in Haskell: ghc -o myexe Source.hs. It’s not one of these languages where you need a gigabyte of installations in order to run a program.
- Alongside the compiler, there’s also an interpreter where you can load your code and interactively test your functions one at a time. Very convenient.
- You need to disable tabs in your text editor. Tabs will produce warnings, and for a good reason. Haskell is whitespace-aware. Indentation can change the meaning of a program, which I didn’t like at first, but makes the code a lot more cleaner.
- They say that “Once a Haskell program compiles, it is almost certain that it does exactly what you intended”. I confirm that this is no exaggeration. My Tetris project consists of 35 small commits. Every commit is me adding functionality and then arguing with the compiler until it shuts up, at which point I run the resulting program and indeed get exactly what I wanted. Always. It happened 35 times in a row and not even once did I have a logical error to fix.
- GHC features the user-friendlier compile errors I’ve ever seen.
- I was a little bit worried at first that a functional language is not a good fit for making games. Besides performance considerations, there’s a lot of interaction going on, which I thought was bound to splatter side effects all over, effectively requiring us to write Haskell code as if it was C, therefore cancelling its advantages. It turns out that there’s an elegant way to handle it. You maintain the mutable game state and the process of updating it in a separate module where you do a minimal amount of imperative programming (designated as “IO” in Haskell), and you feed this module with update and event handling functions that are pure as snow and take as input a game state and output another game state.
- Actually, I didn’t even have to mess around with IO, since the graphics library I used (Gloss) has it already implemented. I’m not particularly happy about the graphics library maintaining my game state and feeding it to my functions when something happens so that they can transform it as needed, but that’s minor considering the convenience the library offers. You’ve got an OpenGL window with input and a basic game loop running at constant intervals with a single line of code.
- Kudos to Gloss’ developers for the “line strip font”! I can tell their target group is people like me, that couldn’t care less about fonts.
- I’m really fond of the “where” syntactic construct. It’s such a simple yet powerful feature. After getting a bit used to it, I caught myself typing functions before being quite sure about how to proceed. Being able to use functions and values before defining them makes you think more about the problem and less about the technicalities. Your source code becomes a record of the mental process you followed in order to solve a problem, which makes it so much easier to read again at a later time. You can also read a function partially, to quickly inspect what it does, without even looking below the “where”, where the obvious stuff go.
- Haskell is concise. Again, that’s no exaggeration. The whole Tetris game is about 400 lines of heavily commented code and the game is pretty functional (yup, intended!).
As mentioned earlier, I documented the entire process in a git repository. I put some effort into making it potentially interesting to others that want to learn Haskell.
Here’s the repository on github:
More specifically, I tried to follow these rules:
- Every commit adds a specific piece of functionality and is as localized as possible.
- To the extent that this is practical, every commit must focus on one Haskell language feature.
- Commit messages briefly explain what was done.
- Extensive temporary comments (they exist for a single commit and then I erase them) discuss stuff in more detail near the relevant piece of code.
- All commits compile without errors.
Contributions of a similar style would be more than welcome, as there are a lot more Tetris features to be implemented and Haskell features to be demonstrated.
- GHC compiler/interpreter system: https://www.haskell.org/downloads
- Real World Haskell, free online book: http://book.realworldhaskell.org/read/
- Learn You a Haskell, another free book: http://learnyouahaskell.com/chapters
- much easier to follow, but a bit overprotective in terms of style.
- Tutorial that I followed for Gloss : http://andrew.gibiansky.com/blog/haskell
- feel free to skip cabal, documentation and sandboxing stuff.
- Gloss documentation: https://hackage.haskell.org/package/gloss-220.127.116.11/docs/Graphics-Gloss.html