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.”
2008-03-16 23:19
In the margins I've included some quotes from my programming journal.
I've also released the source code for the website under the GNU Affero Public License.3 It's roughly 2,000 2,500 lines of Haskell, along with some SQL, Javascript, and CSS. The main program is written in a literate style and I've included the resulting 75-page PDF 85-page PDF. You can read through it and draw your own conclusions.4
The code demonstrates:
The main program's name is Vocabulink, and you can see it in action at www.vocabulink.com.
“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.”
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.
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.
“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.”
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.
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
If you know how to write JavaScript by hand, working with Haskell is no different than working with PHP when it comes to creating dynamic pages. Haskell has good JSON and XML libraries for generating and parsing what you need.
“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.”
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-linked[9] 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 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.
1. I've updated the code since I originally published this article and have marked differences throughout.
2. 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.
3. http://www.gnu.org/licenses/agpl-3.0.html
4. I make no claims that my code is elegant or idiomatic. It is however real code that's running "in the wild".
5. Since then, gitit has been released. If you know of any others, please leave a comment.
6. I've modified both the Network.FastCGI and Network.Memcache libraries to output in UTF-8 by default.
7. I am aware of HaskellDB, but it does not seem mature enough for serious use. Perhaps someday soon...
8. For a good introduction to formlets, see this article in Chris Done's blog.
9. I've been eagerly reading about dynamic linking support in the latest GHC 6.10 releases.
10. I don't yet have a technique for graceful restarts.
Last Updated 2009-08-16 20:01:08 UTC
Mon May 04, 2009 12:57
Oi! A very interesting article. I'm doing all my web related things in perl (with mod_perl2 of course) and I always looked at other languages just because I'm curious and not because Perl is bad for me or anything else. I am just open for other languages and it's quite interesting for me to know that haskell is able to do mature web apps.
Some months ago I got my old documents about Ada95 and I started to hack that again just to find out that there are some good libraries available to do web development but after playing around, I realized that there are bugs nobody else found before though the libs are old enough...well...that's the difference I guess...people are using perl and not ada...people are constantly testing perl but not ada.
However, people start to use and test haskell as well :).
Thanks and have a nice day. Andreas.
—Andreas Schipplock
Mon May 04, 2009 14:45
I'm more of a Python guy, but man I cannot deny the beauty of Haskell. I'm already at Chapter 4 of Learn You a Haskell and I'm loving it. Question though I've heard of a web framework for Haskell, I think it's called Happs if I'm not mistaken. Did you consider using it?
—Mathew Wong
Mon May 04, 2009 16:56
I did. The main thing that threw me off is the way that HAppS maintains state in a type of a log file. It's an interesting idea, but I actually like relational databases. I'm also not sure how it will scale beyond one server.
However, that was a while ago. I know that they HAppS guys have been busy since I last checked in on the project.
—Chris Forno (jekor)
Tue May 05, 2009 07:29
I'm thinking doing some projects in Haskell for the next couple of months. Monads scare me a bit though...do you recommend any sources to better learn them? I can imagine once you learn how monads work, it becomes a mind blowing experience.
—Mathew Wong
Tue May 05, 2009 18:39
Perhaps the most useful thing for me was to see how to use monads in different contexts (not just I/O). It was when I realized that monads are a way of structuring computation that I finally began to understand them a bit more. So, beneficial for me was seeing how Parser and Maybe monads worked. But you might be interested in different monads like List or Reader.
All About Monads was good in that respect: it gives examples of using different monads. Do Notation Considered Harmful helped me get rid of the crutch of do notation (which is useful, but can be confusing at first).
You can read all the tutorials out there (which I did), but in the end I think that just dealing with monads when the need arises in your own programs is the best approach. That way you'll be sufficiently motivated to break through any barriers to understanding by experimentation.
—Chris Forno (jekor)
Mon May 04, 2009 15:37
Although Haskell is a very cool language, I find Ruby far superior for anything slightly OO.
—Martijn Lafeber
Mon May 04, 2009 15:58
i've had to learn haskell as a course requirement for my degree. at first i found it frustrating, awkwardly contrived, and just.. impossible to use.
i now find it less impossible, but i think progress is being made. having a "traditional" upbringing (haha) with C, it's been hard for me, getting my head around such a "pure" language!
i'd never thought of haskell having uses for web-programming but it certainly looks interesting. maybe when my kung fu has increased, i'll tackle something as adventurous as this!
thanks,
—dan
Mon May 04, 2009 16:59
I was also brought up on C. It is an awkward leap to make, especially with the habits we tend to pick up. Languages shape our thinking.
I encourage you to try it. Learn kung fu as you go :P
—Chris Forno (jekor)
Mon May 04, 2009 17:50
I wonder whether by "anything" you mean problems to be solved or ways to think.
My 2 cents: I doubt problems are OO or functional. Perhaps it's only ours ways of conceiving, formalizing, and solving problems (i.e., software design & implementation) that are OO or functional, rather than the problems themselves. If so, OO languages will better express and reinforce OO thinking, and likewise for functional languages & thinking.
I think what's difficult for many programmers is being handed a functional paradigm after they've developed imperative & OO thinking habits.
—Conal Elliott
Mon May 04, 2009 19:56
I've not used it much, but Ruby's a fairly balanced mix of OO and functional, and programs tend to reflect that.
—Bob
Mon May 04, 2009 20:42
I've used both Ruby and Haskell for a number of years; I wouldn't say Ruby has a "balanced mix" of OO and functional. It's got a nice system of comprehension collections, sure -- but the proc/function dichotomy and lack of currying are both major warts when you are trying to build a system with functional programming style.
—Jason Dusek
Mon May 04, 2009 20:47
I have to second Conal on this. Ruby is an OO language -- of course, it's a good fit when you've chosen to build things out of stateful objects and class hierarchies. What does that say about it's relative applicability for a particular problem domain?
I think one of Ruby's overlooked strengths, actually, has nothing to do with OO -- and that's writing shell scripts.
—Jason Dusek
Mon May 04, 2009 22:33
I wrote Turbinado (http://www.turbinado.org), an MVC Haskell framework in the vein of Ruby On Rails. I've been busy and haven't had too much time to work on it recently, but I'm a big fan of using Haskell for web apps. The biggest problem I've had has been the relative lack of maturity in Haskell libraries (mine included).
As a fan, Haskell! For productivity, Rails...
—Alson Kemp
Tue May 05, 2009 07:03
I took a look at Turbinado some months ago. While ORMs and MVC aren't my cup of tea, it's great to have another Haskell choice. Diversity is good, keep it up!
—Chris Forno (jekor)
Mon May 04, 2009 23:32
Personally, I haven't been so positive about using Haskell for web dev. I've found:
many libraries are difficult to evaluate and use due to poor documentation.
many libraries like formlets seem nice at first, but they have limitations that make them a dead-end for most real world use.
That's not to say I'm not enjoying the Haskell web dev project I'm working on, it's just that compared to other platforms (e.g. Django), it's got a long way to go.
BTW, vocabulink pages need a header to define their encoding type -- they come out all wrong in Konqueror, which presumably is not managing to guess the encoding as well as whatever browser you are using.
—Luke Plant
Mon May 04, 2009 23:47
Interesting. It should be transmitting the content encoding with the content type. Perhaps Konqueror is ignoring that and looking in the <head>?
I agree about the potential dead ends, especially with formlets. But I think that if enough of us start using them we can straighten them out.
—Chris Forno (jekor)
Tue May 05, 2009 02:59
If you aren't already, trying run 'strip' on the binary before you upload. That should drop the binary size by a few MB.
Also, saying that Happstack (the new name for HAppS) maintains it state in a 'log file' is perhaps not the most accurate description. Happstack stores it state in RAM. In order to support crash recovery (aka, durability), etc, it uses write-ahead logging to store the 'database' events on a more persistent medium. The default is log files on the disk. However, the logs can be stored other ways by supplying a suitable saver. For example, there was an S3 saver at one point in time (though it may have bitrotted a bit). The key thing is that queries and updates always use the in-memory state. The logs file are only read when the server first starts up. After the state has been restored, only disk writes are done (and they are done in the background). So, disk I/O and seek time is not very important for good performance.
Of course, storing all the state in RAM probably sounds even crazier than using some file on disk :) The current-code base supports multimaster replication across multiple servers for load sharing. The next step is sharding -- to support splitting the state across multiple servers when there is too much to fit in RAM on one machine. Though right now it is not too difficult to get a machine with 32GB of RAM, which is plenty big enough for many projects.
Additionally, you are not required to keep your entire dataset in RAM at once if you don't want to. You can archive some data to disk based on what is sensible for your application.
In a typical LAMP setup, you start with all your state on disk (in the database), and then you hope that database does a good job of caching the relevant information in memory (because disk access is perhaps 100x slower?). Then when that is not good enough you throw some memcached servers in the mix to try to do even more aggressive caching.
Eventually you end up with something like facebook, where they have 800+ memcached servers with over 1.2TB of RAM. All their database tables have only 2 columns, and they don't use transactions, joins, etc. They just get back the simple, raw query results from the database, and then do the 'joins' on the server (in PHP no less). While that takes more CPU power, it scales better to add more front-end servers rather than more database servers.
Additionally, facebook still has custom code to archive data out of the databases so that they don't get too slow. Amazon, eBay, etc, have all gone through similar gyrations.
Happstack turns things upside-down a bit by starting where these sites end up. Since disk is 'too slow', just start with everything in RAM, and then you don't have to run memcached servers or mysql servers. That means less servers to configure, tune, patch, etc. Also, since there is no SQL, you don't have to marshal your data to and from SQL data types and tables. This means less code to write, less code to have bugs in, less code to test. Plus, no SQL injection attacks ;) Instead of SQL queries, you just create plain old Haskell data types to store your state, and query and update the state using plain-old Haskell functions. joy!
So, Happstack will either be a revolutionary success, or a colossal failure ;)
Formlets are pretty cool, though a bit funky at times. I think there are two issues with the current Haskell implementation. First, the monad used for XML generation, and the monad used for validation are required to be the same monad. But there is no reason for that (and good reasons not to). Secondly, I think that the Formlets library should only set the 'name' attribute, not the 'name' and 'id' attributes.
Anyway, cool project!
—Jeremy Shaw
Tue May 05, 2009 07:01
Wow. That was very helpful. I had misunderstood a bit about Happstack.
I'm very familiar with the type of database/memcache/PHP setup and related scaling techniques that you mention. They can definitely be a pain to deal with and I hope we find a better solution, whether it's Happstack or something else. I didn't realize that the guys behind Happstack had been doing that much work on scaling. I'll be looking to see what happens with the sharding setup and will take a look at multimaster.
The idea of not having to deal with SQL is appealing, but I'm not sure I'm ready to leave relational databases behind. I bought into the idea with Zope/Plone and its object database, but I've found that RDBMSes, as flawed as the current implementations are, have a lot going for them. For one, I want to be able to access the data from outside of 1 particular program. And the principles behind relations help me reason about the data.
I'm hoping that HaskellDB, Takusen, or a similar approach catches on and gives us some of the best of both worlds.
—Chris Forno (jekor)
Wed May 06, 2009 21:58
did you try HaskellDB or Takusen ? What did you think of it ?
—zorg
Wed Jun 10, 2009 23:02
Of course, even though the XHtml formlets library on Hackage adds the `id' attribute, you don't have to use that particular implementation. It's trivial to write your own, using whatever markup library you want. I'm interested in the idea of using an XML library that lets you filter and transform XML, though I have not yet been able to think of advantages for it.
Very nice document, Chris. Always nice to have a real codebase to browse through.
—Chris Done
Tue May 05, 2009 22:18
I've been working on a web application in Haskell for the last 9 months - about 20,000 lines of code. I'm an experienced commercial developer, and I've used Python and Java in the past for web development. I was new to Functional Programming when I started this.
My Haskell experience has been very positive, but I took a slightly different approach: I did it at a lower level, avoiding web frameworks and designing it all myself. (I did this because the size of the project justifies a longer-term approach.) I don't like SQL so I have been using Berkeley DBXML as the back end.
The learning curve took a little while but now that I am over it, I find the process of writing the code efficient and effortless, partly because Haskell is a wonderfully expressive language, largely because the type checker means I don't have to even think about certain kinds of potential bugs, and partly because Haskell almost forces me to write good quality code. I would never go back to a "normal" language again by choice.
One thing I didn't anticipate is that Haskell made my standards higher. I thought I was writing brilliant code at first, but I had to re-write a lot of it once I knew it could be done better.
For this large project, I believe Haskell has far more than repaid the learning curve in productivity. One cost was that I had to write some supporting libraries that I wouldn't have needed in a more mature language. This was made worse by my non-mainstream choice of database, and the fact that one side project turned into writing a faster XML library (hexpat). However, the productivity gain has been so great than I think this extra effort has been repaid too.
And Haskell executes very fast. Many people say "Haskell works well for small projects - but will it scale?". I say that scalability is Haskell's greatest strength, and that for a large project, Haskell is a quantum leap.
—Stephen Blackheath
Wed May 06, 2009 06:40
That mirrors my experience pretty closely.
The most frustrating thing about writing an article like this is that it's difficult to convey the mental shift that takes place along with all of the unquantifiable improvements you make as a side-effect of the language holding you to certain disciplines.
—Chris Forno (jekor)
Wed May 06, 2009 02:30
It would be nice if you included a cabal file to automatically install the dependencies. You said you modified fastcgi and memcached, but you didn't seem to release the modified code, did you? I couldn't build your code without your modifications.
—Wei
Wed May 06, 2009 03:24
I cabalized your code and pushed to http://patch-tag.com/repo/Vocabulink. Of course I had to change the definition of memcacheFlush to return ().
—Wei
Wed May 06, 2009 06:36
Thanks for the Cabal file. I had no idea anyone would want to build the project, just perhaps look through it.
I'd like to make a few more changes to the memcached library before releasing a patch (the set command needs to support an expiration time, etc, before it'll become really useful).
—Chris Forno (jekor)
Thu May 07, 2009 06:19
Hi, thanks for the article...Have you come across a well written resource for using both FastCGI and HDBC together to make Haskell web apps?
Thanks, Dave
—Dave
Thu May 07, 2009 14:11
Practical Web Programming in Haskell is what got me started. It has a section on extending the CGI monad, which was the key for using both of them together (for me).
—Chris Forno (jekor)
Tue May 12, 2009 01:20
Abstraction. Great word. How do stacks work again, what is a Heap? Everything is really a Goto?
Engineering, only way to go. Life is stateful. Isn't a Monad a posh thing that helps out with the nastiness of state and holding data in a defined memory location, implicit.
I love the idea that Haskell is a proof language, but the matter of state is one that I just don't get.
I find the idea of liking Databases and using Haskell against one a most horrid concept. Databases are iky things that real programmers use. Not Math types!
How does one prevent Dirty reads and lost updates when a language has no concept of others working at the same time?
Maybe I didn't get monads, but one thing for sure, if a pure functional idea needs a non-functional concept to get around state, then the idea is wrong. Wrapping up a procedural programming concept up as a mini program and calling it a Monad, just to keep the language safe and clean to be most obsurd.
Shrodingers cat is not dead and alive. It is what it is at the time of usage. But the idea that state and reading of it depends on time and motion of the mind does not suit the maths. I think that this pure thinking has gone too far. Next we will be drawing perfect circles.
Paradigms are all good and well, a bit religious, a little too pythagorian. But I believe that life is nothing more than state and my written programs are state in transit until the computers blow up and melt away.
—Erwin Romel
Sat Jul 18, 2009 06:46
You probably fail to understand monads completely, monads are not a way of wrapping up procedural programming, and they have many uses beyond just IO.
—James
Thu Aug 13, 2009 12:47
Hi. Thank you very much for this article and for publishing the source code. It is really helpfull for all who learn haskell and try using it for web development. I've read the code and have the following question - what do you use to run your fcgi app? I'm not familiar with nginx but in documentation at its website they say that nginx can only connect to fcgi processes but not actually run them.
—Dmitry
Wed Aug 19, 2009 05:03
There's a program that comes with lighttpd called spawn-fcgi. Since then, I've switched to SCGI, which means that the program can start itself (it just opens a listening socket itself). Switching to SCGI meant I had to switch from nginx to cherokee since nginx doesn't have up-to-date SCGI support, but I'm kind of liking cherokee.
—Chris Forno (jekor)