How to Efficiently Learn by Doing Things One Step at a Time

I am having more than a little trouble trying to learn OCaml by writing a project/program and learning from my mistakes as the ecosystem talks back. I am using opam, dune and ocamlopt as I can figure it out from professor internet with AI now and then chiming in unreliably. It appears to me that I always have to do n things correctly before I can get any positive feedback at all, and I seldom know how large n is. I have to write a subroutine before I can call it. But when I try to compile it berfore it gets called, I hit errors because it is there and not called. Then when I call it and get an error, I have quite a bit of puzzlement over whether the problem is with the caller, the callee or the opens, or the dune files or the switches, or the project structure or … I start changing this and that, getting different messages back, and having no idea whether I have gotten closer to or farther from joy.

I am an old guy who has programmed lots of stuff for almost 60 years, and I want to keep learning, but I am prone to and not discouraged by lots of small errors on comparatively small tasks. What are the healthy development habits that can help me stay on the path?

1 Like

OCaml’s type inference, especially with idiomatically annotation-light code that takes maximal advantage of it, can be uniquely confusing in that ocaml will complain about a type error that occurs distantly from where your programming error was. This question, “but why do you think that variable has that type?”, is rarely asked in other languages, but in return I’ve had some incredibly easy refactorings of OCaml code.

OCaml’s syntax also has some subtle points (nested matches and if/else) and some points that are unusual across languages as a whole (list and tuple syntax), which can result in your code looking to you like it does something other than what it really does.

What’s helped me with both of these is having vim set up with Merlin and a frequently-used keybinding that runs ocamlformat over my code, and what helps in general is taking advantage of OCaml’s very fast compilation to compile all the time.

Apart from the Merlin bindings, I have just

au FileType ocaml setlocal formatoptions-=r formatoptions-=o formatoptions-=q
au FileType ocaml map <C-p> :silent:w!<Return>:%!ocamlformat --ocaml-version=5.4.1 %<Return>

ocamlformat helps the most, even though it might seem it’s just for enforcing style, because if my code doesn’t actually mean what I think it means, then ocamlformat will change it to look other than how I intended it to look. For example, by default,

[1, 2, 3]

gets formatted into the more obviously wrong

[(1, 2, 3)]

And if after a format the cases of a match suddenly have parentheses or indentation that I didn’t intend, that’s a clue as well.

That’s dune being stricter than ocaml by default, which in my experience is also very helpful. Enough that I wish I could remember how to restore that. The dune FAQ mentions how to lighten this and I think it’s lighter now. You might also have a better time if you write more code in library modules, as it’s not immediately a problem for some function to go unused within the module.

It may also help to learn the language on a smaller scale with the standard toolchain, first. I don’t have OCaml teaching experience, but I always learn languages by writing lots of single-file demonstrations of parts of the language, and that’s worked fine for me here.

Thanks for the reference to the dune FAQ. That’s good and it may gain myself a little more momentum.

You make me think I should learn Vim beyond the first lesson. I worked in non-tech businesses so much that I have not regularly used an editor with un-IBM and un-Microsoft key combinations since wordstar→turbo pascal→brief reached EOL (I don’t even know what keys I hit to accidentally get those arrows). I tried reformatting my code and generating mli files with the command-line tools, but they seemed to not want to work when the code they read contained any errors. If changing those dune settings will fix that – splendid, and if not, maybe I’ll try gvim or vim or something.

Looking for a more pedestrian sort of path on which to start, the longest way around sometimes being the shortest path home, I found:

https://github.com/FedericoBruzzone/setup-ocaml-project-without-dune

Any comments?

Thanks again,

Al


Your code doesn’t need to compile to use ocamlformat, but it needs to parse. I’ll sometimes technically finish some code just to reformat, then remove that and continue. The more frequently you check that your code parses and compiles, the fewer errors you ever have to deal with.

What I mean by using the standard toolchain on a smaller scale is directly using ocaml, ocamlc, etc., including #use’ing into an ocaml or utop session. Or using ocamlfind to pull in opam modules a little more easily. If it gets to the point of having a build system at all (that github link uses ocamlbuild), I switch to dune instead. These commands all have manpages and -help flags.

Hi, I’m (relatively) new to OCaml too, but I feel like I’m improving my skills gradually.

I first was quite annoyed by those unused variable warnings that act as errors. (I guess you can disable them?) But I learned to actually like them as they help me remove unused code or find some errors.

What I use to get unfinished code to compile is two things:

  • failwith "Not implemented" which is polymorphic and can be used where any type is expected (as it never actually returns, see manual),
  • let _ = varname or let _ = varname in in an expression to suppress the unused variable problem (you could also use ignore to “use” a value, but I prefer ignore not for temporarily supressing warnings but for deliberately ignoring a non-unit return value).

I struggled a lot with understanding how modules, files, projects, libraries, signatures, etc. are organized. It took me quite a while to figure it out, and I still haven’t got a deep understanding of how dune works. I sometimes feel like the documentation needed is spread in different places and I don’t always know where to look. My favorite source of documentation is the OCaml manual, which doesn’t help with dune and file structure though.

Debugging is also something I struggle with, especially when abstract types are involved that don’t come with a way to print them. :sweat_smile:

Learning how to use Alcotest wasn’t trival either, but I feel like it was worth the effort learning how to use it.

I suspect one of the first “pro tips” that people would do well to learn at the earliest learning phase possible is to use [@@@warning “-A”] at the start of new source code files, and only remove it later once it looks like it’s mostly working and you want to add polish.

The default compiler options in dune turn many warnings into errors, and this I feel is an attempt to tune for the more experienced OCaml programmer, as a way of preventing them from writing code that depends on corner cases and obsolescent parts of the language to work correctly. It can be a bit daunting to be presented with compiler errors about things that don’t actually prevent it from generating correct code.

Just don’t forget to remove the [@@@warning “-A”] before you’re done. You’ll learn a lot about good conventions and style by fixing the warnings.

Some of the docs are questionable.
Trying to do things by the book, I followed this page:

https://dune.readthedocs.io/en/latest/tutorials/developing-with-dune/introduction.html

It says:


Let’s create a local switch: cd to this directory and run the following command. This can take a few minutes.

opam switch create ./ 5.4.0

This command has created a directory named _opam in the current directory. Now, let’s install some packages by running:

opam install dune.3.15.3 menhir.20231231
_____________________________________________________________________


I shunned that page a few days back, because I didn't consider it likely that see how a menhir release almost 3 years old would work with a recent compiler.  

Tonight, changing my ways, feeling grateful for the much advice here, etc, I did follow that script and:

$ opam install dune.3.15.3 menhir.20231231
[ERROR] Package conflict!
  * No agreement on the version of ocaml-base-compiler:
    - (invariant) → ocaml-base-compiler = 5.4.0
    - dune = 3.15.3 → ocaml < 5.4 → ocaml-base-compiler < 5.3.1~
    You can temporarily relax the switch invariant with `--update-invariant'
  * Incompatible packages:
    - (invariant) → ocaml-base-compiler = 5.4.0
    - dune = 3.15.3 → ocaml < 5.4 → dkml-base-compiler
  * Incompatible packages:
    - (invariant) → ocaml-base-compiler = 5.4.0
    - dune = 3.15.3 → ocaml < 5.4 → ocaml-system >= 3.09.1
  * Incompatible packages:
    - (invariant) → ocaml-base-compiler = 5.4.0
    - dune = 3.15.3 → ocaml < 5.4 → ocaml-variants
  * Missing dependency:
    - dune = 3.15.3 → ocaml < 5.4 → ocaml-variants → ocaml-beta
    unmet availability conditions: 'enable-ocaml-beta-repository'
  * Missing dependency:
    - dune = 3.15.3 → ocaml < 5.4 → ocaml-variants → system-msvc
    unmet availability conditions: 'os = "win32"'

No solution found, exiting

That last line is from the computer, not me. I am not one to give up after only 37 tries.  But that 'os = "win32"' might give me bad dreams. 

Al  

The conflict comes from the dune.3.15.3 part of that command. The menhir should build.

   - (invariant) → ocaml-base-compiler = 5.4.0
   - dune = 3.15.3 → ocaml < 5.4 → ocaml-base-compiler < 5.3.1~

Is there a reason why you want a specific version of those packages?

Maybe use

opam install dune menhir

instead?

Also, when you create or switch to a switch, don’t forget to run eval $(opam env) in your shell:

# To update the current shell environment, run: eval $(opam env)

I haven’t fully found out when it is really necessary, but some invocations of dune warn you like above that you need to update your environment. Don’t miss it.

Also, if you open another terminal window, for example, it may be necessary to run eval $(opam env) if you have changed a switch without logging in again. To fix that, I added eval $(opam env) to my ~/.shrc.

I can confirm. The following works for me:

$ eval $(opam env)
$ opam install dune menhir.20231231

But as I said, maybe install the most recent version of menhir instead?

I was just pointing out that following the published documentation step by step does not work. That was a beginners doc. Look at what it says at the top: “This is is a tutorial: it is meant to be followed in order.” If I’m a smart-ass and I deviate from the instructions when I know that I don’t know anything, what does that make me? I wasn’t looking for a problem when I followed the instructions. If following something in order requires me to add my own ingredients in the middle, I am not smart enough to learn OCaml. When I looked at the lengthy table of contents for those docs, I was struck by how much there was to learn, and I was acting accordingly. The rest of the email was simply a description of the experience that let me point out that if those who wrote and reviewed that document followed it in order and found that it succeeded, they did it in a different environment than the one that I had when I followed it in order. The ecosystem tool chain depends on other factors that they did not address. When I wrote that some of the docs are ‘questionable’, I meant they put three questions into my mind, (1) did anyone ever follow these steps successfully before publishing the document? (2) what else might have gone wrong? and (3) am I in over my head so far that it will be a huge part of a year before I can start throwing up all that I envision? That’s all.

I get all that. I think you also already understand everything I could say about it, and just find it annoying that you’re getting technical support like this tutorial is some black-box product that takes some skill to use and can’t receive over-the-air updates, when it should be fixed. The nature of the responses is more about the forum and how the report’s framed. You’d get different responses from a post to https://github.com/ocaml/dune/issues

That the tutorial is using exact opam versions is probably already an attempt to avoid bitrot like this, but ocaml’s own version was overlooked. But considering ocaml’s version would make the tutorial less immediately useful, as now someone reading the tutorial has to wait to build a switch for that version, to then still run into new-user inconveniences like expected and already-installed tooling disappearing with the change of switch. Even if the entire environment is perfectly reproducible with Nix or a virtual machine, that’s much more hassle and more up-front delay in simply getting started. And if you have long commands to specify the specific opam switch with each command, rather than switch into it, then the presented workflow is very questionable for this reason.

IMO, onboarding documentation just has to refreshed as the rest of the ecosystem is refreshed, with the “what’s ‘OCaml’? → this doc → some result” pipeline considered in full. So bitrot-avoiding reproducibility measures like the exact opam versions is already a mistake, and it would be compounding that mistake to add the ocaml version to this tutorial to keep those versions working in the future. Smoothing out onboarding is a chore and trying to avoid the chore only worsens the onboarding experience.

I want to second the OCamlFormat recommendation and add that you should probably be using let-binding-spacing = double-semicolon as I have found it helps the compiler to correctly identify where in your code syntax errors have occurred.