What are the biggest reasons newcomers give up on OCaml?

If we’re being honest, dune wouldn’t have been born in any organization but Jane Street. 99% of companies wouldn’t develop a build system in the first place, and the remaining 0.9999…% wouldn’t develop two :slight_smile:

But let’s consider this hypothetical seriously. Perhaps they would start with something like json. Eventually, they’d quickly realize that it’s completely unmaintainable when writing all but the simplest build actions. Then they’d give up on json and introduce their own custom DSL.


On the topic of build systems, why did the ecosystem as a whole choose Dune over the other choices? I remember that when I got into OCaml, I didn’t know what build system to use, so I picked Oasis because it was similar to Haskell’s Cabal. Then, I saw that everyone seemed to be using Dune (back when it was called jbuilder), so I switched over.

Did Oasis have problems?

Oh yes indeed. I used Oasis a ton, and it really wasn’t enough. Lots of problems when it came to dealing with C compilation, for instance. Frankly, it wasn’t much better than a simple Makefile. The “tags” file was impenetrable, too.

1 Like

We don’t need a bad contrived hypothetical when we have a good empirical example. Google built blaze internally because automake was unsustainable. They started with Python, not json, because of course you would use a language with real functions. They quickly realized they needed a constrained subset of Python, not all of Python, because letting their coders write arbitrary Python code in BUILD files leads to all manner of eldritch pathologies in CI. Then, because Google needed outside adoption of their new build tool in order manage their absolutely insane quantity of source code they get from vendors and other outsiders, they made the bazel derivative and released that.

p.s. If it were left to me to design yet another competitor to both dune and bazel, then I would almost certainly choose Jsonnet as the language to provide for writing build logic. I’d also reimplement it from scratch in OCaml rather than use the C++ or Scala implementations.

Thanks for the heads up :slight_smile:

I’ll send PRs your way as soon as I think I’ve got something worthy of sharing.

1 Like

I actually like this “feature”. It forces me to put an underscore “_” in front of the unused variable if I really want to keep it for a while. In the end if I happen to use it I just remove the underscore, and if I really never use it it’s still easy to spot and clean it.


this one works fine, once you have dune in place. It doesn’t mention how to get it, does it?

Such missing steps frustrate me immensely and kick me out immediately. They are showstoppers.

Also it’s too verbose for a jumpstart. Editor setup or version control are IMO overkill for a hello, world and not in focus.

P.S.: I once wrote down Bootstrap Raspi4 + 🐫 OCaml | MRo Blog how to get dune starting from bare metal.

1 Like

Those are subjective assessments though, and another user could easily want them in there. Editor setup is already a link away, and the bit about git is minor and can be skipped.

That’s hard because it depends on the OS.

As another example, the Meson build system just hit 1.0. It uses a simple, intuitive DSL for anyone familiar with JS/python/C-like syntax, e.g.

project('tutorial', 'c')
executable('demo', 'main.c')

My concern is that dune will always be operating at a disadvantage due to the obscure syntax choice. This will affect OCaml as a whole as well.

then please explain it.

Is there any OS for which opam install dune does not work for installing dune? (Forgive my ignorance, I don’t develop under Windows.)

Of course for that to work you need an ocaml compiler installed. For that, opam init --comp 4.14.1 or a subsequent opam switch create 4.14.1 works for me: but perhaps that doesn’t work under Windows, dunno. But whatever the answer to that, this may indicate that the quickstart introduces its components in the wrong order, and that the order ought to be (i) install the pre-prepared opam binary as indicated in the quickstart, (ii) initialize opam and install a compiler switch, and (iii) install dune, and whatever else your project needs.

Maybe I am misremembering, but when I first used ocaml, installing the compiler and then dune seemed to be very straightforward. When I was learning ocaml, it was getting used to the language which was the problem, not the infrastructure: however, that was on a unix-like OS

1 Like

It is not that obscure, anyone who’s edited an Emacs configuration file (whether they use it as their main editor or not) can recognize some similarities.
One advantage of dune is that you don’t need to write a lot of build rules, and once you’ve set up a project it requires little maintenance over time, in particular not having to list all files (as long as you follow some conventions on where to place them) is a big advantage.
And more importantly dune builds OCaml correctly and quickly, and has one place to go and read the documentation about the build system. That is an improvement over oasis, where often you had to dive into custom ocamlbuild, etc., and certainly an improvement over custom Makefiles (where I’ve seen inconsistent assumptions errors far too often, or workarounds like forcing -j1 to avoid issues due to an incomplete dependency specification). Having said that I very much appreciate that ‘oasis’ existed at the time, even if it wasn’t ideal.

If syntax really is the problem and a DSL would improve things I don’t think the existence of ‘dune’ prevents that, in fact a well documented syntax for ‘dune’ files should make it possible to write an OCaml DSL to generate ‘dune’ files, and as long as the mapping between ‘dune’ rules and the OCaml DSL is kept 1:1 (so the documentation can apply as is) it might be doable — e.g. ‘dune’ could ship a dune_rules.mli
(A DSL that hides the internals or uses different conventions/naming than the underlying dune file would just make it more confusing when you inevitably have to debug an issue with the build system or want to introduce a new rule: you’d have to understand both the DSL, the implementation details of the DSL and dune)
The danger with a DSL (or using a full OCaml program) is that you can end up with custom logic in your build system (and different projects will end up with their own custom logic), which would make understanding and contributing to existing projects harder. Also that custom logic may become a maintenance burden, e.g. when ‘dune’ didn’t support coverage we had to add custom logic with Jbuild_plugin.V1.send, but when migrating from jbuilder to newer versions of dune all that custom logic had to be deleted and removed (at least if we wanted to use the automatic upgrader).
OTOH if the DSL is OCaml itself then you wouldn’t have to learn a new language to use it, and whatever skills you have in debugging and understanding OCaml you can apply to (or learn to) the build system as well. But it is very important that the OCaml DSL be fully typed (and not e.g. output arbitrary strings to be used as dune snippets, which is what jbuilder/dune’s plugin system did…), and complete (so you wouldn’t have to fall back to write something in the dune syntax).

What is a little bit awkward to explain to newcomers though is why the ‘library names’ you use in dune dependencies (the ‘ocamlfind/findlib’ name) is sometimes different from the ‘opam’ package name (if I’m linking library ‘foo’ why do I need to install ‘opam install bar’ to get ‘foo’?), and why when adding a new library as a dependency you have to update the opam file (or dune-project file) to specify that dependency yet again.

Some linux distributions have this problem too, I want to run the binary named ‘foo’ and I have to guess what package name the distro I happen to use has picked for it (they’re not always consistent), however RPM has a clever solution here: you can specify your dependency on the thing you actually use: if you require /usr/bin/bar, then you can specify that in BuildRequires/Requires and let RPM find it based on the (often automatic) Provides of a package.
If ‘opam’ had the ability to specify ‘library(foo)’ or ‘bin(foo)’, and ‘ppx(foo)’ as a dependency that might simplify this situation (and be fully backwards compatible, you could fall back and specify the opam name to disambiguate if needed), and alleviate the need to declare dependencies in multiple places (you could declare it when declaring the library/executable dependency, including a versioned dependency and have dune generate the right opamfile).


This is a pain point for me when editing the OCaml LLVM bindings and the OCaml compiler itself. LLVM, being a C++ project, uses CMake. The OCaml compiler uses Makefiles and Autoconf.

As someone who recently switched back from zsh to ksh, I thought I’d miss the magic, but honestly it’s not a lot of work to remember to run ‘refreshopam’ and then restart emacs.

I believe it is a classic chicken-and-egg conundrum. Individuals may abandon the language due to a lack of libraries for desired actions such as utilizing certain protocols, utilizing MySQL, utilizing Kafka, or any other desired function. Meanwhile, the absence of these libraries may be attributed to the fact that people have forsaken the language.

I’d argue that in a real-world project running in production this is actually quite bad to have a compiler error here instead of a warning. The reason is that you’re either going to mute it with _ as you indicated, or worse by ignoring the error with a flag. Then you won’t know that your system is messed up in production and potentially runs a bug. On the other hand if it was a warning (which is what cargo does), then you can clean it up at the end once you’re done (and usually CI won’t let you merge code that has warnings)

It would be a useful feature if we could turn errors into warnings during the development.

1 Like

If you’re referring to “unused variable not prefixed with ‘_’” as the error, I thought that that was Dune’s default configuration: certainly when I invoke ocamlc directly, such usage produces warnings, not errors. I’m sure there’s a way to stop Dune from treating them as errors.


You can modify the compiler flags in the dune file via a ‘flags’ field in the relevant executable or library stanza. Unused variables generate warning 26, so you could include a field (flags :standard -warn-error -26) to prevent the warning being treated as an error, or suppress the warning entirely with (flags :standard -w -26). In recent versions of ocaml I believe you can also use predefined names, namely (flags :standard -w -unused-var).

To do this in a more discriminating way you can use attributes.I do not find the attributes manual page particularly helpful (it comprises grammar without a great deal of explanation) but it seems that to suppress a warning about an unused variable x you could define the variable with the built-in warning attribute in the form let [@warning "-26"] x = "test" in, or with a block attribute let x = "test" [@@warning "-26"] in. You can also have module-level attributes such as [@@@warning "-26"]. The warning attribute is in the ocaml namespace so [@@@ocaml.warning "-26"] would do fine also.

There is also a built-in warnerror attribute which works in a similar way as a means of preventing a warning being treated as an error. I don’t know if it stops dune treating warnings as errors (I haven’t tried) but in principle it ought to do so.


It was not.

I got it working so I can use bisect_ppx, and that was worth the hassle on balance, but the hassle was entirely unnecessary and due entirely to the messy state of dune’s documentation.

I still do not understand why I need both a dune-workspace and a dune-project nor do I understand the purpose of this distinction. Some parts of the documentation recommend not checking the workspace file into source control unless some mysterious condition applies, which I think I have because generate-opam won’t pick up the dependency on bisect_ppx unless the workspace mentions it.

Speaking of generate-opam, it always generates a dev-repo line that won’t pass lint because the URL it finds makes OPAM mad. That seems suboptimal. Turns out this was my stupidity. A dumb typo.

These sorts of problems aren’t easy to correct when they arise because the logic that implements them is totally integrated into the dune tool, so fixing them entails trying to move a patch upstream. I think that’s my largest beef right now.