Haskell might be “the closest thing to a secret weapon” when building server-side software, writes Better co-founder Carl Baatz, summarizing their four-year journey using Haskell in production. Baatz’s retrospective takes on four different viewpoints: Can you get stuff done? Does it hold up in practice? Can you hire developers? Should more companies use it?
Here is a list of a few of the lessons that Baatz learned though building a system that “handles half a million actions every week and has been running for well over a year with no downtime or maintenance”:
- Haskell lets you get stuff done with fairly limited knowledge, but to become highly productive, there is a lot to learn.
cabal-install
is not beginner friendly, but Stack makes life easier.- The library ecosystem is “decent, not great”.
- Laziness requires thinking carefully about space complexity.
- Haskell’s type system makes it easy to refactor and extend code.
- Haskell’s type system also reduces the need for unit testing to catch inconsistencies, yet Haskell has great support for it using QuickCheck.
- Hiring programmers was easier than expected and many programmers were attracted by the prospect of using Haskell.
InfoQ has spoken with Carl Baatz to learn more about his experience.
One of the takeaways of your post is that Haskell has allowed you to build an almost bug-free system. Could you elaborate more on this?
I don’t claim that the system is bug-free, but we don’t know of any bugs yet (after over a year of steady usage). Our engineering team certainly deserve a lot of credit for that. As for the language and how we used it, there are probably a few things that helped.
For starters, Haskell’s type system really does ensure a higher degree of consistency compared with many mainstream languages. In my experience, bugs in dynamically typed languages are often “silly” mistakes from refactoring: I changed an object field property, but didn’t update all the places it was used. In Haskell, the compiler tells you what you missed, because your change altered the type of the object and now there’s a mismatch between that and what the old usage assumed.
The language also encourages you to program in a style that has strong static guarantees. For example, you could store your configuration in a dictionary from string to string, and then handle missing or invalid entries wherever you use the configuration. It is usually easier however, to specify the configuration type in detail; you state that it MUST have a port that’s an integer for example. You can then rely on that everywhere – you know that port lookup can’t fail. Reading the configuration from disk can of course fail and you might choose to handle that by exiting the program or supplying a default value.
Another thing that helps is immutable data and purity (constraining side-effects). Immutable data means we can safely share and pass values to functions without worrying about them changing underneath us. Putting constraints on side-effects means that functions that do unpredictable things like read files and get random numbers (that is, input/output) must be explicitly marked as such and they can only be used from other such functions. It’s good practice to keep such input/output functions as simple as possible and move most logic to pure functions. These things make it much easier to reason about the program, in particular when complexity grows.
To be fair, we should also point out one place where Haskell is probably more likely to introduce bugs than traditional languages: space usage. Because Haskell is lazy, it is harder to reason about space complexity and that can cause “leaks” (not actual leaks, but huge memory usage). This is by no means an insurmountable problem, but it can require some care to track these down.
There’s more, but I’d say those are the main highlights. There are of course plenty of things the language and compiler can’t check: is your authorization model sound? Are your calculations correct? Are you showing the correct labels on your website?
You did not do TDD, nor pursued full unit test coverage. How did your approach to testing change? What kinds of tests did you define and which one were the most valuable to have?
We took a pragmatic approach to testing: only write tests if it's cheaper than the expected cost of bugs they would prevent. Thus, we had tests on code that could potentially corrupt the database and we tested performance/load handling before big deployments. We did of course have bugs during development, but I can’t remember any costly ones and they could usually be fixed quickly. In particular, we could confidently refactor code, which is crucial, without tests (again, thanks to the type system).
On a side note, I think it’s actually useful to think of the type system as a form of test suite that the compiler runs. With that perspective, Haskell and other languages with expressive static type systems actually have a lot of “tests” (that are run on every compilation), they’re just not the traditional kind of tests we tend to think about.
What advantage, if any, did Haskell provide when it comes to time-to-market?
Haskell doesn’t have the eco-system of libraries and tools that say, Ruby and JavaScript does for, say, quickly setting up a web-project, so it is slower at the prototyping stage. I don’t think that’s an intrinsic flaw of the language, but the eco-system leaves us wanting. On the other hand, Haskell is strong when it comes to quickly writing and changing code that needs to work in production because we can write fewer tests and refactor confidently. If we generalize somewhat, one might argue that Haskell has a higher initial price tag but lower total-cost-of-ownership. I think the initial price tag can come down too, but we’re not there yet.
How could Haskell provide an even better development experience?
The first thing would probably be libraries. The eco-system is good, but not as rich as those of Java, Ruby, Python, and JavaScript for example. It is steadily improving and the last year has seen significant advances. Still, more high quality libraries and tools would certainly be welcome. I think this will keep improving as the community grows and as more companies start using Haskell in production (though admittedly, there’s a chicken-and-egg problem).
The other thing that can put people off is documentation, which often leaves something to be desired. Many libraries have minimal, if any, documentation other than types. I also think it would be useful to have more high-quality general learning materials. There are some and the situation is improving, but there’s further to go.
Finally, I’d probably say a better IDE story. Though most Haskellers are happy vim/emacs users, I think a modern IDE that understands the Haskell type system could lower barriers to entry and provide a significant boon to productivity. There’s some work on a Haskell IDE server (which would hook up to various text-editors and IDEs) so that’s positive. However, from what I’ve understood from people who know more about this than I do, a really good IDE server would require non-trivial changes to the compiler itself, so that is likely some time off.
Another thing some might put in a list of potential improvements is language stability. Even though the language and base library does introduce breaking changes from time to time and therefore incur a maintenance burden, the upside is that the language is kept relatively clean which improves the day-to-day experience. Personally, I think that’s the better trade-off, but some might beg to differ.
Better was founded in 2012 to build a cloud-based learning platform that could be integrated into existing enterprise Learning Management Systems. At the beginning of 2016, it was acquired by GRC Solutions.