8 months of OCaml after 8 years of Haskell in production

I’ve been using Haskell in production for 8 years. I’ve been using OCaml in production for 8 months.

It’s time to compare those two languages.

Syntax

Haskell probably has the most elegant syntax across all languages I’ve seen (maybe Idris is better because dependently typed code can become ugly in Haskell really quickly).

There’s utter joy in expressing your ideas by typing as few characters as possible.

OCaml, being a language from the ML family is great too, but still, Haskell is more tacit.

Compare a few examples:

Sum of all numbers in a string

Using just the standard library

Haskell

-- strSum "100  -42 15" = 73
strSum :: String -> Int
strSum = sum . map read . words

OCaml

(* str_sum "100  -42 15" = 73 *)
let str_sum (str: string): int =
  str
  |> String.split_on_char ' '
  |> List.filter_map int_of_string_opt
  |> List.fold_left (+) 0

Defining a new binary tree type

Haskell

data Tree a
  = Leaf
  | Node a (Tree a) (Tree a)

OCaml

type 'a tree =
  | Leaf
  | Node of 'a * 'a tree * 'a tree

Parsing

Return the result on successful parsing of lines like the one below where “Status” equals to zero and the result is an even number

"Status: -1 | Result: 42"

Haskell

parseLine :: String -> Maybe Int
parseLine line = do
    ["Status:", "0", _, "Result:", result] <- Just $ words line
    n <- readMaybe result
    guard $ even n
    pure n

OCaml

let parse_line (line: string): int option =
  let ( let* ) = Option.bind in
  let* result =
    match String.split_on_char ' ' line with
    | ["Status:"; "0"; _; "Result:"; result] -> Some result
    | _ -> None
  in
  let* n = int_of_string_opt result in
  if n mod 2 = 0 then Some n else None

The above are just a few random code snippets. They don’t give an idea of all possible programs that could be written in those languages. But I hope they can quickly highlight the similarities and differences between the two languages.

This slowly leads us to the next point.

Features

Haskell has waaaaaay more features than probably any other programming language (well, C++ can compete). This is both good and bad.

It’s good because you have the tools to solve your problems in the best way.

It’s bad because you have those tools. They’re distracting. Every time I need to solve a problem in Haskell, I’m immediately thinking about all the ways I can design the solution instead of, ahem, actually implementing this solution.

I’m interested in building stuff, not sitting near my pond on a warm summer day, thinking if TypeFamilies + DataKinds would be better than GADTs for making illegal states unrepresentable.

If I come to an existing OCaml project, the worst thing previous developers could do to it is have poor variable names, minimal documentation, and 200+ LOC functions. That’s fine, nothing extraordinary, I can handle that.

If I come to an existing Haskell project, the worst thing previous developers could do… Well, my previous 8 years of Haskell experience can’t prepare me for that 😅

That’s why I feel more productive in OCaml.

I do miss some Haskell features at times. But I’ve seen their ugly side and what they can do to your output.

Consider the following table with a full comparison of major features.

Feature comparison table

Feature OCaml Haskell
Expression-oriented syntax
Immutability by default
Higher-Order Functions (HOFs)
Anonymous functions (lambdas)
Algebraic Data Types (ADTs)
Pattern Matching
Parametric Polymorphism
Type Inference
Monadic Syntax Sugar
Garbage Collector
Multithreading
GADTs
Purity by default
Composable laziness
Type classes
Higher-Kinded Types
Opt-in language features
First-class modules
Polymorphic variants
Objects
Classes and Inheritance
Ergonomic mutability

Ecosystem

Let’s be honest, both programming languages are niche FP langs. So you shouldn’t expect first-class support for the latest modern framework that just got published.

However, in my experience, despite needing to write more custom bindings, you have solutions for the majority of common tasks.

For example, in OCaml, you can find:

And so on. Similar story for Haskell.

I’d still say that the Haskell ecosystem has more packages and more ready-to-go solutions.

It’s easy to show the difference in the following example.

Number of Stripe API client libraries:

You may find a solution in Haskell. But often you’ll discover too many solutions, you won’t know which one to choose.

Choosing a library in Haskell becomes a separate skill you need to master. Haskellers even blog their recommendations on how to choose a library! And you’ll face this dilemma over and over again.

Often, a new Haskell library is created not because it solves a different problem.

But because the author wanted to write it differently (using different abstractions, playing with new features, etc. Who doesn’t want a new streaming library based on LinearTypes???).

It’s not exciting to write a GitHub API client and parse tons of JSON.

But it is exciting to design a logger with comonads.

Tooling

The Haskell tooling evokes the most controversial feelings. It’s like an emotional roller coaster:

And so on.

Using Haskell tooling is like always being in the quantum superposition of “How do you even use other PLs without such wholesome Haskell tools???” and “How Haskellers can live like that without these usability essentials???”.


OCaml, on the other hand, hits differently. Because its ecosystem is smaller, you actually get surprised every time you find something working!

For example, the VSCode plugin for OCaml based on Language Server Protocol (LSP) works out-of-the-box. I never had any issues with it. It just works ™️

The ergonomics of starting with OCaml tooling might not be the best but they’re straightforward and robust. And they work most of the time.


To get a full picture, refer to the following table for the full comparison of available tooling in both languages.

Tooling comparison table

Tool OCaml Haskell
Compiler ocaml ghc
REPL utop ghci
Build tool dune cabal, stack
Package manager opam cabal
Package repository opam Hackage
Toolchain installer - ghcup
Linter zanuda hlint
Formatter ocamlformat, topiary fourmolu, stylish-haskell, hindent, ormolu
Type Search Sherlodoc Hoogle
Code search Sherlocode Hackage Search
Online playground TryOCaml Haskell Playground
LSP ocaml-lsp HLS

Compiler messages

I want to highlight the compiler aspect of tooling separately since this is the tool you interact the most with.

Especially, compiler suggestions.

When using FP languages, the compiler is your best friend! You rely on it heavily to understand why your assumptions haven’t been codified precisely.

Therefore, the compiler must present the information in the most accessible way.

In my view, Haskell compiler messages tend to be verbose with lots of contextual, often redundant, and distracting information.

OCaml compiler messages, on the other hand, are quite succinct. Sometimes too succinct.

Consider the following example.

Haskell: Compiler messages example

Program with an error

x = 1 + [3, 1, 2]

Compiler output

Haskell Compiler Error Message

OCaml: Compiler messages example

Program with an error

let x = 1 + [3; 1; 2]

Compiler output

OCaml Compiler Error Message

This is just one example (and most likely not the best one), but you can already see the differences in how information is presented and how types work in different languages.

Standard library

I believe the standard library deserves a separate mention too.

It shapes your first program in a language and guides you through all future journeys.

A great standard library is a cornerstone of your PL success.

A poor standard library is a cornerstone of never-ending bikesheds about a better standard library (including an endless variety of alternative competing standard libraries).

I’m a big proponent of the idea that a standard library should be batteries-included.

Give me an Option-like type, a UTF-8 string, Map and HashMap, JSON and XML parsers, async primitives, and so on, so I can avoid learning your poor implementation of dependency tracking and build tooling. (Build Systems a la Carte is a thorough analysis of the space of dependency trackers and build tools.).

Both Haskell and OCaml have kinda barebones standard libraries. They have minor differences (e.g. Haskell doesn’t include Map and HashMap; OCaml doesn’t have non-empty lists and Bitraversable). But overall they’re similar in the spirit.

The Haskell standard library is called base and OCaml standard library is called.. well, it’s just “the standard library”.

However, one difference is striking. The quality of Haskell documentation sometimes can amaze even a seasoned developer.

Haskell has a few more nice features, like the ability to jump to sources from docs but I’ve been told such features are being cooked for OCaml too 👀

Compare a few doc snippets for the List data type (one of the fundamental structures in FP):

Haskell

Haskell: Data.List.head

Haskell head

Haskell: !?

Haskell index

OCaml

OCaml: List.hd

OCaml hd

OCaml: List.nth_opt

OCaml nth_opt

You may argue that the result of such functions is obvious, therefore there’s no need to write essays under each function.

I’m a fan of example-driven documentation, and I love seeing usage examples in docs! This immediately gives me an idea of how I can leverage the API in the best way.

Conclusion

I want to end this blog post by saying:

Both languages came a long way to support real industrial needs.

They’re still small compared to mainstream languages.

If you’re not critically dependent on the presence of some specific SDK, you can choose any and have lots of joy while coding your next app 🧡

However, I prefer OCaml nowadays because I feel that I can focus on actually building stuff with this language.

Discussions

Besides the comment section below, you can also find the discussions of this blog post in various places:


If you liked this blog post, consider supporting my work on GitHub Sponsors, or following me on the Internet: