Learning Haskell (and Elm in the process)
Introduction
Functional programming has always had a certain allure to me. You catch whiffs of it writing workhorse languages like Python, and there’s certainly something satisfying about writing a concise, functionally-styled block of code where your university lecturers might have suggested using a nested for/while loop. I’d never had a “purely functional” experience before though, and eventually I felt the need to satisfy my curiosity. What pushed me over the edge was reading about GHCJS, a Haskell to JavaScript compiler. I was excited to use a language that’s almost synonymous with FP for web development.
I gave myself 12 days (it was mid-week and I wanted to finish on a Sunday) to get stuck in. Having a deadline on a project helps me prioritise and gives me a good reason not to waste time on ideas that are tangential to my goal, even if they’re worth looking into later. I didn’t initially know what that goal was, besides “learn Haskell”, and I didn’t see a reason to narrow the scope of that until I understood the domain better. In the end I wound up with something tangible that’s embedded further down on this page!
Reading
Usually I’d jump straight into coding by hacking away on examples or following step-by-step tutorials. Since FP contrasts so starkly with the OO paradigm, I decided instead to read a textbook on it. I chose Learn You a Haskell for Great Good, and besides some borderline offensive attempts at humour I can highly recommend it! It does a superb job of introducing concepts in a very logical order. The author regularly gives concise reminders of previously discussed concepts which really helps you understand them. It doesn’t provide much in the way of exercises, but that’s fine by me. I found it much more helpful to inspect chunks of code more thoroughly in GHCI (Haskell’s REPL) if I felt the need to, and to leave writing code until I had a better understanding of the language as a whole.
Inspired by a diagram in the book, I decided that my goals would be:
- Write a clone of Snake for terminal emulators in Haskell
- Transpile it to JS for the web
- Replace the text-based interface with an HTML canvas interface
Snake seemed like it would be represented easily in Haskell since manipulating lists is so common in the language. I named my clone “Adder” since Haskell seems more aligned with mathematics than other language I’ve used and I didn’t feel like spending time coming up with a less unimaginative name.
It wasn’t until the Thursday before my deadline that I finished reading the book. I could have finished earlier, but at that pace my retention of its contents was solid.
Coding
Writing Haskell is fantastic. The declarative style is so elegant that it almost feels like writing psuedo code, which made it surprising when I ran the program for the first time and saw it running exactly as described. Pattern matching makes the corresponding imperative code look clunky and awkward. Currying and function application give you the sense that you’re working with something very cohesive and not just an arbitrary set of rules.
Concurrency wasn’t discussed in Learn You a Haskell so it took a bit more fiddling with than the game logic did, but otherwise it all came naturally to me. It’s not worth saying much more than that after such a brief learning period. In programming circles there’s definitely a culture of jumping to conclusions about languages or frameworks or libraries based on weekend projects, or even less qualifying experiences, so I’ll just say that I enjoyed myself!
Porting
Setting up a working GHCJS dev environment wasn’t as simple as I had hoped. I didn’t have much luck getting it to work in Stack (virtualenv/bundler/etc for Haskell) so I tried Reflex, an FRP library with a set of tools that makes it simple to get a dev environment up and running. I got as far as displaying the game’s initial state on a web page, but Reflex is pretty green and the documentation is sparse.
With only a day remaining, I investigated the alternatives. I settled on Elm quickly because of its maturity and superb documentation. There’s plenty of write-ups around about the differences between Haskell and Elm too. I set up an Elm environment, installed the Vim plugin, dropped my Haskell code into a .elm
file and hacked away at it until the compiler stopped complaining, and hey, it works!
(On a desktop computer, the game will appear here)
On the whole, writing Elm was as much fun as Haskell had been. I missed certain features of Haskell’s syntax - the pattern matching isn’t quite as elegant, and dealing with monads felt clumsy - but the tooling and documentation more than made up for it. I also appreciate that learning the syntax on an ad-hoc basis means that I probably wasn’t making full use of the features available in Elm.
I cheated a bit in the Elm version by doing random number generation with the Native module, which sidesteps functional purity by glueing a chunk of plain JavaScript into the code. It would be better to use The Elm Architecture’s commands, but I didn’t have time to restructure the game to make it play nice with TEA.
Next
I didn’t get as far as writing graphics for Adder, but I’m happy with my progress. The “read first, code later” approach worked very well for me and I will certainly use it again.
When I have the time, I’d like to continue my work here:
- Write a Zipper monad to hold the string representation of the game’s state
- Finish and merge a branch of the Haskell version of Adder that speeds up redraws in slow terminal emulators
- Refactor the Elm version of Adder to use TEA properly and ditch the native JS code for random numbers
- Write a graphical interface for Adder - I had planned on using canvas for this, but apparently Elm has a good interface for SVG
- Give PureScript a go
The Adder code as it is at the time of writing is tagged with v0.1 here.
Update 11/03/18: I’ve since updated Adder with WebGL graphics. You can play it here!