Advent of Code 2022 Days 1-5 in Rust and Haskell

3 minute read

Published:

The elves are preparing for their expedition and we’re using coding to help them do it. I’m using this year’s Advent of Code to acquire/sharpen skills in Rust and Haskell programming and the purpose of this post is to summarize my thoughts on the first five days.

General Observations

  • A fair amount of the challenge is parsing strings read from text files
  • String handling in both Rust and Haskell can be annoying
  • It is getting harder and harder to write single-line Haskell bombs to solve the challenges

Rust Reflections

The process of producing valid Rust code has gotten progressively less painful over the past few days, but it remains quite painful. Because I’ve been doing so much Haskell, I often think of a purely functional approach, but Rust’s functional tools leave something to be desired. This may be a controversial claim, but I’ve found that the rust-specific features are often in direct conflict with functional techniques. For example, consider the following code I used on day 2:

let file_contents = std::fs::read_to_string("./input.txt")?;
let file_content_pairs: Vec<Vec<&str>> = file_contents
    .trim()
    .split("\n")
    .map(|line| line.split(" ").collect())
    .collect();

It’s tempting to try to do this in a single instruction (i.e., inline the first one at the start of the second), but the compiler gets annoyed. The compiler also gets annoyed if you don’t use an explicit type annotation, for reasons completely opaque to me. Finally, the need to do .iter() and .collect() every time you use a map will roughly double the lines of code required to do something simple.

String handling in rust has posed a further challenge, with a number of to_string()s showing up in my code. Things have improved greatly since I discovered that String dereferences to str, meaning you can pass a &String to a function that accepts &str.

Haskell Reflections

I will say that I have found more joy in writing Haskell than in Rust. That might be novelty or it might be the fact that the language doesn’t seem to want to fight you at every turn. Both languages have ludicrously helpful linters, but Rust’s compiler is like the linter’s abusive older sibling that hits you in the face every time you execute cargo run. Haskell, in contrast, feels like it wants you to write code efficiently.

Like Rust, Haskell has some string handling quirks. It has two string types: String, which is an alias for a character array, and Text, whose purpose I do not understand, but am given to believe is the preferred type for most purposes. However, for Advent of Code purposes, a character array is exactly what I want, and so my code is peppered with lots of fromString and unpack functions to convert back and forth between String and Text.

Originally I was trying to have each program contain a single illegible line of code. Unfortunately, as the problems have grown more complex, the illegibility of the single line has made it impossible for me to actually code this way, so I have to follow good programming practices and break my code into smaller bits. The main IO monad of my programs are still quite short and have some juicy functional techniques. Day 5’s is

main = do
  rawData <- readFile "input.txt"
  let filePieces = getFilePieces rawData
  let instructions = map parseInstruction (getLines (filePieces !! 1))
  let stacks = map (reverse . filter (/=' ')) (formStacks $ map parseStackLine (getLines (head filePieces)))
  let finalStacks = foldl' (flip instruct) stacks instructions
  print $ map last finalStacks

Unfortunately, there’s 40 lines of code before that. It’s also tricky to use lambdas to write recursive functions in Haskell, so if you truly want to write a single line, you have to do something clever with things like fold and zip.

All my solutions can be found in my github repo