Is Haskell a Good Choice for Web Applications?
Haskell is the darling of the functional programming crowd. But is it ready for serious web applications? Or is it too immature and academic?
I’ve spent the last
7 months 10 months1 creating a website with Haskell.2 I’ll share the frustrations I encountered and the triumphs I experienced. And I’ll explain the positives and negatives of the language for those of you who are thinking about writing your own web application in Haskell.
“I just finished implementing a simple FastCGI program in Haskell. I wrote it to understand how Haskell web programming works and to see if it’s sufficient for building a language learning site. My conclusion is that not only is it sufficient, it’s much more fun than building in PHP.” Journal entry, 2008-03-16 23:19
I’ve also released the source code for the website under the GNU Affero Public License.3 It’s roughly
The code demonstrates:
FastCGI SCGIServant to serve pages and an API
- moving data to and from a PostgreSQL database
- authenticating users with cookies
- collecting data with HTML forms (formlets)
- communicating with users via email
caching with memcached
- implementing a custom forums system (with threaded comments)
The program’s name is Vocabulink, and you can see it in action at http://www.vocabulink.com/.
Can Haskell do it?
“I had my doubts again with choosing to write in Haskell. Many things that seemingly would be simple to do in PHP require a lot more up-front effort in Haskell.” Journal entry, 2009-01-15 00:14
I was concerned that Haskell wouldn’t be able to handle the task. I’d had experience working on large websites with Perl and PHP, and a part of me doubted that Haskell’s no-compromises approach would work for anything serious and large-scale. At the time, “Real World Haskell” wasn’t yet published, and I could find no examples of large Haskell web applications.5 Much of the libraries in Hackage seemed to be toys compared to what I was used to.
But now, having launched the site I can say: yes. Not only is Haskell capable, it’s an excellent choice. The Haskell environment is surprisingly rich and mature. But more importantly, using Haskell has forced me to keep my code clear and simple. It’s been maddening at times (the language really can feel like a straightjacket), but I’ve come to appreciate the discipline it enforces as the code has grown.
I’ve found a library for almost everything I’ve needed, and they work together extremely well. Vocabulink makes use of
12 a couple dozen libraries.
More interesting than the availability of the libraries has been their simplicity. Even without documentation they’re surprisingly easy to understand, just by reading their source code. This also makes them easy to modify, should you need to.6 And they’ve been an excellent source of ideas and guidance in how to use the language.
Haskell and SQL
Unfortunately, Haskell does not yet have a mature way of interfacing with a relational database in a type-safe way7. You have to build your SQL commands as strings in much the same way as with most other languages. HDBC does take care of preventing SQL injection attacks, but that’s about it.
This is one area where you’re not yet going to see a benefit from using Haskell. In fact, Haskell can be a little bit more verbose and awkward until you get used to passing around database handles in your monads. But you will get used to it.
I’ve begun work on fixing this with a PostgreSQL library that uses compile-time inference to check the syntax, parameters, and return type of statements. It’s available via Hackage as templatepg. Vocabulink is now using it exclusively for database access.
“Before now, I thought I had learned most of the abstractions out there, such as anonymous functions and high order functions. I thought monads were just something to help with doing imperative-style programming while staying safe. I had no idea they could lead to such interesting expressive capabilities.” Journal entry, 2008-11-07 00:02
What article about Haskell would be complete without mentioning monads? Guess what, I still don’t fully understand them. But I was able to write a useful program anyway. So if you don’t understand them yet either, don’t let that keep you from writing some serious code. It’s the best way to learn.
HTML combinators (and formlets)
I love HTML combinators. It surprises me that so much time and effort has been put into template languages. By working with templates, you lose much of the ability to abstract.
An excellent example of this is the Haskell formlets library. While it still has some rough edges, it makes working with forms much nicer than any other abstraction I’ve found.8
“For some roughly 400-500 lines of code I’ve added a basic, yet fully-functional forum to Vocabulink, including an administrative interface for creating forums and fully-threaded comments.” Journal entry, 2009-03-15 00:29
I thought that lack of a good traditional debugger would be a major drawback and hurdle. It turns out that it isn’t. Reasoning about code can uncover a lot more in a pure language than you might be used to in an imperative language. And it’s been rare so far for errors to slip past the compiler.
I did have to use trace statements a few times, but doing so was no different than in an imperative language.
As I’m using a single
FastCGI SCGI program, deployment is pretty simple. I just rsync the statically-linked9 binary to production and restart it.10 I rsync the sources to production and compile it there. This means that I don’t need to have a Haskell compiler or any libraries on any web server. The downside is that the binary is currently 14MB and growing.
I’m sure I’ll have more to say about this once http://www.vocabulink.com/ has more traffic and demands more sophisticated deployment.
Contrary to what some people will tell you, you’re still going to get errors in a purely functional program. The biggest source of mine up until now has been the database. I’m really looking forward to a higher-level relational interface that can catch these types of errors at compile time.
One particularly frustrating problem you might encounter is mixing exceptions with your own monads. Exceptions are meant to be caught in the IO monad. This means that you can’t catch them until you’ve unwrapped any extra layers you’ve added (such as a Reader).
For a web application, this isn’t a deal breaker. The exception will be limited to the current request. Or you can catch all exceptions at the level they’re thrown and use Maybe or Either monads (this is what Vocabulink does). However, it’s something I’m looking forward to seeing the Haskell community improve upon.
If you’re curious about how developing with Haskell actually is, I encourage you to try it. There’s only 1 way to find out what it’s really like.
Read through the literate source of Vocabulink. Most of it was written while I was still new to Haskell and so it might be easier to understand than some of the more theoretical papers out there.
If, however, you just want to get your startup idea implemented ASAP and don’t mind higher maintenance costs in the future, Haskell might not be for you. It’s probably going to be a little bit more work to scale it to a large number of servers and you might not have a ready supply of Haskell programmers.
Don’t expect any miracles. Haskell is no silver bullet. But I do think you’ll be pleasantly surprised with the results. If nothing else, you’ll have expanded your mind in the process.
I’ve updated the code since I originally published this article and have marked differences throughout.↩︎
I worked on the program for a few hours each night and on weekends. I estimate that I spent about 30 hours a week on it. I wasn’t active every week (for instance, I stopped working on it for 2 months in the middle) so I suspect I’ve spent a total of about 500 hours on it so far. After the 7-month mark, I’ve been working on it less frequently.↩︎
I make no claims that my code is elegant or idiomatic. It is however real code that’s running “in the wild”.↩︎
Since then, gitit has been released. If you know of any others, please leave a comment.↩︎
I’ve modified both the Network.FastCGI and Network.Memcache libraries to output in UTF-8 by default.↩︎
I am aware of HaskellDB, but it does not seem mature enough for serious use. Perhaps someday soon…↩︎
For a good introduction to formlets, see this article in Chris Done’s blog.↩︎
I’ve been eagerly reading about dynamic linking support in the latest GHC 6.10 releases.↩︎
I don’t yet have a technique for graceful restarts.↩︎