Profile photo for Aaron Christianson
Aaron Christianson, Software Developer at Goethe University Frankfurt (2016-present)
There’s no objective way in which OCaml is better than Haskell, but I can tell you why I like it more. In short, Haskell is too perfect. OCaml is better because of its imperfection.
What language can match the elegance of Haskell’s level of abstraction, where the machine seems to disappear, and computation is modeled in purely mathematical terms? Not OCaml, and not any other language I’ve ever used.
Both OCaml and Haskell were created in academic contexts, but for different purposes. Haskell was created primarily as a platform from programming language research, and it has been wildly successful in that capacity. Haskell also has the goal of being usable for industrial application, which it is, but that is not the main purpose. OCaml and its predecessor, ML, were created as utility languages for implementing other programming languages (specifically, proof assistants).
Both OCaml and Haskell care about getting the job done right, but the emphasis in Haskell is more on “done right” part, and the emphasis in OCaml is slightly more to the “getting the job done” part—though OCaml’s semantics and culture do emphasize correctness more than any mainstream language (with the possible exception of Rust, now, if it can be considered mainstream).
When Haskell’s abstractions fit the problem you’re trying to solve, there’s no other language like it. It feels like programming in zero gravity or something. However, there are a few problems with this.
For the sake of purity, Haskell puts some red tape around interactions with the outside world. If you’re writing a program that needs to interact with the OS and the network a lot, this can be quite inconvenient. For I/O subsystems in large programs, I think the inconvenience is worth it, but for smaller programs that basically do nothing but I/O, it’s a bit annoying. OCaml, on the other hand, is designed for writing programs that work closely with the OS and makes it very easy to do so.
It’s a similar story with mutation. Both OCaml and Haskell make it easy to program without mutation, which is wonderful, and what you should strive to do most of the time, but only OCaml makes it easy to program with mutation, if you want that. As an example, OCaml is a major language for writing parsers. If you write parsers, you want a great regex engine. If you want your regex stuff to be fast, you’re going to want to use a lot of mutable buffers instead of constantly allocating new strings. OCaml comes out-of-the-box with libraries for working with exactly these kinds of buffers. You can do this stuff in Haskell as well, but it’s going to be a lot more work than it would be in OCaml in the name of purity. Again, sometimes purity is worth that extra cost, but sometimes, it ain’t. Nothing prevents you from using monadic effects in OCaml, but the language gives you the flexibility to decide when that is a fit for your problem or not.
Lazy evaluation is extremely cool and is responsible for the kind of weightless feeling you get when programming in Haskell, but it makes some programs much more difficult to reason about. In a lot of the cases that OCaml is used for (like virtualization), you want to know exactly what the program is doing, in what order, and how much memory is in play. OCaml has an extremely simple execution model that makes this easy, and Haskell has a very complex execution model that makes this difficult.
In summary, Haskell is very focused on an idealized vision of programming, and OCaml is more focused on the messy needs of real programs, while still offering all the benefits of functional programming where you can afford to use them.
There are a few other things that I find nicer in OCaml, but they don’t really go with this theme:
I like module functors more than type classes. The give you the same amount of code reuse, but they force you to be more explicit about the specific types in play at the call-site. This doesn’t look as pretty on the page, but it ends up being a lot easier to read. type classes can get you into this inheritance-spaghetti thing where you’re not sure where the code you’re calling is defined. This happens very seldom with module functors, since you have to explicitly create a new module where all the type-specific functionality lives. (this is perhaps slightly more verbose, but it usually only amounts to one extra line of code). This is pretty subjective, however. Most people prefer type classes because they look prettier.
OCaml’s ecosystem is smaller than Haskell’s, but I still like it more. Most OCaml packages have more of a “professional” or “industrial” feeling about them, where Haskell packages lean more in the academic direction.