What are the biggest reasons newcomers give up on OCaml?

I didn’t say it was. I asked a question. Obviously only a survey of newcomers could tell us if it were typical.

Agreed, but given the context of this thread, you could see how there could be a misunderstanding.

Ok, we’re at 260 messages, let’s go for 300!

Here’s another way of asking more or less the same question:

The heart of Ocaml is the module system. Get rid of it and you have just another pedestrian FP language.

The module system is defined in the language. But for practical reasons programmers like to break their program code into file system units. So it is unavoidable that the language should address the mapping from language to file system. OCaml goes for the minimum (which fwiw I think is the right idea): a .ml file counts as a structure, and a .mli file counts as a signature. But no namespacing. That’s a big issue. So we get module aliases, which are complicated. What is the relationship between `module bar = Foo__bar" and the file system? It depends. Long story short, at a very basic level (source files, meaning both interface and structure files) you have a relatively complex story to tell the newbie about this module stuff. Not even counting things like: you don’t need the compiled implementation of a dependency,you just need its compiled sig, in order to compile something that depends on it. Unless you want cross-module optimizations, that’s different. Then there are includes.

Personally I think this kind of stuff is core to understanding OCaml. The original question is looking for reasons newcomers give up on OCaml. I would like to know to what extent this kind of stuff contributes to surrenders, as opposed to the non-module language stuff.

I’d be curious to hear what your opinion on Rust, or how you view its build processes if you’ve tried to use it.

From what I understand, barring the borrow checker, Rust is apparently a very beginner-friendly language — partially evidenced by the increasing number of front-end/web-devs who are now building utilities using Rust rather than nodejs.

However, as someone who’s tried to build tooling on top of Rust’s ecosystem, I’ve found the cargo build process to be as opaque, if not more than what dune might seem like to a beginner – it’s very hard to work out what cargo does in terms of low level build artefacts such as object files and libraries — I think rust has its own intermediate library format rlib that are completely hidden from the user under normal processes.

Have you looked at Rust? did you also find its build processes to be opaque? if so, then this might be a counter-example to show how knowing how the build-process works is important for being easy to pick up for beginners.

In fact I have, at least for building (but not writing actual code) and I think that’s an excellent point of comparison. I’ve seen many messages in various places on the web extolling the virtues of cargo, but I’ve never understood why. I started bazeling OCaml for the Mina protocol project which had a few rust dependencies. At the time (a few years ago) one used cargo-raze to generate bazel stuff from toml files. It was a major major PITA. I don’t remember the details, but my recollection is that toml files are (were) just kind of ridiculous. Or maybe it was crates that were preposterous, I don’t recall. What I do recall is that it was very very difficult to enable/disable features.

I wanna ask the folks who think cargo is so great: have you ever actually used the stuff in anger? Read the docs? Cause I did and I found it just as maddening and insane as your garden variety Java build system. Again, I don’t remember the details but the specification of dependencies was particularly galling, if I recall. Or maybe it was [features]. In any case it was very far from simple or clear.

I think maybe its a classical case of “works great until it doesn’t and then you’re in for a world of hurt”.

But to the larger question of what blocks new users: Rust has the advantage of funding, I guess. Tons of documentation - that stuff does not write itself. So even if cargo sucks as much as the next build system, well, good docs and examples can cover a lot of blemishes.

Golang is another good point of camparison. I dunno much about it, but I gather it is possible to derive an entire build metaprogram from the sources. I don’t think you can do that in Rust (not sure), but I know for a fact it is not possible even principle with OCaml, due to the open compiler option and module aliasing. Some kind of metadata is essential. Standardizing that kind of stuff might be a good step. For example from source code alone you cannot infer either namespacing (dune ‘library’ components) or archiving. Should the standard toolchain standardize such metadata? I dunno.

1 Like

Rust’s cargo is great, if you’re not actively trying to bypass it by using bazel. A bit like dune in that sense.

The documentation, if you write rust code (not something where you try to build a C library, I must say I haven’t tried that) is pretty decent. Cargo generally just works™ in a simple way, and that’s why people love it.

(Also, in rust, the module structure is derived from files. You start with main.rs or lib.rs, and declare (sub)modules explicitly, and it mirrors directory structure. It’s pretty straightforward really. OCaml doesn’t have that.)

3 Likes

The core problem: a dependency analysis tool like ocamldep or codept can tell you that module A depends on module B, but cannot tell you that module B is in archive Foo, or that it is in namespace X and you need to open X. If we had a standard metadata format that could provide such info in an easily parsible format then we could hope to have more tooling options than Dune. Bazel obviously but why not ninja or any other build system?

I use Cargo / Rust. I think it’s great. As long as there is no build.rs involved, I’ve never had any problem with Cargo.

Clearly cargo must have something going for it, lots of experienced devs like it a lot. But I think the relevant question is: how well does it work when you need to integrate your rust stuff with other languages, like OCaml? I’d be happy to eat my words if it works as well with other languages as it does as an internal rust thingie.

I have never seriously attempted this. The most ‘advanced’ thing I have done with cargo is cargo build --target=wasm32-unknown-unknown, which generates wasm files along with js bindings, which I can call from JS land.

I might have played with Python/Elixir/OCaml bindings for Rust, but never did anything serious enough to comment.

Here’s the part where I take it back: we’re talking about OCaml inhibitors. Integration with rust (or whatever) is probably not a newbie inhibitor. But it is fair to compare the Rust build system with the OCaml build system. Of which there is not one, that’s one difference. But if cargo works great for people writing rust code, we should ask how we can match that in OCaml. I personally like the tooling agnosticism of OCaml - I don’t want an OCaml equivalent of cargo, i.e. an officially blessed build system. Some people like Dune, some don’t. Personally I think Bazel beats the snot out of Dune, but some may think, however wrongly, that is not objectively true. In any case, I think the only way OCaml can begin to compete with Rust (and others) in this respect is to adopt some kind of standardized build metadata format so that we tool developers can work our magic.

1 Like

I think the main difference in our experiences and yours is what we are trying to achieve with the build tools.
I use dune to build ocaml code, cargo for rust, and maven for java. I’m not trying to convert things to bazel, my not trying to integrate this language together.
If I need to use one code base from another language, I’ll make that a service and my integration point will be proto or something similar.
I get that they may be more complex need out there, and you have to deal with some of them. Sorry, but it’s going to stuck. None of the 3 tools are listed are meant for your use case, and it’s a common thing in modern build tools. But honestly, it’s a niche use case that few people care about, even experienced Devs.

2 Likes

It might be I am incompetent with Cargo, but in my mind Cargo & dune are not comparable.

Cargo, to me, was very easy to understand. You can specify a list of dependencies, with version number / git repo / flags. You can do some conditional stuff based on OS / target. That’s it. I don’t know how to do anything else with Cargo.

Dune, on the other hand, to the best of my current understanding, is like a Makefile in s-exp notation, with lots of builtins.

You can copy files in/out of _build/* ??? I have no idea how to do this in Cargo.

You can call arbitrary programs to generate *.ml files on the fly. ??? I don’t know how to do this in Cargo.

If we limited due to just (libraries ...) clauses, it’d have close to the power of Cargo (apologies to Cargo experts; I don’t know how to use Cargo for anything else).

I certainly appreciate the power of dune, and am happy to learn all the cool tricks people are using dune for, but yeah, was definitely (and still is) a stumbling block for me.

I want to reshare OCaml By Example as a great starting point for experienced devs coming from other ecosystems. Thanks @mimoo!

I recall another beginner issue of mine. It gets tricky though when you want to use libraries and projects that depend on different versions of ocaml. Having to manage opam switches takes a few tries to understand but also not too difficult. Is dream incompatible with eio? - #11 by anmonteiro. Still appreciative of all the work going into all these tools atm. Back to building.

2 Likes

Take a look at Features - The Cargo Book and then tell me how easy cargo is.

Yes, great resource! I might as well add: https://github.com/obazl/demos_obazl/tree/main/makefiles
A set of makefile-based builds that I put together when trying to figure out the ocaml build discipline. I dunno if they all still work but they’re pretty simple.

1 Like

I’ve used that before.
Proof: Passing "feature requirement" to child crates - The Rust Programming Language Forum
At one time, I was dealing with a particular nasty setup that tried to get one codebase to build for both x86 and wasm, so there were feature flags for web_sys, stdweb, sdl2, and something else.

I made a typo in my original post. When I wrote “flags” ; I meant to write “features”. My mistake.

==

Anyway, I genuinely think the “Features” feature of Cargo is not all that complex. What is confusing however, is build.rs – which I’ve never gotten comfortable with.

I maintain my belief that Cargo is much easier than Dune because at the end of the day, Cargo fetches the deps (configured with right feature flags), build modules separately, then links them.

In contrast, Dune has the flexibility of a Makefile.

Example: one thing I’m trying to do with Dune, which I’d never attempt with Cargo – is that whenever adt_data/* changes, have it spit out types + ↔ json code + ↔ binary code for all the types involved, for OCaml, TypeScript, and Rust.

I.e there is a build step of my dune setup which does codegen for 3 languages. I’m attempting this in Dune, would not try it in Cargo.

1 Like

Not my experience. See e.g. Raspberry Pi binaries? · helix-editor/helix · Discussion #5841 · GitHub

1 Like

Right, I’ve mostly just installed rustup (via my OS’ package manager),
then cargo through it on the stable toolchain. Looks like you might have
skipped the step where you install rust?

In comparison to the standard way to install a OCaml toolchain, rust’s
is ridiculously simple imho.

1 Like

Care to say why? :slight_smile:

I mean, from my limited perspective, bazel is a huge, java based, RAM
eating tool that will probably not integrate properly with ocamllsp.
Dune is a purpose built, lightweight build system that actually knows
how to build OCaml code. My biggest complain with it is the limited
language for writing rules, but I’m not sure I’d rather be writing
starlark (!).

3 Likes