Real World OCaml – 2nd Edition (2021)
dev.realworldocaml.org
dev.realworldocaml.org
Other great resources are Michael R. Clarkson's (from Cornell) videos [0] and book-like format [1].
I took a lot of inspiration from it when playing with rb-trees [2] and functional, ocaml-looking typescript in general [3].
[0] https://www.youtube.com/playlist?list=PLre5AT9JnKShBOPeuiD9b...
[1] https://cs3110.github.io/textbook/cover.html
[0] https://rescript-lang.org/ [1] https://reasonml.github.io/
I'm sure there's a crowd, especially here on HN, that views this as some sort of purity test for the language, but in my opinion it's needlessly obtuse over <insert more readable and traditional and lintable type here> used by modern build systems.
Then there's the fact that several `init` commands don't actually fully `init` a project (and up until recently, the very example in OCaml's getting started documentation was broken).
Then there's this insanity
dune build
dune test
dune exec # hahaha no, no, this doesn't work.
Then you gotta manually tape together crap between opam and dune (and don't get me started on how insane the lockfile situation is -- I swear to god I complained once and someone told me I should learn to use nix!).This is all very fun and yak shave-ey and I generally grin and bear it, but boy do I totally get why most people look at me like I'm a weirdo because I like this language.
Complaining about sexprs, when one's baseline is the completely ad-hoc nature of go.mod files, or cargo toml files, is pretty silly. Further, sexprs are a very common general-purpose serialization path for OCaml values, so dune's selection here is hardly arbitrary.
Yes, `dune build`, `dune runtest`, and `dune exec ...` do "work", i.e. each command has its semantics, which are pretty extensively documented IMO. Is the complaint that they're not the same commands that one uses with cargo or whatever?
You're right that opam and dune used to not interoperate much (or at all), but that has changed a lot in the last year or so, to the point that, _if you want_, you don't have to write opam files at all (i.e. they'll be generated from metadata in your `dune-project` file). Again, well documented, at least IMO: https://dune.readthedocs.io/en/stable/opam.html
The "lockfile situation" is pretty good AFAICT. I've been using them for a year+ with good / expected results.
You're right about `dune init` not doing the right things. I believe that's been resolved very recently IIRC, but then again, I can't recall the last language/build tool I used init-like functionality with; I almost always just copy over config/structure from another project I've worked on, and tweak from there.
There are absolutely real critiques of dune, but "it's not cargo" or whatever is silly.
Dune itself is fine, and even great once everything is working. It's just that you have to mug through tons of documentation that's arranged in pure reference format, instead of in a task-based format, before it's up and running.
Sure, dune generates opam files...except for the (some pretty important) parts it doesn't support. I've had to fall back to opam template files in two different projects to express things that dune-project format doesn't support. It's a leaky abstraction over opam format.
The lockfile situation is incredibly rudimentary compared to Go or even to npm. Check out https://go.dev/blog/supply-chain and tell me opam has 1/5th of the capabilities of go.sum checksums and their overall supply chain security. I know this was a concern for you pretty recently: https://discuss.ocaml.org/t/opam-repository-security-and-dat...
Dune init is actually the one really good piece of UX in dune. Being able to run a command and generate a valid dune component (e.g. not having to remember the `(preprocess (pps ppx_bla))` dance and just being able to do `dune init --ppx=ppx_bla` is pretty cool. I feel like 'I just endlessly copy configs from other places' is not really a point of pride. It reminds me of the bad old days of init shell scripts before systemd :-)
But there's a market for programming languages that OCaml is objectively losing. Can we really not agree that the most popular/recommended build tool requiring you to write a lisp to do basic config probably isn't helping draw new user into the fold?
I doesn't have to be cargo. But the more it rhymes with that degree of user experience, the more the "masses" will discover the things that make ocaml great.
Kind of disappointed facebook didn't run with ocaml, instead of doing the whole Reason thing. They could have been contributing to and using ocaml directly, instead they decided that programmers will freak out if there's no curly braces.
It's also not on the same planet with tooling pain points.
And then you try to use it in combination with opam and you want to blow your head.
- the 'made up langauge' is just s-expressions, not any more made up than NPM using a json file. There's no standard file extension for them though, which is fair.
- I'll take your word on the rest of them, I didn't do anything heavy duty
TBH, that's why you thought the docs were good
> - the 'made up langauge' is just s-expressions, not any more made up than NPM using a json file. There's no standard file extension for them though, which is fair.
The lack of extension is really the problem, as your editor will not format it or add syntax highlighting. It's very 90's
Compared to Rust or NPM.
Shoot even dotnet/nugget is significantly ahead of Dune.
[0] <https://github.com/ocaml-community/awesome-ocaml#package-man...>
There you can see that dune is the only actively maintained viable build system for OCaml. The others are all in maintenance mode or deprecated.
As I think about why those are I think one of the biggest reasons is familiarity, it's easy to get comfortable doing one thing and it truly sucks to feel like you don't know anything again (I think somebody called this the curse of knowledge? Idk).
Lately I've been intentionally the unknown, or, those languages that I'd look at and be like, "but why!?", for example, why would I want to learn or only use recursion when a for/while has worked for pretty much my whole career? or, following the same functional languages traits, why immutable data? seems like a step backwards?
But the thing is that people, quite likely smarter than me, or at least, a lot more familiar with challenges that I'm not familiar with, came up with solutions that diverge from what I know... and I want to know why! Why they came with immutable data or recursion or a language that pretty much everything is a list (LISP)... and the answers are quite surprising and at the same time I feel that joy of discovery that has almost been erased by years in the industry.
So when you come across a language that you have strong feelings/opinions about, wearing the shoes of the people using it might be quite enlightening and enjoyable.
While I say this, I'm a complete hypocrite because I won't give such courtesy to OOP... but hey, none of us are perfect ;-)
2 - If the language is trying to sell you on a certain paradigm? If so, is there a good escape hatch? This at least means a good FFI like that found in Erlang, but also within the language, OCaml's refs which allow you to write imperative code are a great example. Elm is a language that does this badly, 0.19 removed FFIs and the language is much reduced for it.
3. Is packaging a first class consideration of the language? This is one of the places where say, Rust shines and Python really suffers. I'm going to need packages and if managing them is hard I'm going to get mad.
4. Does the language free me from some class of problems? Big positive.
Some languages I've liked: Common Lisp, C, OCaml, Haskell, Python2, Javascript ES5, Racket, Erlang
Some languages I've disliked: Java5 and earlier, C++, Go, Python3, PHP circa 1999
On the other hand it says "real world" OCaml and as far as I can tell that really is how OCaml used so It's hard to blame the author.
Anyway, this has all been hashed out before.
What makes you say this has to do with elegance? This sounds surprising.
For a function that is going to be written once and called innumerable times, this is a bad justification. You should optimize for the consumers of your library, not for yourself.
> For a data structure not meant to be used for large datasets, why optimize for that use case?
Because it’s not the job of a standard library to be opinionated about how people use data structures. It’s the standard library’s job to do what is asked of it as efficiently as possible. The best implementation would be specialized for small Ns but they don’t do that either.
Good to hear about TRMC, I haven’t been a daily user of OCaml since 4.07, but it’s good to hear that they’re addressing some of these pain points. I still think Core and Base are a better starting point for real-world applications in general.
Well, they're not being opinionated about it per se, they're just not supporting (up until now at least) highly-optimized use of lists. There are tradeoffs to consider here beyond what we have discussed in this thread. Check the OCaml forum threads on optimizing list mapping for the various considerations.
Incidentally I agree with you about lists and only meant to use it as an example of a place where the stdlib authors chose a simple implementation over one that does what you expect. As soon as I started reading the stdlib source and seeing things like that, I immediately understood why Core exists and realized I wouldn’t be able to trust the stdlib in production. It violates the principle of least surprise. It’s one thing to do stuff like this in Haskell where it makes sense because of lazy evaluation, it’s another to do it in an eager language which acknowledges the need to make concessions to practical usage.
Also,
> violates the principle of least surprise
I suppose yes if you don't read the docs which point out that it's not tail-recursive, and a few lines below a tail-recursive alternative `rev_map` is given.
The point is that this pattern makes sense in a lazy-evaluated language, not that everything Haskell does makes sense. I think it's a less defensible choice than OCaml in most cases.
> I suppose yes if you don't read the docs which point out that it's not tail-recursive, and a few lines below a tail-recursive alternative `rev_map` is given.
I read the docs and I still disagree with the choice made here. There's no need to include two functions when one will do. This is not pragmatism, it's laziness.
If providing two slightly different ways to do something is laziness, then pretty much every language is guilty of that. Also, Larry Wall: 'Laziness is a virtue' ;-)
My guess would be ease of maintenance. OCaml has a small team of people working on it that are already bringing improvements to the language that are multi year projects (multicore recently, modular implicits may be next and will take a long time too).
On the other hand, from what I understand, they also added to the compiler something to solve this problem for every class of problem that looks like this: https://v2.ocaml.org/manual/tail_mod_cons.html.
That leaves the question: why not fix the stdlib List.map while waiting for the tail modulo constructor? I honestly don't know. My guess would be that it was easy for everyone that wanted to replace it to either do it or use an array, but that's just a guess.
Things are different for RWO since it relies also on a number of extra tools and libraries which, indeed, change over time (sometimes also substantially)
You can read it for free online at https://johnwhitington.net/ocamlfromtheverybeginning/
i learned out of the unpublished french ora book "developing applications with ocaml". (incidentally it was so useful i had a copy shop bind me a custom "book")
It's available free online as well: https://caml.inria.fr/pub/docs/oreilly-book/html/index.html
This is a fantastic resource that anyone with a passing interest in the OCaml programming language needs to be aware of.
Come to think of it though, I’m convinced the class was sponsored by Jane Street or something.