What are the biggest reasons newcomers give up on OCaml?

Because I learned OCaml for work, I didn’t have the luxury of giving up. Also, I was excited to learn OCaml because of an experience in a university course I took some years earlier, so I also had determination.

So, I’m not sure if this is something that would have otherwise tempted me to give up, but I found myself repeating myself a lot in OCaml.

One thing that I found myself doing a lot was having failwith cases that I was sure couldn’t happen because I’d already checked those conditions previously. But it was too much trouble to create a new type and a new function to “narrow” the old type to the new type.

I didn’t know, yet, that OCaml has “type narrowing”. I wouldn’t have even known that term except that it featured prominently in the documentation of Mypy, the Python optional typechecker. And one day, it just occurred to me to do a web search to see if there was a way to do that in OCaml.

Another thing I feel might be useful is ad-hoc polymorphism (like traits in Rust or templates or “concepts” in C++). I know from reading elsewhere on this forum that it is controversial, though. Also, I’ve understood that something like that can be done with modules, but I haven’t tried it, yet.

I don’t have a ready example of where it would help me, though, because its been a while since I was working on the OCaml parts of my projects.

I’ve seen elsewhere on this forum concerns raised about the debugger. I thought the debugger was pretty good, and it is clever to be able to step backward in code, but it took some getting used to how to set breakpoints. I think I remember it giving some kind of error about threads, and I had to upgrade to 4.14, then it gave some error(s) (or warning(s)) but still worked. Also, it wasn’t integrated with the IDE (VS Code), except for old versions, but I understand that is being worked on now. I suppose the concerns were targeted at being able to use the debugger on native code or for multicore code.

Also, I had to use ppx_deriving in order to print some things that would be easy to print in other languages.

OCaml syntax took more getting used to than I expected. I think it is because I had to get used to when a form of expression indicates that a function is being applied, and I had to get used to the compiler’s error messages. The Reason syntax might help beginners with that. I haven’t tried it, but now I think I would prefer the standard OCaml syntax, and I think that currying is a really clever and elegant feature.

2 Likes

Also, the documentation for the debugger said (and maybe still says) that it doesn’t work under Windows, but I found that it does work, at least within the OCaml MinGW Cygwin environment.

The discussion veered off-topic for a moment, but I couldn’t resist mentioning word or long long types in C…

My preferred solution to this problem is Rust, which has explicitly sized types (i8, u8, i64, etc.), and notably a char that represents a ‘Unicode scalar value’.

However, I find the idea of replacing char with byte a bit absurd—neither is an ideal name for an unsigned 8-bit integer. In reality, the size of a “byte” can be hardware-dependent. As far as I know, in OCaml, the size of char is the same as int.

Note: In C, the char type can be signed or unsigned depending on the compiler. It is signed by default in GCC, unless the -funsigned-char flag is specified. In OCaml, char is always unsigned, which, in my opinion, is a more reasonable behavior.

Edit: This concerns documentation. In the context of issue #12547, it has come to my attention that some functions are indeed misleadingly documented and can be confusing for newcomers. The documentation should specify “read exactly [len] bytes” instead of “read exactly [len] characters”.

Are there any resources on how to split a large module into a directory of sub-modules? Something like splitting lib/module.ml into lib/module/mod1.ml, lib/module/mod2.ml, etc. . I tried this but realized I couldn’t access other modules in the library within these sub-modules and they were also not accessible from other module in the lib/ directory. I tried googling but did not find any material on how this can be done.

1 Like

If you are using dune, you can have a look at include_subdirs — Dune documentation

1 Like

Note that if you only want one level then you don’t even need to mess with subdirs settings. If you have a bin/main.ml like this:

let () = 
    print_endline "Hello, World!";
    print_endline Xyzlib.hello;
    print_endline Xyzlib.Alpha.hello;
    print_endline Xyzlib.Beta.hello

And a directory like this:

xyzlib/
├── alpha.ml
├── beta.ml
├── dune
└── xyzlib.ml

The dune-file doesn’t need alteration:

(library
 (name xyzlib))

You do need to explicitly re-export the sub-modules from the top-level xyzlib.ml (or mli):

module Alpha = Alpha
module Beta = Beta
let hello = "hello from top level Xyzlib"

But alpha.ml / beta.ml don’t need to do anything special:

let hello = "Hello from alpha"
let hello = "Hello from beta"

The thing that caught me out at first was that:

  • If you don’t have an xyzlib/xyzlib.ml dune will effectively create one for you, re-exporting all the sub-modules
  • If you do have xyzlib/xyzlib.ml then you have control over what you export but have to remember to do it yourself.

I believe this behaviour is a dune thing rather than something from ocaml itself. If you are using an alternative build system you probably need to handle it differently.

1 Like