Advent of Code 2019 - it's always day 1
If you missed the first part in this series, I’m currently learning OCaml by (slowly) making my way through the Advent of Code 2019.
Lao Tzu said that a journey of a thousand miles starts with a single step. In my case, the 25-day advent of code started with a very, very long, very, very repetitive first day. Here’s my OCaml Groundhog Day (OCaml enthusiasts, be warned: you’ll be yelling and cursing throughout this piece).
Pitfall 1 : Installing OCaml on Windows
The first step was to get OCaml running on my PC. The OCaml installation page lists Windows after Linux, MacOS, OpenBSD and NetBSD ; in hindsight, not a good sign. The French installation page lists three solutions to install OCaml; the first link, the “official Ocaml distribution”, is no longer active 🤔
While the two other links look more credible, I didn’t want to take my chances with them, especially since they advertise “experimental” OPAM support and although I don’t exactly know what OPAM is, it sounds pretty useful. So I ended up installing OCaml on WSL (the Windows Subsystem for Linux — all the user-friendliness of Linux with the performance of Windows).
Pitfall 2: double semi-colons
After installing OCaml and taking the REPL for a spin, time to write a real program in a real editor or, failing that, vim. And this is where double semi-colons are waiting to trip you up.
In the interpreter, ;;
is used to indicate the end of a statement and that the top-level should start doing its thing.
Outside the REPL however, every tutorial will tell you that double semi-colons are not necessary, and will point to the fact that entire code bases don’t use ;;
even once. This affirmation is both technically correct and unnecessarily cruel. It’s like telling a toddler who just received her first bike that training wheels are not, in fact, necessary to ride a bike. “Look at Lance Amstrong! He didn’t win the Tour de France by using training wheels!”
It is indeed perfectly possible to write an OCaml program without using ;;
once, but it takes a little while to get the hang of it. Personally, it took me about a week before I could reliably do without them. On that first day, I had to sprinkle those suckers every other line. Double semi-colons are a helpful beginner’s crutch and it is very frustrating to try to do without them. Especially since…
Pitfall 3: “Error: Syntax error”
When you learn a new language, you’re going to make mistakes. Glaring, obvious mistakes, and more subtle ones. You’re counting on the compiler or the interpreter to tell you where and what you got wrong so that you can, hopefully, fix it and move on to the next mistake. And in this respect the OCaml interpreter is… underwhelming. The screenshot above says it all: the parser can’t be bothered to display an informative message, and won’t even print out the broken code! Nope, just “syntax error”. Go look up the code yourself.
The seasoned OCaml developers among you are no doubt smirking at this last sentence.
That’s because they know what happens next: you open the file, and check line numbers, and count characters like a Neanderthal, and then you find out that characters 26–28 are, obviously, ;;
. The actual syntax error is somewhere between the beginning of the last statement and those semi-colons — which is why you should add them liberally to your code when you don’t understand what’s breaking and you’re trying to debug it. Speaking of which:
Pitfall 4: where’s my print()
function?
Debugging often amounts to adding a lot of print statements to figure out what’s going on in your program.
In OCaml, printing out the content of variables is incredibly annoying to get right for such a basic use case. It’s not just that you need to choose between print_int
, print_string
, print_char
and their ilk.
These methods are actually only slightly useful to debug — I soon found myself grabbing for print_endline
to get, you know, newlines between debug outputs.
More importantly, printing out even slightly more complex structures quickly becomes a pain. Consider the iconic OCaml object, a list of ints; in this StackOverflow question, the validated solution, is, wait for it… to write a recursive print function. The most concise answer is this clunker:
print_string (String.concat " " (List.map string_of_int list))
Which is actually a pretty elegant solution! But there’s a lot of ways you can get it wrong and honestly, debugging your debug code is not fun. By contrast, Python’s print(list)
is pretty hard to mess up.
(Before you get all righteous: I know about Printf.printf
, which alleviates some of these issues. But if your most useful print function is in a module of its own, you’re doing it wrong).
At last, it works!
There’s only so many pitfalls you can run into before finally getting your code to work. So, eventually, I finished my first OCaml program. Here is my complete solution to the day 1 problem (input list elided for clarity, and without double semi-colons):
let input = [149895; ...; 58085]
let required_fuel mass = max ((mass / 3) - 2) 0
(* apply a fuel function to a list of masses and sum the results *)
let sum_of_fuel fuel_function masses =
List.fold_left (+) 0 (List.map fuel_function masses)
let () = print_endline ("Part 1 " ^ string_of_int (sum_of_fuel required_fuel input))
(* sum the values of f^k(x) from k = 1 until f^k(x) = 0 *)
let fixed_point_sum f x =
let rec accumulate_till_zero acc x =
let y = f x in
if y = 0 then acc else accumulate_till_zero (acc + y) y in
accumulate_till_zero 0 x
let complete_required_fuel = fixed_point_sum required_fuel
let () = print_endline ("Part 2 " ^ string_of_int (sum_of_fuel complete_required_fuel input))
Despite all the tribulations to get there, I’m pretty happy with the final result. It feels idiomatic, and it makes good use of functional paradigms: there’s a map
, a fold
, tail recursion and function composition — all in only 12 lines of code 😁. Notice as well that the solution for part 2 is essentially the same as for part 1, but with a different fuel computing function.
A final word
If this entire article feels whiny, …well, it is whiny. But that’s because the promise of functional programming is to enable a purer, higher-level form of programming. To stick as close as possible to the Platonic ideal of data structures and algorithms. In a word, to be ideas manifest. And when you get it to work, it’s mostly the case! (It certainly feels more natural to write algorithms in OCaml than in, say, Java). So I had high expectations when starting out, and accordingly severe frustration when I was stymied by such pedestrian issues as printing out a list of values.
This article is not about how stupid OCaml is for not supporting Windows. It’s about the impedance mismatch between the lofty promises of FP and the drudgery of learning any new language (even more so one which relies on ;;
for debugging). Now that the worst of the learning curve is behind me, I promise that the next article will be more balanced!