Advent of Code 2024 Day 6: Janet Streams Using Fibers/Coroutines

December 7, 2024

Remember when it was all the rage to write articles about functional programming (FP) in Javascript by explaining how you can chain .map, .filter and .reduce on lists? I hated that. I think that it conditions people into thinking that FP is all about list comprehensions. And when lists aren’t convenient anymore (more on this later), they then resort to imperative programming, thinking that FP isn’t suited to that kind of task.

In Advent of Code (AoC) there are plenty of examples where eager list comprehensions won’t get you very far. An innocent looking map().filter().map() can consume all your memory and make the garbage collector go crazy if you are creating and re-creating huge lists. A straight forward solution, as I mentioned earlier, is to fall back to more imperative patterns, such as the loop macro in Janet.

I was hoping that I could use Janet’s fibers/coroutines in combination with its stdlib map and filter function to work on streams rather than eagerly evaluated lists. But that was not the case:

(->> (gen-stuff input)
     (map foo)
     (filter bar))

In my testing on AoC day 6 (the first one where you walk around in a grid), the imperative version using loop used about 80MB, the version that kicks things off with a generator and then runs this through various list comprehenions immediately took up around 10GB of memory. Here’s a concrete example:

(defn gen-nums [] (coro (for i 0 10000000 (yield i)))

(comment
  # low mem usage
  (each v (gen-nums) (print v))

  # high mem usage
  (each v (map |(string/repeat (string $) 100) (gen-nums)) (print v)))

But it turns out that it is surprisingly straight forward to have your cake and eat it too. You can use easily define streaming versions of map and filter using the coro function (or macro?):

(defn map* [f ds] (coro (each v ds (yield (f v)))))

(defn filter* [f ds] (coro (each v ds (when (f v) (yield v)))))

I’ve used these for an upated implementation of day 6, which you can find here