I recently got into OCaml again. I explored it a few years back, then the tooling was very buggy and the ecosystem didn’t feel quite as mature for web dev, which is what I do.
Trying it again, I am testing it with Dream and it is working… just amazing. Even though I haven’t touched ocaml, and the last time I did it was 4 years ago with reasonml, I have been able to be super productive in the language! Just to list a few of the pros that I noticed:
The build speed is blazingly fast! Over the years, I’ve learnt the importance of compilation speed and having rapid feedback. This is what turned me off Haskell, it was just so slow. Slow iteration speeds are the bane of developer productivity and ocaml just allows you to iterate FAST.
Speaking of Haskell, navigating others codebases is just so easy. The language is very intuitive, and with my basic knowledge of monads I am able from the get go to manage LWT, option + more and make them work seemlessly together. I struggled with this quite a lot in Haskell with monad transformers etc.
The last time I tried ocaml, the lsp didn’t work great for me. Now it is just working flawlessly along with the ocamlformatter, it is also giving me great fast feedback.
The testing tools are great and mature, making it so easy to test your code with inline tests! Compare that to node + jest which i’ve used in the past and it’s night and day.
The ecosystem is surprisingly mature for web dev now. The tooling is great for compiling JSON, sql, generating html etc. There is similar tooling in Haskell, but using the code generators typically slowed compilation down to a crawl, here it remains very snappy! The documentation also comes with proper man pages for dune, ocamlformat etc.!
Even though there I had some dependency issues with opam+dune, it is great that it all compiles down to a single binary. It makes it very easy to incorporate and deploy. The dependency hell did force me also to use a proper tool to manage environments, nix, but maybe that’s a blessing in disguise?
And that’s not even mentioning how clean and powerful the language itself is. It really feels like it was built to be pragmatic and allow you to build good software with good practices.
I must say that I think ocaml is one of the most underrated languages out there. I hope I get a chance to use it outside of just hobby projects.
API discovery with Merlin auto-completion worked beautifully, right out-of-the-box. (This may have been the case in 2019 already, but I never bothered with Merlin back in the day since I knew the OCaml libraries in-and-out in those days.)
Wow, just wow! All the work that so many people have been pouring into the language and its ecosystem has really paid off.
One more point. I always felt that the JaneStreet libraries were of very high quality, but it seems to me that the story here has improved even further. Basically, base, stdio, ppx_jane, and pxx_inline_test are an incredible foundation that is all I need in 95% of cases:
In particular., no need for experimenting with random ppxes unless you need something niche.
Once you set (env (dev (flags (:standard -warn-error -A)))) in your dune-workspace, that is! Since I have given so much praise, allow me one word of criticism: dune’s choice to fail the build on warnings, by default, struck me as really unfortunate. I agree with the “strict by default” mantra that probably drove this decision, but the outcome is bad. Interactive development with dune runtest --watch --autopromote in the way I described is hardly useful with the default, because as little as an unused identifier (which you have all the time during development!) will fail the build and prevent the --autopromote feature I praised so much. Perhaps the dune folks could consider adding a fastdev profile that is dev withouw -warn-error? ↩︎
Rather than changing your dune config, do you know you can silence warnings by putting this metadata at the top of your file?
This approach is perfect for me. Add it when iterating and remove it at the end when cleaning up.
OCaml is indeed a wonderful language! When reflecting, I often feel that it captures the essence of functional programming (or at least what’s important for me). And there’s a “simple” feeling that emanates from the language and its ecosystem that feels quite enjoyable IMO. The type system also allows to to express things quite abstractly if required.
I have yet to explore Haskell further though, and overall I’m still in the learning phase.
Yes. But my point was that this ought to be the default during development, IMO. I’d want warnings to block the submission of code, but not the compilation of code (and in particular not the auto promote feature!).
EDIT: Also imagine the experience for a newcomer who has just installed OCaml and dune following some instructions on the internet. Do we really want the compiler to scream at them when they write their first program and have an unused variable? There is undoubtedly value in being strict during code submission, but during iterative development, it feels very pedantic and IMO makes people focus on the wrong things at the wrong time.
EDIT 2: I also understand that being lenient is a slippery slope. Many users may ignore the warnings, and dune has no mechanism by which it could ensure that warnings are treated as blockers during code submission. So we may end up with more bad/buggy OCaml code in the ecosystem. But I fear that the price we will pay for being pedantic by default is that we turn off newcomers, and my guess is that the default might be so annoying that many experienced people immediately overwrite them anyhow.
I agree and I think this should be a priority. As I wrote in another post, a big part of OCaml newcomers every year is made of thousands of students. They usually don’t need sophisticated stuff but it should be as smooth and useful as possible.
The problem is that Dune has no “debug”/“playing around” and “release” - or “dev” and “prod” or whatever you want to call them - builds as default. Yes, of course you can add them by hand, but the only reason to use Dune is it’s “magic”.
But it does, --profile=release, which is implied if you use the -p option, where a lot of the warnings are disabled for release.
In my opinion it is kind of sensible, like in C where you would develop with -Wall -Werror -pedantic but the release should not have these set as new warnings of new compiler versions will break your build in the future. And the least annoying way to deal with the warnings is to have them emitted when developing instead of being confronted with 1500 warnings when you want to do a release and have forgotten what the code is about (or have gotten it merged in a PR and don’t fully know what the author did).
The unused variable warning can be annoying, but you can either start the variable with an underscore or use --profile=release temporarily when you comment out a lot of code. This has worked fairly well in my experience and is much preferable to forgetting to use some data.
I’ve been playing around with Roc, and they do something really interesting where the build can “fail” due to even incorrect typings, yet you can run roc run my-app and it’ll still try its best. I’m guessing it runs in interactive mode. But the really neat thing is that you could theoretically still run a test of interest, even though you have some other area of your code that’s broken.
No idea if that’s possible in OCaml, but that would be a real boon to development productivity. I also like how it errors in Roc by default, but tells you what command to get it to go anyway.
This is partly what the issue I linked to is about (cf. second item). I hadn’t written the env stanza (which we use with our students) explicitly but this is what I was referring to. As well as the fact that we need tests (à la ppx_inline_tests) to be re-run at each dune invocation (which, IIRC, isn’t the case).
IMO, the right behavior would be for dune to consider both fatal and non-fatal warnings as an output of each build command. Any command which is skipped because dependencies have not changed should still show again the warnings generated on the initial run. And the notion of being fatal or not would only be taken into account for the final status of the build (ok/failed); or possibly, a fatal warning should be observable and be used to prevent e.g. the promotion of the final executable.
But really, in the current situation, a non-fatal warning is almost useless (it’s very likely to go unnoticed), and a fatal one if overly annoying during development.