DEV Community

Dmitrii Kovanikov
Dmitrii Kovanikov

Posted on • Updated on

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Defining a new binary tree type

Haskell

data Tree a
  = Leaf
  | Node a (Tree a) (Tree a)
Enter fullscreen mode Exit fullscreen mode

OCaml

type 'a tree =
  | Leaf
  | Node of 'a * 'a tree * 'a tree
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  • Haskell: 13
  • OCaml: 1 (last change was 8 years ago, so it's more like zero)

You may find a solution a Haskell. But often you'll find 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:

  • 🤩 Hoogle is the best! I can search through the entire ecosystem by using just a type signature!!!
  • 😨 Wait, why build tooling error messages are so bad, what do you mean it couldn't find a build plan for a working project???
  • 🤩 Global content-addressable storage for all dependencies is such an amazing idea!!!
  • 😨 What do you mean I need to recompile my IDE because I changed my package???
  • 🤩 I can automatically test all the code snippets in my package docs!!!
  • 😨 Wait, why the standard library doesn't have docs at all for this version I use???

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]
Enter fullscreen mode Exit fullscreen mode

Compiler output

Haskell Compiler Error Message

OCaml: Compiler messages example

Program with an error

let x = 1 + [3; 1; 2]
Enter fullscreen mode Exit fullscreen mode

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.

If you liked this blog post, consider following me on YouTube, X (formerly known as Twitter) or sponsoring my work on GitHub

Discussions

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

Top comments (26)

Collapse
 
mas281 profile image
Sam • Edited

Nice post. I actually previously interned at Bloomberg (though not on an OCaml team) and most recently at another company using OCaml, and I'm a big fan of the language.

I agree with most of your points, though I think your OCaml examples on the point of tacitness are a bit unidiomatic and make the difference seem larger than it actually is.

For example, with your exponentiation function, in the OCaml codebases I've seen it would be much more likely for this to be written using a match statement, making it essentially the same as Haskell in terms of concision:

let rec pow base power = match power with
  | 0 -> 1
  | n -> base * pow base (n - 1)
Enter fullscreen mode Exit fullscreen mode

Or even making use of = function:

let rec pow base = function
  | 0 -> 1
  | n -> base * pow base (n - 1)
Enter fullscreen mode Exit fullscreen mode

(Of course ignoring the point that an exponentiation function would never actually be written this way)

Collapse
 
chshersh profile image
Dmitrii Kovanikov

Thanks! I hope you enjoyed your time at Bloomberg 🤗

Indeed, this particular example could be written better with the usage of pattern matching. I wanted to show the difference between guards and if-then-else. But looks like this particular example is not that convincing.

I'll change it to a better one when I come up with it!

Meanwhile, you might have some other example in mind?

Collapse
 
yawaramin profile image
Yawar Amin

If we are comparing succinctness, let me offer an alternative parsing example :-)

let parse_line line =
  Scanf.sscanf line "Status: %d | Result: %d" (fun status result ->
    if status = 0 && result mod 2 = 0 then Some result else None)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
chshersh profile image
Dmitrii Kovanikov

Good example! I often forget OCaml has sscanf too as well as printf 😅

Collapse
 
dmbaturin profile image
Daniil Baturin

To.ml: A TOML parser

I was so infuriated by that library that I made one to fix all its problems. ;)
github.com/dmbaturin/otoml/

It's actually compliant with the TOML standard, uses transparent types (but also offers helpers for getting nested values), and uses functors to allow the user to plug any datetime and bignum libraries or stick with built-in native int and string dates.
Well, and offers informative parse errors, too.

Collapse
 
chshersh profile image
Dmitrii Kovanikov

Hey @dmbaturin

I was informed that ez_toml is better than otoml:

discuss.ocaml.org/t/8-months-of-oc...

Do you have a comparison with this library somewhere?

Collapse
 
chshersh profile image
Dmitrii Kovanikov

Thanks for letting me know! Your library looks great, I'll update the link 🔗

Collapse
 
fluffy8unny profile image
ふわふわのうさぎ

I follow you on twitter and saw your recent posts about ocaml, so I decided to actually try it out. Using it for a bit during the holidays made me feel like it's just worse Haskell. When I saw your post, I thought that I'd see some features that are amazing, while it might just be my viewpoint, the stuff you're showing me here makes me want to use Haskell even more.
Less than language features a few of the libraries are really cool tho. I'd love to have something akin to owl (cl.cam.ac.uk/~lw525/owl/chapter/pl...)

Collapse
 
chshersh profile image
Dmitrii Kovanikov

It's totally fine to have different preferences!

If you enjoy using Haskell, you can continue using it 🤗

Collapse
 
bitmaybewise profile image
Hercules Lemke Merscher

Awesome post!

I tried OCaml many years ago. I think around the same time I started playing with Haskell, but gave up on OCaml due the fact that Haskell felt more pleasant to use. You definitely made me wanna try OCaml again :)

Collapse
 
chshersh profile image
Dmitrii Kovanikov

I'm glad my post was inspiring enough 😊

Collapse
 
doby profile image
Escherial

You forgot the best asset OCaml which is its Web framework. Ocsigen is the most advanced web development framework. It's revolutionary once you get how it works. Nothing else can compete, in any language.

Collapse
 
chshersh profile image
Dmitrii Kovanikov

I never tried Ocsigen. I guess, it's hard to compare two languages fully because you can never try everything.

But you reply gave me some motivation to try it 👀

Collapse
 
zeta611 profile image
Jay Lee

I’ve been using OCaml for 4 years now, but never used Ocsigen for web development. I have some experience with ReScript with React tho. I’m really curious about using OCaml for both front/backend; why would you recommend Ocsigen (vs something like Dream)

Collapse
 
etorreborre profile image
Eric Torreborre

Nice article. It's great to have the two languages side by side as I'm tempted to have a closer look at OCaml.

Collapse
 
chshersh profile image
Dmitrii Kovanikov

Thanks!

Yes, I really wanted to compare these two languages.

You can find other blog posts comparing Haskell and OCaml (some are older, some are newer). But I wanted to provide my take anyway and share my experience 😌

Collapse
 
chshersh profile image
Dmitrii Kovanikov

I received tons of feedback on my OCaml vs Haskell blog post! Thanks a lot to all who read and shared their thoughts 🤗

Using the feedback, I improved my post by:

✍️ Changing the most triggering exponentiation example to a different one
✍️ Added links to all discussions of my blog post all around the Internet
✍️ Changed feature from 'Laziness by default' to 'Composable laziness'
✍️ Added 'topiary' as an OCaml formatter
✍️ Changed the suggested TOML library from 'To.ml' to 'otoml'
✍️ Changed the suggested AWS library from 'ocaml-aws' to 'awsm'

Hope you enjoy it!

Collapse
 
bartlisa profile image
Bart

Terrific post!

For the comparison table, possibly include records support. This has been a Haskell deficit for decades, and no thank you, lenses are a bloated, heavyweight bolt-on when you just want simple, lightweight record support.

Collapse
 
chshersh profile image
Dmitrii Kovanikov

Thank you!

Haskell kinda has a better record story nowadays. You can actually have nice records in Haskell without using lenses!

Collapse
 
tonyalaribe profile image
Anthony Alaribe

Lovely article! I look forward to trying out ocaml as well.
It's tradeoffs seem a lot like Go. Fast compile times, minimal syntax and moving parts,

Collapse
 
chshersh profile image
Dmitrii Kovanikov

Thanks! I think you'll enjoy OCaml 🤗

I do see it like Go of FP languages. Which is kinda the best of both worlds 😉

Collapse
 
theoddler profile image
Pablo Bollansée

Great article! Really nice to see you're enjoying OCaml, I've been looking at it too, but have not actually used it yet, but this makes me even more interested in trying it out.

Collapse
 
chshersh profile image
Dmitrii Kovanikov

Thanks a lot, Pablo!

If you liked Haskell, I think you'll enjoy OCaml too 🙂

Collapse
 
renatillas profile image
re.natillas

Nice article, now I have a bigger picture of Functional Languages!

Collapse
 
chshersh profile image
Dmitrii Kovanikov

I'm glad I was able to help with my article! 🙌

Collapse
 
giullianosep profile image
Giulliano Ferreira

Great post! I'd like to see another comparison including F# as well.