I enjoy reading these “first-impression” posts; even if they don’t get really deep into what it’s like to work on a complex codebase, first impressions matter and tell you a lot about what that language community values.
That said, my own impressions with OCaml are very similar; the core language is wonderful (other than its annoying operator precedence rules) but as soon as you want to go add a 3rd-party library you are in for a world of hurt. I found a few years ago that the documentation for the build system seemed to be written for an audience that already knows how to use it but just needs a little refresher; it was woefully inadequate for newcomers.
For example in my code I have this function to get a config filename from the environment variable:
let get_config_filename: string =
try
let path = Sys.getenv "CONFIG" in
path
with Not_found ->
"./websites.yaml"
That’s not a function - that’s a named value. It’ll be evaluated only once. To make it a function, you’d need to take an argument (usually unit, i.e. ()), like:
let get_config_filename () = (* ... *)
I think this confusion is why some ML-syntax-like languages try to differentiate it with a different keyword for functions (like Rust’s fn) or using lambda syntax (like Roc’s name = \arg -> result.)
Apart from that, I agree with the author’s conclusion that OCaml is hard to get into. It’s hard to figure out how you’re supposed to solve common problems, and there’s scarce few “blessed” approaches to do common things. This is amplified by the OCaml 5 and domains situation because the earlier popular approaches for things aren’t necessarily how things should be done in the post-5 world. There’s been a few times where I look up how to do something, and decide on library/approach foo, but while I’m working on it I face issues like what the author did with crawl_website, and I’ll run into threads on reddit where people will be like “Obviously you shouldn’t use foo, use ppx_bar instead” and I’ll give up and resolve not to use OCaml again…
I haven’t looked into it too deeply, but even the newest edition of RW OCaml is not written for OCaml 5+, so it won’t cover domains and whatnot. Add to that the usual Async vs Lwt argument and I don’t recommend it right now.
It’s me, author of this article.
Thanks a lot for clarification. Does that mean even if env var value changes it won’t be reflected because it is executed only once?
kivikakk already covered your question but I’ll throw in a helpful tip for knowing the difference in the future: all functions in OCaml must have an -> in their type. You can use your editor’s LSP integration (e.g. in neovim I have to press K on a symbol to see its docs) to see that the inferred type of get_config_filename is string, while a function would be () -> string.
Yes — you can prove this to yourself by trying something like this:
let main () =
let get_config_filename: string =
print_endline "evaluating get_config_filename";
try
let path = Sys.getenv "CONFIG" in
path
with Not_found ->
"./websites.yaml"
in
print_endline get_config_filename;
print_endline get_config_filename
in
main ()
That said, how are you anticipating the environment variable changing? OCaml’s stdlib itself doesn’t expose setenv or unsetenv — note aborted attempt here.
edit: oh, never mind me! Unix.putenv is mentioned right there, which of course works on macOS. So you can try this:
#load "unix.cma";;
let main () =
let get_config_filename: string =
print_endline "evaluating get_config_filename";
try
let path = Sys.getenv "CONFIG" in
path
with Not_found ->
"./websites.yaml"
in
print_endline get_config_filename;
Unix.putenv "CONFIG" "hello";
print_endline get_config_filename
in
main ()
Which outputs the same as above. Now note the change here:
#load "unix.cma";;
let main () =
let get_config_filename () =
print_endline "evaluating get_config_filename";
try
let path = Sys.getenv "CONFIG" in
path
with Not_found ->
"./websites.yaml"
in
print_endline (get_config_filename ());
Unix.putenv "CONFIG" "hello";
print_endline (get_config_filename ())
in
main ()
This is amplified by the OCaml 5 and domains situation because the earlier popular approaches for things aren’t necessarily how things should be done in the post-5 world.
Keep in mind that OCaml 5 is very very young and that 5.0, 5.1 and even 5.2 are not considered ready yet. I agree that OCaml often lacks a one-true way but OCaml 5 is not a good example of that.
I agree that it is young, but at this point it objectively is the future of OCaml, so for someone just getting into it it doesn’t make sense to not start with it. Doubly so because it affects IO, which is what most basic and common programs do! The author of this article started out writing a scraper, my first attempt was to write a discord bot, and many people kick the tires with a web framework like Dream. So the problem of pre-4 approaches not being the future is more of a problem: as a newcomer, is what I’m learning going to become outdated soon? Especially when it’s the core of what I’m doing?
I don’t want to be overly negative abotu OCaml because I really like the language otherwise, so I’ll point out that there are people like @leostera on twitter who are working really hard on this problem and have made great progress. For people who want to get started with OCaml, I think https://github.com/leostera/minttea is natively OCaml 5 and really fun for making TUIs.
I believe it does make sense to start with Lwt rather than with Eio now. There’s simply more learning resources and more Lwt-compatible libraries. Lwt won’t become obsolete in the near future and the knowledge of OCaml+Lwt will transfer good to OCaml+Eio — same language after all.
One thing I wish could be put into the official documentation is that nobody actually uses the OCaml debugger. My main method of jumping into unfamiliar codebases is to write a unit test then step through it in the debugger, so I tried that with an OCaml codebase and was met with weeks of frustration. The debugger straight up does not work even under the simplest setup. Turns out all my effort to get it to work was wasted and everybody uses advanced REPLs like utop instead. I guess that makes sense for a functional language but all mention of the debugger should really just be removed from official documentation.
Wait, really? I would have thought ocamldebug generally does work. I tried it earlier for a personal project but maybe I was missing something about its functionality. Your suggestion is that it’s better to import functions into utop and then run them from there?
(To be clear, I’m asking genuinely - I like how the language works but I have limited experience in it - so tips are greatly appreciated.)
This question was asked a decade ago and is still unanswered because there is no answer. Setting breakpoints in ocamldebug just doesn’t work, generally: https://stackoverflow.com/q/30399730/2852699
Yeah dune integration with utop is pretty good, so it’s easy to pop open a shell where you can access all your library’s internal functions to try stuff out.
There’s also a non-Medium version of this article that doesn’t have all of its annoying popups: https://pliutau.com/my-first-experience-with-ocaml/
Thanks! I updated the submission with this new link.
Thanks for pointing this out. The only reason I re-posted on Medium, because reddit for some reason banned my domain.
I enjoy reading these “first-impression” posts; even if they don’t get really deep into what it’s like to work on a complex codebase, first impressions matter and tell you a lot about what that language community values.
That said, my own impressions with OCaml are very similar; the core language is wonderful (other than its annoying operator precedence rules) but as soon as you want to go add a 3rd-party library you are in for a world of hurt. I found a few years ago that the documentation for the build system seemed to be written for an audience that already knows how to use it but just needs a little refresher; it was woefully inadequate for newcomers.
But the documentation for the language itself has improved a lot since I first looked at OCaml in 2011!
That’s not a function - that’s a named value. It’ll be evaluated only once. To make it a function, you’d need to take an argument (usually unit, i.e.
()
), like:I think this confusion is why some ML-syntax-like languages try to differentiate it with a different keyword for functions (like Rust’s
fn
) or using lambda syntax (like Roc’sname = \arg -> result
.)Apart from that, I agree with the author’s conclusion that OCaml is hard to get into. It’s hard to figure out how you’re supposed to solve common problems, and there’s scarce few “blessed” approaches to do common things. This is amplified by the OCaml 5 and domains situation because the earlier popular approaches for things aren’t necessarily how things should be done in the post-5 world. There’s been a few times where I look up how to do something, and decide on library/approach
foo
, but while I’m working on it I face issues like what the author did withcrawl_website
, and I’ll run into threads on reddit where people will be like “Obviously you shouldn’t usefoo
, useppx_bar
instead” and I’ll give up and resolve not to use OCaml again…Would following a guide like https://dev.realworldocaml.org/ help with the no-blessed-ways problem, in your opinion?
(I ask because I think I want to try going down the OCaml rabbit hole using that book.)
I haven’t looked into it too deeply, but even the newest edition of RW OCaml is not written for OCaml 5+, so it won’t cover domains and whatnot. Add to that the usual Async vs Lwt argument and I don’t recommend it right now.
Oh, darn. Very good to know…
nooooooooooo! I was hoping I could find a good resource to read.
Not a guide, but recently read this user experience of somebody switching to Eio which might be nice for comparison: https://tarides.com/blog/2024-09-19-eio-from-a-user-s-perspective-an-interview-with-simon-grondin/ - I don’t have any personal experience with OCaml’s async ecosystem though.
The readme is really good and worth a read https://github.com/ocaml-multicore/eio?tab=readme-ov-file
oh neato!
It’s me, author of this article. Thanks a lot for clarification. Does that mean even if env var value changes it won’t be reflected because it is executed only once?
kivikakk already covered your question but I’ll throw in a helpful tip for knowing the difference in the future: all functions in OCaml must have an
->
in their type. You can use your editor’s LSP integration (e.g. in neovim I have to pressK
on a symbol to see its docs) to see that the inferred type ofget_config_filename
isstring
, while a function would be() -> string
.Yes — you can prove this to yourself by trying something like this:
Output:
That said, how are you anticipating the environment variable changing? OCaml’s stdlib itself doesn’t expose
setenv
orunsetenv
— note aborted attempt here.edit: oh, never mind me!
Unix.putenv
is mentioned right there, which of course works on macOS. So you can try this:Which outputs the same as above. Now note the change here:
And the output:
Keep in mind that OCaml 5 is very very young and that 5.0, 5.1 and even 5.2 are not considered ready yet. I agree that OCaml often lacks a one-true way but OCaml 5 is not a good example of that.
I agree that it is young, but at this point it objectively is the future of OCaml, so for someone just getting into it it doesn’t make sense to not start with it. Doubly so because it affects IO, which is what most basic and common programs do! The author of this article started out writing a scraper, my first attempt was to write a discord bot, and many people kick the tires with a web framework like Dream. So the problem of pre-4 approaches not being the future is more of a problem: as a newcomer, is what I’m learning going to become outdated soon? Especially when it’s the core of what I’m doing?
I don’t want to be overly negative abotu OCaml because I really like the language otherwise, so I’ll point out that there are people like @leostera on twitter who are working really hard on this problem and have made great progress. For people who want to get started with OCaml, I think https://github.com/leostera/minttea is natively OCaml 5 and really fun for making TUIs.
I believe it does make sense to start with Lwt rather than with Eio now. There’s simply more learning resources and more Lwt-compatible libraries. Lwt won’t become obsolete in the near future and the knowledge of OCaml+Lwt will transfer good to OCaml+Eio — same language after all.
I have a very similar experience, every time I want to try some networking related I am torn between Eio and Lwt.
One thing I wish could be put into the official documentation is that nobody actually uses the OCaml debugger. My main method of jumping into unfamiliar codebases is to write a unit test then step through it in the debugger, so I tried that with an OCaml codebase and was met with weeks of frustration. The debugger straight up does not work even under the simplest setup. Turns out all my effort to get it to work was wasted and everybody uses advanced REPLs like utop instead. I guess that makes sense for a functional language but all mention of the debugger should really just be removed from official documentation.
There was a recent post on using LLDB with OCaml: https://lambdafoo.com/posts/2024-08-03-lldb-ocaml.html, if that helps at all. (From reading it seems like work needs to be done to improve support for it, however.
Wait, really? I would have thought
ocamldebug
generally does work. I tried it earlier for a personal project but maybe I was missing something about its functionality. Your suggestion is that it’s better to import functions into utop and then run them from there?(To be clear, I’m asking genuinely - I like how the language works but I have limited experience in it - so tips are greatly appreciated.)
This question was asked a decade ago and is still unanswered because there is no answer. Setting breakpoints in ocamldebug just doesn’t work, generally: https://stackoverflow.com/q/30399730/2852699
Yeah dune integration with utop is pretty good, so it’s easy to pop open a shell where you can access all your library’s internal functions to try stuff out.
oh that’s really rough :| I didn’t know it was “unable to set breakpoints” bad