Devlog

Introduction

Small blurbs about what I’ve been working on, learning, and thinking about. There’s a good chance that at least some this contains incorrect information.

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

Advent of Code 2024 Day 2: Parsing Expression Grammars

December 2, 2024

I am fully committed to using Janet this year! At least I hope so. One of my goals is to get really familiar and fluent with using parsing expression grammars (PEG). I’ve always liked parser combinators, which seem to be more or less the same thing. One of the most basic and typical ways to parse input in Advent of Code is to split a string into a lines and split each line on spaces.

1 2 3
4 5 6

This an be achieved quite elegantly with a somewhat more recent addition to the PEG library, split. You give it two patterns, one for the separator and one for the actual contents, and it generally does the right thing. There is just one caveat. In the following snippet, there’s a space at the end of my input string. If you remove the asterisk * from the digit pattern, you get no matches. So split more or less requires you to make the content optional, unless you are absolutely certain that you won’t have a dangling separator at the end of your input.

  (peg/match ~(split :s ':d*) "1 2 3 ")

By making the second pattern optional, the end of the string, “3 “ can now be matched as two digits separated by a space. The second digit is simply empty. Sounds totally logical, I know. But you probably don’t want an empty capture. You can either remove it later through filter or you can call string/trimr on the input, which is my convention for AoC. This removes trailing whitespace, so you no longer need to make the second pattern optional, like this:

(peg/match ~(split :s ':d) (string/trimr "1 2 3 "))

Of course you’ll have to adjust your code for other separators (or filter it out later).

Anyway, back to the real world (of AoC). Here’s the same PEG with and without using split. I think it’s a huge improvement.

# before
(def input-peg
  (peg/compile
    ~{:main (* (some (* :line (? :s))) -1)
      :line (group (some (* :num (any " "))))
      :num (/ (<- :d+) ,scan-number)}))

# after
(def parser
  (peg/compile ~(split "\n" (group (split :s (any (number :d+)))))))

Working on a simple static site

October 21, 2024

I spent the evening working on a very simple static site. First thing I did was copy the CSS from this blog over to that site. I really like the way the CSS variables are organized on this blog.

One thing I don’t like is organizing CSS though. I generally try to utilize element styles, utility classes and classes for specific use cases. Take a site navigation for example. At the most basic level, the body element gets its own, default styles, to set up the font family, size and weight. Next you want to maybe have a horizontal list of navigation entries on larger screens and a vertical list on smaller screens. These could be done via .cluster and .stack classes (taken from one of my favorite books on anything computer, Every Layout). The problem with this is that you can’t apply CSS classes based on media queries. You can only apply CSS rules. You can either have two different chunks of HTML, show and hide them with media queries, and utilize these layout primitives (stack, cluster, …) or you inline the respective rules into a feature class. At least that’s what I call them. These are classes for art direction, where you have one-off CSS rules that aren’t generally useful.

Deciding what goes in a feature class, when you should favor a single chunk of HTML with a feature class or multiple chunks with utility classes and media queries – these are hard decisions to make. I find it much easier to organize actual code than CSS.

I like to believe that I’ve improved quite a bit at this over the years. My rules of thumb are:

  • try to rely on feature classes as little as possible
  • try to use layout primitives as much as possible
  • when none of the above work, try a utility class instead (text-align:center)
  • if all else fails, feature class it is

Bitten by complicated FP once again

October 10, 2024

Before falling asleep I had some ideas for this website. I want to add a dark mode, use the colors from my Neovim theme and also make some layout changes. I thought using the colors from my theme would be straight forward, since I can just generate some CSS variables as an additional output. There’s a new-ish light-dark CSS function that I can use and generate CSS like this:

--color-yellow-fg: light-dark(#000, #fff);

Except that I can’t. The way my theme works is that it all starts with a configuration table that has two entries, for the light and the dark colors. Each variant is then passed to a make_themes function, which is a mini pipeline that outputs the different light or dark themes (Alacritty, Fish, …). The key realization here is that the pipeline only ever has access to light or dark colors, but never both. Now go back and look at the light-dark function. Yes, it needs both.

This is yet another case of making a system unnecessarily constrained. I’m so often reminded of Rich Hickey these days. If I had just dumped all colors into a map that has the complete “theme context”, and passed that around, it would be much simpler to make modifications now. What makes matters worse is that I wrote all of this in a style that resembles functional programming more than idiomatic Lua. There’s lots of mapkv(fn, merge(tableA, tableB)). This also makes it hard to modify my own code, which I wrote mere months ago.

I guess I’ll start by solving the immediate problem and somehow make the full theme context available everywhere. Next, I’ll untangle the FP stuff.


I stepped away from the computer for a bit which brought me some much needed mental clarity. In the end it wasn’t as difficult as I thought. In fact, a pretty sizeable Lua refactor worked on first try, which is really rare for me, when programming in a dynamic language like Lua. I created a context table that has both light and dark and passed this to the theme functions, which now return a table that has a light and a dark key. I tried relying less on FP constructs but the code is still a bit of a mess. At least I managed to always keep the code in a working state. The last hurdle is that the string producting template functions are all called with a huge table that has the key/value color pairs from all themes in it. The idea behind this is that one theme can reference colors from another. This is actually one of the two key ideas behind this theme. Why is this annoying now? I want to create one CSS variable for every base color value my theme has (light.sucess.fg, dark.success.fg, …). The way this works in all themes so far is that you create a template string:

--color-light-success-fg: light-dark(${light-success-fg}, ${dark-success-fg})

This feels tedious though. Can’t I just generate those lines automatically based on the variable names? The problem here is that I have this huge blob of stuff and there’s currently no way to differentiate which slice of keys comes from which theme. It would be nice, in retrospect, if that big table worked like this

local colors = {
  nvim = {
    Normal = {},
  },
  css = {
    LightSuccess = {}
  }
}

instead of

local colors = {
  Normal = {},
  LightSuccess = {},
}

The pragmatic thing for now is to stick with the repetitive string templating.

Dark mode for terminal users

October 9, 2024

I firmly believe that you should use light mode when the ambient lighting is bright. As far as I know, science agrees with that, since dark text on a white background is easier to read than the opposite.

But after the sun has set and you’ve dimmed all the lights, staring at a bright screen seems wrong and it looks out of place when the rest of your system switches to dark mode.

For the longest time I’ve therefore wanted to have the automatic dark/light mode switching in my terminal environment, which consists of Alacritty, tmux, Fish and Neovim (tmux doesn’t define any color values of its own, so we can ignore it).

I finally got around to spending some time on this not so tricky problem and I’d say I’m 85% there. I use my own color theme which exports config files for, among other things, all three programs listed above. And all config files are available in a dark and a light mode. Meaning, in Neovim I can simply switch to the dark version with :color yui_dark. Since Neovim has a nice client/server architecture I can also do this remotely:

:let g:lightline.colorscheme = "yui_dark" | colorscheme yui_dark | call lightline#init() | call lightline#colorscheme() | call lightline#update()<CR>

Alacritty also has a nice API for changing config values from the shell. You can read more about it under man alacritty-msg (and man 5 alacritty for the config values) but the gist is alacritty msg config -w -1 'colors.cursor.background="#CCCCCC"'.

Lastly, for configuring the Fish shell syntax colors you use shell commands anyway, so the exported “config” is a Fish file that I can call whenever I want. The file has commands like this:

set fish_color_match eae9e9
set fish_color_selection --background=474355
set fish_color_search_match --background=4f4b5f
...

In other words: I now have shell commands for remotely changing Alacritty, Fish and Neovim from light to dark mode and vice versa. The “only” thing left to do is to find a way of running either of the two variants when the system switches themes.

Why is `O(log n) + O(log n) = O(log n)`

October 9, 2024

I was thinking about this recently while doing some Leetcode binary search problems. For this particular problem I split it up into two sub problems, each of which used binary search.

This Stack Overflow answer explains it nicely. Constant, multiplicative factors don’t matter much for Big O notation. So the sub problems can also be thought of as O(2 * log n), where the constant factor of 2 doesn’t matter.

During this exercise I also came across a cool graphing tool called Desmos which could be very handy for visualizing and intuitively understanding some of these concepts.