Haskell bright side
Before making a decision about switching to Haskell, I’ve carried out one trial project. I’ve ported Ariadne from JavaScript to Haskell (GHCJS).
Ariadne is a library for finding a minimal CSS query that uniquely identifies some given DOM element. It powers the user actions recorder in Siesta JS testing tool and RootCause service, we’ve developed at Bryntum.
Minimal query means it should contain as less CSS segments as possible, so the query:
.cssClassis smaller than
.cssClass1 .cssClass2If you’ll think some time how you would do it, its not so easy. You’ll need to generate a list of CSS segments for the given element and its parents, and then perform a search in a huge combinatoric space of possible query candidates. I’ll omit the details, but we found a quite performant algorithm, that normally finds a result in <100ms even for big DOM trees.
I’d call it a medium complexity algorithm. Around 30 classes, many of very small size. I’ve implemented in roughly functional style, minimizing state mutation. Still, it required a month to make a solid implementation in JavaScript, with proper test suite, a usual debugging phase of few days, etc.
So I’ve ported it to GHCJS, as my first, “baby haskell” project. There were nothing fancy in it, no contravariants, no dependent types, just a pretty close conversion from imperative version.
The key observation was: After the code has type-checked (no compilation error) it was mostly working!
There were 2 bugs needed to be fixed after this stage:
- In one place a function was returning a list of lists:
[ [a] ]. The base recursion case for such type should be a list with empty list:[ [] ], instead of just empty list[]Because of this bug, the result of the function was always an empty list. Slightly not trivial for me as a beginner, but now I know the pattern. - In another place sorting was performed in wrong direction (ascending vs descending), so the results were in reversed order. Trivial.
There were no need in long debugging period, as it is usually with algorithms of this size in imperative world. It just worked, even without a test suite. Its not that I’m advocating that there’s no need for tests in Haskell, I’m just specifying that without tests it would took much longer to write imperative version, but in functional world it worked even without.
So the rumors about Haskell programs capturing much more inner semantic and “if it compiles it works” are true. Not literally, as there’s always space for real bugs, like wrong order of sorting, but the whole dimension of normal imperative kind of bugs (messing with state) is indeed absent in the functional world.
Again, an imperative algorithm, that required 1 man-month to create, worked same day after it reached the type-checking stage.
Its very hard, or even impossible to do monkey coding in Haskell — that is, when you don’t know why are you writing this line of code. Instead, types guides you gently, trough the control flow, providing a lot of static (compile time) guarantees about the code.
That’s the bright side of Haskell!
As you’ve probably guessed, the next post is going to be about a dark side :)
Stay tuned!
