My Thoughts on OCaml vs Haskell/Rust in 2023

Recently, osa1’s My Thoughts on OCaml generated quite a robust conversation on Hacker News. Overall I felt the blog post was a bit too critical about OCaml. However, everyone has a right to their opinions and I respect whatever has been written.

Except for a couple of points, that post didn’t resonate with me, so I thought I should pen down my good/bad experiences with OCaml and see if others have felt the same way as me.

I’ve written a longish blog post here as sort my reponse! Please check it out!

Your feedback and comments would be useful!

If people think this is a worthy blog post, I’d like to post my blog on Hacker news also :-). If anybody feels enthusiastic about this write up do feel free to put it up on Hackernews yourself too!

19 Likes

Nice! Potential nitpick: you write “ppxlib (for metaprogramming in OCaml) doesn’t work with objects/classes”. That’s somewhat unfair, since there’s class-based AST traversal (mapping and folding) supported. Or maybe it’s unclear what you mean.

My understanding is that ppxlib will allow you do meta programming on modules and module signatures but not on OCaml objects and classes. So you would not be able to attach @@ anotations to OCaml object methods and so forth.

Am I wrong?

I am not sure how you arrived at the notion that ppx annotations did not cover classes?

A quick glance with ocaml -dsource can disprove the notion:

class c = object method m = () [@@method_annotation] end;;
1 Like

Sorry, I relied on something based on recall which was wrong. Happy to have been corrected! Thanks!

P.S. Also @octachron @lukstafi – any other feedback would also be greatly appreciated too. Other things wrong? General agreement/disagreement with blog?

You seem to conflate “ad hoc polymorphism”, which is missing from OCaml but might one day arrive as modular implicits, with nominal typing (as opposed to structural typing). While OCaml’s support for structural typing has no equal, OCaml also supports nominal typing in a few ways: plain records are nominal, abstract types are nominal… There’s no built-in subtyping for nominal types, but one can have injection functions.

I think a positive of OCaml is that it’s a big language feature-wise, and yet the features play well together and each is there for a good reason.

6 Likes

Really appreciate your write-up! Being a Haskell user myself for quite a while, it’s interesting to hear thoughts about the language and some valid and constructive criticism.

Some time ago, Don Syme wrote some criticism of typeclasses. I do love typeclasses in Haskell and traits in Rust, but that post resonated with me and made me appreciate the lack of typeclasses as well. Good for mental sanity while using languages that lack the features you love :relieved:

4 Likes

He suggests OCaml has the dangling else problem? As I see it, you can only claim that if the parser does not address the issue…

He wrote “The only thing that makes OCaml more ‘functional’ than e.g. Dart, Java, or Rust is that it supports tail calls”… Do any of these languages have currying?
No mention of modules…

For someone that worked on GHC, one could hope for something better.

Overall, he has some grievances that seem legitimate to him (fair enough), but the analysis, the tone, and the attitude are not serious…

4 Likes

Some compact feedback on osa1’s post:

  • I agree that having “something like typeclasses” in OCaml would be nice, and that this is one the main pain points with OCaml these days.

  • I wasn’t so impressed with the criticism of the standard library. Most languages get weird issues with their standard library over time (for example people frequently complain about Haskell’s head function being in the prelude and have arguably “fragmented the ecosystem” with many alternative prelude proposals), and having other libraries offer better designs or better feature coverage is fine with me. We get into composability trouble for problem domains that require a sort of central coordination, for example Lwt vs. Async vs. Eio, but mixing stdlib-using code with core/containers-using code has never been much of an issue in my experience.

  • I broadly agree with the criticism of OCaml’s syntax, which has various flaws. Most language have syntactic flaws. Indentation-sensitive language have much less lack-of-bracketing-related flaws of course, but this comes with its own cost. I think that occasionally complaining about the syntax is an acceptable trade-off of using OCaml. (In fact people tried hard to offer better syntaxes at the time of the Camlp4 revised syntax, and most people never bothered because the issues of the current syntax are not that bad in practice.) I do wish we had better syntax error messages, and there is slow-but-existing progress on that (lrgrep)

My thoughts on @sid’s post:

  • I think that comparing OCaml and Haskell is fine, but I think that people also have a point to compare to larger ecosystems with better tooling (in some respects), such as the JVM or the dotnet ecosystem.

  • I agree that OCaml is “practical” and I think that is one of its main strength among the nice functional programming langages out there, even including Rust. If you forget about the tooling for a minute and think just of the code, programming in OCaml is typically easier than programming in Haskell or Rust, with a lower learning curve.

  • Another aspect that was not emphasized in either posts is that OCaml is, in some respects, “small”. The stdlib is small, the runtime is small, the language implementation is less sophisticated than many out there (including GHC, the JVM, dotNet runtimes, Javascript engines…). OCaml as its core is a 80/20 language, getting 80% of the benefits at 20% of the complexity. Other programming language implementations have managed that, for example Chez Scheme is rather impressive in this respect. And OCaml is arguably losing some of this simplicity as time passes. But I think it still matters when thinking about the whole system: OCaml has less sophisticated optimizations than Haskell and a less advanced type system, it doesn’t benefit from the impressive JIT optimizations provided by production dotNET or JVM runtimes, its compiler backend is nowhere as featureful as LLVM as used by Rust, etc., but it still gets a large fraction of the value of these sophisticated systems with simpler approaches, which (1) contribute to an overall simpler ecosystem (which I think is a virtue, even if users may not care directly), and (2) has beneficial side-effects in terms of user experience, for example: fast enough compile times, or a bearable experience using system tools (gdb, perf, etc.) to study a running program. I think that some of the downsides of OCaml and its implementation can be assessed more positively with this systemic quality in mind – which is no excuse to provide a bad experience overall, of course! (@sid touches a bit of this in the TL;DR paragraph at the end.)

  • In some ways, the OCaml runtime has arguably become more advanced than the Golang runtime (and maybe the Haskell runtime).

    I don’t agree with this and I think that it is the one aspect of the post where our affective relation to programming languages are putting rosy glasses on our eyes. The Go and Haskell runtimes are really impressive pieces of engineering and I they have years of production experience for mode of usages that we are now just trying in the OCaml world. Both the OCaml 4 and OCaml 5 runtimes are fine pieces of software, but I don’t think one could objectively see them as “more advanced” than the Go or GHC runtimes. It’s great that we have some primitive support for effects, but Go has had flexible control primitives underlying goroutines for a while, and Haskell (besides an impressive and sophisticated concurrent runtime and IO system) has recently benefitted from lexi-lambda’s impressive delimited-control primops work that certainly opens the door for runtime-supported algebraic effects for Haskell.

14 Likes

Thanks for your detailed feedback!

Yes, I think I overstated the impact of having support for effects in the OCaml 5 runtime now. I’ll correct this claim in the article. The Golang/Haskell runtimes have been doing things for years for which OCaml 5 has just acquired capabilities.

I did mention this in my blog post . My impression is that while the primitives exist, the work to build Eio-like/higher level libraries that use these primitives is still in the preliminary phase. For instance I read somewhere that Haskell might be interested in using io_uring for a future IO manager down the line – this is so that file IO is truly async on Haskell in linux. It would be interesting to find out – will they use C to build this new IO manager or will they use the new primops? If they use C then we can argue that OCaml has adopted a more advanced approach because apart from the lower level bindings to io_uring, Eio is basically written in OCaml.

1 Like

I said it more than once but I think this is a red herring. The pain point is a lack of upstream integrated and mandated runtime type representation.

Even without a built-in deriving mecanism that would improve the eco-system by orders of magnitude, allowing users to define a single value M.repr for their M.t types which can then be used generically by a diversity of libraries of typed indexed combinators (formatters, comparison, equality, ui editors, random value generators, codecs with your pet serialization format, etc.).

19 Likes

It would be great to have an agreed upon runtime type representation. Are there any negatives to having this?

P.S. I’m assuming what you mean is that we should have an OCaml mandated version of things like:

Is my understanding correct?

I don’t think there are negatives, the problem is rather to design a good and forward looking one.

Here are a few others ones than the one you mentioned [1812.11665v1] Generic Programming in OCaml, LexiFi’s runtime types, GitHub - pqwy/tpf: Trivial/Tagless Polytypic Functions.

6 Likes

IIRC the delimited continuation primops in GHC provide multishot continuations by default with no option for linear or affine continuations. This wouldn’t make a useful substrate for implementing user level concurrency.

1 Like

I’ve posted this on Hacker News.

3 Likes

Another example of your idea: ZIO Schema for Scala.

Thanks very much for this very enlightening blog post, @sid.

One very trivial comment: Purescript compiles to a few other backends besides Javascript, including C, C++11, Go, and Erlang: documentation/Alternate-backends.md at master · purescript/documentation · GitHub

That is not at all obvious, though–you have to poke around a bit to find that page–and the the community is clearly focused squarely on Javascript. So what you wrote is just about right anyway.

I’ve seen recent mentions of a couple instances of commercial use of the Erlang backend (PureScript in Industry - #5 by srstrong - Help - PureScript Language Forum), but I have no idea about how often this kind of thing happens.

(I’m not using Purescript. It’s just something I recently became curious about.)

Appreciate your comment !

I’m aware that Purescript has some alternate backends. While I’m not deeply in touch with the language and community currently my impression is that their JavaScript part is really where more most of their (probably not very sizable) user base is. Their alternate backends are going to even smaller than that.

So going from OCaml/Haskell to a niche backend of niche language seemed like a non-starter to me. Rather than caveat/qualify my language about Purescript in the blog post, I just assumed it was really small and ignored it for simplicity.

Thanks for the reference to the Purescript erlang backend success story ! It’s good to know a heartening counter-example. Yes, it would be interesting to learn how much of these non-javascript backend Purescript projects are one-offs by passionate teams or part of a upcoming ecosystem.

1 Like