Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> why isn’t OCaml more popular

I've used OCaml a bit and found various issues with it:

* Terrible Windows support. With OCaml 5 it's upgraded to "pretty bad".

* The syntax is hard to parse for humans. Often it turns into a word soup, without any helpful punctuation to tell you what things are. It's like reading a book with no paragraphs, capitalisation or punctuation.

* The syntax isn't recoverable. Sometimes you can add a single character and the error message is essentially "syntax error in these 1000 lines".

* Ocamlfmt is pretty bad. It thinks it is writing prose. It will even put complex `match`es on one line if they fit. Really hurts readability.

* The documentation is super terse. Very few examples.

* OPAM. In theory... I feel like it should be great. But in practice I find it to be incomprehensible, full of surprising behaviours, and also surprisingly buggy. I still can't believe the bug where it can't find `curl` if you're in more than 32 Unix groups.

* Optional type annotation for function signatures throws away a significant benefit of static typing - documentation/understanding and nice error messages.

* Tiny ecosystem. Rust gets flak for its small standard library, but OCaml doesn't even have a built in function to copy files.

* Like all FP languages it has a weird obsession with singly linked lists, which are actually a pretty awful data structure.

It's not all bad though, and I'd definitely take it over C and Python. Definitely wouldn't pick it over Rust though, unless I was really worried about compile times.





I would suggest that people interested in using OCaml on Windows (or indeed, OCaml at all) try F# instead. It is still an ML-family language. Incidentally, by targeting the CLR, F# is considerably more deployable (both to users and to developers) than OCaml is. Plus, any old NuGet library written in C# or VB.NET can be used almost trivially in F#. This also solves the problem OP listed about a tiny ecosystem, because the C#/.NET ecosystem is massive.

I couldn't agree more with the parent commenter about OCaml documentation. Functional programmers appear to love terseness to an almost extreme degree. Things like `first` are abbreviated to `fst`, which is just odd. Especially now that good IntelliSense means there is no real functional (heh) difference between typing `.fi` and pressing Tab, and typing `.fs` and pressing Tab.

The F# documentation is comparatively very spiffy and detailed, with plenty of examples[1][2][3].

[1]: https://learn.microsoft.com/en-gb/dotnet/fsharp/language-ref...

[2]: https://fsharp.github.io/fsharp-core-docs/

[3]: https://fsprojects.github.io/fsharp-cheatsheet/fsharp-cheats...


To decide on a language for a math research project, I implemented a toy problem in many languages: What is the distribution of cycle counts for signed permutations on n letters? Use all cores in parallel.

  C++  100  19.57s                 
  Rust  96  20.40s                 
  F#  95  20.52s                  
  Nim  75  26.04s                  
  Julia  64  30.40s                 
  Ocaml  48  41.07s                 
  Haskell  41  47.64s                
  Chez  39  49.53s                 
  Swift  33  58.46s                 
  Lean  7  278.88s                 

  Tarjan, n = 10
  Nyx - Apple M4 Max - 12 performance and 4 efficiency cores
  n! * 2^n = 3,715,891,200 signed permutations
  score = gops normalized so best language averages 100
  time = running time in seconds
This had me briefly smitten with F#, till I realized the extent that rusty .NET bed springs were poking through. Same as the JVM and Clojure, or Erlang and Elixir. The F# JIT compiler is nevertheless pretty amazing.

I nearly settled on OCaml. After AI warning me that proper work-stealing parallelism is a massive, sophisticated project to code properly, the 40 lines of OCaml code I wrote that beat available libraries is my favorite code file in years.

Nevertheless, once one understands lazy evaluation in Haskell, it's hard to use any other language. The log slowdown for conventional use of a functional data structure becomes a linear speedup once one exploits persistence.


I would really like to read more about this!

No Fortran?

Interesting, would be a good blog post to read.

F# pretty much assumes you already know C#. I found that it made it hard to get up to speed on it. I really do enjoy the the syntax though.

F# is indeed an ML-family language, but at this point it doesn't have all that much in common with OCaml specifically: it doesn't have the latter's structurally typed object model with inferred row-polymorphic types, nor its extremely powerful module system, not even many convenience features like open variants.

F# has plenty of features that OCaml doesn't have like proper cross-platform support, proper deployment, active patterns, units of measure, computation expressions, type providers, interfaces on unions and records, etc. F# is probably faster as well and definitely has a larger ecosystem through .NET.

I find F# nowhere near as good as OCaml and think this comparison is ugly, but if I was forced to use a .NET platform I would almost certainly use F#

> nowhere near as good as OCaml

I'd really like to hear more about this. From what I've used of F# and OCaml, both languages are around 95% the same.


I'll give you my top 3.

F# is worse because the type inferencing isn't as good. You need to type annotate in more places. It's a drag, because it feels like a missed opportunity to let the machine do work for you.

Additionally, one of the most pleasant and unique features of OCaml, strong named arguments, doesn't exist in F# (except in methods or whatever). Most programming languages don't have this (or it's hamfisted like in more dynamic languages) so it doesn't seem like a loss, unless you are very cozy with them in OCaml.

(I'm bitter about this one because named arguments give safety and convenience. But when you combine them with currying it's like 4 or 5 design patterns that are elegantly provided for you. You just do it implicitly, without ever having to study a book about using them.)

Finally, F# brings back the NULL problem that OCaml worked so hard to eradicate.


interesting. how named arguments work with currying?

And you cannot have default arguments for function parameters in F#. They are only allowed with methods.

If you are interested, I think I adress this here https://xvw.lol/en/articles/why-ocaml.html#ocaml-and-f

Thanks, this is a very nice article.

> * OPAM. In theory... I feel like it should be great. But in practice I find it to be incomprehensible, full of surprising behaviours, and also surprisingly buggy. I still can't believe the bug where it can't find `curl` if you're in more than 32 Unix groups.

I couldn’t believe this was an actual bug in opam, and I found it: https://github.com/ocaml/opam/issues/5373

I don’t think that’s an opam bug, it’s an issue with musl, and they just happened to build their binaries with it.


I kinda feel that singly linked lists isn't a data structure in FP as much as a (dynamic) control flow structure. It's okay in that application.

A lot of these are learning curve issues, but once you scale them you still permanently suffer with a very deficient, fragmented ecosystem. Until you have built up your own curated set of dependencies for your business stack, anyway.

> * Ocamlfmt is pretty bad. It thinks it is writing prose. It will even put complex `match`es on one line if they fit. Really hurts readability.

I suggest configuring ocamlformat to use the janestreet profile for better defaults.

> * Optional type annotation for function signatures throws away a significant benefit of static typing - documentation/understanding and nice error messages.

People should be providing .mli files, but don't. That said, an IDE with type hints helps this enormously. The VS Code plugin for OCaml is the best experience for noobs, hands down.

> OPAM

yes


> * Like all FP languages it has a weird obsession with singly linked lists, which are actually a pretty awful data structure

This made me chuckle. I've had that thought before, shouldn't the default be a vector on modern devices? Of course other collection types are available.


The reason why functional languages like linked lists so much is because they are very easy to make immutable without a lot of pain all around. If you implement an immutable vector in a straightforward way, though, you basically need to do a lot of copying for any operation that needs to construct one (the rigmarole with immutable strings, and existence of hacks such as StringBuilder, is a good illustration of the problem).

But you can have a data structure that is more like vector under the hood while still supporting efficient copy-with-modifications. Clojure vectors, for example.


That makes sense. But LinkedIn lists are horrible for cache efficient things no? I think there was an article on HN from a Golang perspective padding structs or something such that they are efficient wrt to cache hits.

> But LinkedIn lists are horrible for cache efficient things no?

LinkedIn Lists you say? (Sorry. (But not that sorry.))

Edit meta sidenote: in this modern meme world, it's virtually impossible to know if a typo is a typo or just a meme you haven't heard of yet.


lol my phone autocorrected ugh.

But you know what I mean. Some grace would be nice.


I know. It was just too hard to pass up. :)

Well you got me. I audibly groaned and slapped my forehead.

Which is a pity, you can fit entire small arrays in a cache line to end up with no pointer chasing at all.

I'd say that functional programming is probably one of the only domains where linked lists actually make sense. Vectors certainty have their use in more places though.

A good compiler will make the lists disappear in many cases. No runtime overhead. I actually love single linked lists as a way to break down sequences of problem steps.

Why would a specific way of structuring data in memory be relevant to breaking down sequences of problem steps?

If what you mean is the ability to think in terms of "first" and "rest", that's just an interface that doesn't have to be backed by a linked list implementation.


In Haskell, yes, because laziness permits deforestation. ML, including OCaml, is eager and consequently cannot do this.

I believe it's possible in theory - Koka has a whole "functional but in-place" set of compiler optimisations that essentially translate functional algorithms into imperative ones. But I think that's also possible in Koka in part because of its ownership rules that track where objects are created and freed, so might also not be feasible for OCaml.

I mean, the set of valid deforestation transformations you could do to an OCaml program is not literally the empty set, but OCaml functions can do I/O, update refs, and throw exceptions, as well as failing to terminate, so you would have to be sure that none of the code you were running in the wrong order did any of those things. I don't think the garbage collection issues you mention are a problem, though maybe I don't understand them?

Part of what Koka's functional-but-in-place system relies on is the Perceus program analysis, which, as I understand it, is kind of like a limited Rust-like lifetime analysis which can determine statically the lifetime of different objects and whether they can be reused or discarded or whatever. That way, if you're, say, mapping over a linked list, rather than construct a whole new list for the new entries, the Koka compiler simply mutates the old list with the new values. You write a pure, functional algorithm, and Koka converts it to the imperative equivalent.

That said, I think this is somewhat unrelated to the idea of making linked lists disappear - Koka is still using linked lists, but optimising their allocation and deallocation, whereas Haskell can convert a linked list to an array and do a different set of optimisations there.

See: https://koka-lang.github.io/koka/doc/book.html#sec-fbip


I’m curious what you mean. Surely there’s the overhead of unpredictable memory access?

Not GP but bump allocation (OCaml's GC uses a bump allocator into the young heap) mitigates this somewhat, list nodes tend to be allocated near each other. It is worse than the guaranteed contiguous access patterns of a vector, but it's not completely scattered either.

+10 for bad windows support, i think this is a key and weirdly underestimated reason

just to give an idea how bad, until recently, you could not just go to ocaml.org and download ocaml for windows, you had to either download one for mingw or wsl

so for many it was just not installable, i.e. for many we didnt have ocaml for windows, until very very recently


Opam on Windows is a masterpiece of engineering

The Windows support is bad because OCaml is a PL designed by people who are deep in Linux life. Windows is not something that keeps them up at night. (Which isn't to say they didn't try, just, you know, not as much as it takes)

One of the things people often neglect to mention in their love letters to the language (except for Anil Madhavapeddy) is that it actually feels UNIXy. It feels like home.


> designed by people who are deep in Linux life.

> it actually feels UNIXy. It feels like home.

They use single dashes for long options.

This is not home.

https://linux.die.net/man/1/ocaml


If that's what you use as your yardstick of what's Unixy, then I guess you don't consider "find" to be Unixy, in spite of being one of the early Programmer's Workbench tools.

Short options were a compromise to deal with the limits of the input hardware at the time. Double dashes were a workaround for the post-dash option car crash traditional Unix tooling allows because teletypes were so slow. There is nothing particularly Unixy about any of these options other than the leading hyphen convention.

OCaml using single hyphens is not un-Unixy.


Unix doesn't use double dashes for options; that's a GNU thing, and, as everyone knows, GNU's Not Unix.

Normally the Unix/GNU opposition is irrelevant at this point, but you managed to pick one of the few significant points of difference.


Caml was developed in 01985, Linux was first released in 01991, and Caml was extended into OCaml in 01996. I don't think the developers were using Linux at the time; SunOS 4 would be my best guess. I didn't work on it, but I was in the sort of internetty environment that OCaml came from, and I was using Solaris at work and got my first Linux box at home. As another commenter mentioned, the command line follows Unix conventions rather than the Linux conventions from GNU.

> just to give an idea how bad, until recently, you could not just go to ocaml.org and download ocaml for windows

On the other hand, you could get ocaml for Windows from Microsoft ever since 2005.


F# is not "OCaml for Windows". Not even close.

> The syntax is hard to parse for humans

This was my problem as well, the object oriented related syntax is just too much. ML of which caml is a version of, has really tight syntax. The “o” in ocaml ruins it imo.


No offense but the only time I have encountered objects in Ocaml was while using LablGTK a long time ago and the object syntax was pretty clean and not far from the module one. They are also structurally typed which is very handy.

Considering it only impacts a fairly small subset of the language, could you explain how it supposedly ruins everything?


> The syntax is hard to parse for humans.

Funny how tastes differ. I'm glad it has a syntax that eschews all the noise that the blub languages add.


I wish I could make a list like this about rust in this place and not be flagged.

I don't think you would be flagged. Rust definitely has flaws:

* Compile time is only ok. On par with C++.

* Async has a surprisingly number of footguns and ergonomic issues.

* There's no good solution to self-borrowing or partial borrows.

* While using macros is fine, writing them is pretty awful. Fortunately you rarely need to do that. Relatedly it is missing introspection support.

* Sometimes the types and lifetimes get very complex.

But overall I still much prefer it to OCaml. The syntax is much nicer, it's easier to read, the ecosystem and tooling are much better, the documentation is much better, and it actively hates linked lists!


The problem for me is that instead of focusing on the problem at hand and the business logic, I have to focus on the Rust memory model and the borrow checker. And seems very verbose.

Thing is, if I sketch something in pseudocode, I should be able to translate it to any mainstream programming languages. With Rust I can't just do that, I have to bend the problem to fit the way the language works.


You missed one:

* Crates.io is an unmoderated wasteland of infinite transitive dependency chaos

But my "favourite":

* It's 2025 and allocator-api (or its competitors) is still in nightly only and no sign of stabilizing

I work almost exclusively in Rust, and I like the language but in many respects a) Rust is the language that tokio ate, and b) it has been deluged with people coming from the NodeJS/NPM ecosystem doing Web Scale Development(tm) and its terrible practices around dependency management.


> Crates.io is an unmoderated wasteland of infinite transitive dependency chaos

Well, I'm not sure I agree with that. If by "unmoderated" you mean anyone can upload a crate without going through bureaucratic approvals processes then that seems like a good thing to me!

But you did remind me of one other majorly annoying thing about the crate system: the global namespace. It doesn't have the same security issues as Python's global package namespace, but it is really really annoying because you pretty much have to break large projects down into multiple crates for compile time reasons, and then good luck publishing those on crates.io. Plus you end up with a load of crate names like `myproject-foo`, `myproject-bar`, which is very verbose.

Oh and yeah, the Tokio situation is a bit sad.

Also the proliferation of some types of crates like error handling. This should really be in the standard library by now (or at least fairly soon). Any large project ends up with like 5 different error handling libraries just because dependencies made different choices about which to use. It's not a major issue but it is silly.

Overall though, still my favourite language by far.


It's interesting reading many of the associated comments, because there is a genuinely active effort to address many of the pain points of the language:

* Windows support has improved to the point where you can just download opam, and it will configure and set up a working compiler and language tools for you[^1]. The compiler team treat Windows as an first tier target. opam repository maintainers ensure new libraries and library versions added to the opam repository are compiled and tested for Windows compatibility, and authors are encouraged to fix it before making a release if its reasonably straightforward

* debugger support with gdb (and lldb) is slowly being improved thanks to efforts at Tarides

* opam is relatively stable (I've never found it "buggy and surprising"), but there are aspects (like switches that behave more like python venvs) which don't provide the most modern behaviour. dune package management (which is still in the works) will simplify this considerably, but opam continues to see active development and improvement from release to release.

* the platform team (again) are working on improving documentation with worked recipes and examples for popular uses cases (outside of the usual compiler and code generation cases) with the OCaml Cookbook: https://ocaml.org/cookbook

There are other things I find frustrating or that I work around, or are more misperceptions:

* there isn't a builtin way to copy files because the standard library is deliberately very small (like Rust), but there is a significant ecosystem of packages (this is different to other languages which cram a lot into their standard library). The result is a lot of friction for newcomers who have to install something to get what they need done, but that's valued by more experienced developers who don't want the whole kitchen sink in their binary and all its supply chain issues.[^2]

* the type inference can be a bit of a love/hate thing. Many people find it frustrating because of the way it works, and start annotating everything to short-circuit it. I've personally found it requires a bit of work to understand what it is doing, and when to rely on it, and when not to (essentially not trying to make it do things it simply will never be able to do).[^3]

* most people use singly-linked lists because they work reasonably well for their use cases and don't get in their way. There are other data structures, they work well and have better performance (for where it is needed). The language is pragmatic enough to offer mutable and immutable versions.

* ocamlformat is designed to work without defaults (but some of them I find annoying and reconfigure)

Please don't take this as an apology for its shortcomings - any language used in the wild has its frustrations, and more "niche" languages like OCaml have more than a few. But for me it's amazing how much the language has been modernised (effects-based runtime, multicore, etc) without breaking compatibility or adding reams of complexity to the language. Many of these things have taken a long time, but the result is usually much cleaner and better thought out than if they were rushed.

[^1] This in itself is not enough, and still "too slow". It will improve with efforts like relocatable OCaml (enabling binary distribution instead of compiling from source everywhere) and disentangling the build system from Unixisms that require Cygwin.

[^2] I particularly appreciate that the opam repository is actively tested (all new package releases are tested in a CI for dependency compatibility and working tests), curated (if its too small to be library, it will probably be rejected) and pruned (unmaintained packages are now being archived)

[^3] OCaml sets expectations around its type inference ("no annotations!") very high, but the reality is that it relies on a very tightly designed and internally coherent set of language constructs in order to achieve a high level of type inference / low level of annotation, but these are very different to how type inference works in other languages. For example, I try and avoid using the same field name in a module because of the "flat namespace" of field names used to infer record types, but this isn't always possible (e.g. generated code), so I find myself compensating by moving things into separate modules (which are relatively cheap and don't pollute the scope as much).




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: