Lessons Learned from Advent of Code (in JavaScript and Haskell)
Caleb Weeks
Posted on December 31, 2021
This year, I tried doing Advent of Code for the first time. My goal was to write code solutions in Haskell and JavaScript with more or less equivalent implementations. I have been learning functional programming academically for five or six years, so Advent of Code gave me an opportunity to practice what I have been learning. Unfortunately, things didn't go exactly as planned, so here are some of the lessons I learned:
- Don't rush learning
- Just start somewhere
- Functional programming has different flavors
- Functional programming in JavaScript is meh
- Use the strengths of the language
Don't rush learning
The first two days of Advent of Code went according to plan. I came up with relatively concise solutions in Haskell that translated to idiomatic JavaScript, then wrote blog posts summarizing my approach. Day three took me significantly longer to solve, and I was spending too much time coding and writing at the neglect of my family. I took a break for a couple weeks before finally solving day four.
Although I have spent a lot of time learning functional programming from an academic perspective, putting it into practice with real problems required more deliberation. I am certainly not going to give up, but I recognize that forcing myself to solve a problem each day with a difficulty growth rate that was larger than my learning growth rate was unhealthy. My plan is to continue practicing Haskell using Exercism, which makes learning pretty much any language an exciting journey.
Just start somewhere
Part of the reason that I hadn't started programming in Haskell earlier was that I was unsure about what it would take to get started. I was amazed at how easy it was to install the necessary tooling. The VSCode Extension that I installed enables inline code evaluation using a certain comment syntax that made it really easy to test small parts of my code. Truthfully, I avoided any IO or other side effect producing code in Haskell and just focused on the data processing and algorithmic sections of the problem.
Functional programming has different flavors
My definition of functional programming has been shaped by my studies on category theory, particularly through the writings and videos of Bartosz Milewski and others. I enjoyed learning about monoids, functors, monads, algebraic data types, typeclasses, currying, and more. Haskell has been the quintessential functional programming language in my view, and if a language claimed to support functional programming, there were certain features that it needed to have.
JavaScript in particular supports many of the features I considered to be essential to functional programming. ES6 arrow functions make writing curried and higher order functions a breeze. But for many programmers, the lack of algebraic data types, persistent data structures, or pattern matching disqualify JavaScript as a real functional programming language.
I recently started learning Elixir, and it has many amazing features I would want in a language. All data structures are immutable, there are no statements only expressions, and there is both literal and structural pattern matching. Unfortunately, currying is very difficult to write idiomatically, and the dearly loved pipe operator passes data as the first parameter to a function instead of the last (both resulting from the dynamic type system combined with the support of pattern matching).
I think the essence of functional programming can be summarized as the following:
- Discouraged use of mutability
- Encouraged use of higher order functions
- Support for composition of effects and data More on this topic in the near future.
Functional programming in JavaScript is meh
I have been a huge proponent of functional programming in JavaScript. As seen throughout this series, the Haskell solutions can almost always be translated into decent looking JavaScript code. But as some have pointed out, the lack of certain features such as persistent data structures or tail call optimization makes it impossible to implement many real world applications. Using something like ImmutableJS would probably help, but replacing every single data structure with something from a library is objectionable.
If you are stuck writing JavaScript, I would still encourage the functional programming approach, but you'll have to watch out for certain strategies that just won't work because the language does not support them. (By the way, TypeScript does not solve any of these issues and makes some of them worse.) Elitist functional programmers would disallow the use of chainable/fluent code, but I personally think that is the best approach to writing clean code in JavaScript. I certainly wouldn't go out of my way to implement method chains for every object in my code, but any library that adheres to the Fantasy Land spec (including Ramda) already provides some great chainable methods.
Use the strengths of the language
As already mentioned in the previous section, a certain flavor of functional programming can be quite elegant in JavaScript. The simplicity of the object model makes it very convenient to organize and operate on data. Dynamic typing and type coercion allows you to take certain shortcuts (unless you are trying to sort a list of numbers...). Use the strengths of whatever language you are using to your advantage. It is good to push the boundaries of the language to create new things. For example, styled components and GraphQL have normalized the use of tagged template literals for domain specific languages (DSLs). But in general, you'll have a much better time working with the grain of the language than against it.
Posted on December 31, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.