Haskell has a serious Not Invented Here problem where it comes to Web frameworks. Everyone is unhappy with what we have, but no honest features differ between the six or so out there.
Let’s start with what I don’t care about:
- Computer Science
If I cared about these things—or if any other Web developer cared about these things—I wouldn’t write in Ruby (or PHP, Perl, PLT Scheme, etc.).
These are nice but they’re not selling points.
Overall what I—and, Rails has taughts us, other Web developers—want out of a Web framework is a strong opinion. I want to be told where to put things. I want to be told that routes go here, HTML templates go there, tests go in this other place, and so on.
To not do that is to assume that Web apps are simple, that people maintain exactly one Web app, and that Web sites are completely unique. None of those assumptions are true.
Next up I want to bring knowledge over from the rest of my career. Some of my favorite design patterns apply here: observer and MVC. Just use MVC for your Web framework. It makes sense. I mean, it doesn’t, but it approximates it so well that it’s worth it.
The view should look like HTML. I’m not writing it, afterall; the designer is. Or she and I are writing it together. Either way, it shouldn’t be validated. I don’t care if it’s valid; the designer doesn’t care; the Web browser doesn’t care (and sometimes needs it to be invalid); so just relax already. (Any Haskell in the view template needs to be valid. I understand this.)
And I don’t want to write SQL so use an abstraction layer like the active record pattern or Arel or something dead easy. Simulate OO if you need to; it makes sense some times, and invoking behavior on data is that time.
I don’t want to “pretend I’m writing desktop apps” because this isn’t a familar metaphor. I want to write HTTP apps. I want to interact with GET, PUT, POST, and the others. The Web is a very familiar metaphor: data goes in, data comes out, and it’s over. It’s like a function call but with more moving parts. Not only does this make it easy to think about, it makes it easy to test.
I test my code. At work I test-drive my code, which also implies 100% code coverage but we don’t tend to care about code coverage. But that’s the kind of code I write: code that needs to be tested. I want to use an integration test suite to describe how the application should perform, and I want to use a unit-level test suite to ensure that refactoring goes well. My Web app will talk to other Web apps so I damn well better be able to fake that part out of my tests.
Pulling knowledge in from the rest of my career, I want to use Cucumber and something better than Selenium. In Rails we use Webrat and Holy Grail and EnvJS and ShamRack and a whole slew of other tools. There’s nothing stopping me from using Cucumber to describe my behavior except I can’t back it with a Haskell equivalent of Webrat.
Oh speaking of knowledge, I need documentation. The first-pass tutorial for a Web app isn’t “hello, world”, it’s a blog. If “hello, world” requires a full tutorial then your Web framework belongs in 1995. Make a screencast, those are fun. You should say, “Look at all these things I’m not doing!” in it, and mean it. But be aware that I’m not writing a blog. Well, I am, but it’s part of the app. Clients always demand a blog (and an admin interface, statistics, copy changes, A/B testing, …).
I see an idea crossing your mind right now and let me just say that splitting the individual components into discrete divisions that know nothing of each other simply isn’t fun to maintain. And even if it were fun to maintain, you don’t know how to make such a thing in the first version of your framework.
Speaking of things you don’t know how to do at first, I want hooks. Nearly every Web site out there is slightly different from another Web site, and I want to hook into the framework to handle those differences. The part of the framework that catches exceptions and shows a 500? I want to be able to replace that or augment it. The part that maps URL paths to module and function; the part that looks for the location of the HTML templates; the part that loads configuration files—all this, and more, are things I may want to augment.
If you provide a hooks, you should have a directory for storing plugins. That’s a nice, cheap way of having people contribute to your Web framework.
I mentioned errors, and this is one spot where Haskell, as a language, falls short. Here’s what you can get from a Ruby exception: backtrace, message, class name, and anything defined on Object, Class, Module, or Kernel. On the other hand, here’s what you can get from a Haskell exception: message. So you know that somewhere, somehow, maybe in your code, `head’ got an empty list.
(I ran into this while writing a Hoptoad notification library for Haskell. It was a very “wait … what! … waaait. Oh, what!” reaction when I realized this.)
Rumor has it that ghc is working on backtraces.
As a tangent let me tell you about the first (and last) time I tried Merb. I had been a Rails developer for maybe three years by then and I had heard that Merb was a simpler Rails. “That sounds nice,” I thought to myself. “Rails is a big pile of hacks so presumably they’ve brought that pile down to a manageable level.” First thing I noticed was that ActiveSupport was missing. This meant I couldn’t #pluralize. As I wrote more I realized more and more was missing and that, really, Rails was extracted from a real, production-level, money-making Web app … and Merb simply wasn’t.
Anyway, when the Web app is ready I’m going to deploy it to staging and, when that’s ready, it’s off to production. Maybe there will be a pre-staging server, too, or a mirror of staging, or a mirror of production with forked traffic. Either way deployment needs to happen and, when it breaks, it needs to be undone.
So it sounds like Rails has what I want. Why am I still here? Haskell gives me: type checking, a friendly and small community, and threading. It’s a smart language for smart people, and I want to pretend to be a smart person.
The next step
Which Haskell Web framework is the one we should bring into this dream? There’s no need to make yet another; let’s just take an existing one and fully realize it. I can’t figure out which, though. Advice?