I'm far from the first person to make a comparison between carpentry and software engineering.
Or to note that there's a non-negligible contingent of software engineers that enjoy doing carpentry and other hands-on, precise tradecraft.1
Nor am I likely the first to note that both carpentry and software engineering both involve the making of jigs.
But I want to talk about jigs for a minute.
I am decidedly not a carpenter. I have a membership at the local makerspace -- which boasts a formidable wood shop -- but I've spent much more time on the laser cutter + the 3D printers than working with wood. But I find the woodworking jigs fascinating. Dozens of them. Jigs for drilling holes! For routing things! For crosscuts! For squaring things! For joining things! For measuring things!
I think you get the idea.
But my favorite bit of woodworking is that you use tools + jigs to make more tools and jigs. You bootstrap your own working environment, starting from simple tools and materials and building up to more elaborate and complicated setups and outputs.
It reminds me of semiconductor manufacturing, how more advanced techniques (for photomasks, layout optimization, etc) rely on the advances of the previous generations, each one subsequently unlocking new optimizations and improvements. This video from the 38th Chaos Communication Congress is one of my favorites on the topic. Really mind-blowing stuff.
But I digress. I want to talk about jigs in software. The quick scripts we write, the tools we make to do our actual work.2 The little helpers that serve to make it easier (or in some cases, possible) to build the main project.
Here's an example: this blog is written in Markdown and compiled with Zola. Every time I write a new post, the process looked like:
# I always type `cd con<TAB>`, which fails to autocomplete because of the
# `config.toml` file
cd content
mkdir $NEW_POST_PATH
touch $NEW_POST_PATH/index.md
# At this point, I forget the front matter format and have to look it up
ls
hx $RANDOM_EXISTING_POST/index.md
# And then I copy that over to the new post.
This takes like 30 seconds, which is fine, but it also doesn't need to take 30 seconds. So I spent a minute writing a Mise Task for it:
[tasks.new]
description = 'Create a new blog post'
run = '<the script>'
where the script itself is:
#!/usr/bin/env bash
SLUG=$(gum input --placeholder "What's the slug for the post?")
mkdir content/$SLUG
{
echo '+++'
echo "title = \"$(gum input --placeholder "What's the title for the post?")\""
echo "date = \"$(date +%Y-%m-%d)\""
echo 'draft = true'
echo '+++'
echo ''
} > content/$SLUG/index.md
This uses Charms' gum CLI to give me a few pretty input prompts and then creates the new directory + file for the post.
Is it perfect? Of course not. It'll break for all sorts of inputs: I'll probably need to update the date field by the time I publish, and I likely don't want everything to be a draft. But it does what I need for now, and importantly, I can always adapt it for more use cases if I want.
Software jigs are powerful tools. Whether it's automating the migration between two systems, adding a Zen-mode to your favorite text editor, or writing a Tree-sitter grammar for a specific filetype, they can remove the tedium of certain operations and overall make your environment more conducive to your goals. Obligatory XKCDs.
And here's where the analogue to woodworking jigs totally falls apart: carpenters can't tell their computers to make jigs for them.3 Modern LLMs are very good at writing these kinds of one-off scripts and tools. Case in point, and the motivation for this post: kch-rs
For a while, I'd been annoyed at the latency of getting a new terminal prompt on my computer. Like the terminal loads, there's a (250 millisecond) pause, and then I get a fish prompt. Profiling shell startup pointed to keychain as the cause of most of the latency.
So I did what any reasonable person would do and asked an LLM to write a minimal version of keychain in Rust that does only what I need it to do. And it just...did it? Like, it exists now and it does exactly what I want. The keychain-equivalent invocation takes less than 100 milliseconds to run, a 60% reduction.
This is admittedly a kooky and somewhat contrived example, but LLMs wildly lower the activation energy required to do all sorts of tasks and automations. So much so that it's actually worth reevaluating large swaths of one's personal or professional workflows to see where they can be made better.
Recently, the main area in my life where this has manifested is using LLMs to write Beancount importers for managing my finances, but that's a story for another blog post.