8 months of OCaml after 8 years of Haskell in production

Hi everyone :wave:

I’ve been using Haskell in production for the previous 8 years. But for the last 8 months, I used OCaml at Bloomberg.

I wrote a blog post comparing two languages from my POV:

I tried to provide a reasonable and balanced overview of these two languages, although my experience in both is significantly different. I hope this blog post can give a rough idea of both languages :relieved:

Enjoy! And let me know any feedback you have!

19 Likes

Hi, thanks for the blog post! I was wondering why you didn’t write the pow function as

let rec pow base = function
  | 0 -> 1
  | n -> base * (pow base (n-1))
3 Likes

Great point!

I wanted to come up with a function to show the difference between guards and if-then-else. I now see that this is not the best example, as it can be written with pattern-matching in both cases, but I’ll gladly hear other suggestions! :pray:

Although I have only recently dabbled with OCaml, I def. agree with you that the features of ocaml are just easier to work with to be productive. I also find that the ecosystem of OCaml is much smaller but the packages strike a better abstraction level, IMO Dream is much easier to work with and test than most Haskell web frameworks I’ve dealt with.

There are also a lot of fundamental things that are so complex in haskell (lens for records, error handling and monad stacks) which you don’t need to touch if you use ocaml. That means as a consequence, the error message, while maybe less descriptive, become a lot easier to grok. The super fast compile times make the LSP and just working with the language in general sooooo much more ergonomic. Also polymorphic variants make error handling simple.

I will say though that my experience with OCaml is that you are more likely to deal with dependency hell and you’ll need more system level packages, which is not something I had to deal with as much in Haskell.

4 Likes

I feel lucky I haven’t experienced this side of OCaml yet :sweat_smile:

I made my OSS package 8 months ago using OCaml 5.0 and some dependencies.

I updated it recently to OCaml 5.1 and the latest version of all dependencies and nothing broke :four_leaf_clover: I guess I was lucky enough!

You should add topiary as an OCaml code formatter

1 Like

There are areas of opam that are very good with this and some that are not. I guess it really depends on the maintainer and maybe the domain of the deps.

I never heard of topiary but it looks interesting :eyes:
I’ll add it, thanks for the suggestion!

2 Likes

This is not tail-recursive, though.

That’s just an example, you can easily write a tail-recursive pow function using pattern matching in just 3 lines of code.

You might not have noticed, but Ocaml has OOP too. I imagine they keep kind of quiet about it here, but I bet people use it.

One basic difference that I think adds up on Ocaml’s side is evaluation. Lazy evaluation is brilliant, elegant, fascinating and sometimes I miss it, but too many unpleasant surprises for the unwary.

And Ocaml is stable. Code I wrote several versions back, compiles today without any issues.

4 Likes

The parse_line example can be written a bit more pleasantly with Scanf.sscanf_opt:

let parse_line (line: string): int option =
  Option.join (Scanf.sscanf_opt line "Status: %d | Result: %d"
    (fun status result ->
      if status = 0 && result mod 2 = 0 then
        Some result
      else
        None))
1 Like

Thanks! I rarely used sscanf in OCaml, and I often forget about this function :sweat_smile:

I probably need to update examples.

The IDE situation in OCaml wasn’t this nice a few years ago. It’s a very recent thing.
There are some languages where, if you don’t use them for a couple of years and try to recreate a working environment again, you might actually fail if you’re not dedicated (like, if you’re just doing it for the fun of it)

4 Likes

The author did mention it in the checklist section. People sometimes use it.
First-class modules are really cool, I miss them in F# when I use it (I don’t get to use either often these days :frowning:)

2 Likes

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

Using the feedback, I improved my post by:

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

As always, I’d appreciate your feedback!

2 Likes

FYI, ez_toml is better than than otoml. For instance it keeps comments attached to expressions when parsing so that they can be reprinted. It also handles values than can overflow (int or float) better.

Nice post! I love both Haskell and OCaml, though my experience with Haskell is mostly limited to small experiments and toy programs. While my experience is very different from yours, I came to a similar conclusion: Haskell is more elegant on several planes, but actually “getting stuff done” tends to be more straightforward and obvious in OCaml. My feeling is that the extra verbosity in OCaml often also aids readability of the code, though it does make writing certain things more laborious at times.

An example of this would be module functors vs. type classes. While people don’t often speak about module functors in terms of high order polymorphism, they do actually provide a mechanism for abstracting over type constructors. However, because a new module is created when a functor is applied, it tends to be much more obvious at the call site which concrete types are involved, which is helpful for reading/debugging—though perhaps this is less of an issue for a seasoned Haskell developer?

Another issue with functors vs. type classes is that, since defining and/or adding operations to a functor is more laborious,* it can have the effect of limiting their use to when it is really necessary. I’m not trying to defend OCaml’s inelegance in this regard. It’s kind of a wart on the language, at least relative to Haskell. The irony is just that, since we tend to be more judicious in our use of functors, codebases overall tend to be more readable.

And code readability is important—especially since documentation in the OCaml ecosystem is, on average, horrible. :rofl:

* Well, implementing type classes also has its unpleasantness in some cases. i.e. I have to implement Applicative and Functor in order to implement Monad, wherease the structural typing of OCaml modules means that this nonsense never happens. That’s not to say that type classes overall are less elegant.

2 Likes

Nice article.
Notice the Haskell and OCaml codes in “Sum of all numbers in a string” aren’t equivalent, in a way that IMO makes OCaml look a bit more convoluted. The Haskell equivalent should handle bad strings too, to be comparable.

main :: IO ()
main = do
  putStrLn $ show $ strSum "100  -42 a"

Command exited with code 1.
Errors

Main: Prelude.read: no parse
# let () = print_int @@ str_sum "100  -42 a"
58

More generally, I think this code is not representative of real-life situations in the sense that real-life code usually doesn’t reside in the Prelude but in modules that you must import (as OCaml does with List. and String.). It would be nice to come up with an example that show this.