<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ian Henry</title><description>Ian Henry's blog.</description><link>https://ianthehenry.com/</link><atom:link href="https://ianthehenry.com/feed.xml" rel="self" type="application/rss+xml"/><item><title>How to Learn Nix, Part 49: nix-direnv is a huge quality of life improvement</title><description>&lt;p>The &lt;em>reason&lt;/em> I &lt;a href="https://ianthehenry.com/posts/how-to-learn-nix/installing-nix-on-macos/">discovered an ancient blog post&lt;/a> the other day was that I had something new to say about Nix for the first time in over two years.&lt;/p>
&lt;p>The thing I want to say is this: &lt;a href="https://github.com/nix-community/nix-direnv">&lt;code>nix-direnv&lt;/code>&lt;/a> is great. It fixes roughly every problem that I&amp;rsquo;ve had with &lt;code>nix-shell&lt;/code>, and does so in a much nicer way than my previous ad-hoc solutions.&lt;/p>
&lt;p>This is important because I &lt;em>mostly&lt;/em> just use Nix to document and install per-project native dependencies. I do use it to install &amp;ldquo;global&amp;rdquo; tools as well, but that is &lt;a href="https://ianthehenry.com/posts/janet-game/how-to-patch-emacs/">rarely very interesting&lt;/a>, and most of my interaction with Nix these days consists of editing small &lt;code>shell.nix&lt;/code> files.&lt;/p>
&lt;p>But it took a bit of doing to get to the point that I felt &lt;em>good&lt;/em> about using Nix for this. For one thing, shells don&amp;rsquo;t register GC roots, which means that every time you collect garbage you have to re-download all the dependencies for the project you were working on. We overcame that hurdle in &lt;a href="https://ianthehenry.com/posts/how-to-learn-nix/saving-your-shell/">part 37&lt;/a>, by making a custom wrapper around &lt;code>nix-shell&lt;/code> that sets up GC roots correctly, but it was surprisingly difficult.&lt;/p>
&lt;p>For another thing, Nix is pretty insistent that you use &lt;em>bash&lt;/em> as your interactive shell. I figured out a workaround for that in &lt;a href="https://ianthehenry.com/posts/how-to-learn-nix/nix-zshell/">Nix classic&lt;/a>, but &lt;a href="https://ianthehenry.com/posts/how-to-learn-nix/nix-develop/">essentially failed&lt;/a> to make &lt;code>nix develop&lt;/code> similarly usable.&lt;/p>
&lt;p>&lt;a href="https://github.com/nix-community/nix-direnv">&lt;code>nix-direnv&lt;/code>&lt;/a> solves both of these problems. Instead of spawning a new shell, it just adds environment variables to your existing shell. And when it evaluates &lt;code>shell.nix&lt;/code>, it automatically registers the result as a GC root.&lt;/p>
&lt;p>It also only re-evaluates &lt;code>shell.nix&lt;/code> when it actually changes, which means that it in the typical case there&amp;rsquo;s no startup time. In contrast, my GC-root-installing wrapper takes about 750ms to open a typical shell (raw &lt;code>nix-shell&lt;/code>, without the GC root evaluation dance, takes only 400ms). This doesn&amp;rsquo;t sound very long, because it&amp;rsquo;s not &amp;ndash; I&amp;rsquo;m running Nix on what I can only characterize as a supercomputer. But I originally installed Nix on a laptop that pre-dated germ theory, and its startup latency was a lot more annoying.&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/p>
&lt;p>&lt;code>nix-direnv&lt;/code> also automatically updates the environment when &lt;code>shell.nix&lt;/code> changes, so you don&amp;rsquo;t have to close and re-open your &lt;code>nix-shell&lt;/code> whenever you add a dependency. Not only is this ergonomically better, but it also means that you don&amp;rsquo;t mess up your shell history every time you add a dependency or exit a project.&lt;/p>
&lt;p>I had never used &lt;a href="https://direnv.net/">&lt;code>direnv&lt;/code>&lt;/a> before, and to this date the only thing I&amp;rsquo;ve used it for is managing my Nix shells. But it&amp;rsquo;s a general tool for managing per-directory environment variables, which is &lt;em>essentially&lt;/em> all that &lt;code>nix-shell&lt;/code> is. &lt;code>nix-shell&lt;/code> can also register bash functions &amp;ndash; if you&amp;rsquo;re using bash &amp;ndash; which is useful if you want to use it to debug a derivation. But for my purposes, environment variables are all I really need.&lt;/p>
&lt;p>&lt;code>direnv&lt;/code> has some built-in support for Nix, but it isn&amp;rsquo;t great; &lt;a href="https://github.com/direnv/direnv/wiki/Nix#some-factors-to-consider">direnv publishes a table outlining some of the advantages&lt;/a> of using &lt;code>nix-direnv&lt;/code>. &lt;code>nix-direnv&lt;/code> is some sort of plugin(?) that replaces the native Nix support with something much better. And it&amp;rsquo;s great. It makes the &amp;ldquo;reproducible developer environment&amp;rdquo; aspect of Nix just work™. And it&amp;rsquo;s pretty easy to use:&lt;/p>
&lt;p>First off, install &lt;code>nixpkgs.direnv&lt;/code> and &lt;code>nixpkgs.nix-direnv&lt;/code>.&lt;/p>
&lt;p>I installed them with &lt;code>nix-env&lt;/code>, using the same declarative wrapper that I wrote in &lt;a href="https://ianthehenry.com/posts/how-to-learn-nix/declarative-user-environment/">part 22&lt;/a>. If you install &lt;code>nix-direnv&lt;/code> in a different way, the following will be different.&lt;/p>
&lt;p>Installing &lt;code>nix-direnv&lt;/code> doesn&amp;rsquo;t &amp;ldquo;enable&amp;rdquo; the plugin; you have to separately tell &lt;code>direnv&lt;/code> about it:&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">mkdir -p ~/.config/direnv
&lt;span class="nb">echo&lt;/span> &lt;span class="s1">&amp;#39;source ~/.nix-profile/share/nix-direnv/direnvrc&amp;#39;&lt;/span> &amp;gt; ~/.config/direnv/direnvrc
&lt;span class="nb">echo&lt;/span> &lt;span class="s1">&amp;#39;eval &amp;#34;$(direnv hook zsh)&amp;#34;&amp;#39;&lt;/span> &amp;gt;&amp;gt; ~/.zshrc
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Once you do that, you have to run the following commands in every directory that you want to nix-shellify:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="nb">echo&lt;/span> &lt;span class="s1">&amp;#39;use nix&amp;#39;&lt;/span> &amp;gt; .envrc
direnv allow
&lt;/code>&lt;/pre>&lt;/div>&lt;p>And you&amp;rsquo;re done. That&amp;rsquo;s it! Now every time you navigate to that directory, you&amp;rsquo;ll have&amp;hellip;&lt;/p>
&lt;pre class="terminal">&lt;code>$ cd ~src/project
direnv: loading ~/src/project/.envrc
direnv: using nix
direnv: nix-direnv: using cached dev shell
direnv: export +CONFIG_SHELL +HOST_PATH +IN_NIX_SHELL +MACOSX_DEPLOYMENT_TARGET +NIX_BUILD_CORES +NIX_CFLAGS_COMPILE +NIX_COREFOUNDATION_RPATH +NIX_DONT_SET_RPATH +NIX_DONT_SET_RPATH_FOR_BUILD +NIX_ENFORCE_NO_NATIVE +NIX_IGNORE_LD_THROUGH_GCC +NIX_INDENT_MAKE +NIX_NO_SELF_RPATH +NIX_STORE +PATH_LOCALE +SOURCE_DATE_EPOCH +XDG_DATA_DIRS +__darwinAllowLocalNetworking +__impureHostDeps +__propagatedImpureHostDeps +__propagatedSandboxProfile +__sandboxProfile +buildInputs +builder +configureFlags +depsBuildBuild +depsBuildBuildPropagated +depsBuildTarget +depsBuildTargetPropagated +depsHostHost +depsHostHostPropagated +depsTargetTarget +depsTargetTargetPropagated +doCheck +doInstallCheck +dontAddDisableDepTrack +gl_cv_func_getcwd_abort_bug +name +nativeBuildInputs +nobuildPhase +out +outputs +patches +phases +propagatedBuildInputs +propagatedNativeBuildInputs +shell +shellHook +stdenv +strictDeps +system -PS2 ~PATH&lt;/code>&lt;/pre>
&lt;p>Oh. Well that&amp;rsquo;s not great.&lt;/p>
&lt;p>By default &lt;code>direnv&lt;/code> prints every environment variable that it adds, removes, or changes. Which makes sense if you&amp;rsquo;re using it for, like, credentials or something, but for Nix shells it&amp;rsquo;s just a waste of scrollback.&lt;/p>
&lt;p>There&amp;rsquo;s not really a simple way to suppress printing that giant &lt;code>export&lt;/code> line, but you can hack it away by adding something like this to your &lt;code>.zshrc&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="nb">export&lt;/span> &lt;span class="nv">DIRENV_LOG_FORMAT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nb">printf&lt;/span> &lt;span class="s2">&amp;#34;\033[2mdirenv: %%s\033[0m&amp;#34;&lt;/span>&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;span class="nb">eval&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>direnv hook zsh&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
_direnv_hook&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
&lt;span class="nb">eval&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>direnv &lt;span class="nb">export&lt;/span> zsh 2&amp;gt; &amp;gt;&lt;span class="o">(&lt;/span>egrep -v -e &lt;span class="s1">&amp;#39;^....direnv: export&amp;#39;&lt;/span> &amp;gt;&lt;span class="p">&amp;amp;&lt;/span>2&lt;span class="k">)&lt;/span>&lt;span class="s2">)&amp;#34;&lt;/span>
&lt;span class="o">}&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>(The &lt;code>.&lt;/code>s in the regex exclude the &amp;ldquo;dim text&amp;rdquo; control characters at the beginning of the line.)&lt;/p>
&lt;p>That removes the giant export line without removing the rest of the input. And now:&lt;/p>
&lt;pre class="terminal">&lt;code>$ cd ~src/project
direnv: loading ~/src/project/.envrc
direnv: using nix
direnv: nix-direnv: using cached dev shell&lt;/code>&lt;/pre>
&lt;p>Ahhh. That&amp;rsquo;s better.&lt;/p>
&lt;p>I&amp;rsquo;ve been using &lt;code>nix-direnv&lt;/code> for a few months now, and I must say: I wish that I had installed it sooner. It&amp;rsquo;s a &lt;em>much&lt;/em> nicer experience than the default &lt;code>nix-shell&lt;/code>, and I&amp;rsquo;m happy that I can get rid of the bespoke hacks that I&amp;rsquo;ve accrued over the years.&lt;/p>
&lt;p>&amp;hellip;almost. The one thing this does not help with is &lt;code>nix-shell -p&lt;/code>. &lt;code>nix-shell -p&lt;/code> is a useful way to &amp;ldquo;temporarily&amp;rdquo; install packages without actually putting them on your PATH, and I still use my zsh hack so that &lt;code>nix-shell -p&lt;/code> doesn&amp;rsquo;t drop me into a bash session. Although I do this rarely enough that I could probably just suffer through it.&lt;/p>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>To be fair I use tmux and just always have sessions open for the projects I&amp;rsquo;m working on, so it&amp;rsquo;s not like it was annoying very often.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2" role="doc-endnote">
&lt;p>I think this is the sort of thing that &lt;a href="https://github.com/nix-community/home-manager">&lt;code>home-manager&lt;/code>&lt;/a> does for you automatically, but I don&amp;rsquo;t use &lt;code>home-manager&lt;/code>.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description><pubDate>Sun, 28 Jan 2024 00:00:00 +0000</pubDate><link>https://ianthehenry.com/posts/how-to-learn-nix/nix-direnv/</link><guid isPermaLink="true">https://ianthehenry.com/posts/how-to-learn-nix/nix-direnv/</guid></item><item><title>How to Learn Nix, Part 48: Installing (single-user) Nix on macOS</title><description>&lt;blockquote>
&lt;p>Note: I wrote the vast majority of this post on August 24, 2022, but I decided that it was a whiny rant that I didn&amp;rsquo;t want to publish. I came across the draft in January 2024, remembered that this &lt;em>entire series&lt;/em> is a whiny rant, and decided to publish it anyway.&lt;/p>
&lt;/blockquote>
&lt;p>I&amp;rsquo;ve been a happy Nix user for about 18 months now, and&amp;ndash; well, not &lt;em>happy&lt;/em> happy, but satisfied&amp;hellip; no&amp;hellip; not really satisfied either; perhaps it&amp;rsquo;s more of a resigned disgruntlement; a feeling that despite its many flaws, it&amp;rsquo;s still better than anything else out there, and I&amp;rsquo;ve invested so much time into it already that it would be a shame to give up now, so&amp;hellip; am I describing Stockholm syndrome?&lt;/p>
&lt;p>I&amp;rsquo;ve been imprisoned in Nix&amp;rsquo;s castle for about 18 months now, and I recently came into a new laptop &amp;ndash; one of those shiny blue numbers &amp;ndash; so naturally one of the first things I did was install Nix on it.&lt;/p>
&lt;p>It would be nice if this blog post were only like a paragraph long, wouldn&amp;rsquo;t it? Step one: run the installer; there is no step two.&lt;/p>
&lt;p>But I am afraid to report: there is very much a step two.&lt;/p>
&lt;h1 id="a-tale-of-two-nixen">a tale of two nixen&lt;/h1>
&lt;p>There are two flavors of Nix, and you must choose at installation time which variety you will run.&lt;/p>
&lt;p>You can install &amp;ldquo;single-user&amp;rdquo; Nix, in which Nix is a binary that you run as an interactive command that does things and forks subprocesses and works like any other package manager you&amp;rsquo;ve ever used. Or you can install &amp;ldquo;multi-user&amp;rdquo; Nix, in which Nix is a daemon, running as root, and the &lt;code>nix&lt;/code> command merely makes requests to that daemon, which in turn coordinates a set of special &lt;code>nixbld&lt;/code> users that exist so that you don&amp;rsquo;t build all of your software as root.&lt;/p>
&lt;p>If you&amp;rsquo;re using NixOS, you have to use multi-user Nix, which makes sense to me: Nix controls the entire operating system, and there needs to be some central puppeteer to coordinate changes to the kernel or whatever.&lt;/p>
&lt;p>But if you don&amp;rsquo;t use NixOS, then you have a choice. You can choose to set up a persistent daemon, create a dozen &lt;code>nixbld&lt;/code> users, and have every command you run proxy through this centralized service. Or you can choose&amp;hellip; not to do that.&lt;/p>
&lt;p>I don&amp;rsquo;t know why you&amp;rsquo;d choose a multi-user installation when the single-user option is available. There might be a very good reason to prefer multi-user Nix, even when you are installing Nix on a laptop with a single user, but I do not know what it is. Obviously there is no documentation to explain the distinction or persuade the user one way or the other, so I can only speculate.&lt;/p>
&lt;p>And needless to say, when I first tasted the forbidden fruit of the Nix tree all those year ago, I chose a single-user install, and I have never regretted that.&lt;/p>
&lt;p>But then something upsetting happened.&lt;/p>
&lt;p>macOS has always been an unloved corner of Nix, and installing Nix on macOS has been rather fraught with peril ever since macOS Catalina came out.&lt;/p>
&lt;p>The problem seems so trivial: Nix requires a &lt;code>/nix&lt;/code> directory to exist, but macOS does not let you create new top-level root directories, so you have to actually create a separate volume and mount it as &lt;code>/nix&lt;/code>. Which might not sound difficult, but getting that to work with FileVault and the various hardware encryption configurations of different generations of Apple hardware and&amp;hellip; I dunno, probably some other complicating factors, I don&amp;rsquo;t know how computers work&amp;hellip; there was a heroic effort to keep macOS Nix working at all, at a time when some Nix maintainers were arguing that it would be easier to deprecate support for macOS entirely.&lt;/p>
&lt;p>But there was an unfortunate casualty of this struggle: the single-user installation option was removed.&lt;/p>
&lt;p>If you want to install Nix on macOS today, you must use a multi-user installation. That means running &lt;code>nix-daemon&lt;/code>, that means messing with &lt;code>launchctl&lt;/code>, creating a bunch of build users&amp;hellip;&lt;/p>
&lt;p>Which brings things back to me.&lt;/p>
&lt;p>I had a new laptop. I wanted to install Nix on it. And the only option facing me&amp;hellip;&lt;/p>
&lt;p>Was to get in there and hack up an unofficial, unsupported single-user install, because I just can&amp;rsquo;t abide the idea of a multi-user Nix install.&lt;/p>
&lt;p>And it&amp;rsquo;s not &lt;em>only&lt;/em> a philosophical objection! Although I admit that I have a visceral aversion to the idea of installing a daemon running as root for something that &lt;em>absolutely does not need to be a daemon or running as root&lt;/em>, I can understand that many people do not care about that.&lt;/p>
&lt;p>But there is a real, practical reason to avoid the multi-user configuration: if you are using a multi-user Nix install on macOS, then every time you &lt;em>upgrade&lt;/em> macOS, Nix will break, and you will have to &lt;a href="https://github.com/NixOS/nix/issues/3616">go in and do some surgery to fix it&lt;/a>.&lt;/p>
&lt;blockquote>
&lt;p>This was true when I wrote this, in 2022. I don&amp;rsquo;t know if this is still true, but &lt;a href="https://github.com/DeterminateSystems/nix-installer">an alternate Nix installer&lt;/a> &amp;ndash; which also only supports multi-user installs on macOS &amp;ndash; puts &amp;ldquo;survives macOS upgrades&amp;rdquo; at the top of its &lt;a href="https://github.com/DeterminateSystems/nix-installer#motivations">advantages over the official installer&lt;/a>. Which implies to me that this is still a problem.&lt;/p>
&lt;/blockquote>
&lt;p>This is because the multi-user installation edits some of the system-provided config files &amp;ndash; like &lt;code>/etc/bashrc&lt;/code> &amp;ndash; to insert some Nix setup things. And every time (every time?) you upgrade macOS, it will overwrite these files and restore them to the defaults. Is it hard to fix? No. Is it annoying? Yes. Is it sufficiently annoying to try to figure out a single-user install? To each their own.&lt;/p>
&lt;p>In any case: the only difficult thing about installing Nix on macOS is creating that darn &lt;code>/nix&lt;/code> directory. And clearly the multi-user install step knows how to do that. So I should be able to steal that part of it, create the directory, and then manually perform a regular single-user install. Right?&lt;/p>
&lt;p>Right.&lt;/p>
&lt;h1 id="right">right&lt;/h1>
&lt;p>Well it worked great, actually! It wasn&amp;rsquo;t hard at all. The steps are:&lt;/p>
&lt;p>First, download the Nix install script.&lt;/p>
&lt;p>The official Nix installation instructions are of the &lt;code>curl | bash&lt;/code> variety. But if you actually look at the script that you&amp;rsquo;re curling, it just turns around and curls &lt;em>another&lt;/em> script&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> and runs that.&lt;/p>
&lt;p>Actually a directory full of them. So the first thing to do is to get &lt;em>that&lt;/em> directory of actual install instructions.&lt;/p>
&lt;p>&lt;a href="https://github.com/NixOS/nix/tree/master/scripts">You can find them on GitHub&lt;/a>, although I just modified the original script to download the installer, extract it, and then quit before doing anything else (er, and also to not delete the extracted artifacts on &lt;code>EXIT&lt;/code>). The really interesting one is &lt;code>create-darwin-volume.sh&lt;/code>, which, as you can probably guess, creates &lt;code>/nix&lt;/code>.&lt;/p>
&lt;p>But! You cannot just invoke this script by itself. This script is invoked by the larger installation script, which sets some environment variables that the &lt;code>create-darwin-volume&lt;/code> expects to be able to read. &lt;em>Specifically&lt;/em> it expects to be able to read&amp;hellip;&lt;/p>
&lt;p>You know what? I don&amp;rsquo;t think you care. Or rather, if you do care, I&amp;rsquo;m sure that you can figure it out from reading the script. It wasn&amp;rsquo;t very hard, and by the time you&amp;rsquo;re reading this the precise interplay between the two scripts probably changed anyway, and you would have to dive into the code to figure out what the modern version requires anyway.&lt;/p>
&lt;p>Once you&amp;rsquo;ve created your &lt;code>/nix&lt;/code>, modify the &lt;code>install&lt;/code> script to remove the lines that force a multi-user install on macOS:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="k">case&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>uname -s&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> in
&lt;span class="s2">&amp;#34;Darwin&amp;#34;&lt;/span>&lt;span class="o">)&lt;/span>
&lt;span class="nv">INSTALL_MODE&lt;/span>&lt;span class="o">=&lt;/span>daemon&lt;span class="p">;;&lt;/span>
*&lt;span class="o">)&lt;/span>
&lt;span class="nv">INSTALL_MODE&lt;/span>&lt;span class="o">=&lt;/span>no-daemon&lt;span class="p">;;&lt;/span>
&lt;span class="k">esac&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then run the &lt;code>install&lt;/code> script, and you&amp;rsquo;re done.&lt;/p>
&lt;h1 id="or-just-like-just-make-a-multi-user-install">or just, like, just make a multi-user install&lt;/h1>
&lt;blockquote>
&lt;p>It&amp;rsquo;s the future now &amp;ndash; I wrote this section in 2024, which is part of why this segue is so abrupt.&lt;/p>
&lt;/blockquote>
&lt;p>So, I didn&amp;rsquo;t do that. I am still running the single-user nix install that I hacked together a year and a half ago. I was kind of worried initially that I was overlooking something that made this more complicated and it would break the moment I upgraded Nix, but no: it has held up perfectly. I don&amp;rsquo;t know why the official install script forbids single-user installs on macOS, because they seem to work better than multi-user installs &amp;ndash; I have never once had to think about Nix during a macOS upgrade.&lt;/p>
&lt;p>But I feel that I should correct something that I wrote previously: there are actual reasons why you might &lt;em>prefer&lt;/em> a multi-user install, even on macOS.&lt;/p>
&lt;p>It&amp;rsquo;s actually kind of &lt;em>nice&lt;/em> to run Nix builds as special &lt;code>nixbld&lt;/code> users. Those users &lt;em>only&lt;/em> have permission to read and write to the Nix store; they can&amp;rsquo;t accidentally stick config files in your home directory during install time, for example, or reference a source file that you didn&amp;rsquo;t actually copy to the store. Whereas, with a single-user install, it&amp;rsquo;s possible to write Nix expressions that &amp;ndash; accidentally or not &amp;ndash; depend on files anywhere on your computer.&lt;/p>
&lt;p>So the multi-user install has two advantages:&lt;/p>
&lt;ol>
&lt;li>You get some level of sandboxing/protection from malicious packages&lt;/li>
&lt;li>You get an error when you write a non-hermetic nix expression, instead of silently succeeding&lt;/li>
&lt;/ol>
&lt;p>Those advantage don&amp;rsquo;t seem worth any added complexity to me, let alone having to deal with upgrade problems, but not everyone uses Nix exactly the way that I do.&lt;/p>
&lt;p>(1) is interesting from a security perspective &lt;em>in theory&lt;/em>, but since I usually download pre-built derivations from the binary cache and then run it as myself, I am already vulnerable to malicious nixpkgs. Not &lt;em>as&lt;/em> vulnerable, perhaps, if the binary cache did some kind of fancy virus scanning of uploaded executables&amp;hellip; but we&amp;rsquo;re in the same ballpark.&lt;/p>
&lt;p>I don&amp;rsquo;t really care about (2) at all, although I could see this being useful to people who regularly share Nix expressions with other human people. The cost of making a mistake here just seems so low to me, and the fix is so simple, but I guess if you make this mistake very often it would be nice to hear about it right away.&lt;/p>
&lt;p>There might be even more advantages than these! But we have once again reached the limits of my knowledge.&lt;/p>
&lt;h1 id="why-do-you-care-so-much">why do you care so much&lt;/h1>
&lt;blockquote>
&lt;p>It&amp;rsquo;s 2022 again.&lt;/p>
&lt;/blockquote>
&lt;p>I can&amp;rsquo;t really explain why I&amp;rsquo;m so averse to a multi-user Nix installation. But I am.&lt;/p>
&lt;p>And I know that if I had met Nix just a little bit later in life &amp;ndash; after 2.4 was released, when the single-user install option was deprecated &amp;ndash; I never would have come back to Nix in the first place.&lt;/p>
&lt;p>Which is a shame, because I really &lt;em>do&lt;/em> like Nix, and as much as I like to razz it, I still prefer it &amp;ndash; philosophically and practically &amp;ndash; to Homebrew.&lt;/p>
&lt;p>So I am writing this post to say: you &lt;em>can&lt;/em> still install Nix on macOS like any other package manager. You don&amp;rsquo;t need to take one look at it and go &amp;ldquo;whoa what the heck I&amp;rsquo;m not setting up some root-user daemon just to download packages from the internet&amp;rdquo; and close the tab. You can still use it like you would use any other package manager. You just&amp;hellip; have to get your hands dirty first.&lt;/p>
&lt;p>Which might actually be the perfect introduction to Nix, come to think of it.&lt;/p>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>Amusingly, even though the first step included no checksum (&lt;a href="https://nixos.org/download.html#nix-verify-installation">though you can find one if you care&lt;/a>), this script &lt;em>does&lt;/em> checksum the downloaded file. Maybe because it&amp;rsquo;s served from a different subdomain? I don&amp;rsquo;t know.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description><pubDate>Fri, 26 Jan 2024 00:00:00 +0000</pubDate><link>https://ianthehenry.com/posts/how-to-learn-nix/installing-nix-on-macos/</link><guid isPermaLink="true">https://ianthehenry.com/posts/how-to-learn-nix/installing-nix-on-macos/</guid></item><item><title>The Fibonacci Matrix</title><description>&lt;p>When you think about the Fibonacci sequence, you probably imagine a swirling vortex of oscillating points stretching outwards to infinity:&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="teaser" width="384" height="256">&lt;a class="image-container" href="https://ianthehenry.com/posts/fibonacci/hero.f8f0320540a779c19ec927ada02ec9ecbfd90a6d6d418f8e71e99ac4e5d4deca.png">&lt;picture>
&lt;img
class="pixel-art"
src="https://ianthehenry.com/posts/fibonacci/hero.f8f0320540a779c19ec927ada02ec9ecbfd90a6d6d418f8e71e99ac4e5d4deca.png"
width="768"
height="512"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAcklEQVR4nEzNIQoFIRCA4XVnQBDGbrQIZg8wYPI43k7wEl7AYPASIugLr&amp;#43;wf//Lh80kIoZQKITCz&amp;#43;C8AkFI651JKY4xSinjfFxGNMd57ImqtzTnXWqi1jjFaa2utvfe99znn3vvknJmZiADg6/0CAAD//6ojIewsRzhzAAAAAElFTkSuQmCC);"
/>&lt;/a>
&lt;/canvas>&lt;/p>
&lt;p>Okay, no, obviously you don&amp;rsquo;t. &lt;em>Yet&lt;/em>.&lt;/p>
&lt;p>When you think about the Fibonacci sequence, you probably flush with a latent rage when you remember that it is, more often than not, the way that we introduce the concept of &amp;ldquo;recursive functions&amp;rdquo; to new programmers, in some sort of cruel hazing intended to make it harder for them to ever appreciate how recursion can help them write better programs. Sometimes we even add memoization, and call it &amp;ldquo;dynamic programming,&amp;rdquo; in order to impress upon them that even the most trivial problems deserve complex, inefficient solutions.&lt;/p>
&lt;p>Er, okay, you probably don&amp;rsquo;t think about the Fibonacci sequence much at all. It doesn&amp;rsquo;t, you know, come up very often.&lt;/p>
&lt;p>But I hope that you will spend some time thinking about it with me today, because I think that the Fibonacci sequence &amp;ndash; despite being a terrible showcase for recursion &amp;ndash; is a really interesting vector for discussing some techniques from linear algebra.&lt;/p>
&lt;div class="table-container">
&lt;table class="fib-table">
&lt;thead>&lt;tr>&lt;th>how to fibonacci&lt;/th>&lt;th>space complexity&lt;/th>&lt;th>time complexity&lt;/th>&lt;/tr>&lt;/thead>
&lt;tbody>
&lt;tr>&lt;td>insane recursion&lt;/td>&lt;td class="bad">exponential&lt;/td>&lt;td class="bad">exponential&lt;/td>&lt;/tr>
&lt;tr>&lt;td>memoized insane recursion&lt;/td>&lt;td>linear&lt;/td>&lt;td>linear&lt;/td>&lt;/tr>
&lt;tr>&lt;td>trivial iteration&lt;/td>&lt;td class="good">constant&lt;/td>&lt;td>linear&lt;/td>&lt;/tr>
&lt;tr>&lt;td>exponentiation-by-squaring&lt;/td>&lt;td class="good">constant&lt;/td>&lt;td class="good">logarithmic&lt;/td>&lt;/tr>
&lt;tr>&lt;td>eigendecomposition&lt;/td>&lt;td colspan="2">let's talk&lt;/td>&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;p>We will spend no time on the recursive Fibonaccis; I&amp;rsquo;m sure that you&amp;rsquo;ve seen them before. Instead, let&amp;rsquo;s skip right to the &amp;ldquo;obvious&amp;rdquo; way to calculate Fibonacci numbers:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="kd">function&lt;/span> &lt;span class="nx">fib&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="kd">let&lt;/span> &lt;span class="nx">current&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kd">let&lt;/span> &lt;span class="nx">previous&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kd">let&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="nx">n&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">next&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">current&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">previous&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="nx">previous&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">current&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="nx">current&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">next&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="nx">current&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>No recursion, no memoization. We have two pieces of state: the &amp;ldquo;current number&amp;rdquo; and the &amp;ldquo;previous&amp;rdquo; number, and at every step of the iteration we advance both of these to new values.&lt;/p>
&lt;p>But there&amp;rsquo;s something very interesting about this function: the new values for our state are a &lt;em>linear combination&lt;/em> of the old values.&lt;/p>
&lt;pre>&lt;code>current' = current + previous
previous' = current
&lt;/code>&lt;/pre>
&lt;p>Using &lt;code>x'&lt;/code> to mean &amp;ldquo;the next value for &lt;code>x&lt;/code>.&amp;rdquo;&lt;/p>
&lt;p>And you might recognize this as a &amp;ldquo;system of linear equations.&amp;rdquo; I think it&amp;rsquo;s more obvious when we write it like this:&lt;/p>
&lt;pre>&lt;code>current' = 1 * current + 1 * previous
previous' = 1 * current + 0 * previous
&lt;/code>&lt;/pre>
&lt;p>And you might remember that there&amp;rsquo;s another, more cryptic way to write down a system of linear equations:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>current'&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>previous'&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>current&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>previous&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>This is exactly the same thing! This is just another way of writing the equation &amp;ndash; it&amp;rsquo;s just a shorthand notation.&lt;/p>
&lt;p>Here, let&amp;rsquo;s test it out to make sure of that:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>8&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1 • 8 + 1 • 5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1 • 8 + 0 • 5&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>13&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>8&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>Well that&amp;rsquo;s exactly what we expected &amp;ndash; 13 is the next Fibonacci number in the sequence, and 8 was the previous one.&lt;/p>
&lt;p>We can, of course, repeat this process, by applying the system of linear equations again:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>13&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>8&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>21&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>13&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>Or, to put that another way:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>8&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>21&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>13&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>And here&amp;rsquo;s why we care: matrix multiplication is associative, so we can actually think of that like this:&lt;/p>
&lt;div class="math">
&lt;div class="open-paren">&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="close-paren"> &lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>8&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>21&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>13&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>Or:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>8&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>21&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>13&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>In other words: given a system of linear equations to find the &lt;em>next&lt;/em> state of our iteration, we can square the matrix-of-coefficients of the system to find a new system of linear equations that represents &amp;ldquo;two states from now.&amp;rdquo;&lt;/p>
&lt;p>Of course we don&amp;rsquo;t &lt;em>need&lt;/em> matrices to do this. We can compute a formula for &amp;ldquo;two steps&amp;rdquo; of our iteration using term substitution:&lt;/p>
&lt;pre>&lt;code>current' = current + previous
previous' = current
current'' = current' + previous'
previous'' = current'
current'' = (current + previous) + current
previous'' = (current + previous)
current'' = 2 * current + previous
previous'' = current + previous
&lt;/code>&lt;/pre>
&lt;p>Which is a new system of linear equations &amp;ndash; which we can represent as a matrix as well.&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>current''&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>previous''&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>current&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>previous&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>We got the same result, because of course we did: multiplying by this matrix really &lt;em>means&lt;/em> &amp;ldquo;advance to the next state.&amp;rdquo; Multiplying twice means &amp;ldquo;advance to the next state and then advance to the next state after that.&amp;rdquo;&lt;/p>
&lt;p>And we can keep going. What&amp;rsquo;s the state three steps from now?&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>Or, more concisely:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="pow">3&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>If we do this repeatedly, you might notice a familiar pattern start to emerge:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="pow">4&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>5&lt;/td>
&lt;td>3&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>2&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="pow">5&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>8&lt;/td>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5&lt;/td>
&lt;td>3&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="pow">6&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>13&lt;/td>
&lt;td>8&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>8&lt;/td>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>Which makes sense, doesn&amp;rsquo;t it? Because if we multiply this matrix with the matrix &lt;code>[1 0]&lt;/code> &amp;ndash; our starting values &amp;ndash; then it&amp;rsquo;s going to advance forward through six steps of the Fibonacci sequence in a single leap. So naturally we have to be encoding &lt;em>something&lt;/em> about the sequence itself in the matrix &amp;ndash; otherwise we wouldn&amp;rsquo;t be able to advance by N steps in constant time.&lt;/p>
&lt;p>Now, the insight that takes this from linear to logarithmic is that we don&amp;rsquo;t have to do this multiplication one step at a time. We can multiply in leaps and bounds.&lt;/p>
&lt;p>Let&amp;rsquo;s call our original starting matrix F, for Fibonacci.&lt;/p>
&lt;div class="math">
&lt;div>F&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>We&amp;rsquo;ve already calculated F&lt;sup>2&lt;/sup>:&lt;/p>
&lt;div class="math">
&lt;div>F&lt;sup>2&lt;/sup>&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>And now it&amp;rsquo;s only one more matrix multiplication to calculate F&lt;sup>4&lt;/sup>:&lt;/p>
&lt;div class="math">
&lt;div>F&lt;sup>4&lt;/sup>&lt;/div>
&lt;div>=&lt;/div>
&lt;div>F&lt;sup>2&lt;/sup>F&lt;sup>2&lt;/sup>&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>We can use this fact to calculate arbitrary matrix powers, by breaking the problem up into products of powers of two:&lt;/p>
&lt;div class="inline-math">
&lt;div class="content">
F&lt;sup>21&lt;/sup> = F&lt;sup>16&lt;/sup>F&lt;sup>4&lt;/sup>F&lt;sup>1&lt;/sup>
&lt;/div>
&lt;/div>
&lt;p>And by doing that, we can calculate the nth Fibonacci number in only log&lt;sub>2&lt;/sub>(n) steps.&lt;/p>
&lt;aside>
&lt;p>If you really want to talk about &amp;ldquo;dynamic programming,&amp;rdquo; now&amp;rsquo;s the time &amp;ndash; we broke a harder operation into a series of &lt;em>shared subcomputations&lt;/em>. And we did it in constant space!&lt;/p>
&lt;/aside>
&lt;p>Okay, so that&amp;rsquo;s fun and all, but that&amp;rsquo;s not really what this blog post is about.&lt;/p>
&lt;p>I don&amp;rsquo;t know about you, but if I came across this matrix in the wild, I would not think &amp;ldquo;Oh, that&amp;rsquo;s the Fibonacci sequence&amp;rdquo;:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>I would probably think &amp;ldquo;huh, I dunno, it&amp;rsquo;s like, a reflection, sort of, or maybe a shear; what&amp;rsquo;s a shear again, hang on, I need to see a picture.&amp;rdquo;&lt;/p>
&lt;p>That is, I am used to thinking of matrices as transformations of &lt;em>points in space&lt;/em> &amp;ndash; scales and rotations and things like that. I&amp;rsquo;m not really used to thinking of matrices as &amp;ldquo;state machines.&amp;rdquo;&lt;/p>
&lt;p>But this duality is the beauty of linear algebra! Matrices are transformations of points in space and graphs and state machines all at the same time.&lt;/p>
&lt;p>So let&amp;rsquo;s take a look at the Fibonacci &lt;em>transformation&lt;/em>, applied to arbitrary points in R&lt;sup>2&lt;/sup>:&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="transform" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>That animation is progressively applying and removing the transformation, so we can get some intuition for how it deforms a square. But we&amp;rsquo;re really more interested in repeated applications of the transformation. So let&amp;rsquo;s start with the same points, but multiply by that same matrix over and over:&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="transform2" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>Interesting. Over time, they have a tendency to stretch out along the long diagonals of this rhombus. Let&amp;rsquo;s zoom out:&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="transform3" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>Every time a point reflects over that diagonal, it reflects at a slightly different angle, slowly converging towards this straight line.&lt;/p>
&lt;p>You might already have an idea of what that straight line means. You might know that, if you look at the ratio between subsequent Fibonacci numbers, they approximate the &lt;em>golden ratio&lt;/em>:&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/p>
&lt;pre>&lt;code>1 / 1 = 1
2 / 1 = 2
3 / 2 = 1.5
5 / 3 = 1.666...
8 / 5 = 1.6
13 / 8 = 1.625
21 / 13 = 1.61538462
34 / 21 = 1.61904762
&lt;/code>&lt;/pre>
&lt;p>The golden ratio is irrational, but every subsequent Fibonacci number is a better and better rational approximation. (The golden ratio is around 1.618033988749 &amp;ndash; so we&amp;rsquo;re already pretty close.)&lt;/p>
&lt;p>It&amp;rsquo;s interesting to see that these estimations don&amp;rsquo;t &amp;ldquo;sneak up&amp;rdquo; on the golden ratio. In fact they alternate between over- and under-estimating it. Which is exactly what we saw in our visualization!&lt;/p>
&lt;p>If you return to the &amp;ldquo;state machine&amp;rdquo; interpretation of our matrix, remember that the value we&amp;rsquo;re plotting as &lt;code>x&lt;/code> is really &amp;ldquo;the current Fibonacci number,&amp;rdquo; and the value we&amp;rsquo;re plotting as &lt;code>y&lt;/code> is &amp;ldquo;the previous Fibonacci number.&amp;rdquo; So the ratio between successive numbers &amp;ndash; &lt;code>x/y&lt;/code> &amp;ndash; is just the slope of the lines that our points are traveling along. And we could see points reflecting over that diagonal, over- and under-shooting it, slowly converging&amp;hellip; towards the line whose slope is the golden ratio.&lt;/p>
&lt;p>Which is, in fact, the &amp;ldquo;long diagonal&amp;rdquo; of our rhombus.&lt;/p>
&lt;p>And this makes sense, I think &amp;ndash; this isn&amp;rsquo;t some weird coincidence. The golden ratio is all about the ratio between parts and wholes being the same as ratio between parts. And the Fibonacci sequence is all about adding together parts to become wholes that become parts in the next number of the sequence.&lt;/p>
&lt;p>Here, our two parts are the &amp;ldquo;current&amp;rdquo; and &amp;ldquo;previous&amp;rdquo; values, and the whole that they make is the &amp;ldquo;next&amp;rdquo; Fibonacci number. Even if we start with two numbers that are completely unrelated to the Fibonacci sequence &amp;ndash; say, &lt;code>8&lt;/code> and &lt;code>41&lt;/code> &amp;ndash; the simple way that we pick the next number will cause us to approximate the golden ratio after only a few iterations:&lt;/p>
&lt;pre>&lt;code>8 / 41 = 0.1951219
(8 + 41 = 49) / 8 = 6.125
(49 + 8 = 57) / 49 = 1.16326531
(57 + 49 = 106) / 57 = 1.85964912
(106 + 57 = 163) / 106 = 1.53773585
&lt;/code>&lt;/pre>
&lt;p>Why is that? Well, because of the definition of the golden ratio.&lt;/p>
&lt;div class="inline-math">
&lt;div class="content">
(A + B) / A = A / B = φ
&lt;/div>
&lt;/div>
&lt;p>This is &lt;em>extremely&lt;/em> unrigorous, but I can try to sketch out a very informal argument for why this is:&lt;/p>
&lt;p>Let&amp;rsquo;s say the ratio between &lt;code>A&lt;/code> and &lt;code>B&lt;/code> is some unknown quantity &lt;code>S&lt;/code>. It&amp;rsquo;s not the golden ratio, it might not be anywhere near the golden ratio; we have no idea what it is. In my 8 and 41 example, it wasn&amp;rsquo;t even in the right ballpark.&lt;/p>
&lt;div class="inline-math">
&lt;div class="content">A/B = S&lt;/div>
&lt;div class="content">(A + B) / A = (1 + B / A) = 1 + (1/S)&lt;/div>
&lt;/div>
&lt;p>So the ratio between the next element in our series and A will be &lt;code>(1 + (1/S))&lt;/code>.&lt;/p>
&lt;p>We still don&amp;rsquo;t know what &lt;code>S&lt;/code> is! But if we do this &lt;em>again&lt;/em>&amp;hellip;&lt;/p>
&lt;div class="inline-math">
&lt;div class="content">A' / B' = 1 + (1/S)&lt;/div>
&lt;div class="content">(A' + B') / A' =&lt;/div>
&lt;div class="content">(1 + (B' / A')) =&lt;/div>
&lt;div class="content">1 + (1 / (1 + (1 / S)))&lt;/div>
&lt;/div>
&lt;p>After each iteration, the original &lt;code>S&lt;/code> will become a smaller and smaller component in the final answer, until eventually we&amp;rsquo;ll just have an expression that looks like this:&lt;/p>
&lt;div class="inline-math">
&lt;div class="content">1 + (1 / (1 + (1 / (1 + (1 / (1 + (1 / ...)))))))&lt;/div>
&lt;/div>
&lt;p>Whatever our original &lt;code>S&lt;/code> was, its contribution to the final result will eventually be negligible. Even after just a few iterations, we can see that the choice of &lt;code>S&lt;/code> doesn&amp;rsquo;t make a huge difference in the outcome:&lt;/p>
&lt;div class="inline-math">
&lt;div class="content">1 + (1 / (1 + (1 / (1 + (1 / (1 + (1 / -5000))))))) = 1.6667&lt;/div>
&lt;div class="content">1 + (1 / (1 + (1 / (1 + (1 / (1 + (1 / 0.001))))))) = 1.5002&lt;/div>
&lt;/div>
&lt;p>And of course even that will fade away after a few more steps.&lt;/p>
&lt;p>In fact the version of that expression with an infinite number of steps &amp;ndash; where there is no &lt;code>S&lt;/code> at all, but just an infinite sequence of divisions &amp;ndash; is the &amp;ldquo;continued fraction&amp;rdquo; expression of the golden ratio.&lt;/p>
&lt;p>Except, well, I&amp;rsquo;m lying here.&lt;/p>
&lt;p>That residue will not fade away for &lt;em>all&lt;/em> values of &lt;code>S&lt;/code>. First of all, if &lt;code>S&lt;/code> is zero, it doesn&amp;rsquo;t matter how small that term gets &amp;ndash; you&amp;rsquo;re not going to squeeze a number out of it.&lt;/p>
&lt;p>But there is another, more interesting value of &lt;code>S&lt;/code> that breaks this rule. There is &lt;em>one&lt;/em> other number that will not tend towards 1.618 when you repeatedly take its reciprocal and add one. It is the number that is already one plus its own reciprocal:&lt;/p>
&lt;div class="inline-math">
&lt;div class="content">1 + (1 / 1.61803399) = 1.61803399&lt;/div>
&lt;/div>
&lt;p>Oh, gosh, yes, the golden ratio is one plus its own reciprocal. But I was talking about the &lt;em>other&lt;/em> number with that property:&lt;/p>
&lt;div class="inline-math">
&lt;div class="content">1 + (1 / -0.61803399) = -0.61803399&lt;/div>
&lt;/div>
&lt;p>This number is (1 - φ), and it is also -φ&lt;sup>-1&lt;/sup>. The golden ratio is weird like that.&lt;/p>
&lt;p>That number is a weird number, because if we have two numbers with that ratio &amp;ndash; say, &lt;code>-1.236&lt;/code> and &lt;code>2&lt;/code> &amp;ndash; and we applied our transformation, those points would not spread their wings towards the diagonal. What would they do instead?&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="transform4" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>Aha. Well, that makes sense.&lt;/p>
&lt;p>Some points tend towards the top right, some points tend towards the bottom left, but some points get &lt;em>stuck&lt;/em>. Sucked into the origin, cursed to forever travel along this one straight line.&lt;/p>
&lt;p>Points along the long diagonal also travel in a straight line &amp;ndash; they don&amp;rsquo;t bounce over the diagonal, because they&amp;rsquo;re already on it. Let&amp;rsquo;s just focus on these perfectly straight lines:&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="transform5" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>Not all matrices will produce straight lines like this when you apply them repeatedly. A rotation matrix, for example, will always change the direction of every single line each time you multiply a point by it.&lt;/p>
&lt;p>These straight lines are called &lt;em>eigenvectors&lt;/em>, which is German&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup> for something like &amp;ldquo;intrinsic vector&amp;rdquo; or &amp;ldquo;characteristic vector.&amp;quot;&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>&lt;/p>
&lt;p>Well, to be more precise, any particular point on those straight lines is an &amp;ldquo;eigenvector.&amp;rdquo; The vector &lt;code>[φ 1]&lt;/code> is an eigenvector, and so is &lt;code>[-2.1φ -2.1]&lt;/code>. And the vector &lt;code>[-1/φ 1]&lt;/code> is an eigenvector, and so is &lt;code>[-2/φ 2]&lt;/code>.&lt;/p>
&lt;p>But all of the eigenvectors on each line are &amp;ldquo;similar,&amp;rdquo; so I&amp;rsquo;m just going to pick &lt;code>[φ 1]&lt;/code> and &lt;code>[(1-φ) 1]&lt;/code> as our two representative eigenvectors.&lt;/p>
&lt;p>When you multiply an eigenvector of a matrix by the matrix itself, you get back a new eigenvector on &amp;ldquo;the same line.&amp;rdquo; That is to say, you get back another eigenvector that is just some scalar multiple of the original eigenvector.&lt;/p>
&lt;p>For example, when we multiply our first eigenvector by the Fibonacci matrix:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ + 1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>Well&amp;hellip; it&amp;rsquo;s not &lt;em>obvious&lt;/em> that this is the case, but we actually just scaled the vector by φ. Because φ&lt;sup>2&lt;/sup> = φ + 1. The golden ratio is weird.&lt;/p>
&lt;p>Similarly:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>2 - φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>We scaled it by (1 - φ), again somewhat cryptically:&lt;/p>
&lt;div class="inline-math">
&lt;div class="content">(1 - φ)(1 - φ) =&lt;/div>
&lt;div class="content">(1 - 2φ + φ&lt;sup>2&lt;/sup>) =&lt;/div>
&lt;div class="content">(1 - 2φ + φ + 1) =&lt;/div>
&lt;div class="content">(2 - φ)&lt;/div>
&lt;/div>
&lt;p>So when we multiply our Fibonacci matrix with its eigenvectors, we scale those numbers by φ and (1 - φ). These scaling factors are called &amp;ldquo;eigenvalues,&amp;rdquo; and it&amp;rsquo;s &lt;em>weird&lt;/em> that they look so much like the eigenvectors. That&amp;rsquo;s&amp;hellip; that&amp;rsquo;s a weird Fibonacci coincidence, a weird golden ratio thing, and not a general pattern that holds for eigenvectors and eigenvalues in general.&lt;/p>
&lt;p>Okay, so why do we care about this?&lt;/p>
&lt;p>Well, once we know the eigenvectors and eigenvalues of the matrix, we can actually perform repeated matrix multiplication in &lt;em>constant&lt;/em> time.&lt;/p>
&lt;p>&lt;em>&amp;hellip;Sort of&lt;/em>. You have to imagine a big asterisk after that sentence, which I will explain below.&lt;/p>
&lt;p>To explain how, we&amp;rsquo;re going to need to do a little bit of linear algebra. But first, I just want to restate everything I&amp;rsquo;ve said so far in explicit notation:&lt;/p>
&lt;p>Multiplying F with each eigenvector is the same as multiplying that eigenvector by its corresponding eigenvalue. So:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;span style="color:var(--palette-yellow)">φ&lt;/span>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;span style="color:var(--palette-yellow)">1&lt;/span>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div>&lt;span style="color:var(--palette-orange)">φ&lt;/span>&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;span style="color:var(--palette-yellow)">φ&lt;/span>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;span style="color:var(--palette-yellow)">1&lt;/span>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>And:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;span style="color:var(--palette-magenta)">1 - φ&lt;/span>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;span style="color:var(--palette-magenta)">1&lt;/span>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div>&lt;span style="color:var(--palette-purple)">(1 - φ)&lt;/span>&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;span style="color:var(--palette-magenta)">1 - φ&lt;/span>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;span style="color:var(--palette-magenta)">1&lt;/span>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>Right. But there&amp;rsquo;s actually a way to write those two equalities as a single equality:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;span style="color:var(--palette-yellow)">φ&lt;/span>&lt;/td>
&lt;td>&lt;span style="color:var(--palette-magenta)">1 - φ&lt;/span>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;span style="color:var(--palette-yellow)">1&lt;/span>&lt;/td>
&lt;td>&lt;span style="color:var(--palette-magenta)">1&lt;/span>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;span style="color:var(--palette-yellow)">φ&lt;/span>&lt;/td>
&lt;td>&lt;span style="color:var(--palette-magenta)">1 - φ&lt;/span>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;span style="color:var(--palette-yellow)">1&lt;/span>&lt;/td>
&lt;td>&lt;span style="color:var(--palette-magenta)">1&lt;/span>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;span style="color:var(--palette-orange)">φ&lt;/span>&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>&lt;span style="color:var(--palette-purple)">1 - φ&lt;/span>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>Instead of writing out each eigenvector as a separate column vector, I stuck them into a matrix. And instead of scaling each one by a scalar, I multiplied that matrix by a diagonal matrix.&lt;/p>
&lt;p>This is the same statement, though: right-multiplication by a diagonal matrix just means &amp;ldquo;scale the columns of the left matrix by the corresponding diagonal value.&amp;rdquo; We can gut check this by performing the multiplication, and seeing that we&amp;rsquo;re making the exact same statements as before:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ + 1&lt;/td>
&lt;td>2 - φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ + 1&lt;/td>
&lt;td>2 - φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>But now we&amp;rsquo;re making these statement about both eigenvectors in parallel.&lt;/p>
&lt;p>This equality &amp;ndash; this statement about how multiplication by the Fibonacci matrix scales eigenvectors &amp;ndash; is the secret to computing Fibonacci numbers in &amp;ldquo;constant time&amp;rdquo;:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>The trick here is that we&amp;rsquo;re going to right-multiply both sides of the equation by the inverse of our eigenvector matrix. This will eliminate it from the left-hand side entirely:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="pow">-1&lt;/div>
&lt;/div>
&lt;p>And now we have a new way to calculate the &amp;ldquo;next Fibonacci number.&amp;rdquo; Previously we knew how to do it by multiplying with the matrix &lt;code>F&lt;/code>. Now we can do it by multiplying with, uhh, this inverse eigenvector matrix thing, and then the diagonal matrix of eigenvalues, and then the non-inverse matrix-of-eigenvectors.&lt;/p>
&lt;p>Much simpler, right?&lt;/p>
&lt;p>This is getting really long and complicated and I&amp;rsquo;m going to run out of space soon, so let&amp;rsquo;s give these things names:&lt;/p>
&lt;div class="math">
&lt;div>F&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;div class="math">
&lt;div>Q&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;div class="math">
&lt;div>Λ&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>That&amp;rsquo;s an upper-case lambda, and look, it&amp;rsquo;s just the convention for the eigenvalue matrix. Eigenvalues are called λ, and when you put them in a diagonal matrix you call it Λ. I don&amp;rsquo;t make the rules here.&lt;/p>
&lt;p>Now that we have some abbreviations, we can write that as the much more palatable:&lt;/p>
&lt;div class="inline-math">
&lt;div class="content">
&lt;div>F = Q Λ Q&lt;sup>-1&lt;/sup>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>Now, the whole reason that we&amp;rsquo;re doing this is to take advantage of another trick of associativity:&lt;/p>
&lt;div class="inline-math">
&lt;div class="content">
&lt;div>F&lt;sup>2&lt;/sup> = (Q Λ Q&lt;sup>-1&lt;/sup>)(Q Λ Q&lt;sup>-1&lt;/sup>)&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="inline-math">
&lt;div class="content">
&lt;div>F&lt;sup>2&lt;/sup> = Q Λ (Q&lt;sup>-1&lt;/sup>Q) Λ Q&lt;sup>-1&lt;/sup>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="inline-math">
&lt;div class="content">
&lt;div>F&lt;sup>2&lt;/sup> = Q Λ Λ Q&lt;sup>-1&lt;/sup>&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="inline-math">
&lt;div class="content">
&lt;div>F&lt;sup>2&lt;/sup> = Q Λ&lt;sup>2&lt;/sup> Q&lt;sup>-1&lt;/sup>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>That was very abstract, so take a second to think about what this &lt;em>means&lt;/em>. F&lt;sup>2&lt;/sup> is the matrix that calculates two steps of our Fibonacci state machine. And we can use this same trick to calculate &lt;em>any&lt;/em> power of F, just by calculating powers of Λ.&lt;/p>
&lt;div class="inline-math">
&lt;div class="content">
&lt;div>F&lt;sup>n&lt;/sup> = Q Λ&lt;sup>n&lt;/sup> Q&lt;sup>-1&lt;/sup>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>And this is good, because Λ is a &lt;em>diagonal&lt;/em> matrix. And it&amp;rsquo;s really easy to exponentiate a diagonal matrix! You just exponentiate each element of its diagonal. We don&amp;rsquo;t even need to use repeated squaring.&lt;/p>
&lt;p>This means that we can actually calculate arbitrary powers of F in &lt;em>constant&lt;/em> time&amp;hellip; if we pretend that exponentiation of a scalar is a constant time operation.&lt;/p>
&lt;p>It&amp;rsquo;s not, though. I mean, yes, exponentiation of an IEEE 754 64-bit floating-point number &lt;em>is&lt;/em> constant time, but that&amp;rsquo;s not what we said. We&amp;rsquo;re talking about exponentiating an irrational number, and my computer can only represent approximations of that number, and that floating-point error adds up fast. So in order to actually use this to compute large Fibonacci numbers, we would need to use arbitrary-precision floating point, and exponentiating arbitrary precision values is &lt;em>not&lt;/em> constant time. It&amp;rsquo;s&amp;hellip; I don&amp;rsquo;t know, probably logarithmic? But like both to the exponent and the size of the result, and the size of the result is increasing exponentially, so it nets out to linear? I don&amp;rsquo;t actually know.&lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup>&lt;/p>
&lt;p>But I don&amp;rsquo;t want to spoil the fun. This is still a very interesting trick, and it&amp;rsquo;s worth understanding how it works, even if it doesn&amp;rsquo;t actually give us a way to compute arbitrarily large Fibonacci numbers in constant time.&lt;/p>
&lt;p>So: what are we doing.&lt;/p>
&lt;p>We moved a bunch of symbols around, and we wound up with this expression:&lt;/p>
&lt;div class="inline-math">
&lt;div class="content">
&lt;div>F&lt;sup>n&lt;/sup> = Q Λ&lt;sup>n&lt;/sup> Q&lt;sup>-1&lt;/sup>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>But I don&amp;rsquo;t really know what Q&lt;sup>-1&lt;/sup> means, and it&amp;rsquo;s not really clear to me why I should care. Why is multiplying by these three weird matrices the same as multiplying by F? What, intuitively, are we doing here?&lt;/p>
&lt;p>At a high level, we&amp;rsquo;re translating points into a different coordinate system, then doing something to it, and then translating them back into our original coordinate system.&lt;/p>
&lt;p>You already know that we can write any point in space as a vector &amp;ndash; X and Y coordinates. That&amp;rsquo;s what we&amp;rsquo;ve been doing this whole time.&lt;/p>
&lt;p>But we can &lt;em>also&lt;/em> write a point in space as the sum of two other vectors. Like, &lt;code>[5 3]&lt;/code>. We could write that as &lt;code>[1 2] + [4 1]&lt;/code> instead. Which, okay, sure. That&amp;rsquo;s not very interesting.&lt;/p>
&lt;p>One &amp;ldquo;interesting&amp;rdquo; way to write &lt;code>[5 3]&lt;/code> is as the sum of these two vectors: &lt;code>[5 0] + [0 3]&lt;/code>. Or, to say that another way:&lt;/p>
&lt;div class="math">
&lt;div>5&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>+&lt;/div>
&lt;div>3&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>This is interesting because &lt;code>[1 0]&lt;/code> and &lt;code>[0 1]&lt;/code> are basically the &amp;ldquo;X axis&amp;rdquo; and &amp;ldquo;Y axis.&amp;rdquo; And we can think of the point &lt;code>[5 3]&lt;/code> as a (trivial!) linear combination of these two axes.&lt;/p>
&lt;p>But we could pick &lt;em>different&lt;/em> axes. We can pick any vectors we want as our axes,&lt;sup id="fnref:5">&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref">5&lt;/a>&lt;/sup> so let&amp;rsquo;s pretend for a moment that our axes are &lt;code>[1 1]&lt;/code> and &lt;code>[1 -1]&lt;/code> instead. Which means that we would write &lt;code>[5 3]&lt;/code> as:&lt;/p>
&lt;div class="math">
&lt;div>4&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>+&lt;/div>
&lt;div>1&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>-1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>Or, to write that another way:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>-1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>4&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>Now we can think of this vector-of-coefficients, &lt;code>[4 1]&lt;/code>, as another way to identify the point in space &lt;code>x=5 y=3&lt;/code> when we we&amp;rsquo;re pretending that our axes are &lt;code>[1 1]&lt;/code> and &lt;code>[1 -1]&lt;/code>. Except in linear algebra we&amp;rsquo;d call these &amp;ldquo;basis vectors&amp;rdquo; instead of &amp;ldquo;axes.&amp;rdquo;&lt;/p>
&lt;p>But how did we find the coefficients &lt;code>[4 1]&lt;/code>? Well, I just found that one by hand; it was pretty easy. But &lt;em>in general&lt;/em>, if we want to express some other &lt;em>point&lt;/em> using these basis vectors &amp;ndash; let&amp;rsquo;s say &lt;code>[63 -40]&lt;/code> &amp;ndash; we&amp;rsquo;ll need to solve an equation that looks like this:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>-1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>?&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>?&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>63&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>-40&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>And we can do that by, you know, regular algebra. We &amp;ldquo;divide&amp;rdquo; both sides by our matrix-of-basis-vectors, by left-multiplying with the inverse matrix:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>-1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="sub">-1&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>-1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>?&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>?&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>-1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="sub">-1&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>63&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>-40&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>And after the inverses cancel, we&amp;rsquo;re left with the following formula:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>?&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>?&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>-1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="sub">-1&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>63&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>-40&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>And the problem reduces to matrix inversion.&lt;/p>
&lt;p>Now, I don&amp;rsquo;t know about you, but I don&amp;rsquo;t remember how to invert a matrix. I know there&amp;rsquo;s a formula in two dimensions, but the only thing I remember about it is that it involves calculating the determinant, and I forgot how to do that too. So let&amp;rsquo;s just ask a computer to invert it for us:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>?&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>?&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0.5&lt;/td>
&lt;td>0.5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0.5&lt;/td>
&lt;td>-0.5&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>63&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>-40&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>Hmm. I feel like I probably could&amp;rsquo;ve worked that out myself.&lt;/p>
&lt;p>But that lets us solve the equation, and figure out how to write the point &lt;code>[63 -40]&lt;/code> as a combination of the vectors &lt;code>[1 1]&lt;/code> and &lt;code>[1 -1]&lt;/code>:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0.5&lt;/td>
&lt;td>0.5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0.5&lt;/td>
&lt;td>-0.5&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>63&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>-40&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>51.5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>11.5&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>Great! We did it.&lt;/p>
&lt;p>And &lt;em>here&amp;rsquo;s why we care:&lt;/em>&lt;/p>
&lt;p>We can use this exact same trick to write down the points in our Fibonacci sequence as a linear combination of our two eigenvectors. Like this:&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="bases" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>Click or tap to add points there, to see how we can write each point in space as a combination of the &amp;ldquo;short diagonal&amp;rdquo; and &amp;ldquo;long diagonal&amp;rdquo; eigenvectors of our matrix.&lt;/p>
&lt;p>Normally to identify a point in space we would give its XY coordinates: go this far along the X-axis, then this far along the Y-axis. But here we&amp;rsquo;re representing points in &amp;ldquo;φ&amp;rdquo; and &amp;ldquo;1 - φ&amp;rdquo; coordinates: go this far along the short diagonal, then this far along the long diagonal.&lt;/p>
&lt;p>But how do we know how far to go along these diagonals? Well, we &amp;ldquo;divide by&amp;rdquo; the eigenvectors. In other words, we have to compute the inverse of this matrix:&lt;/p>
&lt;div class="math">
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>φ&lt;/td>
&lt;td>1 - φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="pow">-1&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="ratio">
&lt;div>1&lt;/div>
&lt;hr />
&lt;div>√5&lt;/div>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>φ - 1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>-1&lt;/td>
&lt;td>φ&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;aside>
&lt;p>(φ - 1) isn&amp;rsquo;t a typo &amp;ndash; that&amp;rsquo;s -(1 - φ), or φ&lt;sup>-1&lt;/sup>. The golden ratio is &lt;em>weird&lt;/em>.&lt;/p>
&lt;/aside>
&lt;p>Now, matrix inversion is boring, so I&amp;rsquo;m just presenting the answer here. This inverse matrix is how we can convert from &amp;ldquo;XY coordinates&amp;rdquo; into &amp;ldquo;eigenvector coordinates.&amp;rdquo;&lt;/p>
&lt;p>Let&amp;rsquo;s work through a concrete example to make sure this works.&lt;/p>
&lt;p>&lt;code>[8 5]&lt;/code> is a point on the Fibonacci sequence. We can express that as a combination of eigenvectors instead:&lt;/p>
&lt;div class="math">
&lt;div class="ratio">
&lt;div>1&lt;/div>
&lt;hr />
&lt;div>√5&lt;/div>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>φ - 1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>-1&lt;/td>
&lt;td>φ&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>8&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>≈&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>4.96&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0.04&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>4.96 and 0.04 are the coefficients we will pair with our eigenvectors: we have to travel 4.96 units down the long diagonal, and 0.04 units along the short diagonal to arrive at the point &lt;code>[8 5]&lt;/code>.&lt;/p>
&lt;div class="math">
&lt;div>4.96&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>+&lt;/div>
&lt;div>0.04&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1 - Φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>≈&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>8.025&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4.96&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>+&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>-0.025&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0.04&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>8&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>Great. It worked!&lt;/p>
&lt;p>But that wasn&amp;rsquo;t very interesting &amp;ndash; we just converted our point into the eigenvector basis and then right back into the normal XY basis. It was kind of a pointless transformation.&lt;/p>
&lt;p>But we don&amp;rsquo;t have to do the unconversion immediately. We can keep the point in this &amp;ldquo;eigenbasis&amp;rdquo; for a little while, and do stuff to the vector-of-coefficients, and &lt;em>then&lt;/em> convert it back.&lt;/p>
&lt;p>Specifically, we can scale the coefficients by the eigenvalues of our Fibonacci matrix. We can multiply the &amp;ldquo;long diagonal&amp;rdquo; component by Φ&lt;sup>2&lt;/sup>, and multiply the short diagonal component by (1 - Φ)&lt;sup>2&lt;/sup>, and we&amp;rsquo;ll have a new point: something close to &lt;code>[12.985 0.015]&lt;/code>. And if we convert that back into XY coordinates:&lt;/p>
&lt;div class="math">
&lt;div>12.985&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>+&lt;/div>
&lt;div>0.015&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1 - Φ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;div>=&lt;/div>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>21&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>13&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>We just advanced our point two more steps along the Fibonacci sequence, with nothing more than scalar exponentiation and a constant number of vector operations.&lt;/p>
&lt;p>This is exactly the same as the expression:&lt;/p>
&lt;div class="inline-math">
&lt;div class="content">
&lt;div>F&lt;sup>2&lt;/sup> = Q Λ&lt;sup>2&lt;/sup> Q&lt;sup>-1&lt;/sup>&lt;/div>
&lt;/div>
&lt;/div>
&lt;p>But as someone with no background in linear algebra, I find it easy to get lost in the notation, so it&amp;rsquo;s easier for me to think about this as operations on separate column vectors rather than as operations on matrices. Even though they are the same thing.&lt;/p>
&lt;p>Of course, calculating two steps of the Fibonacci sequence in constant time isn&amp;rsquo;t that impressive. But we can do the same with Φ&lt;sup>1000&lt;/sup>, and use that to calculate the thousandth Fibonacci number in constant time.&lt;/p>
&lt;p>&amp;hellip;Assuming we could calculate Φ&lt;sup>1000&lt;/sup> in constant time. Which we can&amp;rsquo;t, in real life.&lt;/p>
&lt;hr>
&lt;p>Alright.&lt;/p>
&lt;p>The post is over; you saw the trick. &amp;ldquo;Eigendecomposition,&amp;rdquo; this is called.&lt;/p>
&lt;p>I glossed over a few steps &amp;ndash; I spent absolutely no time explaining &lt;em>how I knew&lt;/em> the eigenvalues and eigenvectors of this matrix, for example. I just asserted that they were related to the golden ratio. But in reality you can solve for them, or ask a computer to do it for you. It&amp;rsquo;s pretty mechanical, like matrix inversion &amp;ndash; it seems linear algebra is best explored with a repl nearby.&lt;/p>
&lt;p>In any case, I think that the &lt;em>why&lt;/em> of eigendecomposition is more interesting than the &lt;em>how&lt;/em>.&lt;/p>
&lt;p>As for the Fibonacci sequence&amp;hellip; well, this is a pretty terrible way to actually calculate Fibonacci numbers. Even if we pretend that we only care about numbers that can fit in IEEE 754 double-precision floats, we &lt;em>still&lt;/em> can&amp;rsquo;t use this technique to calculate very many Fibonacci numbers, because the floating-point error adds up too quickly.&lt;/p>
&lt;p>But if we only care about double-precision floats&amp;hellip; well, there is one more Fibonacci implementation to consider. It&amp;rsquo;s an algorithm that that runs in constant time, and constant space, and covers the full gamut of floating-point numbers without accumulating any error at all&amp;hellip;&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="kr">const&lt;/span> &lt;span class="nx">fibs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">13&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">21&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">34&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">55&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">89&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">144&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">233&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">377&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">610&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">987&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1597&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2584&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4181&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">6765&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10946&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">17711&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">28657&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">46368&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">75025&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">121393&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">196418&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">317811&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">514229&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">832040&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1346269&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2178309&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3524578&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5702887&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">9227465&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">14930352&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">24157817&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">39088169&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">63245986&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">102334155&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">165580141&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">267914296&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">433494437&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">701408733&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1134903170&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1836311903&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2971215073&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4807526976&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">7778742049&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">12586269025&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">20365011074&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">32951280099&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">53316291173&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">86267571272&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">139583862445&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">225851433717&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">365435296162&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">591286729879&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">956722026041&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1548008755920&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2504730781961&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4052739537881&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">6557470319842&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10610209857723&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">17167680177565&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">27777890035288&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">44945570212853&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">72723460248141&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">117669030460994&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">190392490709135&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">308061521170129&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">498454011879264&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">806515533049393&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1304969544928657&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2111485077978050&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3416454622906707&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5527939700884757&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">8944394323791464&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">fib&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">fibs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">n&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>But it&amp;rsquo;s more fun to overthink it.&lt;/p>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>In case you are one of today&amp;rsquo;s lucky 10,000: the golden ratio is also very close to the conversion rate between miles and kilometers, so you can use Fibonacci numbers to approximate conversions between miles and kilometers in your head. For example, 80km ≈ 50mi. This is a &lt;em>weirdly&lt;/em> good conversion &amp;ndash; the exact answer is 49.7097mi.&lt;/p>
&lt;p>It even works when you don&amp;rsquo;t have a round Fibonacci number to work with. 120kph is probably around 90% of 130kph, and 90% of 80mph is 72mph&amp;hellip; the correct answer would be 74.6mph, but we got a decent ballpark with nothing but eyeball math.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2" role="doc-endnote">
&lt;p>Er, the German word is Eigenvektor, so it&amp;rsquo;s like&amp;hellip; half a loan word? A loan sub-word? Whatever, the &amp;ldquo;eigen&amp;rdquo; part is the relevant bit here.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3" role="doc-endnote">
&lt;p>Straight lines are eigenvectors, but eigenvectors are not necessarily straight lines. Rotation matrices &lt;em>also&lt;/em> have eigenvectors, but they have &lt;em>complex&lt;/em> eigenvectors. Straight lines like this are &lt;em>real&lt;/em> eigenvectors.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4" role="doc-endnote">
&lt;p>The exact same argument applies to the &amp;ldquo;logarithmic&amp;rdquo; exponentiation-by-squaring algorithm as well &amp;ndash; squaring arbitrarily large numbers requires arbitrary precision multiplication. It feels different to me, though, because of floating point error: when you&amp;rsquo;re exponentiating eigenvalues, you need to use arbitrary precision arithmetic even when your final answer could fit into a double. But the integer squaring approach only needs bigints when the Fibonacci numbers themselves become too large to fit into words.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:5" role="doc-endnote">
&lt;p>As long as the vectors we pick are &amp;ldquo;linearly independent&amp;rdquo; &amp;ndash; there&amp;rsquo;s no way to express &lt;code>[5 3]&lt;/code> as a combination of &lt;code>[1 0]&lt;/code> and &lt;code>[2 0]&lt;/code>, for example.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description><pubDate>Sun, 30 Jul 2023 00:00:00 +0000</pubDate><link>https://ianthehenry.com/posts/fibonacci/</link><guid isPermaLink="true">https://ianthehenry.com/posts/fibonacci/</guid></item><item><title>My Kind of REPL</title><description>&lt;p>I want to tell you about an idea that has had a huge influence on the way that I write software. And I mean that in the literal sense: it&amp;rsquo;s changed the &lt;em>way&lt;/em> that I write software; it&amp;rsquo;s re-shaped my development workflow.&lt;/p>
&lt;p>The idea is this: you can write programs that modify themselves.&lt;/p>
&lt;p>And I don&amp;rsquo;t mean macros or metaprogramming or anything fancy like that. I mean that you can write programs that &lt;em>edit their own source code&lt;/em>. Like, the files themselves. The actual text files on disk that have your source code in them.&lt;/p>
&lt;p>That&amp;rsquo;s not the whole idea, though. There&amp;rsquo;s more to it: you write programs that can edit themselves, and then you &lt;em>use that as your REPL&lt;/em>.&lt;/p>
&lt;!-- more -->
&lt;p>Instead of typing something into a prompt and hitting enter and seeing the output on stdout, you type something into a file and hit some editor keybinding and the result gets &lt;em>inserted into the file itself&lt;/em>. Patched on disk, right next to the original expression, ready to be committed to source control.&lt;/p>
&lt;p>This read-eval-patch loop is already a little useful &amp;ndash; it&amp;rsquo;s a REPL with all of the conveniences of your favorite editor &amp;ndash; but we&amp;rsquo;re still not done. There&amp;rsquo;s one more part to the idea.&lt;/p>
&lt;p>Once you have your expression and your &amp;ldquo;REPL output&amp;rdquo; saved into the same file, you can &lt;em>repeat&lt;/em> that &amp;ldquo;REPL session&amp;rdquo; in the future, and you can &lt;em>see if the output ever changes&lt;/em>.&lt;/p>
&lt;p>And then you use this technique to write all of your tests.&lt;/p>
&lt;p>That&amp;rsquo;s the whole idea. You use your source files as persistent REPLs, and those REPL sessions become living, breathing test cases for the code you were inspecting. They let you know if anything &lt;em>changes&lt;/em> about any of the expressions that you&amp;rsquo;ve run in the past. This kind of REPL can&amp;rsquo;t tell you if the new results are &lt;em>right&lt;/em> or &lt;em>wrong&lt;/em>, of course &amp;ndash; just that they&amp;rsquo;re different. It&amp;rsquo;s still up to you to separate &amp;ldquo;test failures&amp;rdquo; from expected divergences, and to curate the expressions-under-observation into a useful set.&lt;/p>
&lt;p>This is pretty much the only way that I have written automated tests for the last six years. Except that it isn&amp;rsquo;t really &amp;ndash; this is the way that I &lt;em>haven&amp;rsquo;t&lt;/em> written automated tests. This is the way that I have caused automated tests to appear for free around me, by doing exactly what I have always done: writing code and then running it, and seeing if it did what I wanted.&lt;/p>
&lt;p>I&amp;rsquo;m exaggerating, but not as much as you might think. Of course these tests aren&amp;rsquo;t &lt;em>free:&lt;/em> it takes work to come up with interesting expressions, and I have to write setup code to actually test the thing I care about, and there is judgment and curation and naming-things involved. And of course there is some effort in verifying that the code under test actually does the thing that I wanted it to do.&lt;/p>
&lt;p>But the reason that it feels free is that nearly all of that is work that &lt;em>I would be doing already&lt;/em>.&lt;/p>
&lt;p>Like, forget about automated tests for a second &amp;ndash; when you write some non-trivial function, don&amp;rsquo;t you usually run it a couple times to see if it actually does the thing that you wanted it to do? I do. Usually by typing in interesting expressions at a REPL, and checking the output, and only moving on once I&amp;rsquo;m convinced that it works.&lt;/p>
&lt;p>And those expressions that I run to convince myself that the code works are &lt;em>exactly&lt;/em> the expressions that I want to run again in the future to convince myself that the code &lt;em>still&lt;/em> works after a refactor. They are exactly the expressions that I want to share with someone else to convince &lt;em>them&lt;/em> that the code works. They are, in other words, exactly the test cases that I care about.&lt;/p>
&lt;p>And yes, I mean, I&amp;rsquo;m still exaggerating a bit. There is a difference between expressions that you run once and expressions that you run a thousand times. In particular, you want your &amp;ldquo;persistent REPL&amp;rdquo; outputs to be relatively &lt;em>stable&lt;/em> &amp;ndash; you don&amp;rsquo;t want tests to fail because the order of keys in a hash table changed. And usually you want to document these cases in some way, to say &lt;em>why&lt;/em> a particular case is interesting, so that it&amp;rsquo;s easier to diagnose a failure in the future.&lt;/p>
&lt;p>But we&amp;rsquo;re getting a little into the weeds there. Let&amp;rsquo;s take a step back.&lt;/p>
&lt;p>You&amp;rsquo;ve heard the idea, now let&amp;rsquo;s see how it feels. I want to demonstrate the workflow to you, but it&amp;rsquo;s going to require a little bit of suspension of disbelief on your part: this is a blog post, and I don&amp;rsquo;t really have time to show you any particularly &lt;em>interesting&lt;/em> tests. I also have to confess that I don&amp;rsquo;t write very many interesting tests outside of work, so this will be a combination of completely fake contrived examples and trivial unit tests lifted from random side projects.&lt;/p>
&lt;p>With that in mind, let&amp;rsquo;s start with one from the &amp;ldquo;extremely contrived&amp;rdquo; camp:&lt;/p>
&lt;div class="video-container">
&lt;video controls
width="724"
height="478"
preload=metadata
poster="/posts/my-kind-of-repl/judge-poster.699fd47b829eedf57916335a9f3672eaab1429b8a90dbeb43cf4f3185494f783.png"
style="
background-size: cover;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAVCAIAAACor3u9AAACiElEQVR4nLxVy27UShDt7mq7257xPO69cXIjhMQOJETILmLPF/ABfCdfEYkVKxQCUUYZmHhitx/9qELOBBSFIQiS4WxctruqXHVOleXrFwcsd2&amp;#43;FkB/ZDSil9vf3j46OiAgR27ZNkgQArLVCCCklEYUQ5vP5Tc9vICL&amp;#43;bO/5cDhcLi8mkzFjfD4/67ruZw6/CwHAdx88REQhBCICQAjhvqIzxgBAZtlgNBHni2bIeaZj03nO2cLDdDRCTrOT07vmUDr1Idr6b7dDxpUSyQhBmarSWlfLwjp/l&amp;#43;g9VYwxU5ZEYjjQgeDk&amp;#43;Hj1bjab3fHbV5CrCzEGrEHi2XiIgUWxdl0zmUwW52dJMtAqNcaE4Dvrt/LtxpTW2khpgdWi&amp;#43;IUirkjeyQdlbU3lvpeGiD/We/3h2jM30JO8smZnZuf/4WAQpwIQQ1G5dJw3porjqG3b6fQfImrbJooia60NHrt68m8OAoqiAIBIyqYxPNLetaY0a1rUW1IQI0kiy2RREwaXb&amp;#43;eLLwulVNPUWmspJYAUwmdq0Mp4uSzqvm90LZpZU0Q2GhP1h5SOkFSiU&amp;#43;tFHMmqtolWAFA3rYqj1QBTL7TYOae1ZkwoHSuluWBaxURsbVflyz18c9jfXCw7RFsua&amp;#43;/9aspPXRd8IGK2axhjzrnLCngIQQgIwQNAHMfOOs&amp;#43;59&amp;#43;snlD95/Oj8wt3O1R8DAETVbCj4FQTjfLMJQIjNJrjf9bkmwUaj/40E8tUBHn5ISz/1zksuO28jiEzbr2vve&amp;#43;ErpUBCberb/3QhhCRJnHP8UjWc8zRNn&amp;#43;Yz&amp;#43;e4Tfi5cGwre66nfFh1rvXdE6H3gnDtniVgI/na2EPtlFQJy3tuXju498a8BAAD//yKTbeWcbt6XAAAAAElFTkSuQmCC);
">
&lt;source src="https://ianthehenry.com/posts/my-kind-of-repl/judge-1448x956.100994ea71205d171f5dd31d6b87ea8067ab0fb7e4e8179a235f702f6c2d666b.mp4" type="video/mp4">
&lt;/video>
&lt;/div>
&lt;p>Yes, I know that you haven&amp;rsquo;t actually implemented a sorting algorithm since the third grade, but I hope that it gives you the gist of the workflow: you write expressions, press a button, and the result appears directly in your source code. It&amp;rsquo;s like working in a Jupyter notebook, except that you don&amp;rsquo;t have to work in a Jupyter notebook. You don&amp;rsquo;t have to leave the comfort of Emacs, or Vim, or VSCode, or BBEdit &amp;ndash; as long as your editor can load files off a file system, it&amp;rsquo;s compatible with this workflow.&lt;/p>
&lt;p>In case you skipped the video, the end result looks something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-clojure" data-lang="clojure">&lt;span class="p">(&lt;/span>&lt;span class="nf">use&lt;/span> &lt;span class="nv">judge&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="kd">defn &lt;/span>&lt;span class="nv">slow-sort&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nv">list&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nf">case&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">length&lt;/span> &lt;span class="nv">list&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="mi">0&lt;/span> &lt;span class="nv">list&lt;/span>
&lt;span class="mi">1&lt;/span> &lt;span class="nv">list&lt;/span>
&lt;span class="mi">2&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">let &lt;/span>&lt;span class="p">[[&lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="nv">y&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="nv">list&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">[(&lt;/span>&lt;span class="nb">min &lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="nv">y&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">max &lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="nv">y&lt;/span>&lt;span class="p">)])&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nf">do&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">def &lt;/span>&lt;span class="nv">pivot&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">in&lt;/span> &lt;span class="nb">list &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">math/floor&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">/ &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">length&lt;/span> &lt;span class="nv">list&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">def &lt;/span>&lt;span class="nv">bigs&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">filter &lt;/span>&lt;span class="err">|&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">&amp;gt; &lt;/span>&lt;span class="nv">$&lt;/span> &lt;span class="nv">pivot&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">list&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">def &lt;/span>&lt;span class="nv">smalls&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">filter &lt;/span>&lt;span class="err">|&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">&amp;lt; &lt;/span>&lt;span class="nv">$&lt;/span> &lt;span class="nv">pivot&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">list&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">def &lt;/span>&lt;span class="nv">equals&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">filter &lt;/span>&lt;span class="err">|&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">= &lt;/span>&lt;span class="nv">$&lt;/span> &lt;span class="nv">pivot&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">list&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">[&lt;/span>&lt;span class="c1">;(slow-sort smalls) ;equals ;(slow-sort bigs)])))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">test &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">slow-sort&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">3&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">])&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">test &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">slow-sort&lt;/span> &lt;span class="p">[])&lt;/span> &lt;span class="p">[])&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">test &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">slow-sort&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Those tests at the end might look like regular equality assertions &amp;ndash; and, in a way, they are. But I only had to write the &amp;ldquo;first half&amp;rdquo; of them &amp;ndash; I only wrote the expressions to run in my &amp;ldquo;REPL&amp;rdquo; &amp;ndash; and my test framework filled in right-hand sides for me.&lt;/p>
&lt;p>Now, as you probably noticed, there were a lot of parentheses in that demo. Don&amp;rsquo;t be alarmed: the parentheses are entirely incidental. I just happen to be &lt;a href="https://ianthehenry.com/posts/why-janet/">spending a lot of time with Janet&lt;/a> lately, and I&amp;rsquo;ve written &lt;a href="https://github.com/ianthehenry/judge">a test framework&lt;/a> that&amp;rsquo;s heavily based around this workflow.&lt;/p>
&lt;p>But there&amp;rsquo;s nothing parentheses-specific about this technique. I first met this type of programming in OCaml, via the &lt;a href="https://github.com/janestreet/ppx_expect">&amp;ldquo;expect test&amp;rdquo; framework&lt;/a>. The specifics of OCaml&amp;rsquo;s expect tests are a little bit different, but the overall workflow is the same:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ocaml" data-lang="ocaml">&lt;span class="k">let&lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="n">expect_test&lt;/span> &lt;span class="s2">&amp;#34;formatting board coordinates&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span>
&lt;span class="n">print_endline&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">to_string&lt;/span> &lt;span class="o">{&lt;/span> &lt;span class="n">row&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">0&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">col&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">0&lt;/span> &lt;span class="o">});&lt;/span>
&lt;span class="o">[%&lt;/span>&lt;span class="n">expect&lt;/span> &lt;span class="s2">&amp;#34;A1&amp;#34;&lt;/span>&lt;span class="o">];&lt;/span>
&lt;span class="n">print_endline&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">to_string&lt;/span> &lt;span class="o">{&lt;/span> &lt;span class="n">row&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">10&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">col&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">4&lt;/span> &lt;span class="o">});&lt;/span>
&lt;span class="o">[%&lt;/span>&lt;span class="n">expect&lt;/span> &lt;span class="s2">&amp;#34;E11&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span>
&lt;span class="o">;;&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Instead of the expression-oriented API we saw in Janet, an OCaml expect test is a series of &lt;em>statements&lt;/em> that print to stdout, followed by automatically-updating assertions about what exactly was printed. This makes sense because OCaml doesn&amp;rsquo;t really have a canonical way to print a representation of any value,&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> so you have to decide exactly what you want your REPL to Print.&lt;/p>
&lt;p>And this workflow works in normal languages too! Here&amp;rsquo;s an example in Rust, using the &lt;a href="https://github.com/aaronabramov/k9">K9 test framework&lt;/a>:&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="cp">#[test]&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="k">fn&lt;/span> &lt;span class="nf">indentation&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">k9&lt;/span>::&lt;span class="n">snapshot&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tokenize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;
&lt;/span>&lt;span class="s">a
&lt;/span>&lt;span class="s"> b
&lt;/span>&lt;span class="s"> c
&lt;/span>&lt;span class="s">d
&lt;/span>&lt;span class="s"> e
&lt;/span>&lt;span class="s"> f
&lt;/span>&lt;span class="s">g
&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;a ␤ → b ␤ c ␤ ← d ␤ → e ␤ → f ␤ ← ← g ␤&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We&amp;rsquo;re pulling from the &amp;ldquo;trivial side project&amp;rdquo; category now. That&amp;rsquo;s a test I wrote for an indentation-sensitive tokenizer, but again, I just wrote an expression that I wanted to check and then checked that the result looked right. I didn&amp;rsquo;t have to spend any time crafting the token stream that I expected to see ahead of time &amp;ndash; I only had to write down an expression that seemed interesting to me.&lt;/p>
&lt;p>The fact that you don&amp;rsquo;t actually have to write the expected value might seem like a trivial detail &amp;ndash; it&amp;rsquo;s not &lt;em>that&lt;/em> hard to write your expectations down up front; people do it all the time &amp;ndash; but it&amp;rsquo;s actually an &lt;em>essential&lt;/em> part of this workflow. It&amp;rsquo;s an implausibly big deal. It is, in fact, the first beautiful thing I want to highlight about this technique:&lt;/p>
&lt;h1 id="1-when-its-this-easy-to-write-tests-you-write-more-of-them">1. When it&amp;rsquo;s this easy to write tests, you write more of them&lt;/h1>
&lt;p>The fact that I don&amp;rsquo;t have to type the expectation for my tests matters a lot more than it seems like it &lt;em>should&lt;/em>. Like, I&amp;rsquo;m an adult, right? I can walk through that code and say &amp;ldquo;a, newline, indent, b, newline&amp;hellip;&amp;rdquo; and I can write down the expected result. How hard can it be?&lt;/p>
&lt;p>I actually just timed myself doing that: it took almost exactly 30 seconds to read this input and write out the expected output by hand (sans Unicode). 30 seconds doesn&amp;rsquo;t sound very long, but it&amp;rsquo;s &lt;em>friction&lt;/em>. It was a &lt;em>boring&lt;/em> 30 seconds. It&amp;rsquo;s something that I want to minimize. And what if I have 10 of these tests? That 30 seconds turns into, like, I don&amp;rsquo;t know; math has never been my strong suit. But I bet it&amp;rsquo;s a lot.&lt;/p>
&lt;p>And that was an &lt;em>easy&lt;/em> example! Let&amp;rsquo;s look at something more complicated: here&amp;rsquo;s one of dozens of tests for the parser that that tokenizer feeds into:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="cp">#[test]&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="k">fn&lt;/span> &lt;span class="nf">test_nested_blocks&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">k9&lt;/span>::&lt;span class="n">snapshot&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;
&lt;/span>&lt;span class="s">foo =
&lt;/span>&lt;span class="s"> x =
&lt;/span>&lt;span class="s"> y = 10
&lt;/span>&lt;span class="s"> z = 20
&lt;/span>&lt;span class="s"> y + z
&lt;/span>&lt;span class="s"> x
&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;foo (n) = (let ((x (let ((y 10) (z 20)) (+ y z)))) x)&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Of course I could have typed out the exact syntax tree that I expected to see ahead of time, but I know that I won&amp;rsquo;t do that &amp;ndash; I don&amp;rsquo;t even want to time myself trying it. If I actually had to type the input &lt;em>and&lt;/em> output for every single test, I&amp;rsquo;d just write fewer of them.&lt;/p>
&lt;p>And yes, you still have to verify that the output is correct. But verification is almost always easier than constructing the output by hand, and in the rare cases where it isn&amp;rsquo;t, you still have the option to write down your expectations explicitly.&lt;/p>
&lt;p>And sometimes you don&amp;rsquo;t have to verify at all! Often when I&amp;rsquo;m very confident that my code already works correctly, but I&amp;rsquo;m about to make some risky changes to it, I&amp;rsquo;ll use this technique to generate a bunch of test cases, and just trust that the output is correct. Then after I&amp;rsquo;m done refactoring or optimizing, those tests will tell me if the new code ever diverges from my reference implementation.&lt;/p>
&lt;p>But I don&amp;rsquo;t just write more tests because it&amp;rsquo;s easy. I usually write more tests because it &lt;em>makes my job easier&lt;/em>. Which is the next beautiful thing that I want to highlight:&lt;/p>
&lt;h1 id="2-this-workflow-makes-tests-useful-immediately">2. This workflow makes tests useful &lt;em>immediately&lt;/em>&lt;/h1>
&lt;p>We all know that tests are valuable, but they&amp;rsquo;re not particularly valuable &lt;em>right now&lt;/em>. The point of automated testing isn&amp;rsquo;t to make sure that your code works, it&amp;rsquo;s to make sure that your code &lt;em>still&lt;/em> works in five years, right?&lt;/p>
&lt;p>But &lt;em>you&lt;/em> won&amp;rsquo;t be working on this same codebase in five years. And there&amp;rsquo;s no way that &lt;em>you&lt;/em> would make a change that breaks one of these subtle invariants you&amp;rsquo;ve come to rely on. The time you spend writing automated tests &lt;em>might&lt;/em> help &lt;em>someone&lt;/em>, eventually, but that&amp;rsquo;s time that you could instead be spending on &amp;ldquo;features&amp;rdquo; that your &amp;ldquo;company&amp;rdquo; needs to &amp;ldquo;make payroll this month.&amp;rdquo; Which means that &amp;ndash; if you&amp;rsquo;re already confident that your code is correct &amp;ndash; it can be tempting to not spend any time hardening it against the cold ravages of time.&lt;/p>
&lt;p>But when tests actually provide immediate value to &lt;em>you&lt;/em> &amp;ndash; when writing tests becomes a useful part of your workflow, when it becomes a part of &lt;em>how&lt;/em> you add new features &amp;ndash; suddenly the equation changes. You don&amp;rsquo;t write tests for some nebulous future benefit, you write tests because it&amp;rsquo;s the easiest way to run your code. The fact that this happens to make your codebase more robust to future changes is just icing.&lt;/p>
&lt;p>But in order to really benefit from this kind of testing, you might have to rethink what a &amp;ldquo;good test&amp;rdquo; looks like. Which brings me to&amp;hellip;&lt;/p>
&lt;h1 id="3-good-tests-are-good-observations-of-your-programs-behavior">3. Good tests are good observations of your program&amp;rsquo;s behavior&lt;/h1>
&lt;p>There are good tests, and there are bad tests. This is true whether or not you&amp;rsquo;re writing this particular style of test, but the ceiling for what a &amp;ldquo;good test&amp;rdquo; can look like is much, much higher when you&amp;rsquo;re using this technique.&lt;/p>
&lt;p>I&amp;rsquo;ll try to show you what I mean. Let&amp;rsquo;s look at a test for a function that finds the winner in a game of &lt;a href="https://en.wikipedia.org/wiki/Hex_(board_game)">Hex&lt;/a>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ocaml" data-lang="ocaml">&lt;span class="k">let&lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="n">expect_test&lt;/span> &lt;span class="s2">&amp;#34;find winning path&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span>
&lt;span class="k">let&lt;/span> &lt;span class="n">board&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">make_board&lt;/span> &lt;span class="o">~&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="n">5&lt;/span> &lt;span class="s2">&amp;#34;A4 A2 B4 B3 C3 C4 D2 D3&amp;#34;&lt;/span> &lt;span class="k">in&lt;/span>
&lt;span class="n">print_s&lt;/span> &lt;span class="o">[%&lt;/span>&lt;span class="n">sexp&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">find_winning_path&lt;/span> &lt;span class="n">board&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="nn">Winning_path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">)];&lt;/span>
&lt;span class="o">[%&lt;/span>&lt;span class="n">expect&lt;/span> &lt;span class="s2">&amp;#34;None&amp;#34;&lt;/span>&lt;span class="o">];&lt;/span>
&lt;span class="n">play_at&lt;/span> &lt;span class="n">board&lt;/span> &lt;span class="s2">&amp;#34;E2&amp;#34;&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="n">print_s&lt;/span> &lt;span class="o">[%&lt;/span>&lt;span class="n">sexp&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">find_winning_path&lt;/span> &lt;span class="n">board&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="nn">Winning_path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">)];&lt;/span>
&lt;span class="o">[%&lt;/span>&lt;span class="n">expect&lt;/span> &lt;span class="s2">&amp;#34;(Black (A4 B4 C3 D2 E2))&amp;#34;&lt;/span>&lt;span class="o">];&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Okay. Sure. I can sort of think for a minute and see that there was no winning path until black played at E2, and then there was one, and it&amp;rsquo;s probably reporting the correct path. But let&amp;rsquo;s compare that with an alternative:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ocaml" data-lang="ocaml">&lt;span class="k">let&lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="n">expect_test&lt;/span> &lt;span class="s2">&amp;#34;find winning path&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span>
&lt;span class="k">let&lt;/span> &lt;span class="n">board&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">make_board&lt;/span> &lt;span class="o">~&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="n">5&lt;/span> &lt;span class="s2">&amp;#34;A4 A2 B4 B3 C3 C4 D2 D3&amp;#34;&lt;/span> &lt;span class="k">in&lt;/span>
&lt;span class="n">print_board&lt;/span> &lt;span class="n">board&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">find_winning_path&lt;/span> &lt;span class="n">board&lt;/span>&lt;span class="o">);&lt;/span>
&lt;span class="o">[%&lt;/span>&lt;span class="n">expect&lt;/span> &lt;span class="o">{|&lt;/span>
&lt;span class="nc">There&lt;/span> &lt;span class="n">is&lt;/span> &lt;span class="n">no&lt;/span> &lt;span class="n">winning&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="o">:&lt;/span>
&lt;span class="n">1&lt;/span> &lt;span class="n">2&lt;/span> &lt;span class="n">3&lt;/span> &lt;span class="n">4&lt;/span> &lt;span class="n">5&lt;/span>
&lt;span class="nn">A&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">.&lt;/span>
&lt;span class="nn">B&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">.&lt;/span>
&lt;span class="nn">C&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="o">.&lt;/span>
&lt;span class="nn">D&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="o">.&lt;/span>
&lt;span class="nn">E&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">.&lt;/span>
&lt;span class="o">|}];&lt;/span>
&lt;span class="n">play_at&lt;/span> &lt;span class="n">board&lt;/span> &lt;span class="s2">&amp;#34;E2&amp;#34;&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="n">print_board&lt;/span> &lt;span class="n">board&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">find_winning_path&lt;/span> &lt;span class="n">board&lt;/span>&lt;span class="o">);&lt;/span>
&lt;span class="o">[%&lt;/span>&lt;span class="n">expect&lt;/span> &lt;span class="o">{|&lt;/span>
&lt;span class="nc">Black&lt;/span> &lt;span class="n">has&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="n">winning&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="o">:&lt;/span>
&lt;span class="n">1&lt;/span> &lt;span class="n">2&lt;/span> &lt;span class="n">3&lt;/span> &lt;span class="n">4&lt;/span> &lt;span class="n">5&lt;/span>
&lt;span class="nn">A&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="o">.&lt;/span>
&lt;span class="nn">B&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="o">.&lt;/span>
&lt;span class="nn">C&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="o">.&lt;/span>
&lt;span class="nn">D&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="o">.&lt;/span>
&lt;span class="nn">E&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="o">.&lt;/span>
&lt;span class="o">|}];&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isn&amp;rsquo;t that better? This test has convinced me in a single glance that the code is working correctly. And if, at some point in the future, the code &lt;em>stops&lt;/em> working correctly &amp;ndash; if I refactor the &lt;code>find_winning_path&lt;/code> function and break something &amp;ndash; I claim that this test will give me a decent head start on figuring out where I went wrong.&lt;/p>
&lt;p>Of course it took some work to write this visualization in the first place. Not a &lt;em>lot&lt;/em> of work &amp;ndash; it&amp;rsquo;s just a nested &lt;code>for&lt;/code> loop &amp;ndash; but still, it did take longer to write this test than the &lt;code>(Black (A4 B4 C3 D2 E2))&lt;/code> version.&lt;/p>
&lt;p>This is some of my favorite kind of work, though. It&amp;rsquo;s work that will speed up all of my future development, by making it easier for me to understand how my code works &amp;ndash; and, even better, by making it easier for &lt;em>other people&lt;/em> to understand how my code works. I only have to write the &lt;code>print_board&lt;/code> helper once, but I can use it in dozens of tests &amp;ndash; and so can my collaborators.&lt;/p>
&lt;p>Here&amp;rsquo;s another test that took a little work to produce, but that I found extremely useful during development:&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/my-kind-of-repl/configuration-spaces_huc30375726a9d7f8e19001b58c01707e4_90208_1138x1138_fit_box_3.png">&lt;picture>&lt;img
class=""
srcset="https://ianthehenry.com/posts/my-kind-of-repl/configuration-spaces_huc30375726a9d7f8e19001b58c01707e4_90208_569x569_fit_box_3.png 563w, https://ianthehenry.com/posts/my-kind-of-repl/configuration-spaces_huc30375726a9d7f8e19001b58c01707e4_90208_1138x1138_fit_box_3.png 1126w, https://ianthehenry.com/posts/my-kind-of-repl/configuration-spaces_huc30375726a9d7f8e19001b58c01707e4_90208_375x375_fit_box_3.png 371w, https://ianthehenry.com/posts/my-kind-of-repl/configuration-spaces_huc30375726a9d7f8e19001b58c01707e4_90208_750x750_fit_box_3.png 742w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 569px"
width="563"
height="569"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAeElEQVR4nEyOQQvCUAyDk7R7uJsywbOI//&amp;#43;nid7c4UX6xnC5lJLmS/P&amp;#43;eNodNiUYIFpr63fl&amp;#43;bJ83i&amp;#43;SkliDy/UWEUlR0wQApFGZTTmOSoURvTuJQQHBMoTufyIyt4WS2Q&amp;#43;okAdiPLYn5tMcCpKjvpokAfgFAAD//4IFHFALN6wwAAAAAElFTkSuQmCC);"
/>&lt;/picture>&lt;/a>
&lt;p>You can imagine this as taking each of the top shapes and tracing them around the perimeter of the bottom shapes, maintaining contact at all times. Sort of a tricky function to write correctly, and one where visualization really helps me see that I got it right &amp;ndash; and, of course, to see what I did wrong when I inevitably break it.&lt;/p>
&lt;p>But that&amp;rsquo;s kind of cheating: that visualization uses Emacs&amp;rsquo;s &lt;code>iimage-mode&lt;/code> to render a literal image inline with my code, which is not something that you can do everywhere. And since part of the appeal of this technique is the universal accessibility, I should really stick to text.&lt;/p>
&lt;p>Here, here&amp;rsquo;s a better example. This is a function that, given a source of uniformly distributed random numbers, gives you a source of normally distributed random numbers:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-clojure" data-lang="clojure">&lt;span class="p">(&lt;/span>&lt;span class="kd">defn &lt;/span>&lt;span class="nv">marsaglia-sample&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nv">rng&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">var &lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">var &lt;/span>&lt;span class="nv">y&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">var &lt;/span>&lt;span class="nv">r2&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nf">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">or &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">= &lt;/span>&lt;span class="nv">r2&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">&amp;gt;= &lt;/span>&lt;span class="nv">r2&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">set &lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">math/rng-uniform&lt;/span> &lt;span class="nv">rng&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">set &lt;/span>&lt;span class="nv">y&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">math/rng-uniform&lt;/span> &lt;span class="nv">rng&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">set &lt;/span>&lt;span class="nv">r2&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">+ &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="nv">x&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="nv">y&lt;/span> &lt;span class="nv">y&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">def &lt;/span>&lt;span class="nv">mag&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">math/sqrt&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">/ &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="mi">-2&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">math/log&lt;/span> &lt;span class="nv">r2&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="nv">r2&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;span class="p">[(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="nv">mag&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="nv">y&lt;/span> &lt;span class="nv">mag&lt;/span>&lt;span class="p">)])&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Except&amp;hellip; does it work? I just &lt;a href="https://en.wikipedia.org/wiki/Marsaglia_polar_method">copied this from Wikipedia&lt;/a>; I don&amp;rsquo;t really have very good intuition for the algorithm yet. Did I implement it correctly?&lt;/p>
&lt;p>It&amp;rsquo;s not immediately obvious how I would write a test for this. But if I &lt;em>weren&amp;rsquo;t&lt;/em> trying to &amp;ldquo;test&amp;rdquo; it &amp;ndash; if I just wanted to convince myself that it worked &amp;ndash; then I&amp;rsquo;d probably plot a few thousand points from the distribution and check if they &lt;em>look&lt;/em> normally distributed. And if I had any doubts after that, maybe I&amp;rsquo;d calculate the mean and standard deviation as well and check that they&amp;rsquo;re close to the values I expect.&lt;/p>
&lt;p>So that&amp;rsquo;s exactly what I want my automated tests to do:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-clojure" data-lang="clojure">&lt;span class="p">(&lt;/span>&lt;span class="nf">deftest&lt;/span> &lt;span class="s">&amp;#34;marsaglia sample produces a normal distribution&amp;#34;&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">def &lt;/span>&lt;span class="nv">rng&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">math/rng&lt;/span> &lt;span class="mi">1234&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nf">test-stdout&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">plot-histogram&lt;/span> &lt;span class="err">|&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">marsaglia-sample&lt;/span> &lt;span class="nv">rng&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="mi">100000&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">`&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣷⣾⣦⣆⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣷⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣷⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣦⣄⡀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣤⣄&lt;/span>
&lt;span class="nv">μ&lt;/span> &lt;span class="nb">= &lt;/span>&lt;span class="mf">0.796406&lt;/span>
&lt;span class="nv">σ&lt;/span> &lt;span class="nb">= &lt;/span>&lt;span class="mf">0.601565&lt;/span>
&lt;span class="o">`&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>And hark! That&amp;rsquo;s clearly wrong. And the test tells me exactly &lt;em>how&lt;/em> it&amp;rsquo;s wrong: it&amp;rsquo;s only producing positive numbers (the histogram is centered around the mean). This helps me pinpoint my mistake, and to fix it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-clojure" data-lang="clojure">&lt;span class="p">(&lt;/span>&lt;span class="kd">defn &lt;/span>&lt;span class="nv">symmetric-random&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nv">rng&lt;/span> &lt;span class="nv">x&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">- &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="mi">2&lt;/span> &lt;span class="nv">x&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">math/rng-uniform&lt;/span> &lt;span class="nv">rng&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="nv">x&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="kd">defn &lt;/span>&lt;span class="nv">marsaglia-sample&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nv">rng&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">var &lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">var &lt;/span>&lt;span class="nv">y&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">var &lt;/span>&lt;span class="nv">r2&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nf">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">or &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">= &lt;/span>&lt;span class="nv">r2&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">&amp;gt;= &lt;/span>&lt;span class="nv">r2&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">set &lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">symmetric-random&lt;/span> &lt;span class="nv">rng&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">set &lt;/span>&lt;span class="nv">y&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">symmetric-random&lt;/span> &lt;span class="nv">rng&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">set &lt;/span>&lt;span class="nv">r2&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">+ &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="nv">x&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="nv">y&lt;/span> &lt;span class="nv">y&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">def &lt;/span>&lt;span class="nv">mag&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">math/sqrt&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">/ &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="mi">-2&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">math/log&lt;/span> &lt;span class="nv">r2&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="nv">r2&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;span class="p">[(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="nv">mag&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="nv">y&lt;/span> &lt;span class="nv">mag&lt;/span>&lt;span class="p">)])&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nf">deftest&lt;/span> &lt;span class="s">&amp;#34;marsaglia sample produces a normal distribution&amp;#34;&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">def &lt;/span>&lt;span class="nv">rng&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">math/rng&lt;/span> &lt;span class="mi">1234&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nf">test-stdout&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">plot-histogram&lt;/span> &lt;span class="err">|&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">marsaglia-sample&lt;/span> &lt;span class="nv">rng&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="mi">100000&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">`&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣦⣾⣿⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⣆⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⡀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⢀⣀⣀⣤⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣀⣀⡀&lt;/span>
&lt;span class="nv">μ&lt;/span> &lt;span class="nb">= &lt;/span>&lt;span class="mf">0.001965&lt;/span>
&lt;span class="nv">σ&lt;/span> &lt;span class="nb">= &lt;/span>&lt;span class="mf">0.998886&lt;/span>
&lt;span class="o">`&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>And, like, yes: writing this test required me to write a Unicode histogram plotter thingy, like a crazy person. But I&amp;rsquo;ve &lt;em>already written&lt;/em> a Unicode histogram plotter thingy, like a crazy person, so I was able to re-use it here &amp;ndash; just as I will be able to re-use it again in the future, the next time I feel like it will be useful.&lt;/p>
&lt;p>In fact I&amp;rsquo;ve accumulated a lot of little visualization helpers like this over the years &amp;ndash; helpers that allow me to better observe the behavior of my code. These helpers are useful when I&amp;rsquo;m writing automated tests, because they help me understand when the behavior of my code changes, but they&amp;rsquo;re also useful for regular ad-hoc development. It&amp;rsquo;s useful to be able to whip up a histogram, or a nicely formatted table,&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup> or a graph of a function, when I&amp;rsquo;m doing regular old printf debugging.&lt;/p>
&lt;p>Which reminds me&amp;hellip;&lt;/p>
&lt;h1 id="4-you-can-use-printf-debugging-right-in-your-tests">4. You can use printf debugging right in your tests&lt;/h1>
&lt;p>I&amp;rsquo;ve shown you two types of these &amp;ldquo;REPL tests&amp;rdquo; so far.&lt;/p>
&lt;p>There&amp;rsquo;s a simple expression-oriented API:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-clojure" data-lang="clojure">&lt;span class="p">(&lt;/span>&lt;span class="nb">test &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">+ &lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>And there&amp;rsquo;s the imperative stdout-based API:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ocaml" data-lang="ocaml">&lt;span class="k">let&lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="n">expect_test&lt;/span> &lt;span class="s2">&amp;#34;board visualization&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span>
&lt;span class="n">print_board&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">make_board&lt;/span> &lt;span class="o">~&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="n">5&lt;/span> &lt;span class="s2">&amp;#34;A4 A2 B4 B3 C3 C4 D2 D3&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
&lt;span class="o">[%&lt;/span>&lt;span class="n">expect&lt;/span> &lt;span class="o">{|&lt;/span>
&lt;span class="n">1&lt;/span> &lt;span class="n">2&lt;/span> &lt;span class="n">3&lt;/span> &lt;span class="n">4&lt;/span> &lt;span class="n">5&lt;/span>
&lt;span class="nn">A&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">.&lt;/span>
&lt;span class="nn">B&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">.&lt;/span>
&lt;span class="nn">C&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="o">.&lt;/span>
&lt;span class="nn">D&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="o">.&lt;/span>
&lt;span class="nn">E&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">.&lt;/span>
&lt;span class="o">|}];&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>When I first starting writing tests like this, the stdout-based API seemed a little weird to me. Most of my tests just printed simple little expressions, and the fact that I had to spread that out across multiple lines was annoying. And if I did want to create a more interesting visualization, like the one above, couldn&amp;rsquo;t I just write an expression to return a formatted string?&lt;/p>
&lt;p>Yes, but: &lt;code>print&lt;/code> is quick, easy, and familiar. It&amp;rsquo;s &lt;em>nice&lt;/em> that rendering that board was just a nested for loop where I printed out one character at a time. Of course I could have allocated some kind of string builder, and appended characters to that, but that&amp;rsquo;s a &lt;em>little&lt;/em> bit more friction than just calling &lt;code>print&lt;/code>. I&amp;rsquo;d rather let the test framework do it for me automatically: the easier it is to write visualizations like this, the more likely I am to do it.&lt;/p>
&lt;p>But when I actually spent time writing tests using this API, I came to really appreciate a subtle point that I had initially overlooked: stdout is dynamically scoped.&lt;/p>
&lt;p>Which means that if I call &lt;code>print&lt;/code> in a helper of a helper of a helper, it&amp;rsquo;s going to appear in the output of my test. I don&amp;rsquo;t have to thread a string builder through the call stack; I can just &lt;code>printf&lt;/code>, and get &lt;code>printf&lt;/code>-debugging right in my tests.&lt;/p>
&lt;p>For example: our function to make normally distributed random numbers relies on &amp;ldquo;rejection sampling&amp;rdquo; &amp;ndash; we have to generate uniformly distributed random numbers until we find a pair that lies inside the unit circle.&lt;/p>
&lt;p>I&amp;rsquo;m curious how often we actually have to re-roll the dice. It&amp;rsquo;s not an observable property of our function, but we can just print it out in the implementation:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-clojure" data-lang="clojure">&lt;span class="p">(&lt;/span>&lt;span class="kd">defn &lt;/span>&lt;span class="nv">marsaglia-sample&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nv">rng&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">var &lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">var &lt;/span>&lt;span class="nv">y&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">var &lt;/span>&lt;span class="nv">r2&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">var &lt;/span>&lt;span class="nv">iterations&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nf">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">or &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">= &lt;/span>&lt;span class="nv">r2&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">&amp;gt;= &lt;/span>&lt;span class="nv">r2&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">set &lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">symmetric-random&lt;/span> &lt;span class="nv">rng&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">set &lt;/span>&lt;span class="nv">y&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">symmetric-random&lt;/span> &lt;span class="nv">rng&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">set &lt;/span>&lt;span class="nv">r2&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">+ &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="nv">x&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="nv">y&lt;/span> &lt;span class="nv">y&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nf">++&lt;/span> &lt;span class="nv">iterations&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">def &lt;/span>&lt;span class="nv">rejections&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">- &lt;/span>&lt;span class="nv">iterations&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">when &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">&amp;gt; &lt;/span>&lt;span class="nv">rejections&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nf">printf&lt;/span> &lt;span class="s">&amp;#34;rejected %d values&amp;#34;&lt;/span> &lt;span class="nv">rejections&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">def &lt;/span>&lt;span class="nv">mag&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">math/sqrt&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">/ &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="mi">-2&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">math/log&lt;/span> &lt;span class="nv">r2&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="nv">r2&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;span class="p">[(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="nv">x&lt;/span> &lt;span class="nv">mag&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">* &lt;/span>&lt;span class="nv">y&lt;/span> &lt;span class="nv">mag&lt;/span>&lt;span class="p">)])&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>And then re-run our test, exactly like we did before:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-clojure" data-lang="clojure">&lt;span class="p">(&lt;/span>&lt;span class="nf">deftest&lt;/span> &lt;span class="s">&amp;#34;marsaglia sample produces a normal distribution&amp;#34;&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">def &lt;/span>&lt;span class="nv">rng&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">math/rng&lt;/span> &lt;span class="mi">1234&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nf">test-stdout&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">plot-histogram&lt;/span> &lt;span class="err">|&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">marsaglia-sample&lt;/span> &lt;span class="nv">rng&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="mi">100&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">`&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="nv">rejected&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">values&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⡇⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⡇⠀⡀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⡇⠀⡇⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⢠⠀⢸⣤⡄⡇⢠⣧⡇⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⢠⡇⢸⡄⣼⣿⡇⡇⢸⣿⡇⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆⡆⠀⢸⢸⡇⢸⡇⣿⣿⡇⡇⣾⣿⡇⡇⠀⠀⠀⢰⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⠀⠀⠀⡆⠀⠀⠀⡇⣷⣶⢸⣾⣷⣾⣷⣿⣿⣷⣷⣿⣿⣷⡇⢰⡆⠀⣾⡇⡆⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⢰⠀⠀⡇⠀⡆⡆⣷⣿⣿⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⢸⣷⡆⣿⡇⣷⢰⡆⠀⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="err">⠀⠀⠀⢸⢸⡇⡇⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⣿⣿⣿⣿⡇⣿⠀⠀⠀⠀⠀&lt;/span>
&lt;span class="nv">μ&lt;/span> &lt;span class="nb">= &lt;/span>&lt;span class="mf">-0.054361&lt;/span>
&lt;span class="nv">σ&lt;/span> &lt;span class="nb">= &lt;/span>&lt;span class="mf">1.088199&lt;/span>
&lt;span class="o">`&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Alright, so we rejected 33 points, which means that we generated 133 &amp;ndash; a rejection rate of around 25%. The area of the unit circle is π, and the area of its bounding square is 4, so I&amp;rsquo;d expect us to reject around (1 - π/4) ≈ 21.5% of points. Pretty close!&lt;/p>
&lt;p>Now, this is the sort of thing that I wouldn&amp;rsquo;t keep around in an automated test. This was exploratory; this was a gut check. That output is not in a very useful format, and I don&amp;rsquo;t think it&amp;rsquo;s really worth ensuring that this rate remains stable over time &amp;ndash; I can&amp;rsquo;t imagine that I&amp;rsquo;ll screw that up without breaking the function entirely. But the important takeaway is that it was really, really easy to sprinkle &lt;code>printf&lt;/code>s into code, and to see their output right next to the code itself, using the exact same workflow that I&amp;rsquo;m already using to test my code.&lt;/p>
&lt;p>And while this output was temporary, I actually do wind up committing many of the exploratory expressions that I run as I&amp;rsquo;m developing. Because&amp;hellip;&lt;/p>
&lt;h1 id="5-good-tests-make-good-documentation">5. Good tests make good documentation&lt;/h1>
&lt;p>Compare:&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>sep-when&lt;/code> is a function that takes a list and a predicate, and breaks the input list into a list of sub-lists every time the predicate returns true for an element.&lt;/p>
&lt;/blockquote>
&lt;p>With:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-clojure" data-lang="clojure">&lt;span class="p">(&lt;/span>&lt;span class="nb">test &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">sep-when&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="mi">6&lt;/span> &lt;span class="mi">7&lt;/span> &lt;span class="mi">8&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="nv">even?&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">[[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">6&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">8&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">]])&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>My brain can intuitively understand the behavior of this function from the example code more easily than it can parse and understand the (ambiguous!) English description. The description is still helpful, sure, but the example conveys &lt;em>more&lt;/em> information &lt;em>more quickly&lt;/em>. To me, anyway.&lt;/p>
&lt;p>I think that this is mostly because my brain is very comfortable reading REPL sessions, which might not be true for everyone. And I&amp;rsquo;m not saying that good English-language documentation isn&amp;rsquo;t important &amp;ndash; ideally, of course, you have both. But there are two important advantages to the second version:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Tests don&amp;rsquo;t lie; they can&amp;rsquo;t become stale.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>I didn&amp;rsquo;t have to write it.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>I spent a couple minutes trying to come up with a pithy English description of this function, and I don&amp;rsquo;t even think I did a very good job. Writing good documentation is hard, but generating examples is easy &amp;ndash; especially when my test framework is doing half the work.&lt;/p>
&lt;p>And since it&amp;rsquo;s easy to generate examples, I do it in cases where I wouldn&amp;rsquo;t have bothered to write documentation in the first place. This &lt;code>sep-when&lt;/code> example was a real actual helper function that I pulled from a random side project. A private, trivial helper, not part of any public API, that I just happened to write as a standalone function &amp;ndash; in other words, not the sort of function that I would ever think to document.&lt;/p>
&lt;p>It&amp;rsquo;s also a trivial function, far from the actual domain of the problem I was working on, so it&amp;rsquo;s not the sort of thing I would usually bother to &lt;em>test&lt;/em> either. And I might regret that &amp;ndash; bugs in &amp;ldquo;trivial&amp;rdquo; helpers can turn into bugs in actual domain logic that can be difficult to track down, and I probably only have a 50/50 shot of getting a trivial function right on the first try in a dynamically-typed language.&lt;/p>
&lt;p>But when the ergonomics of writing tests is this nice, I actually &lt;em>do&lt;/em> bother to test trivial helpers like this. Testing just means writing down an expression to try in my &amp;ldquo;REPL,&amp;rdquo; and I can do that right next to the implementation of the function itself:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-clojure" data-lang="clojure">&lt;span class="p">(&lt;/span>&lt;span class="kd">defn &lt;/span>&lt;span class="nv">sep-when&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nb">list &lt;/span>&lt;span class="nv">f&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">var &lt;/span>&lt;span class="nv">current-chunk&lt;/span> &lt;span class="nv">nil&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="k">def &lt;/span>&lt;span class="nv">chunks&lt;/span> &lt;span class="o">@&lt;/span>&lt;span class="p">[])&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nf">eachp&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nv">i&lt;/span> &lt;span class="nv">el&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="nv">list&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">when &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">or &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">= &lt;/span>&lt;span class="nv">i&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">f&lt;/span> &lt;span class="nv">el&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">set &lt;/span>&lt;span class="nv">current-chunk&lt;/span> &lt;span class="o">@&lt;/span>&lt;span class="p">[])&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nf">array/push&lt;/span> &lt;span class="nv">chunks&lt;/span> &lt;span class="nv">current-chunk&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nf">array/push&lt;/span> &lt;span class="nv">current-chunk&lt;/span> &lt;span class="nv">el&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="nv">chunks&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">(&lt;/span>&lt;span class="nb">test &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">sep-when&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="mi">6&lt;/span> &lt;span class="mi">7&lt;/span> &lt;span class="mi">8&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="nv">even?&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">[[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">6&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">8&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">]])&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Which means that, not only will I catch dumb mistakes early, but I also automatically generate examples for myself as I code. If I come back to this code a year from now and can&amp;rsquo;t remember what this function was supposed to do, I only have to glance at the test to remind myself.&lt;/p>
&lt;h1 id="and-on-and-on">And on, and on&amp;hellip;&lt;/h1>
&lt;p>I could keep going, but I think that my returns are starting to diminish here. If you still aren&amp;rsquo;t convinced, &lt;a href="https://jsomers.net/">James Somers&lt;/a> wrote &lt;a href="https://blog.janestreet.com/the-joy-of-expect-tests/">an excellent article about this workflow&lt;/a> that I would recommend if you want to see more examples of what good tests can look like in a production setting.&lt;/p>
&lt;p>But I think that it&amp;rsquo;s difficult to convey just how good this workflow actually is in any piece of writing. I think that it&amp;rsquo;s something you have to try for yourself.&lt;/p>
&lt;p>And you probably can! This technique is usually called &amp;ldquo;inline snapshot testing,&amp;rdquo; or sometimes &amp;ldquo;golden master testing,&amp;rdquo; and if you google those phrases you will probably find a library that lets you try it out in your language of choice. I have personally only used this technique in OCaml, Janet, and Rust, but in the spirit of inclusivity, I also managed to get this working working in JavaScript, in VSCode, using a test framework called &lt;a href="https://jestjs.io/docs/snapshot-testing#inline-snapshots">Jest&lt;/a>:&lt;/p>
&lt;div class="video-container">
&lt;video controls
width="877"
height="475"
preload=metadata
poster="/posts/my-kind-of-repl/jest-poster.87d57f699a578be16d470cd91f35cc93a268b17f748a37c6f4b7924d29f0985e.png"
style="
background-size: cover;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAARCAIAAAAzPjmrAAAB10lEQVR4nLSUW24VPQzHbceTyVzO96kt8MwK&amp;#43;gQsg32wOZ7ZAdvgBSEBLe10rrnYKFOEjujpqOgcfk&amp;#43;jSez/37ETfvvmtXkmnzzQD3iMpmmcc8aYEEJKCQCYeVmWYRgebj47O4sxElGMUUTw8vKSiO6629KWtqqJKKWkqgrQdx0R/o5c//0CER9zc7&amp;#43;tLEsRSSkxM9&amp;#43;&amp;#43;eOWvPiiEoNPqrhAR7xcfwr7AX4L3RnOl9eePwbIjVS6csyIJiRWiKosKZlOgABKAiLIvTdu52VBIEkJ0ZZkrUFUk6oP44bptmrau4jS1KKmyWlr1cxJFRa0qIV6CTP33bYGYBJIAQNs2nFISkTh5JAIAH1MfIAlrmJR4WKJ1/&amp;#43;VqQBAJQbkArv&amp;#43;P4&amp;#43;0TT4qttSJZjVaBtuSbLs/TsloBgDCPD8OIzEZSVblvNZHheZ7317pxMWYr&amp;#43;CnEqJC7BiKJcSUfjvfrWjgy&amp;#43;x/kDu8P&amp;#43;Mmhf5f6xALW2sMC1lrn3PECzHxY4FQNGMcD0zzPC208W8czjCNtP43Hgxfv3te7HRv8dheft&amp;#43;Z6FFfgrqQpSP5mvKgJERDpax/7RXcWXEGW8WpIL8&amp;#43;Lu0VuJrmoafAKqp3X88o0Nt&amp;#43;tL10sZfoZAAD//4IFDj8c6dvsAAAAAElFTkSuQmCC);
">
&lt;source src="https://ianthehenry.com/posts/my-kind-of-repl/jest-1754x950.8a767fd291f5f664d68306606066827dc2609325127e8d371408c068864cc262.mp4" type="video/mp4">
&lt;/video>
&lt;/div>
&lt;p>I will be the first to admit that that workflow is not ideal; Jest is absolutely not designed for this &amp;ldquo;read-eval-patch-loop&amp;rdquo; style of development. Inline snapshots are a bolted-on extra feature on top of a traditional &amp;ldquo;fluent&amp;rdquo; assertion-based testing API, and it might take some work to twist Jest into something useful if you actually wanted to adopt this workflow in JavaScript. But it&amp;rsquo;s possible! The hard part is done at least; the source rewriter is written. The rest is just ergonomics.&lt;/p>
&lt;p>There is a cheat code, though, if you cannot find a test framework that works in your language: &lt;a href="https://bitheap.org/cram/">Cram&lt;/a>.&lt;/p>
&lt;p>Cram is basically &amp;ldquo;inline snapshot tests for bash,&amp;rdquo; but since you can write a bash command to run a program written in any other language, you can use Cram as a language-agnostic snapshot testing tool. It isn&amp;rsquo;t as nice as a native testing framework, but it&amp;rsquo;s still far, far nicer than writing assertion-based tests. And it works anywhere &amp;ndash; I use Cram to &lt;a href="https://github.com/ianthehenry/judge/blob/master/tests/basic.t">test the test framework&lt;/a> that I wrote in Janet, for example, since I can&amp;rsquo;t exactly use it to test itself.&lt;/p>
&lt;p>Okay. I really am almost done, but I feel that I should say one last thing about this type of testing, in case you are still hesitant: this is a &lt;em>mechanic&lt;/em>. This is not a philosophy or a dogma. This is just an ergonomic way to write tests &amp;ndash; &lt;em>any&lt;/em> tests.&lt;/p>
&lt;p>You can still argue as much as you want about unit tests vs. integration tests and mocks vs. stubs. You can test for narrow, individually significant properties, or just broadly observe entire data structures. You can still debate the merits of code coverage, and you can even combine this technique with quickcheck-style automatic property tests. Here, look:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ocaml" data-lang="ocaml">&lt;span class="k">module&lt;/span> &lt;span class="nc">Vec2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">struct&lt;/span>
&lt;span class="k">type&lt;/span> &lt;span class="n">t&lt;/span> &lt;span class="o">=&lt;/span>
&lt;span class="o">{&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="kt">float&lt;/span>
&lt;span class="o">;&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="kt">float&lt;/span>
&lt;span class="o">}&lt;/span>
&lt;span class="o">[@@&lt;/span>&lt;span class="n">deriving&lt;/span> &lt;span class="n">quickcheck&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">sexp_of&lt;/span>&lt;span class="o">]&lt;/span>
&lt;span class="k">let&lt;/span> &lt;span class="n">length&lt;/span> &lt;span class="o">{&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="o">}&lt;/span> &lt;span class="o">=&lt;/span>
&lt;span class="n">sqrt&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">*.&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">+.&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="o">*.&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="o">)&lt;/span>
&lt;span class="o">;;&lt;/span>
&lt;span class="k">let&lt;/span> &lt;span class="n">normalize&lt;/span> &lt;span class="n">point&lt;/span> &lt;span class="o">=&lt;/span>
&lt;span class="k">let&lt;/span> &lt;span class="n">length&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">length&lt;/span> &lt;span class="n">point&lt;/span> &lt;span class="k">in&lt;/span>
&lt;span class="o">{&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">point&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">/.&lt;/span> &lt;span class="n">length&lt;/span>
&lt;span class="o">;&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">point&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="o">/.&lt;/span> &lt;span class="n">length&lt;/span>
&lt;span class="o">}&lt;/span>
&lt;span class="o">;;&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Pretty simple code, but does it work for all inputs? Well, let&amp;rsquo;s try a property test:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ocaml" data-lang="ocaml">&lt;span class="k">let&lt;/span> &lt;span class="n">assert_nearly_equal&lt;/span> &lt;span class="n">here&lt;/span> &lt;span class="n">actual&lt;/span> &lt;span class="n">expected&lt;/span> &lt;span class="o">~&lt;/span>&lt;span class="n">epsilon&lt;/span> &lt;span class="o">=&lt;/span>
&lt;span class="o">[%&lt;/span>&lt;span class="n">test_pred&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">float&lt;/span>&lt;span class="o">]&lt;/span>
&lt;span class="o">~&lt;/span>&lt;span class="n">here&lt;/span>&lt;span class="o">:[&lt;/span>&lt;span class="n">here&lt;/span>&lt;span class="o">]&lt;/span>
&lt;span class="o">(&lt;/span>&lt;span class="k">fun&lt;/span> &lt;span class="n">actual&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nn">Float&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">(&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="o">)&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="nn">Float&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">abs&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">actual&lt;/span> &lt;span class="o">-.&lt;/span> &lt;span class="n">expected&lt;/span>&lt;span class="o">))&lt;/span> &lt;span class="n">epsilon&lt;/span>&lt;span class="o">)&lt;/span>
&lt;span class="n">actual&lt;/span>
&lt;span class="k">let&lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="n">expect_test&lt;/span> &lt;span class="s2">&amp;#34;normalize returns a vector of length 1&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span>
&lt;span class="nn">Quickcheck&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">test&lt;/span> &lt;span class="o">[%&lt;/span>&lt;span class="n">quickcheck&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">generator&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nn">Vec2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">]&lt;/span>
&lt;span class="o">~&lt;/span>&lt;span class="n">sexp_of&lt;/span>&lt;span class="o">:[%&lt;/span>&lt;span class="n">sexp_of&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nn">Vec2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">]&lt;/span>
&lt;span class="o">~&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="o">:(&lt;/span>&lt;span class="k">fun&lt;/span> &lt;span class="n">point&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span>
&lt;span class="n">assert_nearly_equal&lt;/span> &lt;span class="o">[%&lt;/span>&lt;span class="n">here&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="nn">Vec2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="nn">Vec2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">normalize&lt;/span> &lt;span class="n">point&lt;/span>&lt;span class="o">))&lt;/span> &lt;span class="n">1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">0&lt;/span> &lt;span class="o">~&lt;/span>&lt;span class="n">epsilon&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="n">0&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">0001&lt;/span>&lt;span class="o">)&lt;/span>
&lt;span class="o">[@@&lt;/span>&lt;span class="n">expect&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">uncaught_exn&lt;/span> &lt;span class="o">{|&lt;/span>
&lt;span class="c">(* CR expect_test_collector: This test expectation appears to contain a backtrace.
&lt;/span>&lt;span class="c"> This is strongly discouraged as backtraces are fragile.
&lt;/span>&lt;span class="c"> Please change this test to not include a backtrace. *)&lt;/span>
&lt;span class="o">(&lt;/span>&lt;span class="s2">&amp;#34;Base_quickcheck.Test.run: test failed&amp;#34;&lt;/span>
&lt;span class="o">(&lt;/span>&lt;span class="n">input&lt;/span> &lt;span class="o">((&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">3&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">3810849992682576E&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="n">272&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="n">440&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">10383674821787&lt;/span>&lt;span class="o">)))&lt;/span>
&lt;span class="o">(&lt;/span>&lt;span class="n">error&lt;/span>
&lt;span class="o">((&lt;/span>&lt;span class="n">runtime&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">lib&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">runtime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="nc">E&lt;/span> &lt;span class="s2">&amp;#34;predicate failed&amp;#34;&lt;/span>
&lt;span class="o">((&lt;/span>&lt;span class="nc">Value&lt;/span> &lt;span class="n">0&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="nc">Loc&lt;/span> &lt;span class="n">test&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">demo&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ml&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="n">20&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="n">15&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="nc">Stack&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">test&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">demo&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ml&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="n">29&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="n">26&lt;/span>&lt;span class="o">))))&lt;/span>
&lt;span class="s2">&amp;#34;Raised at Ppx_assert_lib__Runtime.test_pred in file &lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">runtime-lib/runtime.ml&lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">, line 58, characters 4-58\
&lt;/span>&lt;span class="s2"> &lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">Called from Base__Or_error.try_with in file &lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">src/or_error.ml&lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">, line 84, characters 9-15\
&lt;/span>&lt;span class="s2"> &lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="o">)))&lt;/span>
&lt;span class="nc">Raised&lt;/span> &lt;span class="n">at&lt;/span> &lt;span class="nn">Base__Error&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">raise&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">file&lt;/span> &lt;span class="s2">&amp;#34;src/error.ml&amp;#34;&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">inlined&lt;/span>&lt;span class="o">),&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="n">9&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">characters&lt;/span> &lt;span class="n">14&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">30&lt;/span>
&lt;span class="nc">Called&lt;/span> &lt;span class="n">from&lt;/span> &lt;span class="nn">Base__Or_error&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ok_exn&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">file&lt;/span> &lt;span class="s2">&amp;#34;src/or_error.ml&amp;#34;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="n">92&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">characters&lt;/span> &lt;span class="n">17&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">32&lt;/span>
&lt;span class="nc">Called&lt;/span> &lt;span class="n">from&lt;/span> &lt;span class="nn">Expect_test_collector&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nn">Make&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nn">Instance_io&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">exec&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">file&lt;/span> &lt;span class="s2">&amp;#34;collector/expect_test_collector.ml&amp;#34;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="n">262&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">characters&lt;/span> &lt;span class="n">12&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">19&lt;/span> &lt;span class="o">|}]&lt;/span>
&lt;span class="o">;;&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is not, you know, a &lt;em>good&lt;/em> property test, but just focus on the workflow &amp;ndash; even though it&amp;rsquo;s just an assertion failure, it&amp;rsquo;s nice to see that error in context. I don&amp;rsquo;t need to manually correlate line numbers with my source files, even if I&amp;rsquo;m working in a text editor that can&amp;rsquo;t jump to errors automatically.&lt;/p>
&lt;p>So the mechanic of source patching is still somewhat useful regardless of what sort of tests you&amp;rsquo;re writing. This post has focused on pretty simple unit tests because, you know, it&amp;rsquo;s a blog post, and we just don&amp;rsquo;t have time to page in anything complicated together. And I&amp;rsquo;ve especially emphasized testing-as-observation because that happens to be my favorite way to write &lt;em>most&lt;/em> tests, and it&amp;rsquo;s a unique superpower that you can&amp;rsquo;t really replicate in a traditional test framework.&lt;/p>
&lt;p>But that doesn&amp;rsquo;t mean that read-eval-patch loops are only useful in these cases, or that you can only adopt this technique with a radical change to the way you think about testing.&lt;/p>
&lt;p>Although it might not hurt&amp;hellip;&lt;/p>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>The convention where I work is to render all data structures as s-expressions, but I didn&amp;rsquo;t want to upset you any further.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2" role="doc-endnote">
&lt;p>Rust also has &lt;a href="https://docs.rs/expect-test/latest/expect_test/">&lt;code>expect_test&lt;/code>&lt;/a>, which I have not tried, and &lt;a href="https://insta.rs/">Insta&lt;/a>, but I had trouble getting it to work when I tried it a few years ago.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3" role="doc-endnote">
&lt;p>I couldn&amp;rsquo;t find room for this in the post &amp;ndash; it isn&amp;rsquo;t very flashy &amp;ndash; but in real life I think that tables are the most useful tool in my testing lunchbox. You can see &lt;a href="https://blog.janestreet.com/computations-that-differentiate-debug-and-document-themselves/">a few examples here&lt;/a> of tests with tables in them, although the tables are not really the stars there.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description><pubDate>Wed, 05 Jul 2023 00:00:00 +0000</pubDate><link>https://ianthehenry.com/posts/my-kind-of-repl/</link><guid isPermaLink="true">https://ianthehenry.com/posts/my-kind-of-repl/</guid></item><item><title>Generalized Macros</title><description>&lt;p>I&amp;rsquo;ve been &lt;a href="https://ianthehenry.com/posts/janet-for-mortals/">writing&lt;/a> a &lt;a href="https://ianthehenry.com/posts/why-janet/">lot&lt;/a> of &lt;a href="https://janet-lang.org/">Janet&lt;/a> lately, and I&amp;rsquo;ve been especially enjoying my time with &lt;a href="https://ianthehenry.com/posts/janet-game/the-problem-with-macros/">the macro system&lt;/a>.&lt;/p>
&lt;p>Janet macros are Common Lisp-flavored unhygienic &lt;code>gensym&lt;/code>-style macros. They are extremely powerful, and very easy to write, but they can be &lt;a href="https://janet.guide/macro-mischief/">pretty tricky to get right&lt;/a>. It&amp;rsquo;s easy to make mistakes that lead to unwanted variable capture, or to write macros that only work if they&amp;rsquo;re expanded in particular contexts, and it can be pretty difficult to detect these problems ahead of time.&lt;/p>
&lt;p>So people have spent a lot of time thinking about ways to write macros more safely &amp;ndash; sometimes at the cost of expressiveness or simplicity &amp;ndash; and almost all recent languages use some sort of hygienic macro system that defaults to doing the right thing.&lt;/p>
&lt;p>But as far as I know, no one has approached macro systems from the other direction. No one looked at Common Lisp&amp;rsquo;s macros and said &amp;ldquo;What if these macros &lt;em>aren&amp;rsquo;t dangerous enough?&lt;/em> What if we could make them even &lt;em>harder&lt;/em> to write correctly, in order to &lt;em>marginally&lt;/em> increase their power and expressiveness?&amp;rdquo;&lt;/p>
&lt;p>So welcome to my blog post.&lt;/p>
&lt;p>I want to show you an idea for a new kind of macro. A macro that can not only rewrite &lt;em>itself&lt;/em>, but actually rewrite &lt;em>any&lt;/em> form in your entire program.&lt;/p>
&lt;p>I think that &amp;ldquo;what, no, why on earth&amp;rdquo; is an entirely reasonable response to that statement, so let&amp;rsquo;s take a look at a motivating example together.&lt;/p>
&lt;p>Lots of languages have something like &lt;code>defer&lt;/code> that will run an expression at the end of a block, whether or not the rest of the code raises an exception. &lt;code>defer&lt;/code> is just like wrapping the rest of the function in a try-finally block, except that, well, you don&amp;rsquo;t actually have to do any wrapping. Which means no indentation increase, and no extra nested parentheses.&lt;/p>
&lt;p>Here&amp;rsquo;s an example of what &lt;code>defer&lt;/code> might look in Janet:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(do
(def f (file/open &amp;quot;foo.txt&amp;quot;))
(defer (file/close f))
(def contents (file/read f))
(do-something-dangerous-with contents))
&lt;/code>&lt;/pre>&lt;p>Now, we can&amp;rsquo;t implement &lt;code>defer&lt;/code> as a traditional macro, because a traditional macro can only rewrite &lt;em>itself&lt;/em>. But what we really want to do is rewrite the parent form that the &lt;code>defer&lt;/code> appears in, to wrap its &lt;em>siblings&lt;/em> in a finally expression:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(do
(def f (file/open &amp;quot;foo.txt&amp;quot;))
(finally
(do
(def contents (file/read f))
(do-something-dangerous-with contents))
(file/close f)))
&lt;/code>&lt;/pre>&lt;p>So generalized macros let us write exactly this.&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/p>
&lt;p>I want to start at the punchline and work backwards, because my favorite part is how &lt;em>simple&lt;/em> it is to write this. So although I don&amp;rsquo;t actually expect this to make any sense yet, let&amp;rsquo;s go ahead and look at the implementation of this &lt;code>defer&lt;/code> macro:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defmacro defer [] [expr]
(macro lefts rights
~(,;lefts (finally (do ,;rights) ,expr))))
&lt;/code>&lt;/pre>&lt;p>Pretty tame, right?&lt;/p>
&lt;p>So in order to do understand how this works, we&amp;rsquo;ll have to change three things about macros:&lt;/p>
&lt;ol>
&lt;li>Macros no longer have to appear at the head of the form; they can appear anywhere within a form.&lt;/li>
&lt;li>Macros now have two argument lists: the forms &amp;ldquo;to the left&amp;rdquo; of the macro, and the forms &amp;ldquo;to the right&amp;rdquo; of the macro.&lt;/li>
&lt;li>Macros can either return new abstract syntax trees &amp;ndash; like a traditional macro &amp;ndash; or they can return new, anonymous &lt;em>macros&lt;/em> as first-class values.&lt;/li>
&lt;/ol>
&lt;p>Let&amp;rsquo;s go through these ideas one at a time in slightly more detail, and then we&amp;rsquo;ll circle back to the definition of &lt;code>defer&lt;/code>.&lt;/p>
&lt;h1 id="macros-can-appear-anywhere">Macros can appear anywhere&lt;/h1>
&lt;p>Scheme already has something called &amp;ldquo;identifier macros,&amp;rdquo; which can appear anywhere within a form. You can use them to say that &lt;code>foo&lt;/code> is a macro, and it can appear in any expression context, and then make &lt;code>(+ 1 foo)&lt;/code> expand to something like &lt;code>(+ 1 (some complicated expression))&lt;/code>.&lt;/p>
&lt;p>But identifier macros can still only rewrite &lt;em>themselves&lt;/em>. In order to do anything interesting with this, we need to add&amp;hellip;&lt;/p>
&lt;h1 id="macros-can-see-forms-to-the-left-and-to-the-right">Macros can see forms &amp;ldquo;to the left&amp;rdquo; and &amp;ldquo;to the right&amp;rdquo;&lt;/h1>
&lt;p>On the face of it this might sound like I&amp;rsquo;m trying to introduce &amp;ldquo;infix macros,&amp;rdquo; so that you could write something like &lt;code>(1 + 2)&lt;/code> and rewrite that to the traditional &lt;code>(+ 1 2)&lt;/code> syntax. And, to be clear, that &lt;em>is&lt;/em> a thing you can do:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defmacro + [left] [right]
[+ left right])
&lt;/code>&lt;/pre>&lt;p>I think that infix macros could be very useful &amp;ndash; we&amp;rsquo;ll talk more about that in a bit &amp;ndash; even if infix &lt;em>math&lt;/em> is not particularly compelling to someone practiced in the prefixual arts.&lt;/p>
&lt;p>But the real reason to support &amp;ldquo;infix&amp;rdquo; macros is for cases like &lt;code>defer&lt;/code>, where the &lt;code>(defer ...)&lt;/code> expression occurs in the middle of a form, and acts as sort of an &amp;ldquo;infix&amp;rdquo; expansion point. But in order for that to work, we need to add&amp;hellip;&lt;/p>
&lt;h1 id="first-class-macros">First-class macros&lt;/h1>
&lt;p>I think this is the trickiest part to wrap your head around, but it&amp;rsquo;s the most important. This is the trick that allows macros to rewrite not only themselves, but also the forms around themselves &amp;ndash; their parents, their grandparents, their&amp;hellip; cousins? I guess? Any other form in your program, actually.&lt;/p>
&lt;p>So the idea is that we can create first-class anonymous macros, and return them from our macro implementations. And then those macros will get expanded &lt;em>in the context of the parent form&lt;/em> that they now appear in.&lt;/p>
&lt;p>This is a lot like returning an anonymous function, except that functions are perfectly reasonable values to put in your abstract syntax trees&amp;hellip; so it&amp;rsquo;s like returning a &lt;em>special&lt;/em> function, a function with a little tag attached that says &amp;ldquo;hey, I&amp;rsquo;m not a real runtime value, I&amp;rsquo;m a macro, so you should call me before you finish macro expansion.&amp;rdquo;&lt;/p>
&lt;p>And just to be super explicit: this is different from a macro returning a &lt;em>syntax tree&lt;/em> that contains another macro invocation. You can already write &amp;ldquo;recursive macros,&amp;rdquo; or macros that return &lt;em>invocations of&lt;/em> other macros. But by creating actual new first-class macros at expansion time, you can close over macro arguments and reference them during the next phase of expansion.&lt;/p>
&lt;p>So with these changes in mind, let&amp;rsquo;s come back to the implementation of &lt;code>defer&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defmacro defer [] [expr]
(macro lefts rights
~(,;lefts (finally (do ,;rights) ,expr))))
&lt;/code>&lt;/pre>&lt;p>Our &lt;code>defer&lt;/code> macro takes two binding forms: &lt;code>[]&lt;/code> and &lt;code>[expr]&lt;/code>. So it expects no arguments to the left &amp;ndash; the word &lt;code>defer&lt;/code> has to appear at the beginning of its form &amp;ndash; and it expects exactly one argument to its right. In other words, it looks like a normal, traditional prefix macro of one argument.&lt;/p>
&lt;p>But then it returns an anonymous macro that closes over its &lt;code>expr&lt;/code> argument. So if we just look at one step of the expansion, we&amp;rsquo;ll see an abstract syntax tree that looks like this:&lt;/p>
&lt;pre tabindex="0">&lt;code>(do
(def f (file/open &amp;quot;foo.txt&amp;quot;))
&amp;lt;macro&amp;gt;
(def contents (file/read f))
(do-something-dangerous-with contents))
&lt;/code>&lt;/pre>&lt;p>But macro expansion isn&amp;rsquo;t over. After expanding the &lt;code>(defer ...)&lt;/code> form, the macro expander will notice that it expanded to another macro, so it will expand &lt;em>that&lt;/em>. Which winds up invoking our anonymous macro, passing it &lt;code>['do '(def f ...)]&lt;/code> as its &amp;ldquo;left&amp;rdquo; arguments and &lt;code>['(def contents ...) '(do-something...)]&lt;/code> as its &amp;ldquo;right&amp;rdquo; arguments.&lt;/p>
&lt;p>And then that will return a replacement for the entire &lt;code>(do ...)&lt;/code> form, giving us our final result:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(do
(def f (file/open &amp;quot;foo.txt&amp;quot;))
(finally
(do
(def contents (file/read f))
(do-something-dangerous-with contents))
(file/close f)))
&lt;/code>&lt;/pre>&lt;p>Neat, right?&lt;/p>
&lt;p>This type of macro gives us a lot more freedom to decide how we want our code to look. I&amp;rsquo;m honestly not really sure &lt;em>how much&lt;/em> more freedom, because I haven&amp;rsquo;t spent very much time with the idea yet. But I&amp;rsquo;ve been thinking about it for a while, and I&amp;rsquo;ve come up with a few examples of things that we can do with this &amp;ndash; some much dumber than others.&lt;/p>
&lt;p>Let&amp;rsquo;s take a look at a few of them.&lt;/p>
&lt;h1 id="nest-less">Nest less&lt;/h1>
&lt;p>I think that reducing the number of nested parentheses and general indentation might be the most compelling use case for this sort of macro. I mean, really this is all &lt;code>defer&lt;/code> does: it lets you write &lt;code>finally&lt;/code> with a little less nesting, and with the expressions in a slightly different order.&lt;/p>
&lt;p>I spend most of my programming time writing OCaml. OCaml doesn&amp;rsquo;t have &amp;ldquo;block scope&amp;rdquo; or &amp;ldquo;function scope&amp;rdquo; like most languages &amp;ndash; it has &lt;em>expression scope&lt;/em>. You introduce new &amp;ldquo;variables&amp;rdquo; (they can&amp;rsquo;t actually &lt;em>vary&lt;/em>; all OCaml bindings are &amp;ldquo;&lt;code>const&lt;/code>&amp;quot;) using &lt;code>let ... in&lt;/code>, and the binding only exists on the right-hand side of that particular expression.&lt;/p>
&lt;p>If you think about the nesting of the OCaml abstract syntax tree, it looks something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ocaml" data-lang="ocaml">&lt;span class="o">(&lt;/span>&lt;span class="k">let&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">10&lt;/span> &lt;span class="k">in&lt;/span>
&lt;span class="o">(&lt;/span>&lt;span class="k">let&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">20&lt;/span> &lt;span class="k">in&lt;/span>
&lt;span class="o">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="o">)))&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>But of course you don&amp;rsquo;t write OCaml like that. For one thing, the parentheses are redundant:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ocaml" data-lang="ocaml">&lt;span class="k">let&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">10&lt;/span> &lt;span class="k">in&lt;/span>
&lt;span class="k">let&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">20&lt;/span> &lt;span class="k">in&lt;/span>
&lt;span class="n">x&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">y&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>For another thing, this triangular indentation is really annoying. So you actually write it like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ocaml" data-lang="ocaml">&lt;span class="k">let&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">10&lt;/span> &lt;span class="k">in&lt;/span>
&lt;span class="k">let&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">20&lt;/span> &lt;span class="k">in&lt;/span>
&lt;span class="n">x&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">y&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>The parse tree for that expression is still nested, but it doesn&amp;rsquo;t &lt;em>look&lt;/em> nested &amp;ndash; you always format your code linearly.&lt;/p>
&lt;p>So lisps also have &lt;code>let&lt;/code>, but lisps don&amp;rsquo;t have the luxury of leaving off the parentheses, so we&amp;rsquo;re back to the first example:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(let [x 10]
(let [y 20]
(+ x y)))
&lt;/code>&lt;/pre>&lt;p>And if we tried to write that without indentation, then&amp;hellip;&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(let [x 10]
(let [y 20]
(+ x y)))
&lt;/code>&lt;/pre>&lt;p>Immediate aneurysm.&lt;/p>
&lt;p>Fortunately, every lisp dialect that I know of mitigates this problem substantially by allowing &lt;code>let&lt;/code> to introduce multiple bindings in a single form:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(let [x 10
y 20]
(+ x y))
&lt;/code>&lt;/pre>&lt;p>Which means that we only have to increase the nesting by one level in the very common case that we have a series of &lt;code>let&lt;/code> expressions. But by using generalized macros instead, we can write a version of &lt;code>let&lt;/code> that doesn&amp;rsquo;t increase indentation at all. I&amp;rsquo;ll call it &lt;code>def&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defmacro def [] [name value]
(macro lefts rights
~(,;lefts (let [,name ,value] ,;rights))))
&lt;/code>&lt;/pre>&lt;p>&lt;code>def&lt;/code> lets us write code like this:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(def x 10)
(def y 20)
(+ x y)
&lt;/code>&lt;/pre>&lt;p>Which gets transformed into code like this:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(let [x 10]
(let [y 20]
(+ x y)))
&lt;/code>&lt;/pre>&lt;p>Of course Janet &amp;ndash; and most lisps &amp;ndash; have a &amp;ldquo;linear assignment&amp;rdquo; form like this built into the language. In Janet it&amp;rsquo;s called &amp;ndash; &lt;em>coincidentally enough&lt;/em> &amp;ndash; &lt;code>def&lt;/code>.&lt;/p>
&lt;p>In fact in Janet, &lt;code>def&lt;/code> is actually the &lt;em>primitive&lt;/em> way to create new bindings, and &lt;code>let&lt;/code> is a macro that just desugars to &lt;code>do&lt;/code> + &lt;code>def&lt;/code>, which is very reasonable and pragmatic, but feels &lt;em>weird&lt;/em> to me.&lt;/p>
&lt;p>It feels weird to me because, in my mind, &lt;code>let&lt;/code> should be syntax sugar for &lt;code>fn&lt;/code> &amp;ndash; Janet&amp;rsquo;s word for &lt;code>lambda&lt;/code>. Because, after all, these two expressions are equivalent:&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(let [x 10] (+ x 1))
((fn [x] (+ x 1)) 10)
&lt;/code>&lt;/pre>&lt;p>&lt;code>let&lt;/code> allows us to write the expression in a much more natural order, but we can introduce new bindings without any &lt;code>let&lt;/code>s at all.&lt;/p>
&lt;p>This might sound like weird mathematical lambda calculus trivia, but it&amp;rsquo;s not: it&amp;rsquo;s important to understand introducing new variables as a special-case of function application, even if this &lt;em>particular&lt;/em> function application happens to be trivial.&lt;/p>
&lt;p>Because we can apply the same technique that we just used &amp;ndash; rewriting &lt;code>def&lt;/code> to &lt;code>let&lt;/code>, and rewriting &lt;code>let&lt;/code> to &lt;code>fn&lt;/code> &amp;ndash; to do something much more interesting.&lt;/p>
&lt;h2 id="generalized-function-application">Generalized function application&lt;/h2>
&lt;p>So Haskell has something called &lt;code>do&lt;/code> notation. You&amp;rsquo;ve probably seen something like this before:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-haskell" data-lang="haskell">&lt;span class="nf">addAll&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="kt">Int&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="kt">Int&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="kt">Int&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="nf">addAll&lt;/span> &lt;span class="n">xs&lt;/span> &lt;span class="n">ys&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kr">do&lt;/span>
&lt;span class="n">x&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">xs&lt;/span>
&lt;span class="n">y&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">ys&lt;/span>
&lt;span class="n">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>ghci&amp;gt; addAll [1, 2] [10, 20]
[11,21,12,22]
&lt;/code>&lt;/pre>&lt;p>This is equivalent to the following Janet code:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defn add-all [xs ys]
(mapcat (fn [x]
(mapcat (fn [y]
[(+ x y)])
ys))
xs))
&lt;/code>&lt;/pre>&lt;p>But I think the Haskell code is easier to read. Partly that&amp;rsquo;s because the argument order to Janet&amp;rsquo;s &lt;code>mapcat&lt;/code> function makes the values we&amp;rsquo;re traversing appear in reverse order in our source code, and we could fix this by redefining &lt;code>mapcat&lt;/code> with a argument different order:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defn add-all [xs ys]
(mapcat xs (fn [x]
(mapcat ys (fn [y]
[(+ x y)])))))
&lt;/code>&lt;/pre>&lt;p>This reminds me of the transformation we did when we changed &lt;code>((fn [x] (+ x 1)) 10)&lt;/code> into &lt;code>(let [x 10] (+ x 1))&lt;/code>. So what if we take it one step further, and do the same thing we did to get &lt;code>def&lt;/code>?&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defn add-all [xs ys]
(as x mapcat xs)
(as y mapcat ys)
[(+ x y)])
&lt;/code>&lt;/pre>&lt;p>It&amp;rsquo;s not quite as concise as Haskell&amp;rsquo;s &lt;code>do&lt;/code> notation: Haskell is able to use the type of the expression to determine what &lt;code>&amp;lt;-&lt;/code> means, so there&amp;rsquo;s no need to specify the &lt;code>mapcat&lt;/code> bit: it&amp;rsquo;s implied from the fact that we gave it a list.&lt;/p>
&lt;p>Janet doesn&amp;rsquo;t have an analog for &lt;a href="https://en.wikipedia.org/wiki/Type_class">type classes&lt;/a>, so we have to be a little more explicit, but this means that we can do more than just &amp;ldquo;bind&amp;rdquo; with the &lt;code>as&lt;/code> macro. We can also &lt;code>map&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defn add-all [xs ys]
(as x mapcat xs)
(as y map ys)
(+ x y))
&lt;/code>&lt;/pre>&lt;p>Implementing &lt;code>as&lt;/code> is just as easy as implementing &lt;code>def&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defmacro as [] [name f arg]
(macro lefts rights
~(,;lefts (,f (fn [,name] ,;rights) ,arg))))
&lt;/code>&lt;/pre>&lt;p>If you haven&amp;rsquo;t programmed in a language like Haskell, this particular syntax sugar might seem a little odd at first. But a specialized notation for generalized function application is extremely useful &amp;ndash; we have it in OCaml too, through a syntax extension called &lt;a href="https://github.com/janestreet/ppx_let">&lt;code>ppx_let&lt;/code>&lt;/a>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ocaml" data-lang="ocaml">&lt;span class="k">let&lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="n">bind&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">xs&lt;/span> &lt;span class="k">in&lt;/span>
&lt;span class="k">let&lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="n">bind&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ys&lt;/span> &lt;span class="k">in&lt;/span>
&lt;span class="n">return&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="o">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>I think OCaml&amp;rsquo;s notation is actually more clear than Haskell&amp;rsquo;s &amp;ndash; it highlights the symmetry between &amp;ldquo;ordinary&amp;rdquo; let bindings and &amp;ldquo;fancy&amp;rdquo; let bindings like these. And because it can do more than just &lt;code>bind&lt;/code>, we can also avoid the explicit &lt;code>return&lt;/code> in OCaml:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ocaml" data-lang="ocaml">&lt;span class="k">let&lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="n">bind&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">xs&lt;/span> &lt;span class="k">in&lt;/span>
&lt;span class="k">let&lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="n">map&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ys&lt;/span> &lt;span class="k">in&lt;/span>
&lt;span class="n">x&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">y&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>map&lt;/code> and &lt;code>bind&lt;/code> aren&amp;rsquo;t the only functions of this variety, either. Although monads are ubiquitous in OCaml, I spend a lot of my time &lt;a href="https://opensource.janestreet.com/bonsai/">working with arrows&lt;/a> as well. And arrows have yet another notation: &lt;code>let%sub&lt;/code> and &lt;code>let%arr&lt;/code>.&lt;/p>
&lt;p>All of these are generalizations of regular function application. Without worrying about what any of this means, just look at how similar the &lt;em>shape&lt;/em> of these different type signatures are:&lt;/p>
&lt;pre tabindex="0">&lt;code>val (@@) : 'a -&amp;gt; ('a -&amp;gt; 'b) -&amp;gt; 'b
val map : 'a f -&amp;gt; ('a -&amp;gt; 'b) -&amp;gt; 'b f
val bind : 'a f -&amp;gt; ('a -&amp;gt; 'b f) -&amp;gt; 'b f
val sub : 'a s -&amp;gt; ('a r -&amp;gt; 'b s) -&amp;gt; 'b s
val arr : 'a r -&amp;gt; ('a -&amp;gt; 'b) -&amp;gt; 'b s
&lt;/code>&lt;/pre>&lt;p>Okay, I know; this isn&amp;rsquo;t supposed to be a blog post about monads or arrows. Let&amp;rsquo;s get back to macros.&lt;/p>
&lt;h1 id="infix-operators">Infix operators&lt;/h1>
&lt;p>So Janet has some very useful &amp;ldquo;threading&amp;rdquo; macros that allow you to write code in a more &amp;ldquo;linear&amp;rdquo; fashion than you could without them. They&amp;rsquo;re useful when you&amp;rsquo;re performing a series of transformations to a value:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(filter |(string/has-prefix? &amp;quot;a&amp;quot; $)
(map string/ascii-lower
(map |($ :name)
people)))
&lt;/code>&lt;/pre>&lt;p>With the power of threading macros, you could write that like this instead:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(-&amp;gt;&amp;gt; people
(map |($ :name))
(map string/ascii-lower)
(filter |(string/has-prefix? &amp;quot;a&amp;quot; $)))
&lt;/code>&lt;/pre>&lt;p>This is a lot like &amp;ldquo;method chaining&amp;rdquo; in other languages:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="nx">people&lt;/span>
&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">person&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">person&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">name&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toLowerCase&lt;/span>&lt;span class="p">())&lt;/span>
&lt;span class="p">.&lt;/span>&lt;span class="nx">filter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">name&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">startsWith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;a&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>It&amp;rsquo;s easier for me to read the linear notation that uses the threading macro. But it&amp;rsquo;s not actually any easier to &lt;em>write&lt;/em> it.&lt;/p>
&lt;p>I like that method chaining allows me to write the code in the order of the operations: &amp;ldquo;Start with people, get the name, lowercase it, filter it to names that start with &amp;lsquo;a.'&amp;rdquo;&lt;/p>
&lt;p>When writing the threading macro, though, the way you &lt;em>type&lt;/em> this is &amp;ldquo;start with people, okay wait, go back, surround it in parentheses, add a &lt;code>-&amp;gt;&amp;gt;&lt;/code> at the beginning, now move the cursor to the end of the form, and then get the name&amp;hellip;&amp;rdquo;&lt;/p>
&lt;p>I don&amp;rsquo;t like that. And I know that there are &lt;em>fancy&lt;/em> editors that allow you to easily wrap expressions in threading macros or any other without repositioning the cursor, but I&amp;rsquo;d rather use a syntax that doesn&amp;rsquo;t require a structural editor to work with comfortably.&lt;/p>
&lt;p>So here&amp;rsquo;s another way to write this:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(people
@ (map |($ :name))
@ (map string/ascii-lower)
@ (filter |(string/has-prefix? &amp;quot;a&amp;quot; name)))
&lt;/code>&lt;/pre>&lt;p>This uses &lt;code>@&lt;/code> as an infix function application macro. I prefer &lt;code>|&lt;/code> myself, and that&amp;rsquo;s the notation that I chose for &lt;a href="https://bauble.studio/">Bauble&lt;/a>, but &lt;code>|&lt;/code> is how you create short anonymous functions in Janet, so I don&amp;rsquo;t want to step on that.&lt;/p>
&lt;p>The main reason I prefer this notation is that it&amp;rsquo;s easier for me to &lt;em>type&lt;/em>. I don&amp;rsquo;t know that it&amp;rsquo;s any easier to &lt;em>read&lt;/em> than &lt;code>-&amp;gt;&lt;/code>, but it allows me to write code in the order that I think it, and I like being able to choose a syntax that maps neatly onto my brain.&lt;/p>
&lt;p>Another infix macro that I like is &lt;code>.&lt;/code>. &lt;code>.&lt;/code> is a convenient macro for looking up a keyword in a struct or table, so that you can write &lt;code>struct.key&lt;/code> instead of &lt;code>(get struct :key)&lt;/code>.&lt;/p>
&lt;p>In Janet &lt;code>struct.key&lt;/code> parses as a single symbol, so we can&amp;rsquo;t actually implement this as a generalized macro without a separate preprocessing step to split it into three symbols. But we &lt;em>can&lt;/em> use it as &lt;code>struct . key&lt;/code>, which parses as three separate symbols:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defmacro . lefts [key &amp;amp; rights]
~(,;(drop-last lefts)
(get ,(last lefts) ,(keyword key))
,;rights))
&lt;/code>&lt;/pre>&lt;p>Which we can then use like this:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(print foo . bar)
&lt;/code>&lt;/pre>&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(print (get foo :bar))
&lt;/code>&lt;/pre>&lt;p>This is very goofy looking, but you could imagine a language where &lt;code>.&lt;/code> always parsed as its own symbol, so that we could just write &lt;code>foo.bar&lt;/code> and have that expand to &lt;code>(get foo :bar)&lt;/code> automatically.&lt;/p>
&lt;p>Another infix macro that I think could be interesting is &lt;code>:&lt;/code>, to create a pair.&lt;/p>
&lt;p>Janet already uses &lt;code>:&lt;/code> as a leader character for declaring &lt;em>keywords&lt;/em>, so this won&amp;rsquo;t work in Janet. But you could imagine, again, a different language where &lt;code>:&lt;/code> is used as a short way to create a pair of two elements. So:&lt;/p>
&lt;pre>&lt;code>foo:bar
&lt;/code>&lt;/pre>
&lt;p>Would become:&lt;/p>
&lt;pre>&lt;code>(foo bar)
&lt;/code>&lt;/pre>
&lt;p>This might be useful in languages that use wrapped &lt;code>let&lt;/code>s, where you have to write:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(let ((x 10)
(y 20))
(+ x y))
&lt;/code>&lt;/pre>&lt;p>Instead, you could write that as:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(let (x:10 y:20)
(+ x y))
&lt;/code>&lt;/pre>&lt;p>But have it parse in exactly the same way.&lt;/p>
&lt;p>You could imagine it in &lt;code>cond&lt;/code> as well:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(cond
(&amp;gt; x 0): &amp;quot;positive&amp;quot;
(&amp;lt; x 0): &amp;quot;negative&amp;quot;
(= x 0): &amp;quot;zero&amp;quot;
true: &amp;quot;nan&amp;quot;)
&lt;/code>&lt;/pre>&lt;p>Of course Janet doesn&amp;rsquo;t require wrapping each entry in a &lt;code>cond&lt;/code> expression in parentheses, so this isn&amp;rsquo;t as compelling in Janet.&lt;/p>
&lt;p>For a slight variation on this, imagine a macro called &lt;code>::&lt;/code>. It&amp;rsquo;s just like &lt;code>:&lt;/code> &amp;ndash; it creates a pair &amp;ndash; but the pair appears in reverse order from how you write it. We&amp;rsquo;re well off the Janet path now, but we could use this as a concise notation for adding type annotations without an explosion of parentheses.&lt;/p>
&lt;p>Let&amp;rsquo;s say we have a &amp;ndash; function? macro? &amp;ndash; &lt;em>something&lt;/em> called &lt;code>Int&lt;/code> that provides a type annotation to our compiler. We&amp;rsquo;d normally write the type of an expression like this:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(def x (Int (compute-thing)))
&lt;/code>&lt;/pre>&lt;p>But look all those close parens! I don&amp;rsquo;t want to balance those. So instead, we could write:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(def x (compute-thing) :: Int)
&lt;/code>&lt;/pre>&lt;p>Which is equivalent to the less intuitive (to me):&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(def x Int:(compute-thing))
&lt;/code>&lt;/pre>&lt;p>&lt;code>::&lt;/code> doesn&amp;rsquo;t mean &amp;ldquo;type annotation,&amp;rdquo; though, it just means &amp;ldquo;wrap in parentheses.&amp;rdquo; We could use it to do dumber things:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">&amp;quot;hello&amp;quot; :: print
&lt;/code>&lt;/pre>&lt;p>Which would expand, of course, to &lt;code>(print &amp;quot;hello&amp;quot;)&lt;/code>. But&amp;hellip; I don&amp;rsquo;t know why you would want to do that.&lt;/p>
&lt;h1 id="comment">Comment&lt;/h1>
&lt;p>Something that I occasionally wish for is a &lt;code>(comment ...)&lt;/code> macro that lets me ignore code.&lt;/p>
&lt;p>You can&amp;rsquo;t actually write such a macro in Janet. Janet &lt;em>has&lt;/em> a macro called &lt;code>comment&lt;/code> in the standard library, but &lt;code>comment&lt;/code> always expands to &lt;em>nil&lt;/em>, and &lt;code>nil&lt;/code> is not &lt;em>nothing&lt;/em>. This means there are lots of places you can&amp;rsquo;t use &lt;code>(comment ...)&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defn foo [a (comment this is a comment)]
(print a))
&lt;/code>&lt;/pre>&lt;p>If you tried to compile that, you&amp;rsquo;d get an error:&lt;/p>
&lt;pre tabindex="0">&lt;code>compile error: unexpected type in destruction, got nil
&lt;/code>&lt;/pre>&lt;p>Because after macro expansion, the compiler actually sees:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defn foo [a nil]
(print a))
&lt;/code>&lt;/pre>&lt;p>Which is not valid.&lt;/p>
&lt;p>With generalized macros, though, you can write a comment that actually disappears:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defmacro comment [] [&amp;amp;]
(macro lefts rights [;lefts ;rights]))
(defn foo [a (comment this is a comment)]
(print a))
&lt;/code>&lt;/pre>&lt;p>After expansion, that will just be:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defn foo [a]
(print a))
&lt;/code>&lt;/pre>&lt;p>Like it never happened.&lt;/p>
&lt;h1 id="ifelse">&lt;code>if&lt;/code>/&lt;code>else&lt;/code>&lt;/h1>
&lt;p>One thing that sometimes trips me up when I&amp;rsquo;m writing Janet is &lt;code>if&lt;/code>.&lt;/p>
&lt;p>&lt;code>if&lt;/code> takes three forms: a boolean expression, an expression to evaluate if it&amp;rsquo;s truthy, and an expression to evaluate if it&amp;rsquo;s falsy. Which is nice and concise, but it&amp;rsquo;s different enough from other languages that I use &amp;ndash; languages with explicit &lt;code>else&lt;/code>s &amp;ndash; that sometimes I&amp;rsquo;ll write code like this by mistake:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(if should-do-something-important
(print &amp;quot;okay, performing important work:&amp;quot;)
(perform-important-work))
&lt;/code>&lt;/pre>&lt;p>That actually &lt;em>doesn&amp;rsquo;t&lt;/em> perform important work &amp;ndash; &lt;code>(perform-important-work)&lt;/code> is the &amp;ldquo;else&amp;rdquo; section of that conditional. In order to do more than one thing in the &amp;ldquo;then&amp;rdquo; branch, we have to wrap all of the statements in &lt;code>do&lt;/code>.&lt;/p>
&lt;p>And of course Janet has a &lt;code>when&lt;/code> macro that does exactly what I want:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(when should-do-something-important
(print &amp;quot;okay, performing important work:&amp;quot;)
(perform-important-work))
&lt;/code>&lt;/pre>&lt;p>Which doesn&amp;rsquo;t have an &lt;code>else&lt;/code> branch, and usually when I&amp;rsquo;m writing an &lt;code>if&lt;/code> without an &lt;code>else&lt;/code> I should just use &lt;code>when&lt;/code> in the first place.&lt;/p>
&lt;p>But.&lt;/p>
&lt;p>Generalized macros actually let us write &lt;code>if&lt;/code> with an explicit &lt;code>else&lt;/code>. I&amp;rsquo;m not saying this is a &lt;em>good idea&lt;/em>, but they let us write something like:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(if (empty? name)
(print &amp;quot;you must provide a valid name&amp;quot;))
(else
(print &amp;quot;okay i think it checks out&amp;quot;))
&lt;/code>&lt;/pre>&lt;p>This example is a little weird because, in order for this to work nicely, we&amp;rsquo;ll have to rename the built-in &lt;code>if&lt;/code>. I&amp;rsquo;ll call the ternary version &lt;code>if-then-else&lt;/code> for this example, and say that &lt;code>if&lt;/code> now means the same thing as Janet&amp;rsquo;s &lt;code>when&lt;/code>.&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defmacro else [] [else-exprs]
(macro lefts rights
(match (last lefts)
['if &amp;amp; then-exprs]
~(,;(drop-last lefts)
(if-then-else (do ,;then-exprs) (do ,;else-exprs))
,;rights)
(error &amp;quot;else must come immediately after if&amp;quot;))))
&lt;/code>&lt;/pre>&lt;p>This is interesting because the &lt;code>else&lt;/code> macro actually rewrites the form &lt;em>before&lt;/em> itself, changing the &lt;code>if&lt;/code> to an &lt;code>if-then-else&lt;/code> and then fussing with its arguments.&lt;/p>
&lt;h1 id="weirder-more-exotic-things">Weirder, more exotic things&lt;/h1>
&lt;p>I could keep going, but, as you have probably noticed, this is already a very long blog post. There&amp;rsquo;s a lot more that you can do with generalized macros &amp;ndash; some of it useful, some of it unhinged, and we don&amp;rsquo;t have time to talk about all of it.&lt;/p>
&lt;p>So far we&amp;rsquo;ve only seen macros that rewrite their parents or immediate siblings, but you can write macros that return macros that return macros, and use them to rewrite arbitrary forms anywhere in your program. You could write a macro that rewrites &amp;ldquo;the nearest enclosing function definition,&amp;rdquo; recursively accumulating first-class macros until finally expanding all of them.&lt;/p>
&lt;p>You can write actual left- and right-associative infix operators, and I think that if you tried hard enough, you could even use dynamic variables and controlled macro expansion to implement infix operator precedence (although I don&amp;rsquo;t think you &lt;em>should&lt;/em>).&lt;/p>
&lt;p>You could implement (a weaker version of) Janet&amp;rsquo;s &lt;code>splice&lt;/code> built-in as a generalized macro. You could implement &amp;ldquo;identifier macros&amp;rdquo; that look around themselves and expand to something different when they appear as the first argument to a &lt;code>(set ...)&lt;/code> form. You could, you could&amp;hellip;&lt;/p>
&lt;p>You could do a lot of things, but I&amp;rsquo;m going to have to leave these as exercises to the reader, because it&amp;rsquo;s time to switch gears and talk about why you &lt;em>shouldn&amp;rsquo;t&lt;/em> do this.&lt;/p>
&lt;h1 id="problem-one-the-repl">Problem one: the repl&lt;/h1>
&lt;p>The main reason that this seems like a bad idea is that macros like this don&amp;rsquo;t work at the top level.&lt;/p>
&lt;p>If you&amp;rsquo;re just using the repl, and you type &lt;code>(defer (file/close f))&lt;/code>, what happens? One of the arguments to that macro is &amp;ldquo;everything to the right.&amp;rdquo; But there isn&amp;rsquo;t anything to the right! At least, not yet. And it won&amp;rsquo;t be able to supply &lt;em>everything&lt;/em> to the right until you stop typing altogether.&lt;/p>
&lt;p>This might not seem like a big deal for &lt;code>defer&lt;/code> &amp;ndash; just don&amp;rsquo;t use &lt;code>defer&lt;/code> at the repl &amp;ndash; but it is a big deal for, say, &lt;code>def&lt;/code>. And I don&amp;rsquo;t know an elegant way to solve this problem: in the general case, macros could look arbitrarily far ahead, so we&amp;rsquo;d have to wait until we closed the repl session to be able to expand them. And that&amp;rsquo;s kinda gross.&lt;/p>
&lt;h1 id="problem-two-generalized-macros-dont-always-compose">Problem two: generalized macros don&amp;rsquo;t always compose&lt;/h1>
&lt;p>All of the examples that we&amp;rsquo;ve seen so far play nicely together, but it&amp;rsquo;s possible to write generalized macros that don&amp;rsquo;t compose with one another.&lt;/p>
&lt;p>The problem is that macro behavior can depend on expansion order. Regular macros always get a chance to run &lt;em>before&lt;/em> their arguments get expanded, which is very convenient. Generalized macros don&amp;rsquo;t have that luxury &amp;ndash; because macros can see the forms around them, the order that you expand those forms matters.&lt;/p>
&lt;p>In my implementation I chose to expand macros in a depth-first, left-to-right order. So macros always see the arguments &amp;ldquo;to the left&amp;rdquo; of themselves fully expanded, and the arguments &amp;ldquo;to the right&amp;rdquo; completely unexpanded.&lt;/p>
&lt;p>And this can be problematic. For example, let&amp;rsquo;s say we make an infix alias for &lt;code>set&lt;/code>, called &lt;code>:=&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(var x 0)
(x := 1)
&lt;/code>&lt;/pre>&lt;p>Which expands to:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(var x 0)
(set x 1)
&lt;/code>&lt;/pre>&lt;p>This is a trivial generalized macro to write.&lt;/p>
&lt;p>Now let&amp;rsquo;s say we have &lt;em>another&lt;/em> macro, which looks at the form to its left to see if it immediately follows &lt;code>set&lt;/code>. When it does, it rewrites that &lt;code>set&lt;/code> to something else. We could use this to implement some kind of custom associative data structure:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(defmacro at [] [dict key]
(macro lefts rights
(if (= lefts ['set])
~(assign ,dict ,key ,;rights)
~(,;lefts (lookup ,dict ,key) ,;rights))))
&lt;/code>&lt;/pre>&lt;p>So that macro lets us write:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(set (at dict key) 10)
&lt;/code>&lt;/pre>&lt;p>And have that expand to:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(assign dict key 10)
&lt;/code>&lt;/pre>&lt;p>Meanwhile, if we write &lt;code>(print (at dict key))&lt;/code>, that will expand to &lt;code>(print (lookup dict key))&lt;/code>.&lt;/p>
&lt;p>Each of these generalized macros make sense on their own. But if we try to use them together, they just don&amp;rsquo;t work:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">((at dict key) := 1)
&lt;/code>&lt;/pre>&lt;p>Because &lt;code>(at dict key)&lt;/code> expands first. It looks at the forms to its left, sees that there aren&amp;rsquo;t any, so after one step of expansion we have:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">((lookup dict key) := 1)
&lt;/code>&lt;/pre>&lt;p>Then we expand &lt;code>:=&lt;/code>, and finish with:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">(set (lookup dict key) 1)
&lt;/code>&lt;/pre>&lt;p>Which of course was not what we wanted.&lt;/p>
&lt;p>I think that using generalized macros safely requires really understanding the effect that they have on the syntax tree of your program. They&amp;rsquo;re more like &lt;code>-&amp;gt;&amp;gt;&lt;/code> and friends &amp;ndash; explicit syntax re-arrangers &amp;ndash; than they are like other kinds of macros.&lt;/p>
&lt;h1 id="problem-three-you-have-created-in-your-code-a-work-of-madness-that-no-other-human-being-can-possibly-hope-to-understand">Problem three: you have created in your code a work of madness that no other human being can possibly hope to understand&lt;/h1>
&lt;p>ugh not again&lt;/p>
&lt;h1 id="prior-art">Prior art&lt;/h1>
&lt;p>I feel like this approach is so simple that it must have been done before, but I can&amp;rsquo;t find any references to it. That said, I have no idea how to search for it effectively! So if you&amp;rsquo;ve seen this technique before, or if you&amp;rsquo;ve heard of it being used in the past, I&amp;rsquo;d love to hear about it.&lt;/p>
&lt;h1 id="proof-of-concept">Proof of concept&lt;/h1>
&lt;p>I implemented this macro system in Janet, in order to play around with it and test out my macro implementations.&lt;/p>
&lt;p>It was pretty easy to write! It really is a very modest generalization of a traditional macro system. I didn&amp;rsquo;t actually write a custom module loader that would let you &lt;em>use&lt;/em> this as your default macro system in Janet code, but I wrote (the equivalent of) &lt;code>macex&lt;/code>, and adding the custom module loader would be pretty easy if you wanted to use it &amp;ldquo;for real.&amp;rdquo;&lt;/p>
&lt;p>You can look at the code here: &lt;a href="https://github.com/ianthehenry/macaroni">https://github.com/ianthehenry/macaroni&lt;/a>&lt;/p>
&lt;p>Or &lt;a href="https://github.com/ianthehenry/macaroni/tree/master/test">take a peek at some of the tests&lt;/a>, to see the examples in this post in action, as well as some weirder things that &lt;a href="https://github.com/ianthehenry/macaroni/blob/master/test/grandparent.janet">didn&amp;rsquo;t make the cut&lt;/a>.&lt;/p>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>Of course we could teach &lt;code>do&lt;/code> to scan through all of its arguments and look for &lt;code>defer&lt;/code>s, and implement this that way, but then this would only work in &lt;code>do&lt;/code> expressions. What if we want to do this inside a &lt;code>(fn [] ...)&lt;/code> block? Or an &lt;code>if&lt;/code>? Or a &lt;code>while&lt;/code>? By making &lt;code>defer&lt;/code> itself do the transformation, we can use it anywhere &amp;ndash; and make it easy to add new macros that behave like &lt;code>defer&lt;/code>, without having to teach &lt;code>do&lt;/code> about them.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2" role="doc-endnote">
&lt;p>Back in the Old Days, JavaScript only had function-scoped variables, so the &lt;em>only&lt;/em> way to create new bindings in JavaScript code was to create and invoke an anonymous function like this. The pattern was so common that we called them &amp;ldquo;immediately invoked function expressions,&amp;rdquo; and it was a real thing that you had to do in order to, for example, close over distinct values in a loop.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description><pubDate>Tue, 18 Apr 2023 00:00:00 +0000</pubDate><link>https://ianthehenry.com/posts/generalized-macros/</link><guid isPermaLink="true">https://ianthehenry.com/posts/generalized-macros/</guid></item><item><title>Why Janet?</title><description>&lt;p>I never thought it could happen to me. I mean, parentheses? In this day and age? But for the past couple years, my go-to programming language for fun side projects has been a little Lisp dialect called &lt;a href="https://janet-lang.org/">Janet&lt;/a>.&lt;/p>
&lt;pre tabindex="0">&lt;code>(print &amp;quot;hey janet&amp;quot;)
&lt;/code>&lt;/pre>&lt;p>I like Janet so much that &lt;a href="https://janet.guide/">I wrote an entire book about it&lt;/a>, and put it on The Internet for free, in the hopes of attracting more Janetors to the language.&lt;/p>
&lt;p>I think you should read it, but I know that you don&amp;rsquo;t believe me, so I&amp;rsquo;m going to try to convince you. Here&amp;rsquo;s my attempt at a sales pitch: here is why you &amp;ndash; &lt;em>you of all people&lt;/em> &amp;ndash; should give Janet a chance.&lt;/p>
&lt;h1 id="janet-is-simple">Janet is simple&lt;/h1>
&lt;p>Janet is an imperative language with first-class functions, a single namespace for identifiers, and lexical block scoping. The core of the language is very small, consisting of only eight instructions: &lt;code>do&lt;/code>, &lt;code>def&lt;/code>, &lt;code>var&lt;/code>, &lt;code>set&lt;/code>, &lt;code>if&lt;/code>, &lt;code>while&lt;/code>, &lt;code>break&lt;/code>, &lt;code>fn&lt;/code>. But thanks to macros, there are lots of high-level wrappers that give you more powerful or convenient control flow.&lt;/p>
&lt;aside>
&lt;p>There are actually five more instructions that exist to support macros: &lt;code>quote&lt;/code>, &lt;code>unquote&lt;/code>, &lt;code>quasiquote&lt;/code>, &lt;code>splice&lt;/code>, and &lt;code>upscope&lt;/code>. But you don&amp;rsquo;t have to write those in &amp;ldquo;regular&amp;rdquo; code.&lt;/p>
&lt;/aside>
&lt;p>You can &amp;ldquo;learn&amp;rdquo; Janet in an afternoon, because the runtime semantics are extremely familiar: think JavaScript, plus value types, minus all the wats. And the rest of the language is small: the entire standard library &lt;a href="https://janet-lang.org/api/index.html">fits on one page&lt;/a>. It was this ease of getting started that got me hooked in the first place.&lt;/p>
&lt;h1 id="janet-is-distributable">Janet is distributable&lt;/h1>
&lt;p>It&amp;rsquo;s easy to compile Janet programs into native executables that statically link the Janet runtime. And you can share those programs with other people, without asking them to install Janet first &amp;ndash; or your project&amp;rsquo;s dependencies, or anything else for that matter. You don&amp;rsquo;t even have to tell them it&amp;rsquo;s written in Janet!&lt;/p>
&lt;p>The way that Janet pulls this off is very elegant: Janet compiles itself to bytecode, and then writes that bytecode into a &lt;code>.c&lt;/code> file that also starts up the Janet runtime. Then it compiles that C file with your system&amp;rsquo;s C compiler. Since Janet is designed to be easy to embed, this makes perfect sense: it is, essentially, embedding itself into a trivial C executable.&lt;/p>
&lt;p>A simple Janet &amp;ldquo;hello world&amp;rdquo; compiled to a native binary weighs under a megabyte (784K for Janet 1.27.0 on aarch64 macOS, but your mileage may vary). This includes the full Janet runtime, garbage collector, and even the bytecode compiler &amp;ndash; so you can write programs that evaluate Janet code at runtime, &lt;a href="https://bauble.studio/">if you want to&lt;/a>.&lt;/p>
&lt;p>This makes Janet an excellent choice for writing little command-line apps. Which is especially true when you consider that&amp;hellip;&lt;/p>
&lt;h1 id="janet-is-unrealistically-good-at-parsing-text">Janet is unrealistically good at parsing text&lt;/h1>
&lt;p>Instead of regular expressions, Janet&amp;rsquo;s text wrangling is based around &lt;em>parsing expression grammars&lt;/em>. Parsing expression grammars are simpler, more powerful, and more predictable than regular expressions. They aren&amp;rsquo;t line-oriented, so they can parse multi-line text without a problem. They can also parse HTML, or JSON, or any other non-regular language. They can also parse &lt;em>binary&lt;/em> file formats &amp;ndash; they have no problems with arbitrary null bytes.&lt;/p>
&lt;p>They really are &lt;em>parsers&lt;/em>: structured, composable, first-class parsers. &lt;a href="https://janet.guide/pegular-expressions/">And they&amp;rsquo;re pretty easy to learn!&lt;/a>&lt;/p>
&lt;h1 id="janet-has-the-best-subprocess-dsl-of-any-high-level-language">Janet has the best subprocess DSL of any high-level language&lt;/h1>
&lt;p>There is a &lt;a href="https://github.com/andrewchambers/janet-sh">third-party library called &lt;code>sh&lt;/code>&lt;/a> that provides a shell scripting DSL that allows you to express pipes and redirects directly in Janet. Like this:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-janet" data-lang="janet">($ find . -name *.janet | say)
&lt;/code>&lt;/pre>&lt;p>It&amp;rsquo;s pretty incredible. It&amp;rsquo;s such a nice DSL that &lt;a href="https://janet.guide/scripting/">I dedicated a whole chapter of &lt;em>Janet for Mortals&lt;/em> to it&lt;/a> &amp;ndash; and the things that you can do with it. It elevates Janet from a reasonable alternative to Perl to a reasonable alternative to &lt;em>Bash&lt;/em> for a surprisingly large range of programs.&lt;/p>
&lt;h1 id="janet-is-embeddable">Janet is embeddable&lt;/h1>
&lt;p>Lua has become the de facto &amp;ldquo;embedded language,&amp;rdquo; which is a shame, because&amp;hellip; well, this isn&amp;rsquo;t a post about Lua. You might not care about this very much, but there&amp;rsquo;s a chance that it&amp;rsquo;s just because you haven&amp;rsquo;t tried it yet: being able to write &lt;a href="https://bauble.studio/">progams that expose scripting interfaces&lt;/a> is a pretty fun superpower.&lt;/p>
&lt;p>Embedding Janet is very easy: the Janet runtime is a small C library, and all you have to do is link it in and then call regular C functions to manipulate Janet values. You can even embed it into &lt;em>websites&lt;/em>, and write &lt;a href="https://toodle.studio/">static sites with custom programmable DSLs&lt;/a>!&lt;/p>
&lt;h1 id="janet-has-mutable-and-immutable-collections">Janet has mutable and immutable collections&lt;/h1>
&lt;p>Janet&amp;rsquo;s collection types come in mutable and immutable flavors. Immutable collections have value semantics: the immutable vector &lt;code>[1 2]&lt;/code> is indistinguishable from &lt;code>(take 2 [1 2 3])&lt;/code>, despite the fact that they have different memory addresses. Mutable collections, on the other hand, have reference semantics: the hash table &lt;code>@{:x 1 :y 2}&lt;/code> is only equal to itself. Another hash table with the same keys and values is a distinct object.&lt;/p>
&lt;p>Not every language has immutable composite values built right into the standard library!&lt;/p>
&lt;h1 id="macros-macros-macros">Macros, macros, macros&lt;/h1>
&lt;p>I think this is the real reason you should learn Janet, but I didn&amp;rsquo;t want to lead with it because I didn&amp;rsquo;t want to scare you off.&lt;/p>
&lt;p>You can write Janet just fine without ever learning how to write macros. But you should learn how, because writing macros is &lt;em>fun&lt;/em>. It feels different than any sort of programming that I&amp;rsquo;ve done before.&lt;/p>
&lt;p>Writing macros requires thinking twice at once: you&amp;rsquo;re writing code to write code, so you have to keep two threads of execution straight in your mind: the code that is running now, at compile time, manipulating values and abstract syntax trees, and the code that you are manipulating, the application code that you produce, the code that will run in the future.&lt;/p>
&lt;p>Janet&amp;rsquo;s macros are not hygienic, and Janet does not have a separate namespace for functions. But by allowing you to unquote literal functions, Janet makes it possible to write macros that are completely referentially transparent. It&amp;rsquo;s an incredibly simple and elegant solution to an &lt;a href="https://ianthehenry.com/posts/janet-game/the-problem-with-macros/">otherwise very delicate problem&lt;/a>. And the fact that it is possible to do this in Janet highlights my next favorite feature&amp;hellip;&lt;/p>
&lt;h1 id="janet-lets-you-pass-values-from-compile-time-to-run-time">Janet lets you pass values from compile-time to run-time&lt;/h1>
&lt;p>This is the most interesting thing about Janet, in my opinion. But it might not sound very interesting at first &amp;ndash; really all it means is that any Janet value can be serialized to disk and read back in later.&lt;/p>
&lt;p>But this serialization is implicit: when you compile a Janet program, it runs all of the top-level instructions &amp;ndash; regular statements, function declarations, whatever &amp;ndash; and then, once it&amp;rsquo;s executed all of the top-level values, Janet writes down a snapshot of your program&amp;rsquo;s state to disk.&lt;/p>
&lt;p>And it&amp;rsquo;s a &lt;em>full&lt;/em> snapshot of your program&amp;rsquo;s state: shared references are preserved, so mutable values can still be mutated after you &amp;ldquo;resume&amp;rdquo; the snapshot. Generators remember exactly what instruction they need to run the next time you resume them. Closures gonna close.&lt;/p>
&lt;p>Macros are a special-case of compile-time code execution &amp;ndash; manipulating abstract syntax trees to create new functions &amp;ndash; but this is a superpower that you can enjoy without any macros at all. Making a game? Reticulate your splines ahead of time! Or embed assets in your final binary by reading files at compile time &amp;ndash; you can perform arbitrary side effects!&lt;/p>
&lt;p>&lt;a href="https://janet.guide/macros-and-metaprogramming/">&lt;em>Janet for Mortals&lt;/em> has an example of using this to autogenerate database bindings based on a SQL schema file&lt;/a> &amp;ndash; a bit of a silly example, but something that would be quite difficult to do in most languages.&lt;/p>
&lt;h1 id="janet-feels-good-in-the-hand">Janet feels good in the hand&lt;/h1>
&lt;p>This is completely subjective, but I love Janet&amp;rsquo;s syntax. It strikes a perfect balance of simplicity, uniformity, and variety.&lt;/p>
&lt;p>It uses pervasive parentheses, but breaks them up with &lt;code>[]&lt;/code> for lists and &lt;code>{}&lt;/code> for tables.&lt;/p>
&lt;p>Mutable literals are always prefixed with &lt;code>@&lt;/code>: &lt;code>@&amp;quot;mutable string&amp;quot;&lt;/code>, &lt;code>{:immutable hash-table}&lt;/code>, etc.&lt;/p>
&lt;p>Anonymous functions are written &lt;code>(fn [x] (+ 1 x))&lt;/code>, but there&amp;rsquo;s a shorthand notation for lifting any expression into a function with &lt;code>|&lt;/code>: &lt;code>|(+ 1 $)&lt;/code>.&lt;/p>
&lt;p>Janet supports &amp;ldquo;splats&amp;rdquo; or &amp;ldquo;spreads&amp;rdquo; with &lt;code>;&lt;/code>: &lt;code>(+ ;args)&lt;/code>.&lt;/p>
&lt;p>String literals can be written with any number of backticks, and closed with the same number of backticks. Escape sequences like &lt;code>\n&lt;/code> don&amp;rsquo;t apply in backtick-quoted strings, so you can create strings with any contents without ever thinking about how to escape them &amp;ndash; all you have to do is wrap them in a sufficient number of backticks.&lt;/p>
&lt;p>Rest parameters use &lt;code>&amp;amp;&lt;/code> instead of &lt;code>.&lt;/code>: &lt;code>(defn foo [first &amp;amp; rest] ...)&lt;/code>.&lt;/p>
&lt;p>Janet doesn&amp;rsquo;t support reader macros, so the syntax itself is fixed. If you know how to read Janet, you can read all Janet programs. Which is not to say you can make sense of them&amp;hellip;&lt;/p>
&lt;h1 id="janet-prefers-comfort-to-tradition">Janet prefers comfort to tradition&lt;/h1>
&lt;p>Janet does not adhere to the ancient customs. &lt;code>CAR&lt;/code> is called &lt;code>first&lt;/code>. &lt;code>PROGN&lt;/code> is called &lt;code>do&lt;/code>. &lt;code>LAMBDA&lt;/code> is &lt;code>fn&lt;/code>, and &lt;code>SETQ&lt;/code> is &lt;code>def&lt;/code>. &lt;code>nil&lt;/code> is not the empty list; it is its own type, and there are first-class Booleans in the language. It eschews &lt;code>EQ&lt;/code>, &lt;code>EQL&lt;/code>, &lt;code>EQUAL&lt;/code>, and &lt;code>EQUALP&lt;/code>. There is nary a linked list in sight.&lt;/p>
&lt;p>This isn&amp;rsquo;t really &lt;em>good&lt;/em> or &lt;em>bad&lt;/em>, but I thought it was worth calling out: if you saw the parentheses and assumed &lt;code>FORMAT&lt;/code> was not far behind, maybe give Janet a second look.&lt;/p></description><pubDate>Wed, 12 Apr 2023 00:00:00 +0000</pubDate><link>https://ianthehenry.com/posts/why-janet/</link><guid isPermaLink="true">https://ianthehenry.com/posts/why-janet/</guid></item><item><title>Janet for Mortals</title><description>&lt;p>I wrote a book.&lt;/p>
&lt;p>It&amp;rsquo;s called &lt;a href="https://janet.guide/">&lt;em>Janet for Mortals&lt;/em>&lt;/a>, and it&amp;rsquo;s free, and it&amp;rsquo;s on the internet, and you can read it right now.&lt;/p>
&lt;p>And you &lt;em>should&lt;/em> read it right now, instead of reading this blog post, because this blog post is not very interesting if you haven&amp;rsquo;t read the book. Heck, this blog post is not very interesting even if you &lt;em>have&lt;/em> read the book. This blog post is a thinly-veiled promotion for my book to slip into my newsletter and RSS feed, with just enough additional content to pad it out to the length of a real post.&lt;/p>
&lt;p>The book is about &lt;a href="https://janet-lang.org/">Janet&lt;/a>, a programming language that &lt;a href="https://ianthehenry.com/posts/janet-game/">I have written about before&lt;/a>. I&amp;rsquo;ve been using Janet a lot lately, and I&amp;rsquo;ve been having a lot of fun with it, and I think that more people should know about it so that they can have fun with it too. People like you.&lt;/p>
&lt;p>I&amp;rsquo;m not really going to talk much about &lt;em>why&lt;/em> you should read the book, or even why you should care about Janet in the first place &amp;ndash; that will come in a later post. Instead, this is going to be a short retrospective of what it was like to write my first technical book.&lt;/p>
&lt;hr>
&lt;p>We&amp;rsquo;ll start with some numbers.&lt;/p>
&lt;p>It took me twenty weeks to write the book, working in my spare time. I had originally estimated twelve weeks, which turned out to be a really good guess for how long I spent &lt;em>writing&lt;/em> the book, but I didn&amp;rsquo;t account for how much time I would spend working on book-adjacent coding side quests.&lt;/p>
&lt;p>The final book is pretty short: 44k words of English prose, if you don&amp;rsquo;t count any of the code snippets. I tried to find an example of a famous book with a similar word count to put that number in perspective, and the best I can do is &lt;em>The Great Gatsby&lt;/em>, which clocks in at 47k words. It&amp;rsquo;s right on the border between novella- and novel-length, but it&amp;rsquo;s less than half as long as &lt;a href="https://ianthehenry.com/posts/how-to-learn-nix/">my series of posts about Nix&lt;/a>, which is sort of terrifying.&lt;/p>
&lt;p>But writing English words was only a fraction of the work. Over the course of those five months, I also spent a lot of time on:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://janet.guide/">the website itself&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/ianthehenry/jimmy">&lt;code>jimmy&lt;/code>&lt;/a>, bindings to a C++ library of persistent data structures&lt;/li>
&lt;li>&lt;a href="https://toodle.studio">Toodle.Studio&lt;/a>, an interactive turtle graphics playground&lt;/li>
&lt;li>&lt;a href="https://github.com/ianthehenry/cmd">&lt;code>cmd&lt;/code>&lt;/a>, a command-line argument parsing library&lt;/li>
&lt;li>&lt;a href="https://github.com/ianthehenry/judge">&lt;code>judge&lt;/code>&lt;/a>, an inline snapshot testing framework&lt;/li>
&lt;li>&lt;a href="https://github.com/ianthehenry/to-do">&lt;code>to do&lt;/code>&lt;/a>, a command-line todo list manager&lt;/li>
&lt;/ul>
&lt;p>These things are not very interesting by themselves, but this blog post just exists to promote the book, so I&amp;rsquo;m going to reflect on them now. You are welcome to stop reading at any point and &lt;a href="https://janet.guide/">go read the actual book instead&lt;/a>.&lt;/p>
&lt;h2 id="the-website-2-weeks">The Website (2 weeks)&lt;/h2>
&lt;p>The most interesting thing about &lt;em>Janet for Mortals&lt;/em> is that it has a built-in repl. At any point you can press escape and pull it up, and it&amp;rsquo;s docked to the bottom of the page, just out of the way of the text. I&amp;rsquo;m sure that it&amp;rsquo;s not the first programming book to include a repl like this, but I&amp;rsquo;ve never actually seen it done before.&lt;/p>
&lt;p>The editor portion of the repl is &lt;a href="https://codemirror.net/">CodeMirror&lt;/a>, which I had used previously in &lt;a href="bauble.studio/">Bauble&lt;/a>. CodeMirror doesn&amp;rsquo;t know anything about Janet out of the box, but I had already implemented some basic &lt;a href="github.com/ianthehenry/codemirror-lang-janet">language support&lt;/a> when I wrote Bauble.&lt;/p>
&lt;p>But I skipped a lot of the Janet language when I was writing the grammar for Bauble, like &lt;code>``multi`backtick`quoted``&lt;/code> strings, because they didn&amp;rsquo;t really matter in Bauble&amp;rsquo;s constrained DSL. But they mattered for the book, so I had to spend time figuring out how to implement them.&lt;/p>
&lt;p>But fleshing out the CodeMirror grammar (or more precisely, the &lt;a href="https://lezer.codemirror.net/docs/guide/">Lezer&lt;/a> grammar that CodeMirror uses) had an unexpected side effect: it let me re-use the grammar to do syntax highlighting for the code snippets in the book itself.&lt;/p>
&lt;p>&lt;em>Janet for Mortals&lt;/em> is just a static site, but no static site generators that I know of know how to highlight Janet code. For a long time the book was entirely black-and-white, which I don&amp;rsquo;t mind, but I knew I needed to add some color before I released it upon an unsuspecting public &amp;ndash; I think Janet&amp;rsquo;s syntax is pretty unfamiliar to most people reading the book, and anything to make it look friendlier helps.&lt;/p>
&lt;p>So I knew that I&amp;rsquo;d have to roll my own syntax highlighter of some kind, and I ended up just writing a simple static site generator in &lt;a href="https://github.com/apenwarr/redo">&lt;code>redo&lt;/code>&lt;/a>, which was not a very good fit, but&amp;hellip; well, least bad choice that I know of. The meat of the generator is written in JavaScript, so that I could plug in the Lezer grammar for free, but it&amp;rsquo;s all tied together by a fragile web of shell.&lt;/p>
&lt;p>I used &lt;a href="https://github.com/remarkjs/remark">Remark&lt;/a> to implement the parsing of the book&amp;rsquo;s source, and it was nice how much control I had over the generated output. I even added a simple extension to label code blocks, which was pretty easy.&lt;/p>
&lt;p>I also re-used Remark in the client itself, as part of the repl. The docstrings in the Janet standard library are written in Markdown, and I&amp;rsquo;m actually parsing and rendering them to HTML on the fly as part of the repl autocomplete.&lt;/p>
&lt;p>I spent a long time getting the repl autocomplete working well &amp;ndash; because this book was written for newcomers to the Janet language, I thought that the help-as-you-type would be really useful for people trying to follow along in the repl.&lt;/p>
&lt;p>Autocomplete works by dynamically querying the Janet environment via WebAssembly at repl startup. This means if you define a new symbol with a docstring, it won&amp;rsquo;t actually appear in the autocomplete output, but I think that&amp;rsquo;s&amp;hellip; fine. I could re-generate the autocompletions after every command is run, but&amp;hellip; I don&amp;rsquo;t think there&amp;rsquo;s much value in that.&lt;/p>
&lt;p>The most interesting part of the repl is probably the &lt;code>(report)&lt;/code> function, which takes a string and POSTs it to a simple web server that sticks it into a SQLite database for me to peruse later. It&amp;rsquo;s not really any different than a comment box, but I feel like there&amp;rsquo;s something fun about doing it from the repl. I&amp;rsquo;m really glad that I added it &amp;ndash; it&amp;rsquo;s been fun reading people&amp;rsquo;s feedback, and I&amp;rsquo;ve fixed quite a few errors because of it. I&amp;rsquo;m sad that I didn&amp;rsquo;t implement any way to respond, though!&lt;/p>
&lt;p>The backend for reports is &lt;em>not&lt;/em> written in Janet; it&amp;rsquo;s a tiny Haskell application that just listens for POST requests and sticks them into a SQLite database.&lt;/p>
&lt;p>There are people using Janet to make websites, but I am not one of them: the primary thing I want out of a web server is security, and I just don&amp;rsquo;t think Janet or its HTTP libraries are &amp;ldquo;battle-tested&amp;rdquo; enough for me to connect them to the internet.&lt;/p>
&lt;p>I also just think the idea of using a dynamically-typed interpreted language to build a web service is crazy, when there are optimizing compilers &lt;em>right there&lt;/em>, but that&amp;rsquo;s a whole other conversation.&lt;/p>
&lt;h2 id="jimmy-1-week">&lt;code>jimmy&lt;/code> (1 week)&lt;/h2>
&lt;p>I spent a little bit of time writing bindings to &lt;a href="https://github.com/arximboldi/immer">immer&lt;/a>, a library of persistent data structures. I never finished them, and probably won&amp;rsquo;t, at least not until I have a use for them. But as a demonstration of how to interop with C++ code from Janet, I think it was successful.&lt;/p>
&lt;h2 id="httpstoodlestudio-2-weeks">&lt;a href="https://toodle.studio">https://toodle.studio&lt;/a> (2 weeks)&lt;/h2>
&lt;p>Last year I wrote a little art playground called &lt;a href="https://bauble.studio/">Bauble&lt;/a>. It was my first time embedding Janet in the browser, and I had a pretty tough time figuring out how to do that.&lt;/p>
&lt;p>There weren&amp;rsquo;t a lot of resources back then about embedding Janet &lt;em>period&lt;/em>, and doing it in the browser added an extra layer of difficulty. I&amp;rsquo;d never used WebAssembly or Emscripten before, or even TypeScript, and it turns out there are no tutorials on how to write TypeScript Emscripten WebAssembly Janet bindings, so I spent a while figuring out how all the pieces worked together.&lt;/p>
&lt;p>And I&amp;rsquo;m glad I did, because I think the final product is really neat: it&amp;rsquo;s a website that is &lt;em>not written in JavaScript&lt;/em>. I mean, a lot of it is. The UI is, still. But the actual application logic is all Janet.&lt;/p>
&lt;p>I thought that that was a really useful superpower of Janet, and I wanted to make the technique more accessible. In fact this was a big motivation for writing this book about Janet &amp;ndash; I wanted people to know that this was &lt;em>possible&lt;/em> in the first place, and I wanted to make it easier to get started with it.&lt;/p>
&lt;p>But &lt;em>Janet for Mortals&lt;/em> doesn&amp;rsquo;t talk about Bauble at all. Bauble is actually not very interesting from an interop perspective: Bauble is completely stateless, and basically uses Janet to implement a pure function from strings to strings (they&amp;rsquo;re&amp;hellip; pretty complicated strings; Bauble is a Janet-to-GLSL compiler, but they are strings nonetheless). I didn&amp;rsquo;t think it was a very good showcase for everything you can do with Janet, so I briefly considered talking about how I implemented the repl in the book, but I decided that that was far too boring. So I wrote &lt;a href="https://toodle.studio/">Toodle.Studio&lt;/a> &amp;ndash; an obvious fork of Bauble &amp;ndash; instead.&lt;/p>
&lt;p>Toodle.Studio &lt;em>seems&lt;/em> a lot simpler than Bauble, but the interop with JavaScript is much more involved. Toodle.Studio has to execute long-running Janet programs asynchronously over time. It has to think about memory management, as the JavaScript code retains multiple references to the same Janet values. It has to pass complex nested data structures to and from Janet, going through C++ as an intermediary. It does a very simple version of all of these things, but it&amp;rsquo;s a pretty good showcase for the techniques.&lt;/p>
&lt;p>But the most interesting part of Toodle.Studio isn&amp;rsquo;t the interop or the memory management. The most interesting part of Toodle.Studio is the logo.&lt;/p>
&lt;p>I wasn&amp;rsquo;t really planning on making a logo &amp;ndash; this is a demo project for a book, after all &amp;ndash; but sadly I had no choice. When I was getting ready to release the website, I showed it to my partner, because it&amp;rsquo;s rare that I work on something comprehensible to normal human beings. I thought she&amp;rsquo;d like it, but she was &lt;em>aghast&lt;/em>.&lt;/p>
&lt;p>&amp;ldquo;You said you were working on turtle graphics,&amp;rdquo; she said. &amp;ldquo;Where are the turtles?&amp;rdquo;&lt;/p>
&lt;p>I tried to explain that the turtles aren&amp;rsquo;t &lt;em>really&lt;/em> turtles, that it&amp;rsquo;s like a flea circus, and the turtles are metaphors &amp;ndash; but she was having none of it. The lack of turtles was a base betrayal, so I had to spend a day or so &lt;a href="https://gist.github.com/ianthehenry/612c980f0db04ea3c2ccab2741475870">modeling a cute animated turtle in Bauble&lt;/a> to act as the logo. And making its eyes follow the mouse, of course.&lt;/p>
&lt;p>Relationship repaired. The logo wound up being my favorite part of the site, and it was fun to get a chance to use Bauble to make something &amp;ldquo;real.&amp;rdquo;&lt;/p>
&lt;h2 id="cmd-2-weeks">&lt;code>cmd&lt;/code> (2 weeks)&lt;/h2>
&lt;p>One of the things that I spent the most time on, oddly enough, was a command-line argument parsing library. The library itself only gets, like, three paragraphs of screen time in the book, but it was very important to me that it exist before the book came out, so that I could unambiguously claim that &amp;ldquo;Janet is an excellent scripting language.&amp;rdquo; Before &lt;code>cmd&lt;/code>, that was still true, but the phrasing was more &amp;ldquo;Janet is a great choice for scripting and writing CLI tools, except that the argument parsing is kind of janky, sorry, but hey at least it&amp;rsquo;s better than Bash.&amp;rdquo;&lt;/p>
&lt;p>&lt;code>cmd&lt;/code> was heavily inspired by &lt;a href="https://ocaml.org/p/core/latest/doc/Core/Command/Param/index.html">&lt;code>Core.Command&lt;/code>&lt;/a>, which is the best command-line argument parsing library that I have ever used. I&amp;rsquo;m extremely spoiled by how easy it makes writing CLIs, and I wanted to replicate that experience in Janet. &lt;code>cmd&lt;/code> is definitely not as good as &lt;code>Core.Command&lt;/code> &amp;ndash; types, my goodness, types make everything so much easier &amp;ndash; but it has 95% of the features I care about, and the concise notation makes it more pleasant to use in ad-hoc scripts.&lt;/p>
&lt;p>One thing that I miss, though, is that &lt;code>Core.Command&lt;/code> autogenerates Bash completion functions. I want to add that to &lt;code>cmd&lt;/code> one day &amp;ndash; the API is designed so that that will be &lt;em>possible&lt;/em> to do. But&amp;hellip; so many projects, so little time.&lt;/p>
&lt;h2 id="judge-1-week">&lt;code>judge&lt;/code> (1 week)&lt;/h2>
&lt;p>&lt;code>judge&lt;/code> was &lt;a href="https://ianthehenry.com/posts/janet-game/judging-janet/">one of the first things that I wrote in Janet&lt;/a>, all the way back in 2021. I think that it worked pretty well considering that I didn&amp;rsquo;t know anything about Janet when I wrote it, but now I do, so I rewrote it from scratch. Not only is the API much nicer to use now, but the implementation is way simpler &amp;ndash; and easier to make changes to.&lt;/p>
&lt;p>The main differences between Judge v1 and Judge v2 are that tests can now appear inside regular source files, not just the &lt;code>test/&lt;/code> directory, and I added the &lt;code>test-macro&lt;/code> and &lt;code>test-stdout&lt;/code> helpers, which are &lt;em>extremely&lt;/em> useful. &lt;a href="https://blog.janestreet.com/the-joy-of-expect-tests/">The OCaml equivalent of &lt;code>test-stdout&lt;/code>&lt;/a> is pretty much the only way that I write tests professionally, because OCaml doesn&amp;rsquo;t really have a way to embed arbitrary data in source code, so we turn everything into a string.&lt;/p>
&lt;p>After publishing the book &amp;ndash; which &lt;a href="https://janet.guide/testing-and-debugging/">has a whole chapter on testing with Judge&lt;/a> &amp;ndash; I had a chance to spend a little more time improving Judge, and I finally added an &lt;code>--interactive&lt;/code> mode, which I&amp;rsquo;ve been wanting for a long time. And since I&amp;rsquo;m not spending all my time working on this book anymore, I&amp;rsquo;ve actually had a few opportunities to &lt;em>use&lt;/em> the new Judge, and I gotta say: it&amp;rsquo;s nice. It&amp;rsquo;s really nice. I know I can&amp;rsquo;t impress upon you just how nice it is in this post &amp;ndash; it really needs a demo, and I&amp;rsquo;m too lazy to record one right now &amp;ndash; but I&amp;rsquo;m very happy with how it feels to use it to write Janet.&lt;/p>
&lt;h2 id="to-do-2-hours">&lt;code>to do&lt;/code> (2 hours)&lt;/h2>
&lt;p>I picked this project to highlight for &lt;a href="https://janet.guide/scripting/">the scripting chapter&lt;/a>, because it&amp;rsquo;s a non-trivial thing that I had done in Bash before, and actually found it pretty painful. Parsing multi-line text with Sed is not fun, and trying to do date manipulation with &lt;code>date&lt;/code> in a way that works the same on macOS and Linux is&amp;hellip; basically impossible, as far as I can tell. I quickly ran into the limits of my patience, and gave up on the idea some years ago.&lt;/p>
&lt;p>It was really fun to return to this with the full power of PEGs and &lt;a href="https://github.com/andrewchambers/janet-sh">&lt;code>sh&lt;/code>&lt;/a> and &lt;a href="https://github.com/ianthehenry/cmd">&lt;code>cmd&lt;/code>&lt;/a> at my disposal. I immediately surpassed all of features of my original Bash todo list, and was able to add quite a few more (like &lt;code>fzf&lt;/code> multi-select &amp;ndash; good luck constructing null-terminated strings in Bash).&lt;/p>
&lt;p>The book covers a very simplified version of the app &amp;ndash; it can&amp;rsquo;t schedule tasks for the future, and there&amp;rsquo;s no concept of &amp;ldquo;skipping&amp;rdquo; tasks. Those features are important for &lt;em>my&lt;/em> todo list workflow, but they are probably not important for &lt;em>your&lt;/em> todo list workflow, so the book only discusses the core functionality of adding things to a list and crossing them off. I think that it makes a good starting point to run with and make your own &amp;ndash; paired with &lt;a href="https://github.com/ianthehenry/zsh-autoquoter">&lt;code>zsh-autoquoter&lt;/code>&lt;/a>, it&amp;rsquo;s actually a surprisingly useful app!&lt;/p>
&lt;hr>
&lt;p>If you put all of these projects together, I was writing code for almost half the time that I spent working on the book. Eight out of twenty weeks, plus some periods where I was doing both at once.&lt;/p>
&lt;p>I didn&amp;rsquo;t really budget for that going into this. I thought that I&amp;rsquo;d improve Judge, and I thought that I&amp;rsquo;d write an argument parser. But I thought that writing an argument parser would be &lt;em>way easier&lt;/em> than it actually was. And I thought that I&amp;rsquo;d just talk about Bauble &amp;ndash; it never occurred to me that I&amp;rsquo;d write &lt;em>another&lt;/em> art playground just because Bauble was &lt;em>too easy&lt;/em>.&lt;/p>
&lt;p>So that&amp;rsquo;s the story of writing the book. Or really, everything &lt;em>but&lt;/em> writing the book. All the other things. The writing itself isn&amp;rsquo;t that interesting. I wrote it in Markdown, in Sublime Text, which is my favorite editor for writing long-form prose. I have nothing interesting to say about that part.&lt;/p>
&lt;p>Two new versions of Janet came out while I was writing the book, and I did have to go back and update the chapters on debugging and native modules to keep up with changes to the language. I plan on keeping the book up to date with the latest Janet release &amp;ndash; we&amp;rsquo;ll see how long I can keep that up.&lt;/p>
&lt;hr>
&lt;p>I haven&amp;rsquo;t done much to promote the book yet. I &lt;a href="https://news.ycombinator.com/item?id=35386405">submitted it to Hacker News&lt;/a>, and I &lt;a href="https://lobste.rs/s/duwkz7">submitted it to Lobsters&lt;/a>, and I wrote &lt;a href="https://twitter.com/ianthehenry/status/1641797578739306499">a very half-hearted tweet about it&lt;/a>. The reception was pretty much as good as I could have hoped for: it was on the Hacker News front page all day, and even held the number one spot for a while.&lt;/p>
&lt;p>What does that actually mean? Well, according my Nginx access logs, I got:&lt;/p>
&lt;ul>
&lt;li>30,025 unique visitors on Friday&lt;/li>
&lt;li>9,568 unique visitors on Saturday&lt;/li>
&lt;li>3,777 unique visitors on Sunday&lt;/li>
&lt;/ul>
&lt;p>Those numbers don&amp;rsquo;t mean very much, though. Those are just people who clicked on a link &amp;ndash; the number of people who actually &lt;em>read&lt;/em> the book is much, much smaller.&lt;/p>
&lt;p>I don&amp;rsquo;t actually know how much smaller exactly, because I don&amp;rsquo;t have any client-side behavior-tracking analytics on the site. But I can sort of try to guess, by looking at my access logs. It seems like retention is not great:&lt;/p>
&lt;ul>
&lt;li>Chapter One had 22% as many visitors as the home page.&lt;/li>
&lt;li>Chapter Two had 20% as many visitors as Chapter One.&lt;/li>
&lt;li>Chapter Three had 69% as many visitors as Chapter Two.&lt;/li>
&lt;li>Chapters Four and Nine, &amp;ldquo;&lt;a href="https://janet.guide/pegular-expressions/">Pegular Expressions&lt;/a>&amp;rdquo; and &amp;ldquo;&lt;a href="https://janet.guide/xenofunctions/">Xenofunctions&lt;/a>,&amp;rdquo; had more visitors than Chapter Three.&lt;/li>
&lt;/ul>
&lt;p>I&amp;rsquo;m guessing that last bit is because people clicked on those chapters to see what they were about, which just goes to show that unique visitor count is not a very dependable metric.&lt;/p>
&lt;p>My best attempt at answering the question &amp;ldquo;how many people are actually reading the book&amp;rdquo; is 387, as of the end of the launch weekend. So far 387 unique IP addresses have loaded five or more distinct chapters, which is probably a decent proxy for the metric I care about.&lt;/p>
&lt;p>I really had no expectations for what these numbers would be before I launched the book. It&amp;rsquo;s a big time commitment to read a weird book about a programming language you&amp;rsquo;ve barely heard of, and 387 seems simultaneously low (compared to, say, any blog post) and high (I don&amp;rsquo;t think &lt;em>I&amp;rsquo;ve&lt;/em> ever read a book off a HN link). But it&amp;rsquo;s more than zero!&lt;/p>
&lt;p>Alright, I think that&amp;rsquo;s enough. I&amp;rsquo;ll close with some fun facts:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>The Janet language is named after an immortal being in &lt;a href="https://en.wikipedia.org/wiki/The_Good_Place">&lt;em>The Good Place&lt;/em>&lt;/a> who helps mortals navigate the afterlife, hence the title.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The chapter with the fewest visits is currently &amp;ldquo;Testing and Debugging,&amp;rdquo; despite being the third-to-last chapter. This does not surprise me at all, but I think it&amp;rsquo;s a shame: the last three chapters are by far the most interesting in the book, and the style of testing described in that chapter is one of the biggest productivity upgrades that I have personally experienced in my engineering career.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>So far I&amp;rsquo;ve received 494 reports from the built-in repl reporting feature. Most of these were of the &amp;ldquo;hey nice book&amp;rdquo; or &amp;ldquo;testing&amp;rdquo; variety, but I&amp;rsquo;ve gotten several dozen typo reports, clarification requests, or otherwise useful comments through it as well.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The most interesting report was just &amp;ldquo;you should listen to this song: &lt;a href="https://www.youtube.com/watch?v=46i3LbIbbhI">https://www.youtube.com/watch?v=46i3LbIbbhI&lt;/a>.&amp;rdquo; No context, no explanation, and I have no way to reply for clarification. But&amp;hellip; thanks! It&amp;rsquo;s a good song. I&amp;rsquo;m into it.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>A few people asked me questions without including any kind of contact info, so I have no way to answer them. I hope that they found peace, wherever they are. I&amp;rsquo;m not ignoring you. I just&amp;hellip; I only implemented an extremely primitive one-way feedback function.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>I&amp;rsquo;m going to plug the book one last time.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>With feeling: &lt;em>&lt;a href="https://janet.guide/">Janet for Mortals&lt;/a>!&lt;/em> Out now! The first infinity visitors get their copy for free!&lt;/p></description><pubDate>Tue, 04 Apr 2023 00:00:00 +0000</pubDate><link>https://ianthehenry.com/posts/janet-for-mortals/</link><guid isPermaLink="true">https://ianthehenry.com/posts/janet-for-mortals/</guid></item><item><title>Visualizing Delaunay Triangulation</title><description>&lt;p>Take a look at this:&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="bad-triangulation" width="384" height="256">&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/preview.e48ae4ff4031e60ea3a2ff790d6225cee4f48afb22940bb559d5d0276c3e4eb0.png">&lt;picture>
&lt;img
class="pixel-art"
src="https://ianthehenry.com/posts/delaunay/preview.e48ae4ff4031e60ea3a2ff790d6225cee4f48afb22940bb559d5d0276c3e4eb0.png"
width="768"
height="512"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAa0lEQVR4nBTFwQ3CMAwFUP9vO2mlIg6wA/svxo1SxY6DOD0DQJqI/KW77xFn5oXWj5lj2&amp;#43;5UN&amp;#43;sZV1UCUFIfzxfVAX7Pd8Q5Z7jv6P1WNc26iJBWlaptjA9E4L63doBcVQKRtdaqXwAAAP//GaEpsQQ6Fh4AAAAASUVORK5CYII=);"
/>&lt;/a>
&lt;/canvas>&lt;/p>
&lt;p>This is a triangulation of a set of random points, such that all the points are connected to one another, all of the faces are triangles, and the edges include the convex hull of the points.&lt;/p>
&lt;p>I would like to claim that this is not a very &amp;ldquo;good&amp;rdquo; triangulation. This algorithm tends to produce lots of long, slivery triangles, and a really uneven distribution of edge counts across different vertices &amp;ndash; some vertices have &lt;em>way&lt;/em> more edges than they need to.&lt;/p>
&lt;p>Here&amp;rsquo;s a different triangulation. These are the exact same points, but I triangulated them smarter:&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="good-triangulation" width="384" height="256" class="interactive">&lt;/canvas>&lt;/p>
&lt;p>Isn&amp;rsquo;t that better? (Click to compare!)&lt;/p>
&lt;p>Depending on your dice rolls you might still have a few slivers, usually around the outer perimeter, but I&amp;rsquo;m willing to bet that it&amp;rsquo;s a noticeable improvement over the first attempt.&lt;/p>
&lt;p>This triangulation is called the &lt;em>Delaunay triangulation&lt;/em>, and I would like to claim that it is a &lt;em>very good&lt;/em> triangulation. It is, in fact, the &lt;em>best possible&lt;/em> triangulation, for some definitions of &amp;ldquo;best.&amp;rdquo;&lt;/p>
&lt;h1 id="why-should-i-a-busy-person-with-important-responsibilities-care-about-triangles">Why should I, a busy person with important responsibilities, care about triangles?&lt;/h1>
&lt;p>Okay, right, good question. You&amp;rsquo;ve made it this far in life without triangulating anything; why should you start now?&lt;/p>
&lt;p>There are some obvious applications of triangulation algorithms in computer graphics, where everything is made out of triangles, but you knew that already and you are still &lt;em>unmoved&lt;/em>.&lt;/p>
&lt;p>Let&amp;rsquo;s try something else.&lt;/p>
&lt;p>One textbook example of a non-graphics application of the Delaunay triangulation is &lt;em>interpolating spatial data&lt;/em>.&lt;/p>
&lt;p>Pretend, for a moment, that you&amp;rsquo;re a surveyor, and you&amp;rsquo;ve just finished measuring the elevation of a bunch of discrete points in some area. You sling your weird tripod thingy over your shoulder, and get ready to leave for the first vacation you&amp;rsquo;ve been able to take with your son since Terence left &amp;ndash; when your boss appears at your door.&lt;/p>
&lt;p>&amp;ldquo;What&amp;rsquo;s the elevation three clicks south of the north ridge, or however surveyors talk?&amp;rdquo; she asks.&lt;/p>
&lt;p>You glance down at the oversized roll of mapping paper (?) on your desk, but you already know what you&amp;rsquo;ll find: you didn&amp;rsquo;t take a measurement three clicks south of the north ridge. But you &lt;em>did&lt;/em> measure a few points nearby. &lt;em>Perhaps&lt;/em>, you think, &lt;em>I can construct a triangulation of these points, and then map it to a three-dimensional mesh, and use that to interpolate the elevations between nearby points.&lt;/em>&lt;/p>
&lt;p>You call your son to tell him that you&amp;rsquo;ll be a little late &amp;ndash; it&amp;rsquo;s 1998 in this situation; texting isn&amp;rsquo;t a thing yet &amp;ndash; and get to work.&lt;/p>
&lt;p>But after the montage ends, you look upon your work, and find&amp;hellip; oh.&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="bad-interpolation" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>Well that&amp;rsquo;s not going to work.&lt;/p>
&lt;p>You pick up the phone to make another difficult call, but as your finger hovers over the rotary dial, you suddenly remember an incredibly long blog post that you read once &amp;ndash; or that you will read; it&amp;rsquo;s 1998 still; this blog post hasn&amp;rsquo;t been published yet; it&amp;rsquo;s too late to change it now; just go with it &amp;ndash; and you put the receiver back on the cradle.&lt;/p>
&lt;p>And then you construct the &lt;em>Delaunay&lt;/em> triangulation of the exact same points:&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="good-interpolation" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>(Click to add sheep, obviously.)&lt;/p>
&lt;p>You shoot your boss a quick fax and get ready to hit the road. But as you stand up to leave, you see her standing in your doorway yet again.&lt;/p>
&lt;p>&amp;ldquo;I just got off the corded phone with President Clinton,&amp;rdquo; she says. &amp;ldquo;Your triangles are so good that he&amp;rsquo;s appointing you Surveyor General. When you get back from vacation, &lt;em>I&amp;rsquo;ll&lt;/em> be reporting to &lt;em>you&lt;/em>.&amp;rdquo;&lt;/p>
&lt;p>So that&amp;rsquo;s the first reason to learn about Delaunay triangulation.&lt;/p>
&lt;p>Here&amp;rsquo;s another one: did this idea of interpolating &amp;ldquo;nearby&amp;rdquo; points remind you of anything else? Perhaps&amp;hellip; the &lt;a href="https://en.wikipedia.org/wiki/Voronoi_diagram">Voronoi diagram&lt;/a>? If so, you are smart and attractive: one easy way to construct the Voronoi diagram is to calculate the Delaunay triangulation, because the Delaunay triangulation is the &lt;em>dual&lt;/em> of the Voronoi diagram.&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> Which is neat if you know what &amp;ldquo;dual&amp;rdquo; means. If you don&amp;rsquo;t know what dual means, keep reading! We&amp;rsquo;re going to talk about duals a lot later on.&lt;/p>
&lt;p>So those are like&amp;hellip; some &lt;em>good&lt;/em> reasons to learn about Delaunay triangulations.&lt;/p>
&lt;p>But I did not learn about Delaunay triangulations for a good reason.&lt;/p>
&lt;p>I learned about Delaunay triangulations for the dumbest reason you can possibly imagine.&lt;/p>
&lt;p>Here, it&amp;rsquo;s easier if I just show you.&lt;/p>
&lt;p class="button-container">&lt;input class="smashable" type="button" value="what if you pressed the button" />&lt;/p>
&lt;p>I heard the phrase &amp;ldquo;smash that subscribe button&amp;rdquo; once, and after a brief moment&amp;rsquo;s existential crisis as I realized that I am now old enough that I recoil from the parlance of youth, I thought it would be funny to make a subscribe button that you could smash.&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/p>
&lt;p>In case you already smashed that one, here&amp;rsquo;s another:&lt;/p>
&lt;p class="button-container">&lt;input class="smashable" type="button" value="randomly generated quip" data-regrow=true data-randomize=true />&lt;/p>
&lt;p>It&amp;rsquo;s kinda fun, right?&lt;/p>
&lt;p>I&amp;rsquo;ve been meaning to add an email subscription button to my website for a while, and the thought of implementing this visual gag was enough of an incentive for me to actually get around to it.&lt;/p>
&lt;p>I thought that a decent way to randomly fracture a shape into polygons would be to compute the &lt;a href="https://en.wikipedia.org/wiki/Gabriel_graph">Gabriel graph&lt;/a> of a set of random points inside the button.&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup> And the easiest way to compute the Gabriel graph is to compute the Delaunay triangulation and then remove a few edges from it. So that&amp;rsquo;s why I started looking into this in the first place.&lt;/p>
&lt;p class="button-container">&lt;input class="smashable" type="button" value="Let's see that in slow motion" data-regrow=true data-rate=0.2 />&lt;/p>
&lt;p>But it was pretty hard! Although it&amp;rsquo;s easy to find explanations of Delaunay triangulation algorithms in vague, mathematical terms, I couldn&amp;rsquo;t find very many resources describing how to actually &lt;em>implement&lt;/em> them. Like, I&amp;rsquo;m &lt;em>trying&lt;/em> to construct an infinitely large triangle, but my computer keeps catching on fire &amp;ndash; what do I do now, Professor Triangle?&lt;/p>
&lt;p>So this is going to be an explanation for the &amp;ldquo;working programmer.&amp;rdquo; I&amp;rsquo;m going to describe a deceptively simple algorithm for Delaunay triangulation, and then the actual &lt;em>edge&lt;/em>-cases that you have to think about when you try to implement it. And I&amp;rsquo;m going to do it without ever using the word &amp;ldquo;manifold.&amp;rdquo;&lt;/p>
&lt;p>Starting&amp;hellip; now.&lt;/p>
&lt;h1 id="what-is-a-delaunay-triangulation">What is a Delaunay triangulation?&lt;/h1>
&lt;p>The Delaunay triangulation is the triangulation that &amp;ldquo;maximizes the minimum angle in all of the triangles.&amp;rdquo;&lt;/p>
&lt;p>Here&amp;rsquo;s, it&amp;rsquo;s easier if we look at an example:&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/fig1_hu39647200ef58c71feda4cd0ee9e9b4ef_120237_1536x1536_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/fig1_hu39647200ef58c71feda4cd0ee9e9b4ef_120237_768x768_fit_q75_h3_box_3.webp 768w, https://ianthehenry.com/posts/delaunay/fig1_hu39647200ef58c71feda4cd0ee9e9b4ef_120237_1536x1536_fit_q75_h3_box_3.webp 1536w, https://ianthehenry.com/posts/delaunay/fig1_hu39647200ef58c71feda4cd0ee9e9b4ef_120237_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/fig1_hu39647200ef58c71feda4cd0ee9e9b4ef_120237_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 768px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/fig1_hu39647200ef58c71feda4cd0ee9e9b4ef_120237_768x768_fit_box_3.png 768w, https://ianthehenry.com/posts/delaunay/fig1_hu39647200ef58c71feda4cd0ee9e9b4ef_120237_1536x1536_fit_box_3.png 1536w, https://ianthehenry.com/posts/delaunay/fig1_hu39647200ef58c71feda4cd0ee9e9b4ef_120237_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/fig1_hu39647200ef58c71feda4cd0ee9e9b4ef_120237_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 768px"
width="768"
height="233"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAACCAIAAADq9gq6AAAANElEQVR4nBzAixGAMAwC0ATws/&amp;#43;22iDe9Qmb0F39zJz34fUlgYCLrOrXJullCEn&amp;#43;AAAA//9kCxIgBgPQ1gAAAABJRU5ErkJggg==);"
/>&lt;/picture>&lt;/a>
&lt;p>Obviously all the angles of a triangle add up to 180°, but the triangles on the left get there with two very acute angles and one obtuse angle, while the triangles on the right have only acute angles &amp;ndash; but they are not &lt;em>as&lt;/em> acute as the acute angles on the left.&lt;/p>
&lt;p>These are the only two triangulations of these four points, so the one on the right is the &lt;em>optimal&lt;/em> triangulation according to our criteria &amp;ndash; in other words, it is the &lt;em>Delaunay triangulation&lt;/em> of this point set.&lt;/p>
&lt;p>Here&amp;rsquo;s an equivalent, but more useful definition: the Delaunay triangulation is the triangulation such that, if you circumscribe a circle around every triangle, none of those circles will contain any other points.&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="circle-proof" width="384" height="256" class="interactive">&lt;/canvas>&lt;/p>
&lt;p>(Click or tap to pause that, if the flashing is distracting.)&lt;/p>
&lt;p>I&amp;rsquo;m going to make a potentially surprising claim: this is the &lt;em>only&lt;/em> such triangulation of these points that satisfies that property. If you were to change any single edge &amp;ndash; rearrange any of these triangles in &lt;em>any&lt;/em> way &amp;ndash; you would have at least one point inside another triangle&amp;rsquo;s circle territory.&lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup>&lt;/p>
&lt;p>It&amp;rsquo;s worth noting that this is a &lt;em>global&lt;/em> property of the triangulation &amp;ndash; it&amp;rsquo;s a statement about every triangle and every point at the same time. Global properties are expensive to check and expensive to change, so we&amp;rsquo;d much rather focus on a &lt;em>local&lt;/em> property during our algorithm.&lt;/p>
&lt;p>So let&amp;rsquo;s consider a single edge on the triangulation. Specifically an interior edge, not an edge around the perimeter.&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/fig2_hudc9ece0c9ae65c64fec5e1fa0ddcd5cc_164727_1536x1536_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/fig2_hudc9ece0c9ae65c64fec5e1fa0ddcd5cc_164727_768x768_fit_q75_h3_box_3.webp 768w, https://ianthehenry.com/posts/delaunay/fig2_hudc9ece0c9ae65c64fec5e1fa0ddcd5cc_164727_1536x1536_fit_q75_h3_box_3.webp 1536w, https://ianthehenry.com/posts/delaunay/fig2_hudc9ece0c9ae65c64fec5e1fa0ddcd5cc_164727_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/fig2_hudc9ece0c9ae65c64fec5e1fa0ddcd5cc_164727_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 768px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/fig2_hudc9ece0c9ae65c64fec5e1fa0ddcd5cc_164727_768x768_fit_box_3.png 768w, https://ianthehenry.com/posts/delaunay/fig2_hudc9ece0c9ae65c64fec5e1fa0ddcd5cc_164727_1536x1536_fit_box_3.png 1536w, https://ianthehenry.com/posts/delaunay/fig2_hudc9ece0c9ae65c64fec5e1fa0ddcd5cc_164727_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/fig2_hudc9ece0c9ae65c64fec5e1fa0ddcd5cc_164727_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 768px"
width="768"
height="384"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r&amp;#43;mnAAAAS0lEQVR4nBTJyxHCMBADUH02mEn/5WothvMbAJLOOfdeAEkktaVtSSTRBbELACQlyfbuej4t3/c7M0n4PE/bP9gk2yaZmV8AAAD//5/9IVuFCfg9AAAAAElFTkSuQmCC);"
/>&lt;/picture>&lt;/a>
&lt;p>There are two triangles on either side of this edge. Pick one at random, and draw a circle around it.&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/fig3_hu9dc11d71013b93ca08e26f3f42246ce6_230970_1536x1536_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/fig3_hu9dc11d71013b93ca08e26f3f42246ce6_230970_768x768_fit_q75_h3_box_3.webp 768w, https://ianthehenry.com/posts/delaunay/fig3_hu9dc11d71013b93ca08e26f3f42246ce6_230970_1536x1536_fit_q75_h3_box_3.webp 1536w, https://ianthehenry.com/posts/delaunay/fig3_hu9dc11d71013b93ca08e26f3f42246ce6_230970_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/fig3_hu9dc11d71013b93ca08e26f3f42246ce6_230970_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 768px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/fig3_hu9dc11d71013b93ca08e26f3f42246ce6_230970_768x768_fit_box_3.png 768w, https://ianthehenry.com/posts/delaunay/fig3_hu9dc11d71013b93ca08e26f3f42246ce6_230970_1536x1536_fit_box_3.png 1536w, https://ianthehenry.com/posts/delaunay/fig3_hu9dc11d71013b93ca08e26f3f42246ce6_230970_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/fig3_hu9dc11d71013b93ca08e26f3f42246ce6_230970_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 768px"
width="768"
height="384"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r&amp;#43;mnAAAAVklEQVR4nBTLURLCIAwE0GR3a9XR6f0vCkVI4vD&amp;#43;n8wMwHW9MzKyev8BqCon&amp;#43;f2c91gZAfpxqLXp7tiIOYN6RNjreUpaa5Gk6PeYu7tbZetD0j8AAP//vv8oLnYLmZEAAAAASUVORK5CYII=);"
/>&lt;/picture>&lt;/a>
&lt;p>Now check if the tip of the other triangle adjacent to this edge lies inside or outside of this circle. If it lies outside the circle, then this &lt;em>edge&lt;/em> is &amp;ldquo;locally Delaunay.&amp;rdquo; If it lies inside the circle, then the edge is not &amp;ldquo;locally Delaunay.&amp;rdquo;&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/fig4_hu9dc11d71013b93ca08e26f3f42246ce6_238759_1536x1536_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/fig4_hu9dc11d71013b93ca08e26f3f42246ce6_238759_768x768_fit_q75_h3_box_3.webp 768w, https://ianthehenry.com/posts/delaunay/fig4_hu9dc11d71013b93ca08e26f3f42246ce6_238759_1536x1536_fit_q75_h3_box_3.webp 1536w, https://ianthehenry.com/posts/delaunay/fig4_hu9dc11d71013b93ca08e26f3f42246ce6_238759_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/fig4_hu9dc11d71013b93ca08e26f3f42246ce6_238759_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 768px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/fig4_hu9dc11d71013b93ca08e26f3f42246ce6_238759_768x768_fit_box_3.png 768w, https://ianthehenry.com/posts/delaunay/fig4_hu9dc11d71013b93ca08e26f3f42246ce6_238759_1536x1536_fit_box_3.png 1536w, https://ianthehenry.com/posts/delaunay/fig4_hu9dc11d71013b93ca08e26f3f42246ce6_238759_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/fig4_hu9dc11d71013b93ca08e26f3f42246ce6_238759_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 768px"
width="768"
height="384"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r&amp;#43;mnAAAAVklEQVR4nBTAAQ6DMAgFUOB/42bWeP&amp;#43;LtrYUWHwUETO77ysjI2uMZWZVpQDa73zmzgiDHgd7d1W1F8w9Wrsi5Ps5Se69AYDQZ/ryUFWp7GOS/AcAAP//wekoOlrvt54AAAAASUVORK5CYII=);"
/>&lt;/picture>&lt;/a>
&lt;p>In this case, the point is very much inside the circle, so we know that this is a &lt;em>bad&lt;/em> edge. But if we then &lt;em>flip&lt;/em> the edge so that it connects the other two points of our quadrangle, then this flipped edge is guaranteed to be locally Delaunay:&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/fig5_hucb02c66aee6f1a234ef86502f7cbc8bd_206555_1536x1536_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/fig5_hucb02c66aee6f1a234ef86502f7cbc8bd_206555_768x768_fit_q75_h3_box_3.webp 768w, https://ianthehenry.com/posts/delaunay/fig5_hucb02c66aee6f1a234ef86502f7cbc8bd_206555_1536x1536_fit_q75_h3_box_3.webp 1536w, https://ianthehenry.com/posts/delaunay/fig5_hucb02c66aee6f1a234ef86502f7cbc8bd_206555_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/fig5_hucb02c66aee6f1a234ef86502f7cbc8bd_206555_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 768px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/fig5_hucb02c66aee6f1a234ef86502f7cbc8bd_206555_768x768_fit_box_3.png 768w, https://ianthehenry.com/posts/delaunay/fig5_hucb02c66aee6f1a234ef86502f7cbc8bd_206555_1536x1536_fit_box_3.png 1536w, https://ianthehenry.com/posts/delaunay/fig5_hucb02c66aee6f1a234ef86502f7cbc8bd_206555_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/fig5_hucb02c66aee6f1a234ef86502f7cbc8bd_206555_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 768px"
width="768"
height="384"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r&amp;#43;mnAAAAVElEQVR4nBTKWwpCMQwE0MlkUqsouP99ClrykPt/HADJ1/uJsdte3SOJpLk7SQAmYBBL388xM5J09&amp;#43;6&amp;#43;P3bnxSVlpkXEzFSVpNjKU&amp;#43;d3JP0DAAD//y8kHZqGZL2eAAAAAElFTkSuQmCC);"
/>&lt;/picture>&lt;/a>
&lt;p>Notice that there might be &lt;em>other&lt;/em> points that lie inside this circle that we &lt;em>didn&amp;rsquo;t&lt;/em> check.&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/fig6_hucb02c66aee6f1a234ef86502f7cbc8bd_225291_1536x1536_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/fig6_hucb02c66aee6f1a234ef86502f7cbc8bd_225291_768x768_fit_q75_h3_box_3.webp 768w, https://ianthehenry.com/posts/delaunay/fig6_hucb02c66aee6f1a234ef86502f7cbc8bd_225291_1536x1536_fit_q75_h3_box_3.webp 1536w, https://ianthehenry.com/posts/delaunay/fig6_hucb02c66aee6f1a234ef86502f7cbc8bd_225291_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/fig6_hucb02c66aee6f1a234ef86502f7cbc8bd_225291_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 768px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/fig6_hucb02c66aee6f1a234ef86502f7cbc8bd_225291_768x768_fit_box_3.png 768w, https://ianthehenry.com/posts/delaunay/fig6_hucb02c66aee6f1a234ef86502f7cbc8bd_225291_1536x1536_fit_box_3.png 1536w, https://ianthehenry.com/posts/delaunay/fig6_hucb02c66aee6f1a234ef86502f7cbc8bd_225291_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/fig6_hucb02c66aee6f1a234ef86502f7cbc8bd_225291_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 768px"
width="768"
height="384"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r&amp;#43;mnAAAAVElEQVR4nATAUQqCMQwD4CRNW&amp;#43;GfoA/i/Q8piIzOLwBIuj8WDvtWM8e2JEaEJAA0cJDl7&amp;#43;dHUpIiYmaudVnubtt7b2SmbZKZ&amp;#43;Xo/qwuA7X8AAAD//zcDDzfH6i9TAAAAAElFTkSuQmCC);"
/>&lt;/picture>&lt;/a>
&lt;p>Because we only checked &lt;em>local&lt;/em> Delaunayness, which is only concerned with this one particular point on the other side of the edge.&lt;/p>
&lt;p>But here&amp;rsquo;s the trick: as long as &lt;em>every edge&lt;/em> of the triangulation is locally Delaunay, the &lt;em>entire triangulation&lt;/em> is a Delaunay triangulation. If this were a computational geometry class we&amp;rsquo;d spend the next ten minutes proving that statement, but for now I&amp;rsquo;ll just ask you to trust me on it.&lt;/p>
&lt;p>Okay. So now that we know these things, we can start to imagine an algorithm. We want to make a triangulation such that all edges are &amp;ldquo;locally Delaunay.&amp;rdquo; If we started with &lt;em>any&lt;/em> triangulation, we could just loop over all the edges, flipping any edges that are not locally Delaunay, and repeat until we don&amp;rsquo;t find any edges that need to be flipped.&lt;/p>
&lt;p>Would that work? Or is there a chance that we&amp;rsquo;d get into an infinite loop, repeatedly flipping and un-flipping edges due to some weird complicated cycle? It&amp;rsquo;s not obvious to me that this is the case, but apparently it &lt;em>does&lt;/em> work &amp;ndash; but only in two dimensions! &amp;ndash; and will eventually terminate after O(n&lt;sup>2&lt;/sup>) flips.&lt;/p>
&lt;p>But we can do better.&lt;/p>
&lt;h1 id="guibas--stolfis-incremental-triangulation-algorithm">Guibas &amp;amp; Stolfi&amp;rsquo;s incremental triangulation algorithm&lt;/h1>
&lt;p>There are several &amp;ldquo;better&amp;rdquo; algorithms, but the one I&amp;rsquo;m going to describe comes from Guibas &amp;amp; Stolfi&amp;rsquo;s seminal 1985 page-turner &lt;a href="https://dl.acm.org/doi/10.1145/282918.282923">&amp;ldquo;Primitives for the Manipulation of General Subdivisions and the Computation of Voronoi Diagrams.&amp;quot;&lt;/a>&lt;/p>
&lt;p>It&amp;rsquo;s an incremental algorithm, which means that you can start triangulating something even if you don&amp;rsquo;t know all of the points up-front, or you can use it to add new points to an existing Delaunay triangulation without having to re-triangulate it from scratch.&lt;/p>
&lt;p>It&amp;rsquo;s the most popular algorithm that I came across, probably because it&amp;rsquo;s very easy to understand it conceptually.&lt;sup id="fnref:5">&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref">5&lt;/a>&lt;/sup> But for all the explanations of it that exist on The Internet, I could not find a &lt;em>single&lt;/em> resource that actually explains it in sufficient detail that you could go write the code for it! I had to cobble an understanding together from the original paper, lecture notes, actual lectures on YouTube &amp;ndash; and ultimately by sitting down and working out some details myself.&lt;/p>
&lt;p>So I&amp;rsquo;m going to try to correct that here.&lt;/p>
&lt;p>But before I describe the algorithm, let me show it to you. You already know about &amp;ldquo;locally Delaunay&amp;rdquo; edges, which is the only real trick here &amp;ndash; see if that&amp;rsquo;s sufficient to work out what the algorithm is doing.&lt;/p>
&lt;p>Click to start/stop the animation, or drag the scrubber to move frame-by-frame.&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="example-with-bounding-triangle" width="384" height="256" class="interactive">&lt;/canvas>&lt;/p>
&lt;p class="slideshow-controls">&lt;button data-action="pause">❙❙&lt;/button>&lt;button data-action="play">▶&lt;/button>&lt;button data-action="fast">▶▶&lt;/button>&lt;input type="range" class="scrubber" value="0" min="0" max="0" />&lt;/p>
&lt;p>Okay, so at a high level, the algorithm is deceptively simple:&lt;/p>
&lt;p>Start with a single &amp;ldquo;infinitely large&amp;rdquo; triangle &amp;ndash; although note that, in tonight&amp;rsquo;s performance, infinitely large triangle will be portrayed by appropriately-sized finite triangle, in order to make the visualization easier to understand.&lt;/p>
&lt;p>Next, loop through your points, and add them into the triangulation one-by-one. Notice that our single giant triangle is, trivially, a valid Delaunay triangulation, which is a precondition that will hold at the beginning of every iteration.&lt;/p>
&lt;p>Every time you add a point, split the triangle that contains it into three new triangles, and then check the locally Delaunay condition. You know that you &lt;em>started&lt;/em> with a valid Delaunay triangulation, so you only have to look at the edges immediately surrounding the point that you added &amp;ndash; the rest of the edges are already good. Flip any edges that are not locally Delaunay, and then check the edges adjacent to those flipped edges, because the act of flipping might have made them invalid. Then once you&amp;rsquo;re done checking and potentially flipping all of the &amp;ldquo;dirty&amp;rdquo; edges, you&amp;rsquo;re done! You have a valid Delaunay triangulation once again, and you&amp;rsquo;re ready for the next point.&lt;/p>
&lt;p>Once you run out of points, just remove the infinitely large outer triangle &amp;ndash; and the edges connected to it &amp;ndash; and you have your final triangulation.&lt;/p>
&lt;p>Easy, right?&lt;/p>
&lt;h1 id="no-youve-used-the-phrase-deceptively-simple-twice-already">no; you&amp;rsquo;ve used the phrase &amp;ldquo;deceptively simple&amp;rdquo; twice already&lt;/h1>
&lt;p>Right. So that explanation might be sufficient if you&amp;rsquo;re a mathematician, but if you actually tried to write the code for this, you&amp;rsquo;ll quickly run into questions like the following:&lt;/p>
&lt;ol>
&lt;li>I can&amp;rsquo;t actually construct an infinitely large bounding triangle &amp;ndash; is it okay if I just construct a triangle large enough that it contains all of my points?&lt;/li>
&lt;li>How am I supposed to find the triangle that contains my new point?&lt;/li>
&lt;li>What if the new point lies on the edge of an existing triangle?&lt;/li>
&lt;li>How am I supposed to do this &amp;ldquo;circle test&amp;rdquo; in the first place?&lt;/li>
&lt;li>What edges do I actually need to check, once I&amp;rsquo;ve added my new edges?&lt;/li>
&lt;li>Wait, how do I &amp;ldquo;flip&amp;rdquo; edges?&lt;/li>
&lt;li>Wait, how do I even &lt;em>model&lt;/em> edges? How do you even represent triangulations on a computer? I thought I could just model this as a graph, with vertices and edges and pointers, but it seems like I need to keep track of faces as well, and somehow this is a fundamentally different sort of thing than I&amp;rsquo;ve ever encountered before?&lt;/li>
&lt;/ol>
&lt;p>Well, those were the questions I had, anyway.&lt;/p>
&lt;p>And now I have the answers.&lt;/p>
&lt;p>Let&amp;rsquo;s start with (3) because (3) is easy: if a point falls on an existing edge, you want to delete the edge, then create &lt;em>four&lt;/em> new triangles instead of the normal three. The odds of this happening in a randomly generated point set is astronomically low, so here&amp;rsquo;s a hand-picked example that demonstrates what this looks like:&lt;sup id="fnref:6">&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref">6&lt;/a>&lt;/sup>&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="quincunx" width="384" height="256" class="interactive">&lt;/canvas>&lt;/p>
&lt;p class="slideshow-controls">&lt;button data-action="pause">❙❙&lt;/button>&lt;button data-action="play">▶&lt;/button>&lt;button data-action="fast">▶▶&lt;/button>&lt;input type="range" class="scrubber" value="0" min="0" max="0" />&lt;/p>
&lt;p>This prompts a new question &amp;ndash; &amp;ldquo;how do I delete an edge?&amp;rdquo; &amp;ndash; but we&amp;rsquo;ll answer that at the same time we figure out how to flip edges.&lt;/p>
&lt;aside>
&lt;p>Update! A subtle bug!&lt;/p>
&lt;p>When I first published this post, I was only checking one of the edges of the triangle containing the point in question, which sounds dumb but is somewhat natural due to how the &amp;ldquo;find enclosing triangle&amp;rdquo; step works: you don&amp;rsquo;t actually find a &lt;em>triangle&lt;/em>, you find a &lt;em>representative edge&lt;/em> of the triangle, and I was only checking for an intersection with that &amp;ldquo;representative edge.&amp;rdquo; But the point might fall onto any one of the other edges. So&amp;hellip; don&amp;rsquo;t do that! Check all the edges! It&amp;rsquo;s really hard to write test cases for this sort of thing!&lt;/p>
&lt;/aside>
&lt;p>The rest of the questions have&amp;hellip; slightly more involved answers.&lt;/p>
&lt;p>So let&amp;rsquo;s start with a fun one.&lt;/p>
&lt;h1 id="how-to-check-if-a-point-is-inside-a-circle">How to check if a point is inside a circle&lt;/h1>
&lt;p>It&amp;rsquo;s very easy to check if a point is in a circle if you know the circle&amp;rsquo;s radius and center &amp;ndash; you compare the distance from the point to the center of the circle against the circle&amp;rsquo;s radius.&lt;sup id="fnref:7">&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref">7&lt;/a>&lt;/sup> But we don&amp;rsquo;t know the circle&amp;rsquo;s center or radius; we only know these three points on its circumference.&lt;/p>
&lt;p>Now, we could solve for the circle&amp;rsquo;s center and radius. There are &lt;a href="https://math.stackexchange.com/questions/213658">many techniques for doing this&lt;/a>, but it&amp;rsquo;s relatively expensive &amp;ndash; we don&amp;rsquo;t want to be taking square roots or doing any floating-point division here, because we&amp;rsquo;re going to be doing this point-in-circle test a lot.&lt;/p>
&lt;p>Instead, Guibas &amp;amp; Stolfi describe a really interesting technique that, when I first encountered it, made &lt;em>absolutely no sense to me&lt;/em>.&lt;/p>
&lt;p>Their technique is to calculate the determinant of this 4x4 matrix:&lt;/p>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>A&lt;sub>x&lt;/sub>&lt;/td>
&lt;td>A&lt;sub>y&lt;/sub>&lt;/td>
&lt;td>A&lt;sub>x&lt;/sub>&lt;sup>2&lt;/sup> + A&lt;sub>y&lt;/sub>&lt;sup>2&lt;/sup>&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>B&lt;sub>x&lt;/sub>&lt;/td>
&lt;td>B&lt;sub>y&lt;/sub>&lt;/td>
&lt;td>B&lt;sub>x&lt;/sub>&lt;sup>2&lt;/sup> + B&lt;sub>y&lt;/sub>&lt;sup>2&lt;/sup>&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>C&lt;sub>x&lt;/sub>&lt;/td>
&lt;td>C&lt;sub>y&lt;/sub>&lt;/td>
&lt;td>C&lt;sub>x&lt;/sub>&lt;sup>2&lt;/sup> + C&lt;sub>y&lt;/sub>&lt;sup>2&lt;/sup>&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>P&lt;sub>x&lt;/sub>&lt;/td>
&lt;td>P&lt;sub>y&lt;/sub>&lt;/td>
&lt;td>P&lt;sub>x&lt;/sub>&lt;sup>2&lt;/sup> + P&lt;sub>y&lt;/sub>&lt;sup>2&lt;/sup>&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;p>(Where (A, B, C) are the points that define the circle, and P is the point to test.)&lt;/p>
&lt;p>If the determinant is positive, the point is inside the circle. If it&amp;rsquo;s negative, the point is outside of the circle. If the determinant is zero, the point also lies on the circle.&lt;/p>
&lt;p>Okay &lt;em>what&lt;/em>.&lt;/p>
&lt;p>So I don&amp;rsquo;t expect this to make sense to you. It didn&amp;rsquo;t make sense to me when I first heard it. But I&amp;rsquo;m going to try to explain it, in a little more detail than Guibas &amp;amp; Stolfi explain it.&lt;/p>
&lt;p>First off: we have a bunch of 2D points. We&amp;rsquo;re going to transform them into 3D points, by saying:&lt;/p>
&lt;p class="math">z = x&lt;sup>2&lt;/sup> + y&lt;sup>2&lt;/sup>&lt;/p>
&lt;p>This gives us a bunch of points in 3D. Specifically, it projects these points upwards onto this weird parabaloid shape:&lt;/p>
&lt;p class="canvas-container">&lt;canvas class="parabaloid" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>(Click and drag the points to move them around.)&lt;/p>
&lt;p>Okay. So that&amp;rsquo;s&amp;hellip; something.&lt;/p>
&lt;p>We know that three points in 3D space uniquely determine a plane (unless the points are co-linear). Planes seem simpler than circles. So you can maybe see how this is a step in the right direction. Let&amp;rsquo;s draw the plane:&lt;/p>
&lt;p class="canvas-container">&lt;canvas class="parabaloid" data-plane="true" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>Now, there&amp;rsquo;s something very interesting about this plane: if we look at the points where it intersects our parabaloid, we can see that it forms some kind of ellipse shape.&lt;/p>
&lt;p>But if we look at it straight down from the top, we can see that the ellipse projects a perfect circle onto the XY plane.&lt;/p>
&lt;p class="canvas-controls">&lt;button data-action="topDown">Top-down view&lt;/button> &lt;button data-action="dimetric">Dimetric view&lt;/button>&lt;/p>
&lt;p class="canvas-container">&lt;canvas class="parabaloid" data-plane="true" data-circle-sky="true" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>Specifically, the circle that passes through all of our original points. So if we project the intersection of this plane with the parabaloid back down onto the XY plane, we get the circle that we actually care about:&lt;/p>
&lt;p class="canvas-container">&lt;canvas class="parabaloid" data-plane="true" data-circle-sky="true" data-circle-ground="true" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>Okaaaaay. So we&amp;rsquo;ve established a correspondence between this weird parabaloid shape and the circle in question. But let&amp;rsquo;s not forget why we&amp;rsquo;re doing this: we want to check if a point is inside this circle.&lt;/p>
&lt;p>So let&amp;rsquo;s add another point.&lt;/p>
&lt;p class="canvas-container">&lt;canvas class="parabaloid" data-plane="true" data-circle-sky="true" data-circle-ground="true" data-other-point="true" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>Drag it around a little bit, and hopefully you can see where we&amp;rsquo;re going with this.&lt;/p>
&lt;p>When we project that point upwards to the parabaloid, the point where it hits is either going to lie &amp;ldquo;under&amp;rdquo; or &amp;ldquo;over&amp;rdquo; our plane. And you can see that it&amp;rsquo;s going to hit &amp;ldquo;under&amp;rdquo; the plane &lt;em>exactly&lt;/em> when the point is in the circle.&lt;/p>
&lt;p>Okay! So this is interesting. This already suggests a very simple circle test algorithm: instead of finding the equation for the &lt;em>circle&lt;/em>, find the equation for &lt;em>this plane&lt;/em>. Then project our point upwards to both the plane and the parabaloid. If the projection up to the parabaloid is lower than the projection to the plane, then it&amp;rsquo;s inside the circle.&lt;/p>
&lt;p>It&amp;rsquo;s easy to find the equation for a plane. Given three points (A, B, C), all we have to do is find any vector orthogonal to the plane, which we can do by taking the cross product of any two vectors on the plane:&lt;/p>
&lt;p class="math">N = (B - A) ⨯ (C - A)&lt;/p>
&lt;p>And that, combined with any one of our original points, gives us an equation for the plane:&lt;/p>
&lt;p class="math">N&lt;sub>x&lt;/sub>(x - A&lt;sub>x&lt;/sub>) + N&lt;sub>y&lt;/sub>(y - A&lt;sub>y&lt;/sub>) + N&lt;sub>z&lt;/sub>(z - A&lt;sub>z&lt;/sub>) = 0&lt;/p>
&lt;p>Which we can use to solve for z, giving us a function of x and y:&lt;/p>
&lt;p class="math">plane(x, y) = -(N&lt;sub>x&lt;/sub>(x - A&lt;sub>x&lt;/sub>) + N&lt;sub>y&lt;/sub>(y - A&lt;sub>y&lt;/sub>)) / N&lt;sub>z&lt;/sub> + A&lt;sub>z&lt;/sub>&lt;/p>
&lt;p>Okay, neat. Now if you recall our parabaloid equation:&lt;/p>
&lt;p class="math">parabaloid(x, y) = x&lt;sup>2&lt;/sup> + y&lt;sup>2&lt;/sup>&lt;/p>
&lt;p>Then all we have to do is compare the value of these two functions, and we have our circle test:&lt;/p>
&lt;p class="math">contains(x, y) = parabaloid(x, y) &amp;lt; plane(x, y)&lt;/p>
&lt;p>Okay. That makes sense, right?&lt;/p>
&lt;p>But that&amp;rsquo;s &lt;em>not&lt;/em> what Guibas &amp;amp; Stolfi did.&lt;/p>
&lt;p>Notice how there are no weird complicated matrices involved in that calculation. Yes, I did take a cross product, but that&amp;rsquo;s a pretty small amount of linear algebra to stomach compared to the determinant of a 4x4 matrix.&lt;/p>
&lt;p>So.&lt;/p>
&lt;p>Let&amp;rsquo;s return to Guibas &amp;amp; Stolfi&amp;rsquo;s solution. Remember that they propose calculating the determinant of this matrix:&lt;/p>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>A&lt;sub>x&lt;/sub>&lt;/td>
&lt;td>A&lt;sub>y&lt;/sub>&lt;/td>
&lt;td>A&lt;sub>x&lt;/sub>&lt;sup>2&lt;/sup> + A&lt;sub>y&lt;/sub>&lt;sup>2&lt;/sup>&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>B&lt;sub>x&lt;/sub>&lt;/td>
&lt;td>B&lt;sub>y&lt;/sub>&lt;/td>
&lt;td>B&lt;sub>x&lt;/sub>&lt;sup>2&lt;/sup> + B&lt;sub>y&lt;/sub>&lt;sup>2&lt;/sup>&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>C&lt;sub>x&lt;/sub>&lt;/td>
&lt;td>C&lt;sub>y&lt;/sub>&lt;/td>
&lt;td>C&lt;sub>x&lt;/sub>&lt;sup>2&lt;/sup> + C&lt;sub>y&lt;/sub>&lt;sup>2&lt;/sup>&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>P&lt;sub>x&lt;/sub>&lt;/td>
&lt;td>P&lt;sub>y&lt;/sub>&lt;/td>
&lt;td>P&lt;sub>x&lt;/sub>&lt;sup>2&lt;/sup> + P&lt;sub>y&lt;/sub>&lt;sup>2&lt;/sup>&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;p>Hmm.&lt;/p>
&lt;p>I can understand 3x3 matrices. I&amp;rsquo;ve made my peace with them. I can think of them as transformations of 3D points to other 3D points. I&amp;rsquo;ve even almost, sort of, reluctantly made peace with determinants: I can understand the determinant of a 3x3 matrix as the rate by which it scales &lt;em>volumes&lt;/em> when you transform shapes with it.&lt;/p>
&lt;p>4x4 matrices&amp;hellip; I have no intuition for them. I would expect the determinant of a 4x4 matrix to be some kind of scaler of hypervolumes. But since it&amp;rsquo;s only &lt;em>barely&lt;/em> four-dimensional &amp;ndash; all values on the &amp;ldquo;W axis&amp;rdquo; are 1 &amp;ndash; we can intuit that we&amp;rsquo;re probably just dealing with plain volumes again.&lt;/p>
&lt;p>And in fact that&amp;rsquo;s correct: this determinant is actually the (oriented!) volume of the &lt;a href="https://en.wikipedia.org/wiki/Parallelepiped">parallelepiped&lt;/a> that has these four points as corners.&lt;/p>
&lt;p class="canvas-container">&lt;canvas class="parabaloid" data-circle-ground="true" data-other-point="true" data-parallelepiped="true" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>Whoa, that&amp;rsquo;s&amp;hellip; a lot. That&amp;rsquo;s kind of hard for me to parse. But in the same way that a parallelogram consists of (2! = 2) identical triangles, a parallelepiped consists of (3! = 6) identical tetrahedra. Let&amp;rsquo;s focus on just one of them:&lt;/p>
&lt;p class="canvas-container">&lt;canvas class="parabaloid" data-circle-ground="true" data-other-point="true" data-tetrahedron="true" width="384" height="256">&lt;/canvas>&lt;/p>
&lt;p>So I have two things to say about this.&lt;/p>
&lt;p>First off: notice that sometimes the tetrahedron is &amp;ldquo;inside out:&amp;rdquo; it has a negative volume, which I represent by changing the color of the wireframe. The crux of the point-in-circle test is checking the sign of that volume, and you can see if you move the free point around that it does invert when the free point passes into or out of the circle.&lt;/p>
&lt;p>But! If you move the &lt;em>other&lt;/em> points around, you can change whether positive or negative means &amp;ldquo;point in circle&amp;rdquo; or &amp;ldquo;point outside of circle.&amp;rdquo; Try it: swap the location of any two points on the circle, and watch as the sign flips, even though the circle remains the same.&lt;/p>
&lt;p>This is because the orientation of the tetrahedron depends not only on the position of the free point relative to the three circle points, but &lt;em>also&lt;/em> the orientation of the three circle points relative to one another. So this test &lt;em>only works&lt;/em> if you make sure that you list the points (A, B, C) in a clockwise order. If you list them in counterclockwise order, the circle will be &amp;ldquo;inside out,&amp;rdquo; and the test will give you the wrong answer.&lt;/p>
&lt;p>Which is pretty annoying.&lt;/p>
&lt;p>So&amp;hellip; I don&amp;rsquo;t like this test as much as the plane test &lt;em>in general&lt;/em>. It seems more expensive&lt;sup id="fnref:8">&lt;a href="#fn:8" class="footnote-ref" role="doc-noteref">8&lt;/a>&lt;/sup> to calculate the determinant than to calculate the equation of the plane, and it seems really annoying that you have to be careful that you provide the points in the right order or you&amp;rsquo;ll get the wrong result.&lt;/p>
&lt;p>This isn&amp;rsquo;t actually a problem for us, because we&amp;rsquo;ll be use a fancy data structure for storing edges that ensures we can only provide points in the correct order.&lt;/p>
&lt;p>Anyway.&lt;/p>
&lt;p>Second thing:&lt;/p>
&lt;p>A more obvious way to calculate the volume of a parallelepiped is to pick one point as the origin, and then to write the other three points relative to this origin. Then we have three points in three dimensions, which we can express as a square matrix:&lt;/p>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>A&lt;sub>x&lt;/sub> - P&lt;sub>x&lt;/sub>&lt;/td>
&lt;td>A&lt;sub>y&lt;/sub> - P&lt;sub>y&lt;/sub>&lt;/td>
&lt;td>(A&lt;sub>x&lt;/sub>&lt;sup>2&lt;/sup> + A&lt;sub>y&lt;/sub>&lt;sup>2&lt;/sup>) - (P&lt;sub>x&lt;/sub>&lt;sup>2&lt;/sup> + P&lt;sub>y&lt;/sub>&lt;sup>2&lt;/sup>)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>B&lt;sub>x&lt;/sub> - P&lt;sub>x&lt;/sub>&lt;/td>
&lt;td>B&lt;sub>y&lt;/sub> - P&lt;sub>y&lt;/sub>&lt;/td>
&lt;td>(B&lt;sub>x&lt;/sub>&lt;sup>2&lt;/sup> + B&lt;sub>y&lt;/sub>&lt;sup>2&lt;/sup>) - (P&lt;sub>x&lt;/sub>&lt;sup>2&lt;/sup> + P&lt;sub>y&lt;/sub>&lt;sup>2&lt;/sup>)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>C&lt;sub>x&lt;/sub> - P&lt;sub>x&lt;/sub>&lt;/td>
&lt;td>C&lt;sub>y&lt;/sub> - P&lt;sub>y&lt;/sub>&lt;/td>
&lt;td>(C&lt;sub>x&lt;/sub>&lt;sup>2&lt;/sup> + C&lt;sub>y&lt;/sub>&lt;sup>2&lt;/sup>) - (P&lt;sub>x&lt;/sub>&lt;sup>2&lt;/sup> + P&lt;sub>y&lt;/sub>&lt;sup>2&lt;/sup>)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;p>And the determinant of this matrix gives us the volume of the same parallelepiped as before, but in the simple straightforward way that we remember from that one linear algebra class we almost passed.&lt;/p>
&lt;p>Now, the determinant of this matrix is exactly equal to the determinant of the 4x4 matrix that you get by padding each point with a 1. They&amp;rsquo;re just two ways of saying the same thing. You could verify that algebraically, but I feel like there&amp;rsquo;s probably some intuitive explanation of this fact, and understanding why this is would lead to a better understanding of determinants in general and maybe teach me how to use higher dimensional matrices to more easily perform calculations in lower dimensions. Like in some way the fourth point we added sort of acts as a new origin? At least in this particular case?&lt;/p>
&lt;p>But look: I have to stop somewhere, or I&amp;rsquo;m never going to finish this blog post. And this is the place I decided to stop exploring Guibas &amp;amp; Stolfi&amp;rsquo;s point-in-circle test.&lt;/p>
&lt;p>For now.&lt;/p>
&lt;h1 id="back-to-the-triangulation">Back to the triangulation&lt;/h1>
&lt;p>Where were we?&lt;/p>
&lt;ol>
&lt;li>I can&amp;rsquo;t actually construct an infinitely large bounding triangle &amp;ndash; is it okay if I just construct a triangle large enough to contain all of my points?&lt;/li>
&lt;li>How am I supposed to find the triangle that contains my new point?&lt;/li>
&lt;li>&lt;del>What if the new point lies on the edge of an existing triangle?&lt;/del>&lt;/li>
&lt;li>&lt;del>How am I supposed to do this circle test in the first place?&lt;/del>&lt;/li>
&lt;li>What edges do I actually need to check, once I&amp;rsquo;ve added my new edges?&lt;/li>
&lt;li>Wait, how do I &amp;ldquo;flip&amp;rdquo; edges?&lt;/li>
&lt;li>Wait, how do I even &lt;em>model&lt;/em> edges? How do you even represent triangulations on a computer? I thought I could just model this as a graph, with vertices and edges and pointers, but it seems like I need to keep track of faces as well, and somehow this is a fundamentally different sort of thing than I&amp;rsquo;ve ever encountered before?&lt;/li>
&lt;/ol>
&lt;p>Okay. Let&amp;rsquo;s talk about (7) next, because you&amp;rsquo;re losing steam and it&amp;rsquo;s very interesting. The other questions are pretty specific to this one algorithm that you don&amp;rsquo;t actually care about &amp;ndash; although I appreciate how polite you are being &amp;ndash; but this one is broadly applicable to lots of graph-related, mesh-related, and computational geometry-related problems.&lt;/p>
&lt;h1 id="the-quad-edge-data-structure">The quad-edge data structure&lt;/h1>
&lt;p>Okay, so you know how to represent a graph, right? You have some &lt;code>Node&lt;/code> or &lt;code>Vertex&lt;/code> objects, and you have some &lt;code>Edge&lt;/code> objects, and vertices have pointers to their edges and edges have pointers to their vertices. Or you store an adjacency matrix; that&amp;rsquo;s a thing I&amp;rsquo;ve heard of.&lt;/p>
&lt;p>This works great when you&amp;rsquo;re dealing with graphs in the abstract &amp;ndash; if all you&amp;rsquo;re doing is Dijkstra&amp;rsquo;s algorithm or finding biconnected components or whatever, this simple representation works fine.&lt;/p>
&lt;p>But in this case we&amp;rsquo;re also concerned with &lt;em>faces&lt;/em> &amp;ndash; when we flip an edge, we need to know the specific faces we&amp;rsquo;re flipping it on, so we don&amp;rsquo;t do something illegal:&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/fig7_hu1956f63c751c3fbd837aeea7394512b2_127528_1536x1536_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/fig7_hu1956f63c751c3fbd837aeea7394512b2_127528_768x768_fit_q75_h3_box_3.webp 768w, https://ianthehenry.com/posts/delaunay/fig7_hu1956f63c751c3fbd837aeea7394512b2_127528_1536x1536_fit_q75_h3_box_3.webp 1536w, https://ianthehenry.com/posts/delaunay/fig7_hu1956f63c751c3fbd837aeea7394512b2_127528_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/fig7_hu1956f63c751c3fbd837aeea7394512b2_127528_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 768px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/fig7_hu1956f63c751c3fbd837aeea7394512b2_127528_768x768_fit_box_3.png 768w, https://ianthehenry.com/posts/delaunay/fig7_hu1956f63c751c3fbd837aeea7394512b2_127528_1536x1536_fit_box_3.png 1536w, https://ianthehenry.com/posts/delaunay/fig7_hu1956f63c751c3fbd837aeea7394512b2_127528_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/fig7_hu1956f63c751c3fbd837aeea7394512b2_127528_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 768px"
width="768"
height="384"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r&amp;#43;mnAAAARklEQVR4nGzLMQ6EMAwF0Z&amp;#43;xt4jk&amp;#43;x91QYBtFNFQ8KppBr0AEWFmklzSnNON47x6KSAzGWN053/bq2pN&amp;#43;gL83J&amp;#43;&amp;#43;AwAA//9NkxU5JkpB6wAAAABJRU5ErkJggg==);"
/>&lt;/picture>&lt;/a>
&lt;p>The obvious-to-me way to do this is to add another object to the mix: model &lt;code>Face&lt;/code>s explicitly, and have edges keep track of the faces they&amp;rsquo;re a part of, and every time you change anything about the graph you have to make sure to keep the faces and edges and vertices all in sync. I actually tried this, during my first forays into triangulation. It was awful. Just a case study on how denormalization will ruin your day. We&amp;rsquo;re not going to do that.&lt;/p>
&lt;p>Fortunately, Guibas &amp;amp; Stolfi describe a &lt;em>very clever&lt;/em> alternate representation called the &amp;ldquo;quad-edge&amp;rdquo; data structure.&lt;/p>
&lt;p>To understand the quad-edge structure, you first have to understand the idea of the &lt;a href="https://en.wikipedia.org/wiki/Dual_graph">dual&lt;/a> of a graph. Very loosely, if you take a drawing of a graph&lt;sup id="fnref:9">&lt;a href="#fn:9" class="footnote-ref" role="doc-noteref">9&lt;/a>&lt;/sup> and put a vertex in the middle of every face, and then connect all of the adjacent &amp;ldquo;face-vertices&amp;rdquo; with edges, you&amp;rsquo;ll have the dual graph. It&amp;rsquo;s easier to understand with a picture:&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/duals_hu2c428b58352ac6a41586acbeaea16ef5_180889_1536x1536_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/duals_hu2c428b58352ac6a41586acbeaea16ef5_180889_768x768_fit_q75_h3_box_3.webp 768w, https://ianthehenry.com/posts/delaunay/duals_hu2c428b58352ac6a41586acbeaea16ef5_180889_1536x1536_fit_q75_h3_box_3.webp 1536w, https://ianthehenry.com/posts/delaunay/duals_hu2c428b58352ac6a41586acbeaea16ef5_180889_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/duals_hu2c428b58352ac6a41586acbeaea16ef5_180889_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 768px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/duals_hu2c428b58352ac6a41586acbeaea16ef5_180889_768x768_fit_box_3.png 768w, https://ianthehenry.com/posts/delaunay/duals_hu2c428b58352ac6a41586acbeaea16ef5_180889_1536x1536_fit_box_3.png 1536w, https://ianthehenry.com/posts/delaunay/duals_hu2c428b58352ac6a41586acbeaea16ef5_180889_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/duals_hu2c428b58352ac6a41586acbeaea16ef5_180889_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 768px"
width="768"
height="384"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r&amp;#43;mnAAAAQ0lEQVR4nHSMUQ6AMAjFhCdMZZjd/6gzA8O3sf1uefsBAIaPFaub29XnM93uiGCUEIjuqtKY&amp;#43;GhnZlZFRN/VGwAA//&amp;#43;UpwzmmwifPwAAAABJRU5ErkJggg==);"
/>&lt;/picture>&lt;/a>
&lt;p>That&amp;rsquo;s actually a simplification &amp;ndash; I&amp;rsquo;m missing one face from all of those graphs: the outer face, the inside-out face bounded by the outside of the graphs. Here are the &lt;em>actual&lt;/em> duals:&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/full-duals_hu2f11216529d2062c89f8f5bbd7bc723a_298626_1536x1536_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/full-duals_hu2f11216529d2062c89f8f5bbd7bc723a_298626_768x768_fit_q75_h3_box_3.webp 768w, https://ianthehenry.com/posts/delaunay/full-duals_hu2f11216529d2062c89f8f5bbd7bc723a_298626_1536x1536_fit_q75_h3_box_3.webp 1536w, https://ianthehenry.com/posts/delaunay/full-duals_hu2f11216529d2062c89f8f5bbd7bc723a_298626_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/full-duals_hu2f11216529d2062c89f8f5bbd7bc723a_298626_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 768px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/full-duals_hu2f11216529d2062c89f8f5bbd7bc723a_298626_768x768_fit_box_3.png 768w, https://ianthehenry.com/posts/delaunay/full-duals_hu2f11216529d2062c89f8f5bbd7bc723a_298626_1536x1536_fit_box_3.png 1536w, https://ianthehenry.com/posts/delaunay/full-duals_hu2f11216529d2062c89f8f5bbd7bc723a_298626_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/full-duals_hu2f11216529d2062c89f8f5bbd7bc723a_298626_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 768px"
width="768"
height="384"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r&amp;#43;mnAAAAS0lEQVR4nDzMUQqAMAwE0d1sIyKp9v5nbVJR0Df/YwBkzSgALsdHux&amp;#43;jD1ERPaJj8YwrZzYaClmonCk0grUSJJ5eRnNt/&amp;#43;oOAAD//4yhDuTTWinMAAAAAElFTkSuQmCC);"
/>&lt;/picture>&lt;/a>
&lt;p>But since that is a &lt;em>mess&lt;/em> to look at, I will instead adopt the following shorthand, and trust that you can imagine the outer face:&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/short-duals_hu2f11216529d2062c89f8f5bbd7bc723a_212628_1536x1536_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/short-duals_hu2f11216529d2062c89f8f5bbd7bc723a_212628_768x768_fit_q75_h3_box_3.webp 768w, https://ianthehenry.com/posts/delaunay/short-duals_hu2f11216529d2062c89f8f5bbd7bc723a_212628_1536x1536_fit_q75_h3_box_3.webp 1536w, https://ianthehenry.com/posts/delaunay/short-duals_hu2f11216529d2062c89f8f5bbd7bc723a_212628_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/short-duals_hu2f11216529d2062c89f8f5bbd7bc723a_212628_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 768px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/short-duals_hu2f11216529d2062c89f8f5bbd7bc723a_212628_768x768_fit_box_3.png 768w, https://ianthehenry.com/posts/delaunay/short-duals_hu2f11216529d2062c89f8f5bbd7bc723a_212628_1536x1536_fit_box_3.png 1536w, https://ianthehenry.com/posts/delaunay/short-duals_hu2f11216529d2062c89f8f5bbd7bc723a_212628_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/short-duals_hu2f11216529d2062c89f8f5bbd7bc723a_212628_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 768px"
width="768"
height="384"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r&amp;#43;mnAAAASklEQVR4nHSMwQrFMAgEn67GvGAl/f8/DVVKoPTWPeycZvj3MQhkxpmV4dOHr2sdI6qSAQGjqamqtc7E3f6PRUT7N&amp;#43;lN3QEAAP//SFUKAajo834AAAAASUVORK5CYII=);"
/>&lt;/picture>&lt;/a>
&lt;p>Okay, so duals. Neat. Who cares?&lt;/p>
&lt;p>Well, the high level idea of the &amp;ldquo;quad-edge&amp;rdquo; structure is that you break every undirected edge &lt;code>A &amp;lt;-&amp;gt; B&lt;/code> of your mesh into four separate &lt;em>directed&lt;/em> edges: one that represents &lt;code>A -&amp;gt; B&lt;/code>, one that represents &lt;code>B -&amp;gt; A&lt;/code>, and two others that represent the directed &lt;em>dual&lt;/em> edges of the faces on either side of the edge. So for each undirected edge on our graph, we would have four directed edges like so:&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/quad-edge_hudc65ac562d632b0fedef557017d3acae_129840_1536x1536_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/quad-edge_hudc65ac562d632b0fedef557017d3acae_129840_768x768_fit_q75_h3_box_3.webp 768w, https://ianthehenry.com/posts/delaunay/quad-edge_hudc65ac562d632b0fedef557017d3acae_129840_1536x1536_fit_q75_h3_box_3.webp 1536w, https://ianthehenry.com/posts/delaunay/quad-edge_hudc65ac562d632b0fedef557017d3acae_129840_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/quad-edge_hudc65ac562d632b0fedef557017d3acae_129840_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 768px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/quad-edge_hudc65ac562d632b0fedef557017d3acae_129840_768x768_fit_box_3.png 768w, https://ianthehenry.com/posts/delaunay/quad-edge_hudc65ac562d632b0fedef557017d3acae_129840_1536x1536_fit_box_3.png 1536w, https://ianthehenry.com/posts/delaunay/quad-edge_hudc65ac562d632b0fedef557017d3acae_129840_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/quad-edge_hudc65ac562d632b0fedef557017d3acae_129840_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 768px"
width="768"
height="384"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r&amp;#43;mnAAAATUlEQVR4nBzMQQ4DIAwDQZskReL/r02DXdHDHmcDwDlbgu1zNrGuBCAykwTAqvoU&amp;#43;6uIkPSqTJuLcWUbsQLEW81cPOTu5p/PzC8AAP//HeYiP&amp;#43;NHPvUAAAAASUVORK5CYII=);"
/>&lt;/picture>&lt;/a>
&lt;p>So the quad-edge structure represents the structure of a graph &lt;em>and its dual&lt;/em> at the same time: a quad-edge is just a list of these four directed edges.&lt;/p>
&lt;p>I was very confused about this when I was reading Guibas &amp;amp; Stolfi.&lt;sup id="fnref:10">&lt;a href="#fn:10" class="footnote-ref" role="doc-noteref">10&lt;/a>&lt;/sup> They never give a type definition for quad-edges, and they describe operations on quad-edges that don&amp;rsquo;t &lt;em>actually&lt;/em> apply to quad-edges &amp;ndash; they apply to the directed edges that comprise quad-edges. Guibas &amp;amp; Stolfi call these &amp;ldquo;quad-edge refs,&amp;rdquo; but because that is confusing and clumsy I will call them &amp;ldquo;quarter-edges&amp;rdquo; from here on out.&lt;/p>
&lt;p>The paper is hard to follow sometimes because it uses the term &amp;ldquo;edge&amp;rdquo; a lot, which could mean one of &lt;em>three different things:&lt;/em>&lt;/p>
&lt;ul>
&lt;li>A logical edge on a graph &amp;ndash; that is, the normal sense of the word.&lt;/li>
&lt;li>A &amp;ldquo;quad-edge,&amp;rdquo; which &lt;em>represents&lt;/em> a single logical edge, and consists of four individual parts.&lt;/li>
&lt;li>A &amp;ldquo;quarter-edge&amp;rdquo; (my term) or &amp;ldquo;quad-edge ref&amp;rdquo; (their term), which is one of the individual parts of a quad-edge.&lt;/li>
&lt;/ul>
&lt;p>Now here&amp;rsquo;s the trick: whenever they say &amp;ldquo;edge&amp;rdquo; or &amp;ldquo;quad-edge,&amp;rdquo; they &lt;em>actually&lt;/em> mean &amp;ldquo;quarter-edge.&amp;rdquo; For example:&lt;/p>
&lt;blockquote>
&lt;p>The second operator is denoted by &lt;code>Splice[a, b]&lt;/code> and takes as parameters two edges &lt;code>a&lt;/code> and &lt;code>b&lt;/code>, returning no value.&lt;/p>
&lt;/blockquote>
&lt;p>Splice is actually an operation on &lt;em>quarter-edges&lt;/em>. If you try to make sense of it as an operation on &lt;em>quad-edges&lt;/em>, you will have a bad time and be very confused.&lt;/p>
&lt;p>Anyway.&lt;/p>
&lt;p>Digression over.&lt;/p>
&lt;p>Guibas &amp;amp; Stolfi don&amp;rsquo;t prescribe a concrete representation, but here&amp;rsquo;s a straightforward way&lt;sup id="fnref:11">&lt;a href="#fn:11" class="footnote-ref" role="doc-noteref">11&lt;/a>&lt;/sup> to represent the quad-edge as they describe it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="k">typedef&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="n">quarter_edge&lt;/span> &lt;span class="n">refs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="p">}&lt;/span> &lt;span class="n">quad_edge&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">typedef&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="n">quad_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">my_quad_edge&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="n">my_index&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">quad_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">next_quad_edge&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="n">next_index&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kt">void&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span> &lt;span class="n">quarter_edge&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nf">rot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">edge&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">my_quad_edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">refs&lt;/span>&lt;span class="p">[(&lt;/span>&lt;span class="n">edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">my_index&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nf">sym&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">edge&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">my_quad_edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">refs&lt;/span>&lt;span class="p">[(&lt;/span>&lt;span class="n">edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">my_index&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nf">tor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">edge&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">my_quad_edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">refs&lt;/span>&lt;span class="p">[(&lt;/span>&lt;span class="n">edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">my_index&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nf">next&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">edge&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">next_quad_edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">refs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">next_index&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>rot&lt;/code> (&amp;ldquo;rotate&amp;rdquo;), &lt;code>sym&lt;/code> (&amp;ldquo;symmetric edge&amp;rdquo;), and &lt;code>tor&lt;/code> (&amp;ldquo;rotate the other way&amp;rdquo;) are functions that bring you to the other three quarter-edges on the same &lt;code>QuadEdge&lt;/code>. &lt;code>next&lt;/code> is a pointer that allows you to navigate around to adjacent &lt;code>QuadEdge&lt;/code>s in the structure. (We&amp;rsquo;ll come back to this in a minute.)&lt;/p>
&lt;p>So you can see that a quad-edge isn&amp;rsquo;t really a thing; it&amp;rsquo;s just a bundle of four other things. And in fact I think it&amp;rsquo;s &lt;em>much simpler&lt;/em> if we just pretend that quad-edges don&amp;rsquo;t exist, and define everything in terms of quarter edges:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="k">typedef&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kt">void&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">next&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">rot&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span> &lt;span class="n">quarter_edge&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nf">rot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">edge&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">rot&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nf">sym&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">edge&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">rot&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">rot&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nf">tor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">quarter_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">edge&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">edge&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">rot&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">rot&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">rot&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Whenever we create these, we can just create four of them at a time, and set their &lt;code>rot&lt;/code> pointers in a cycle such that &lt;code>edge == edge-&amp;gt;rot-&amp;gt;rot-&amp;gt;rot-&amp;gt;rot&lt;/code>. This representation is slightly less efficient, but it&amp;rsquo;s &lt;em>much&lt;/em> simpler, especially when we start manipulating it.&lt;/p>
&lt;p>Okay, with that out of the way: what is &lt;code>next&lt;/code>?&lt;/p>
&lt;p>&lt;code>next&lt;/code> points to a quarter-edge that:&lt;/p>
&lt;ol>
&lt;li>Has the same starting point (be it a vertex or a face) as this quarter-edge.&lt;/li>
&lt;li>Lies immediately counter-clockwise of this quarter-edge.&lt;/li>
&lt;/ol>
&lt;p>This is all a little abstract, so let&amp;rsquo;s make it concrete. This is a little demo that lets you play with the quad-edge structure: the &amp;ldquo;current&amp;rdquo; edge is highlighted in yellow, and you can traverse the structure by pressing WASD (if you have a keyboard).&lt;/p>
&lt;ul>
&lt;li>&lt;code>W&lt;/code> &lt;code>sym&lt;/code>&lt;/li>
&lt;li>&lt;code>A&lt;/code> &lt;code>tor&lt;/code>&lt;/li>
&lt;li>&lt;code>S&lt;/code> &lt;code>next&lt;/code>&lt;/li>
&lt;li>&lt;code>D&lt;/code> &lt;code>rot&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Notice that the &lt;code>WAD&lt;/code> keys will only move you between each of the four quarter-edges of the current quad-edge, while &lt;code>S&lt;/code> will allow you to move to other edges.&lt;/p>
&lt;p class="canvas-controls">&lt;button data-action="next">.next&lt;/button> &lt;button data-action="rot">.rot&lt;/button> &lt;button data-action="sym">.sym&lt;/button> &lt;button data-action="tor">.tor&lt;/button>&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="quad-exploration" width="384" height="256" tabindex="1">&lt;/canvas>&lt;/p>
&lt;p>(Notice that, because the outer face is &amp;ldquo;inside out,&amp;rdquo; pressing &lt;code>S&lt;/code> while on the outer face will actually move &lt;em>clockwise&lt;/em> around the perimeter.)&lt;/p>
&lt;p>You can compose these operations predictably to navigate around. For example, to go to the &amp;ldquo;previous&amp;rdquo; edge that shares the same point &amp;ndash; to navigate clockwise, in other words &amp;ndash; press &lt;code>DSD&lt;/code>. To navigate around the triangle to the left of the current edge, press &lt;code>ASD&lt;/code> repeatedly. To navigate around the triangle to the right of the edge, press &lt;code>WS&lt;/code>.&lt;/p>
&lt;p>We&amp;rsquo;ll be doing some of these a lot, so we&amp;rsquo;ll give them names:&lt;/p>
&lt;pre>&lt;code>prev = rot.next.rot
lnext = tor.next.rot
&lt;/code>&lt;/pre>
&lt;p>The last thing that I didn&amp;rsquo;t explain is the &lt;code>data&lt;/code> pointer.&lt;/p>
&lt;p>The &lt;code>data&lt;/code> pointer stores arbitrary information about the vertex &lt;em>or face&lt;/em> at the beginning of each directed quarter-edge. For our purposes, we&amp;rsquo;ll use &lt;code>data&lt;/code> to store the actual location in space of each vertex, and we won&amp;rsquo;t store anything on faces. So note that, since there are multiple directed edges out of each vertex, all quarter-edges that you reach by traversing the &lt;code>next&lt;/code> pointers &amp;ndash; by pressing &lt;code>S&lt;/code>, in the demo &amp;ndash; will have identical &lt;code>data&lt;/code> pointers.&lt;/p>
&lt;p>One thing that&amp;rsquo;s a little weird about this representation is that each &amp;ldquo;directed edge&amp;rdquo; doesn&amp;rsquo;t actually store anything about its destination. Its &lt;code>data&lt;/code> pointer only contains its origin, and the destination of the edge is implicit from its sibling quarter-edges. Specifically, each quarter-edge represents a directed edge from &lt;code>edge.data&lt;/code> to &lt;code>edge.sym.data&lt;/code>.&lt;/p>
&lt;p>We&amp;rsquo;ll refer to &lt;code>edge.sym.data&lt;/code> a lot, so I&amp;rsquo;m going to give it a shorthand as well:&lt;/p>
&lt;pre>&lt;code>dest = sym.data
&lt;/code>&lt;/pre>
&lt;p>So: that&amp;rsquo;s it! That&amp;rsquo;s the structure.&lt;/p>
&lt;p>How do we&amp;hellip; use it?&lt;/p>
&lt;p>Well first we need to be able to create quarter-edges &amp;ndash; which we always do in groups of four:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="kd">function&lt;/span> &lt;span class="nx">makeQuadEdge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">start&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">end&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">startEnd&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">QuarterEdge&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">leftRight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">QuarterEdge&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">endStart&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">QuarterEdge&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">rightLeft&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">QuarterEdge&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="nx">startEnd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">start&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="nx">endStart&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">end&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="nx">startEnd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rot&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">leftRight&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="nx">leftRight&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rot&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">endStart&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="nx">endStart&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rot&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">rightLeft&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="nx">rightLeft&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rot&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">startEnd&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="c1">// normal edges are on different vertices,
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// and initially they are the only edges out
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// of each vertex
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">startEnd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">next&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">startEnd&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="nx">endStart&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">next&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">endStart&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="c1">// but dual edges share the same face,
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// so they point to one another
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">leftRight&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">next&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">rightLeft&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="nx">rightLeft&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">next&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">leftRight&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="nx">startEnd&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>That represents a single edge that cuts through a single face &amp;ndash; the face on the left and right of it are the same, and it exists on its own, connected to nothing. Guibas &amp;amp; Stolfi have a nice illustration of what this means topologically:&lt;/p>
&lt;figure>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/make-edge_hu98aad74021586d460597b88bf4cc6098_38299_1502x1502_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/make-edge_hu98aad74021586d460597b88bf4cc6098_38299_751x751_fit_q75_h3_box_3.webp 751w, https://ianthehenry.com/posts/delaunay/make-edge_hu98aad74021586d460597b88bf4cc6098_38299_1502x1502_fit_q75_h3_box_3.webp 1502w, https://ianthehenry.com/posts/delaunay/make-edge_hu98aad74021586d460597b88bf4cc6098_38299_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/make-edge_hu98aad74021586d460597b88bf4cc6098_38299_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 751px">&lt;img
class="image-with-light-background"
srcset="https://ianthehenry.com/posts/delaunay/make-edge_hu98aad74021586d460597b88bf4cc6098_38299_751x751_fit_box_3.png 751w, https://ianthehenry.com/posts/delaunay/make-edge_hu98aad74021586d460597b88bf4cc6098_38299_1502x1502_fit_box_3.png 1502w, https://ianthehenry.com/posts/delaunay/make-edge_hu98aad74021586d460597b88bf4cc6098_38299_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/make-edge_hu98aad74021586d460597b88bf4cc6098_38299_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 751px"
width="751"
height="249"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAIAAAAhqtkfAAAAP0lEQVR4nEzLMQoAMQgEwFsI2ZDK/79SRCtluTbTz&amp;#43;ruiLj3nnO&amp;#43;x3J3M6sqADMjCQBJZKYkknvvd/wBAAD//wTuF&amp;#43;wsRLPoAAAAAElFTkSuQmCC);"
/>&lt;/picture>&lt;/a>
&lt;figcaption>
© 1985 ACM 0730-0301/85/0400-0074 &amp;mdash; ACM Transactions on Graphics, Vol. 4, No. 2, April 1985&lt;br/>
Reprinted by permission of the Association for Computing Machinery
&lt;/figcaption>
&lt;/figure>
&lt;p>Simple.&lt;/p>
&lt;p>The next operation lets us connect edges together, or tear them apart. It&amp;rsquo;s called &lt;code>splice&lt;/code>, and it&amp;rsquo;s probably just as simple. Let&amp;rsquo;s see how Guibas &amp;amp; Stolfi explain it:&lt;/p>
&lt;figure>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/splice_hud5797dc177ab7bdd451c0078f297cf83_112422_1430x1430_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/splice_hud5797dc177ab7bdd451c0078f297cf83_112422_715x715_fit_q75_h3_box_3.webp 715w, https://ianthehenry.com/posts/delaunay/splice_hud5797dc177ab7bdd451c0078f297cf83_112422_1430x1430_fit_q75_h3_box_3.webp 1430w, https://ianthehenry.com/posts/delaunay/splice_hud5797dc177ab7bdd451c0078f297cf83_112422_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/splice_hud5797dc177ab7bdd451c0078f297cf83_112422_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 715px">&lt;img
class="image-with-light-background"
srcset="https://ianthehenry.com/posts/delaunay/splice_hud5797dc177ab7bdd451c0078f297cf83_112422_715x715_fit_box_3.png 715w, https://ianthehenry.com/posts/delaunay/splice_hud5797dc177ab7bdd451c0078f297cf83_112422_1430x1430_fit_box_3.png 1430w, https://ianthehenry.com/posts/delaunay/splice_hud5797dc177ab7bdd451c0078f297cf83_112422_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/splice_hud5797dc177ab7bdd451c0078f297cf83_112422_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 715px"
width="715"
height="350"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAAAAACWpiEsAAAAM0lEQVR4nGL5xcz0k/WaECvTm3vvr3yX&amp;#43;MLIIvifU4j13idWhj&amp;#43;//v7//&amp;#43;LTf0AAAAD//yv2E1VZwkYDAAAAAElFTkSuQmCC);"
/>&lt;/picture>&lt;/a>
&lt;figcaption>
© 1985 ACM 0730-0301/85/0400-0074 &amp;mdash; ACM Transactions on Graphics, Vol. 4, No. 2, April 1985&lt;br/>
Reprinted by permission of the Association for Computing Machinery&lt;br/>
&lt;/figcaption>
&lt;/figure>
&lt;p>Aaaah! Oh dear. Topology is happening to us, and it doesn&amp;rsquo;t feel good.&lt;/p>
&lt;p>But the implementation is so simple:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="kd">function&lt;/span> &lt;span class="nx">splice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">swapNexts&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">next&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rot&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">next&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rot&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">swapNexts&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="kd">function&lt;/span> &lt;span class="nx">swapNexts&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">anext&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">next&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="nx">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">next&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">next&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">next&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">anext&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>We&amp;rsquo;re just swapping a couple of pointers &amp;ndash; it &lt;em>seems&lt;/em> like it should be simple to understand. But it&amp;rsquo;s very difficult to intuit the effect that this actually has on our mesh.&lt;/p>
&lt;p>Let&amp;rsquo;s use it in a sentence. &lt;code>splice&lt;/code> is a low-level primitive operation, and we&amp;rsquo;ll never actually call it directly in our algorithm. Instead, we&amp;rsquo;ll use it to build higher-level operations, and seeing how these operations use &lt;code>splice&lt;/code> under the hood is probably the best way to gain intuition for how it works.&lt;/p>
&lt;p>So the first thing we&amp;rsquo;ll need to do is construct a bounding triangle:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="kd">function&lt;/span> &lt;span class="nx">makeTriangle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">c&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">ab&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">makeQuadEdge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">bc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">makeQuadEdge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">c&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">ca&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">makeQuadEdge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">splice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ab&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sym&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bc&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">splice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">bc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sym&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ca&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">splice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ca&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sym&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ab&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="nx">ab&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then we&amp;rsquo;ll need to know how to add new edges:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="kd">function&lt;/span> &lt;span class="nx">connect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">newEdge&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">makeQuadEdge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dest&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">splice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">newEdge&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lnext&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">splice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">newEdge&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sym&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="nx">newEdge&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>And how to remove edges:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="kd">function&lt;/span> &lt;span class="nx">sever&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">edge&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">splice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">edge&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">edge&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">prev&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">splice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">edge&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sym&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">edge&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sym&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">prev&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>And lastly we&amp;rsquo;ll need to &amp;ldquo;flip&amp;rdquo; the diagonal edge of a quadrangle.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="kd">function&lt;/span> &lt;span class="nx">flip&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">edge&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">edge&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">prev&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">edge&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sym&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">prev&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="nx">splice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">edge&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">splice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">edge&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sym&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">splice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">edge&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lnext&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">splice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">edge&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sym&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lnext&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">edge&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dest&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="nx">edge&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dest&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dest&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>And that&amp;rsquo;s it! Those are the only operations we need.&lt;/p>
&lt;p>I think the easiest way to understand &lt;code>splice&lt;/code> is to work through these high-level operations and convince yourself that this series of pointer updates gives you the &lt;code>QuarterEdge&lt;/code> structure that you want. But also: you can just trust me. You aren&amp;rsquo;t actually trying to implement this.&lt;/p>
&lt;h1 id="are-we-done-yet">are we done yet&lt;/h1>
&lt;p>Almost!&lt;/p>
&lt;ol>
&lt;li>I can&amp;rsquo;t actually construct an infinitely large bounding triangle &amp;ndash; is it okay if I just construct a triangle large enough to contain all of my points?&lt;/li>
&lt;li>How am I supposed to find the triangle that contains my new point?&lt;/li>
&lt;li>&lt;del>What if the new point lies on the edge of an existing triangle?&lt;/del>&lt;/li>
&lt;li>&lt;del>How am I supposed to do this circle test in the first place?&lt;/del>&lt;/li>
&lt;li>What edges do I actually need to check, once I&amp;rsquo;ve added my new edges?&lt;/li>
&lt;li>&lt;del>Wait, how do I &amp;ldquo;flip&amp;rdquo; edges?&lt;/del>&lt;/li>
&lt;li>&lt;del>Wait, how do I even &lt;em>model&lt;/em> edges? How do you even represent triangulations on a computer? I thought I could just model this as a graph, with vertices and edges and pointers, but it seems like I need to keep track of faces as well, and somehow this is a fundamentally different sort of thing than I&amp;rsquo;ve ever encountered before?&lt;/del>&lt;/li>
&lt;/ol>
&lt;p>Now that you understand the quad-edge/quarter-edge structure, we can talk about (2). It&amp;rsquo;s clever! It&amp;rsquo;s not what you&amp;rsquo;re expecting!&lt;/p>
&lt;h1 id="triangle-where">triangle where&lt;/h1>
&lt;p>So in order to add a new point to your mesh, you first need to locate the triangle that contains that point.&lt;/p>
&lt;p>There&amp;rsquo;s an easy, inefficient way to do this: loop through every triangle, check if your point is inside this triangle, and return as soon as you find one. We&amp;rsquo;re not going to do that, but it&amp;rsquo;s useful to talk about &lt;em>how&lt;/em> to do it. Specifically: how do you determine if a point lies inside a triangle?&lt;/p>
&lt;p>The trick we&amp;rsquo;re going to use is another linear algebra thing. If you could follow the point-in-circle test, this is going to be easy.&lt;/p>
&lt;p>Given a triangle &lt;code>ABC&lt;/code>, and another point &lt;code>P&lt;/code> that you want to test, construct three new triangles: &lt;code>ABP&lt;/code>, &lt;code>BCP&lt;/code>, and &lt;code>CAP&lt;/code>. Then calculate the sign of the oriented area of each of these new triangles. You can find the oriented area of a triangle &lt;code>ABC&lt;/code> by taking the determinant of this matrix:&lt;/p>
&lt;div class="matrix">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>A&lt;sub>x&lt;/sub>&lt;/td>
&lt;td>A&lt;sub>y&lt;/sub>&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>B&lt;sub>x&lt;/sub>&lt;/td>
&lt;td>B&lt;sub>y&lt;/sub>&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>C&lt;sub>x&lt;/sub>&lt;/td>
&lt;td>C&lt;sub>y&lt;/sub>&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;p>(That actually gives you the area of the parallelogram consisting of two copies of the triangle, but since we only care about the sign, we won&amp;rsquo;t bother to divide it by two.)&lt;/p>
&lt;p>The sign of the oriented area tells you whether your triangle&amp;rsquo;s vertices are listed in clockwise (negative) or counter-clockwise (positive) order. If all three of &lt;code>ABP&lt;/code>, &lt;code>BCP&lt;/code>, and &lt;code>CAP&lt;/code> are counter-clockwise, then the point is inside the triangle.&lt;sup id="fnref:12">&lt;a href="#fn:12" class="footnote-ref" role="doc-noteref">12&lt;/a>&lt;/sup>&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/winding-test_hud3a68137b279523b9858e27bdd6d6f7c_105986_1384x1384_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/winding-test_hud3a68137b279523b9858e27bdd6d6f7c_105986_692x692_fit_q75_h3_box_3.webp 692w, https://ianthehenry.com/posts/delaunay/winding-test_hud3a68137b279523b9858e27bdd6d6f7c_105986_1384x1384_fit_q75_h3_box_3.webp 1384w, https://ianthehenry.com/posts/delaunay/winding-test_hud3a68137b279523b9858e27bdd6d6f7c_105986_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/winding-test_hud3a68137b279523b9858e27bdd6d6f7c_105986_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 692px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/winding-test_hud3a68137b279523b9858e27bdd6d6f7c_105986_692x692_fit_box_3.png 692w, https://ianthehenry.com/posts/delaunay/winding-test_hud3a68137b279523b9858e27bdd6d6f7c_105986_1384x1384_fit_box_3.png 1384w, https://ianthehenry.com/posts/delaunay/winding-test_hud3a68137b279523b9858e27bdd6d6f7c_105986_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/winding-test_hud3a68137b279523b9858e27bdd6d6f7c_105986_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 692px"
width="692"
height="293"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAIAAAAhqtkfAAAAPklEQVR4nDTMQQ6AMAgF0YGUEhrvf1cW9puofevJOK/M5KgqYADrWoZ191fMOVBaREgyt31v938g6QkAAP//EksNfG0iwmoAAAAASUVORK5CYII=);"
/>&lt;/picture>&lt;/a>
&lt;p>Another, simpler interpretation: given a directed edge &lt;code>AB&lt;/code> of a triangle, this test tells you if point &lt;code>P&lt;/code> lies &amp;ldquo;to the left&amp;rdquo; of the line crossing &lt;code>AB&lt;/code> (counter-clockwise) or &amp;ldquo;to the right&amp;rdquo; of it (clockwise).&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/left-right_hu984b6b8b18c534e976a5febb4ffb102f_71310_1002x1002_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/left-right_hu984b6b8b18c534e976a5febb4ffb102f_71310_501x501_fit_q75_h3_box_3.webp 501w, https://ianthehenry.com/posts/delaunay/left-right_hu984b6b8b18c534e976a5febb4ffb102f_71310_1002x1002_fit_q75_h3_box_3.webp 1002w, https://ianthehenry.com/posts/delaunay/left-right_hu984b6b8b18c534e976a5febb4ffb102f_71310_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/left-right_hu984b6b8b18c534e976a5febb4ffb102f_71310_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 501px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/left-right_hu984b6b8b18c534e976a5febb4ffb102f_71310_501x501_fit_box_3.png 501w, https://ianthehenry.com/posts/delaunay/left-right_hu984b6b8b18c534e976a5febb4ffb102f_71310_1002x1002_fit_box_3.png 1002w, https://ianthehenry.com/posts/delaunay/left-right_hu984b6b8b18c534e976a5febb4ffb102f_71310_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/left-right_hu984b6b8b18c534e976a5febb4ffb102f_71310_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 501px"
width="501"
height="312"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAVklEQVR4nDyMQQ6FIAwFee2DD1/d6/1v6IYoAQ2h0k3TTGfUfRN&amp;#43;BKS1Nk4ZKy0prvHY//PPDDjcuVDxFCdkrbUDVSXZMWQLPPNlFoBZ8N4abwAAAP//osoOoryER1wAAAAASUVORK5CYII=);"
/>&lt;/picture>&lt;/a>
&lt;p>If the point is &amp;ldquo;to the left of&amp;rdquo; every (directed, counter-clockwise) edge, then the point is inside the triangle.&lt;/p>
&lt;aside>
&lt;p>I probably mixed up positive/negative and clockwise/counter-clockwise and left/right every single time during that explanation, for which I apologize.&lt;/p>
&lt;/aside>
&lt;h1 id="but-triangle-where">but triangle where&lt;/h1>
&lt;p>So: now we know how to check if a point is inside a triangle. But! We actually get more than one bit of information when we do that test &amp;ndash; we get &lt;em>three&lt;/em> bits of information. Instead of &lt;code>AND&lt;/code>ing them down to a single boolean value, we can use all of the information at our disposal to do something smarter.&lt;/p>
&lt;p>So Guibas &amp;amp; Stolfi describe a technique that they credit to Green &amp;amp; Sibson:&lt;sup id="fnref:13">&lt;a href="#fn:13" class="footnote-ref" role="doc-noteref">13&lt;/a>&lt;/sup> traverse the mesh one triangle at a time, always moving &amp;ldquo;towards&amp;rdquo; the target point, using the information we learned during the triangle test to decide what &amp;ldquo;towards&amp;rdquo; means.&lt;/p>
&lt;p>If we find that the point is not &amp;ldquo;to the left of&amp;rdquo; one particular edge, then we know that it&amp;rsquo;s to the right of that edge. And since we know the whole mesh, we know exactly which triangle lies to the right of that edge, and we can check that triangle next.&lt;/p>
&lt;p>The triangle immediately to the right of that edge may not contain our point either, but it&amp;rsquo;s going to be &lt;em>closer&lt;/em> to the triangle that does, and if we repeat this enough we will &lt;em>eventually&lt;/em> find a home for our point.&lt;/p>
&lt;p>Here, let&amp;rsquo;s take a look at this in action. I omitted the locating step from my first visualization of the triangulation algorithm, but we can add it back in to see the full story:&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="triangulation-with-location" width="384" height="256" tabindex="1" class="interactive">&lt;/canvas>&lt;/p>
&lt;p class="slideshow-controls">&lt;button data-action="pause">❙❙&lt;/button>&lt;button data-action="play">▶&lt;/button>&lt;button data-action="fast">▶▶&lt;/button>&lt;input type="range" class="scrubber" value="0" min="0" max="0" />&lt;/p>
&lt;aside>
&lt;p>In this visualization, notice that I&amp;rsquo;m starting the search from wherever the previous point was inserted. This doesn&amp;rsquo;t help &lt;em>that&lt;/em> much because these points are randomly generated, but it makes it more likely that we start our search from a triangle close to the &amp;ldquo;middle&amp;rdquo; of the mesh. In other situations, though, this can be a fantastic optimization &amp;ndash; imagine finding the triangle under the mouse cursor as it moves around, for example.&lt;/p>
&lt;/aside>
&lt;p>This algorithm is nice because we &lt;em>probably&lt;/em> won&amp;rsquo;t need to traverse the entire graph, but in the worst case we&amp;rsquo;ll still have to check every single triangle. Guibas &amp;amp; Stolfi claim that for a set of &amp;ldquo;reasonably uniform&amp;rdquo; random points, this search will only take O(√n) tests on average, and you can reduce that to roughly constant time if your newly inserted points are &amp;ldquo;close&amp;rdquo; to the previously inserted point. So it&amp;rsquo;s a bit data-dependent, but worst-case linear.&lt;sup id="fnref:14">&lt;a href="#fn:14" class="footnote-ref" role="doc-noteref">14&lt;/a>&lt;/sup>&lt;/p>
&lt;h1 id="is-that-it">is that it&lt;/h1>
&lt;p>We&amp;rsquo;re so close!&lt;/p>
&lt;ol>
&lt;li>I can&amp;rsquo;t actually construct an infinitely large bounding triangle &amp;ndash; is it okay if I just construct a triangle large enough to contain all of my points?&lt;/li>
&lt;li>&lt;del>How am I supposed to find the triangle that contains my new point?&lt;/del>&lt;/li>
&lt;li>&lt;del>What if the new point lies on the edge of an existing triangle?&lt;/del>&lt;/li>
&lt;li>&lt;del>How am I supposed to do this circle test in the first place?&lt;/del>&lt;/li>
&lt;li>What edges do I actually need to check, once I&amp;rsquo;ve added my new edges?&lt;/li>
&lt;li>&lt;del>Wait, how do I &amp;ldquo;flip&amp;rdquo; edges?&lt;/del>&lt;/li>
&lt;li>&lt;del>Wait, how do I even &lt;em>model&lt;/em> edges? How do you even represent triangulations on a computer? I thought I could just model this as a graph, with vertices and edges and pointers, but it seems like I need to keep track of faces as well, and somehow this is a fundamentally different sort of thing than I&amp;rsquo;ve ever encountered before?&lt;/del>&lt;/li>
&lt;/ol>
&lt;p>The last two questions are very specific to this particular algorithm, and the answers won&amp;rsquo;t really make you a smarter person. So if you aren&amp;rsquo;t &lt;em>actually&lt;/em> trying to implement this algorithm, you can safely &lt;a href="#are-we-there-yet">skip to the end&lt;/a>.&lt;/p>
&lt;p>Whenever you add a new point, and connect it to the points of its enclosing triangle or quadrangle, you&amp;rsquo;ll have a point at the middle of a spoke. Like this:&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/spokes_hufa198a314e148a89be0a4e16c6e1e9db_107561_848x848_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/spokes_hufa198a314e148a89be0a4e16c6e1e9db_107561_424x424_fit_q75_h3_box_3.webp 424w, https://ianthehenry.com/posts/delaunay/spokes_hufa198a314e148a89be0a4e16c6e1e9db_107561_848x848_fit_q75_h3_box_3.webp 848w, https://ianthehenry.com/posts/delaunay/spokes_hufa198a314e148a89be0a4e16c6e1e9db_107561_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/spokes_hufa198a314e148a89be0a4e16c6e1e9db_107561_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 424px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/spokes_hufa198a314e148a89be0a4e16c6e1e9db_107561_424x424_fit_box_3.png 424w, https://ianthehenry.com/posts/delaunay/spokes_hufa198a314e148a89be0a4e16c6e1e9db_107561_848x848_fit_box_3.png 848w, https://ianthehenry.com/posts/delaunay/spokes_hufa198a314e148a89be0a4e16c6e1e9db_107561_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/spokes_hufa198a314e148a89be0a4e16c6e1e9db_107561_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 424px"
width="424"
height="297"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAIAAABxZ0isAAAAc0lEQVR4nDyO2wrDIBAFj3tWXUMK&amp;#43;f/PDBjiZU1pC53HmZdRfIkxllIAP8/6MzSznDOA67pIkNHdP0FEUorKdRwlZ87pr32/7xbMjKQSrTdVriXPA5Kcc4oIVdcKY/i2WQhSaw0AUkr/hd77GAPAOwAA//&amp;#43;GIymYdkIEjgAAAABJRU5ErkJggg==);"
/>&lt;/picture>&lt;/a>
&lt;p>All of those newly created edges are &lt;em>already fine&lt;/em>. We don&amp;rsquo;t have to check them. But we do have to check the edges adjacent to them &amp;ndash; the edges that form a ring around the newly inserted point.&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/ring_hudd67548716ad4f8181d256be6dfe8a4f_112678_848x848_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/ring_hudd67548716ad4f8181d256be6dfe8a4f_112678_424x424_fit_q75_h3_box_3.webp 424w, https://ianthehenry.com/posts/delaunay/ring_hudd67548716ad4f8181d256be6dfe8a4f_112678_848x848_fit_q75_h3_box_3.webp 848w, https://ianthehenry.com/posts/delaunay/ring_hudd67548716ad4f8181d256be6dfe8a4f_112678_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/ring_hudd67548716ad4f8181d256be6dfe8a4f_112678_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 424px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/ring_hudd67548716ad4f8181d256be6dfe8a4f_112678_424x424_fit_box_3.png 424w, https://ianthehenry.com/posts/delaunay/ring_hudd67548716ad4f8181d256be6dfe8a4f_112678_848x848_fit_box_3.png 848w, https://ianthehenry.com/posts/delaunay/ring_hudd67548716ad4f8181d256be6dfe8a4f_112678_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/ring_hudd67548716ad4f8181d256be6dfe8a4f_112678_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 424px"
width="424"
height="297"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAIAAABxZ0isAAAAe0lEQVR4nDyO2w6CQAwF29OepVziJWL0/z9RJMCyWY2aOI/zMBmTLySv46FpfFnyz0hEDEN/OraAuvt46QAVEdRaSVu3OvRdG3xM5X47q6pGhJkl6vRcAfQd162amZVSADg9EcCLbEQwz/Mnl1L6L&amp;#43;Sc930XkXcAAAD//&amp;#43;2UIHPr275GAAAAAElFTkSuQmCC);"
/>&lt;/picture>&lt;/a>
&lt;p>Whenever you flip an edge, you make this ring bigger, effectively adding two new segments to the ring.&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/ring-flipped_hu2e063f25857b0bc063eec050de13d9e5_115321_848x848_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/ring-flipped_hu2e063f25857b0bc063eec050de13d9e5_115321_424x424_fit_q75_h3_box_3.webp 424w, https://ianthehenry.com/posts/delaunay/ring-flipped_hu2e063f25857b0bc063eec050de13d9e5_115321_848x848_fit_q75_h3_box_3.webp 848w, https://ianthehenry.com/posts/delaunay/ring-flipped_hu2e063f25857b0bc063eec050de13d9e5_115321_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/ring-flipped_hu2e063f25857b0bc063eec050de13d9e5_115321_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 424px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/ring-flipped_hu2e063f25857b0bc063eec050de13d9e5_115321_424x424_fit_box_3.png 424w, https://ianthehenry.com/posts/delaunay/ring-flipped_hu2e063f25857b0bc063eec050de13d9e5_115321_848x848_fit_box_3.png 848w, https://ianthehenry.com/posts/delaunay/ring-flipped_hu2e063f25857b0bc063eec050de13d9e5_115321_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/ring-flipped_hu2e063f25857b0bc063eec050de13d9e5_115321_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 424px"
width="424"
height="297"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAIAAABxZ0isAAAAeUlEQVR4nDyO2w7CIBAFdw97gF5sNdb//0cjVnQDRk2cx3mYTJAvJC/bkpLt&amp;#43;/NnJOc8z9NxHQA1s&amp;#43;08AioiaK2R4VHbPI2Hdbje/LQtqhpI9i45hXKv/mocgqgaGNwdgNEiAXQyNZdSyicXY/wv1FrdXUTeAQAA///GLCRiIlrmfAAAAABJRU5ErkJggg==);"
/>&lt;/picture>&lt;/a>
&lt;p>You then have to check those new edges. But notice that they&amp;rsquo;re still a part of the same ring! And in fact as you flip these edges, they will add new segments to the ring as well.&lt;/p>
&lt;p>So you don&amp;rsquo;t need to store a list of &amp;ldquo;dirty edges&amp;rdquo; or recurse to clean up all of these edges. You can just store a specific edge on the ring, check it, and then either move &amp;ldquo;forward&amp;rdquo; (if the edge passed) or &amp;ldquo;backwards&amp;rdquo; (if you had to flip it).&lt;/p>
&lt;p>So the number of edges that you have to check depends on the number of triangles that have the newly inserted point as one of their corners. In the worst case this means means doing O(n) work, but &lt;em>on average&lt;/em> across every point insertion this will net out to constant time, apparently, although I can&amp;rsquo;t find a proof of that.&lt;sup id="fnref:15">&lt;a href="#fn:15" class="footnote-ref" role="doc-noteref">15&lt;/a>&lt;/sup> But we&amp;rsquo;re already doing worst-case O(n) work to find the triangle, so the total runtime of the algorithm remains O(n&lt;sup>2&lt;/sup>).&lt;/p>
&lt;p>Easy! Okay. Which brings us to the last question: what do we do about the &amp;ldquo;infinite bounding triangle?&amp;rdquo;&lt;/p>
&lt;p>There is no triangle you can construct that will be &amp;ldquo;big enough&amp;rdquo; to pass for infinite when we do the point-in-circle test &amp;ndash; as three points get closer to being colinear, the radius of the circle they define will approach infinity &amp;ndash; so we just won&amp;rsquo;t do the point-in-circle test. Whenever a point on our boundary triangle is involved, we&amp;rsquo;ll instead check a series of conditions:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>If the edge in question is a boundary edge, do not flip it. This one is easy so I&amp;rsquo;m not going to draw it for you.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>If the edge contains a boundary vertex, flip it, &lt;em>unless doing so would create an inside-out triangle&lt;/em>.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/edge-case-2_hu5c9ca0611e60e4307091b662fc270cdd_306048_1536x1536_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/edge-case-2_hu5c9ca0611e60e4307091b662fc270cdd_306048_768x768_fit_q75_h3_box_3.webp 768w, https://ianthehenry.com/posts/delaunay/edge-case-2_hu5c9ca0611e60e4307091b662fc270cdd_306048_1536x1536_fit_q75_h3_box_3.webp 1536w, https://ianthehenry.com/posts/delaunay/edge-case-2_hu5c9ca0611e60e4307091b662fc270cdd_306048_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/edge-case-2_hu5c9ca0611e60e4307091b662fc270cdd_306048_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 768px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/edge-case-2_hu5c9ca0611e60e4307091b662fc270cdd_306048_768x768_fit_box_3.png 768w, https://ianthehenry.com/posts/delaunay/edge-case-2_hu5c9ca0611e60e4307091b662fc270cdd_306048_1536x1536_fit_box_3.png 1536w, https://ianthehenry.com/posts/delaunay/edge-case-2_hu5c9ca0611e60e4307091b662fc270cdd_306048_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/edge-case-2_hu5c9ca0611e60e4307091b662fc270cdd_306048_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 768px"
width="768"
height="461"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAW0lEQVR4nBTLyRFEIQwDUcsL/kP&amp;#43;0bJIU1y764WZjTG&amp;#43;b9xLAFUFgGTOOSUBbwOAv3rOib03gMwkJemeW/mQR0RVhUNSRPy6Ra610N2S3B2AmZF0dzP7BwAA//8g3yqfX5a1VAAAAABJRU5ErkJggg==);"
/>&lt;/picture>&lt;/a>
&lt;ol start="3">
&lt;li>If an edge separates our newly inserted point from a boundary vertex, do not flip it.&lt;/li>
&lt;/ol>
&lt;a class="image-container" href="https://ianthehenry.com/posts/delaunay/edge-case-3_huca4a1b7fc304ce20231275d7c77e6d41_59182_788x788_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/delaunay/edge-case-3_huca4a1b7fc304ce20231275d7c77e6d41_59182_394x394_fit_q75_h3_box_3.webp 394w, https://ianthehenry.com/posts/delaunay/edge-case-3_huca4a1b7fc304ce20231275d7c77e6d41_59182_788x788_fit_q75_h3_box_3.webp 788w, https://ianthehenry.com/posts/delaunay/edge-case-3_huca4a1b7fc304ce20231275d7c77e6d41_59182_375x375_fit_q75_h3_box_3.webp 375w, https://ianthehenry.com/posts/delaunay/edge-case-3_huca4a1b7fc304ce20231275d7c77e6d41_59182_750x750_fit_q75_h3_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 394px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/delaunay/edge-case-3_huca4a1b7fc304ce20231275d7c77e6d41_59182_394x394_fit_box_3.png 394w, https://ianthehenry.com/posts/delaunay/edge-case-3_huca4a1b7fc304ce20231275d7c77e6d41_59182_788x788_fit_box_3.png 788w, https://ianthehenry.com/posts/delaunay/edge-case-3_huca4a1b7fc304ce20231275d7c77e6d41_59182_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/delaunay/edge-case-3_huca4a1b7fc304ce20231275d7c77e6d41_59182_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 394px"
width="394"
height="289"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAIAAABxZ0isAAAAS0lEQVR4nHzJUQrFIBBD0YzJgAH3v9f3mEpBivar9&amp;#43;vCaXgVEfsP2B5jbHsgM6UQZ2YekEQS4O9/VVXvHQAl2Z6r1ugVProDAAD///EaDell5FzbAAAAAElFTkSuQmCC);"
/>&lt;/picture>&lt;/a>
&lt;p>That&amp;rsquo;s it! Those conditions will give you answers consistent with the answers you would get if your boundary vertices really were infinitely far from the rest of your points. Now all you need to do is to construct a triangle large enough to contain all of your points so that your &amp;ldquo;point in triangle&amp;rdquo; test works correctly, and you&amp;rsquo;re done. (If you don&amp;rsquo;t know all your points ahead of time, you can make any triangle and resize it as you learn points.)&lt;/p>
&lt;h1 id="are-we-there-yet">are we there yet&lt;/h1>
&lt;p>Yes! We did it. That&amp;rsquo;s the algorithm. And the data structure. Two for the price of one.&lt;/p>
&lt;p>Now that you understand how it works, in far more detail than you ever wanted to, let&amp;rsquo;s watch the visualization again. I&amp;rsquo;m excluding the boundary triangle this time so we can zoom in a bit and just bask in the triangular glow.&lt;/p>
&lt;p class="canvas-container">&lt;canvas id="final-demonstration" width="384" height="256" tabindex="1" class="interactive">&lt;/canvas>&lt;/p>
&lt;p class="slideshow-controls">&lt;button data-action="pause">❙❙&lt;/button>&lt;button data-action="play">▶&lt;/button>&lt;button data-action="fast">▶▶&lt;/button>&lt;input type="range" class="scrubber" value="0" min="0" max="0" />&lt;/p>
&lt;p>Well, that&amp;rsquo;s it. From here it&amp;rsquo;s a short, linear-time jaunt to computing the Voronoi diagram, the Gabriel graph, and probably some other interesting stuff. But that&amp;rsquo;ll have to wait for the next post.&lt;/p>
&lt;p>&lt;em>Speaking of next posts,&lt;/em> if you enjoyed this one, don&amp;rsquo;t forget to&amp;hellip;&lt;/p>
&lt;p>No. I can&amp;rsquo;t actually say it.&lt;/p>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>You might be wondering why you would want to construct a Voronoi diagram in the first place, to which I can only respond: why &lt;em>wouldn&amp;rsquo;t&lt;/em> you? They&amp;rsquo;re so cool!&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2" role="doc-endnote">
&lt;p>I googled this phrase before I wrote this post to make sure that this actually is real slang. It seems that &amp;ldquo;smash that like button&amp;rdquo; is a more common phrase, but I don&amp;rsquo;t have a like button. Should I have a like button? Is that a thing that people would smash? What is&amp;hellip; what are we doing here?&lt;/p>
&lt;p>Here, if you really want to smash a like button, you can smash this one:&lt;/p>
&lt;p class="button-container">&lt;input class="smashable" type="button" value="Like" />&lt;/p>
&lt;p>Did that feel good? Did you enjoy that?&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3" role="doc-endnote">
&lt;p>Actually, what you&amp;rsquo;re seeing here is not &lt;em>exactly&lt;/em> the Gabriel graph &amp;ndash; it&amp;rsquo;s the union of the Gabriel graph and the convex hull, so that the outline of the button is always solid.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4" role="doc-endnote">
&lt;p>The Delaunay triangulation is not always unique: imagine four points arranged in a square. There are two ways to cut the diagonal, and either choice gives you a valid Delaunay triangulation. But it is only not unique in the case of co-circular points like that, and this randomly generated example doesn&amp;rsquo;t have any.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:5" role="doc-endnote">
&lt;p>In the same paper, Guibas &amp;amp; Stolfi describe another algorithm &amp;ndash; a divide-and-conquer algorithm, kinda like merge-sort &amp;ndash; that &lt;em>seems&lt;/em> more complex, but is actually a lot simpler to code. Originally I wanted to describe &lt;em>both&lt;/em> of these algorithms in this blog post, but, well, come on. This is so long already.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:6" role="doc-endnote">
&lt;p>Fun fact: this pattern of points is called a &lt;a href="https://en.wikipedia.org/wiki/Quincunx">quincunx&lt;/a>. Say it. Quincunx. &lt;em>say it out loud&lt;/em>&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:7" role="doc-endnote">
&lt;p>Since finding the distance between two points requires taking a square root, you actually compare the &lt;em>square&lt;/em> of the distance against the square of the radius. But you knew that.&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:8" role="doc-endnote">
&lt;p>Citation needed. The test as I presented it requires a division operation, while the determinant calculation only requires multiplication and addition. We can remove the division by subtracting A&lt;sub>z&lt;/sub> multiplying both sides of the inequality by N&lt;sub>z&lt;/sub>, but then we need to invert the logic when N&lt;sub>z&lt;/sub> is negative (which we can do without branching). But I mean&amp;hellip; I neither profiled it nor tallied the individual operations, so I won&amp;rsquo;t try to argue that the plane test is actually faster. Also, like, numerical precision is a thing that computation geometry people worry about? Is the determinant solution more robust in the face of floating-point errors? I don&amp;rsquo;t know.&amp;#160;&lt;a href="#fnref:8" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:9" role="doc-endnote">
&lt;p>More precisely, the dual is only defined for a particular &lt;em>planar embedding&lt;/em> of a graph. There are lots of ways to draw a picture of a graph, and depending on how you draw it, you might end up with a different dual.&amp;#160;&lt;a href="#fnref:9" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:10" role="doc-endnote">
&lt;p>The paper actually describes &lt;em>two&lt;/em> versions of the data structure, first a general version, and then a simplified version that only works for orientable manifolds. What does that mean? I have no idea. I&amp;rsquo;ve never studied topology. Manifolds are a topology thing, right? Also sorry I know I said I wouldn&amp;rsquo;t say manifold and now I&amp;rsquo;ve said it a bunch.&amp;#160;&lt;a href="#fnref:10" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:11" role="doc-endnote">
&lt;p>For a less straightforward representation, I was able to &lt;a href="https://www.ic.unicamp.br/~stolfi/EXPORT/software/c/2000-05-04/libquad/quad.h">find some code on Stolfi&amp;rsquo;s university homepage&lt;/a> that uses a more space-efficient representation. Paraphrasing slightly:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="k">typedef&lt;/span> &lt;span class="kt">char&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">quad_edge_ref&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">typedef&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="n">quad_edge_ref&lt;/span> &lt;span class="n">next&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="kt">void&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="p">}&lt;/span> &lt;span class="n">quad_edge&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>The idea here is that a &lt;code>quad_edge_ref&lt;/code> is really a pointer to the &lt;em>interior&lt;/em> of a &lt;code>quad_edge&lt;/code> &amp;ndash; anywhere from 0 to 3 bytes from the struct&amp;rsquo;s base address &amp;ndash; and you use bitmasking operations on the pointer to identify &lt;code>my_index&lt;/code> and to compute the actual base address:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="c1">// 32 bits ought to be enough for anybody
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">quad_edge_ref&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nf">rot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">quad_edge_ref&lt;/span> &lt;span class="n">edge&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">edge&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="mh">0xfffffffcu&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="n">edge&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="mi">3u&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">quad_edge_ref&lt;/span> &lt;span class="nf">next&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">quad_edge_ref&lt;/span> &lt;span class="n">edge&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="n">quad_edge&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">edge&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="mh">0xfffffffcu&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">next&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">edge&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="mi">3u&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>So it only works if you know how your struct is aligned, and the size of your pointers, and&amp;hellip; look maybe this sort of thing could fly in 1985 but I will speak no more of it here.&amp;#160;&lt;a href="#fnref:11" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:12" role="doc-endnote">
&lt;p>Assuming the points of your triangle are defined in counter-clockwise order in the first place. If you think of triangles as being clockwise to begin with, then everything is backwards. Gosh winding order is confusing.&amp;#160;&lt;a href="#fnref:12" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:13" role="doc-endnote">
&lt;p>&amp;ldquo;Computing Dirichlet tesselation in the plane,&amp;rdquo; 1977.&amp;#160;&lt;a href="#fnref:13" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:14" role="doc-endnote">
&lt;p>There is an even more clever but much more complicated triangle traversal algorithm that works in logarithmic time by building up something called the &amp;ldquo;Delaunay tree.&amp;rdquo; It was first described by Boissonnat &amp;amp; Teillaud in their 1989 paper &amp;ldquo;On the randomized construction of the Delaunay tree.&amp;rdquo; It&amp;rsquo;s conceptually very simple, but I&amp;rsquo;m not going to explain it because just &lt;em>look&lt;/em> at how long this blog post is already. There is a decent overview of it starting on &lt;a href="https://people.csail.mit.edu/indyk/6.838-old/handouts/lec9.pdf">page 46 of this PDF&lt;/a>.&amp;#160;&lt;a href="#fnref:14" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:15" role="doc-endnote">
&lt;p>I assume because you can calculate the average number of edges incident to each vertex and that is going to be some small finite number that doesn&amp;rsquo;t depend on the number of vertices. Like some kind of Euler&amp;rsquo;s formula for Delaunay triangulations. But I don&amp;rsquo;t know. I&amp;rsquo;m sure the proof is out there, somewhere, but I am too tired to look any further.&amp;#160;&lt;a href="#fnref:15" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description><pubDate>Mon, 11 Jul 2022 00:00:00 +0000</pubDate><link>https://ianthehenry.com/posts/delaunay/</link><guid isPermaLink="true">https://ianthehenry.com/posts/delaunay/</guid></item><item><title>zsh-autoquoter makes shell quoting slightly less annoying</title><description>&lt;p>Earlier this year I wrote &lt;a href="https://github.com/ianthehenry/zsh-autoquoter">zsh-autoquoter&lt;/a>, a little zsh extension that automatically quotes arguments to commands of my choice.&lt;/p>
&lt;p style="min-height: 368px;">
(asciinema demo)
&lt;/p>
&lt;p>I&amp;rsquo;ve been using it for a few months now, and I&amp;rsquo;ve gotta say: it&amp;rsquo;s pretty nice! I wish I&amp;rsquo;d written this a long time ago. Here are a few use cases I&amp;rsquo;ve found for it so far:&lt;/p>
&lt;h1 id="1-commit-messages">1. commit messages&lt;/h1>
&lt;p>&lt;code>git&lt;/code> has a neat feature where you can specify a commit message directly on the command line, with the &lt;code>-m&lt;/code> flag:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">$ git commit -m &lt;span class="s1">&amp;#39;fix typo&amp;#39;&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>It&amp;rsquo;s really useful for repositories like this blog, where almost every commit is a short little memo like that. But sometimes I want to write something a little more complicated:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">$ git commit -m &lt;span class="s2">&amp;#34;\&amp;#34;fire\&amp;#34; wasn&amp;#39;t clear; rename to \&amp;#34;fire_the_missiles\&amp;#34;&amp;#34;&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, I like to commit &amp;ldquo;early and often,&amp;rdquo; using a patch queue workflow, and then rebase entire branches in order to get them ready for &amp;ldquo;publication&amp;rdquo; to the outside world. So I do this a lot. And it&amp;rsquo;s not &lt;em>that&lt;/em> annoying to open up a vim buffer to write this, but it&amp;rsquo;s &lt;em>slightly&lt;/em> more friction than just typing it directly.&lt;/p>
&lt;h1 id="2-ssh-commands">2. &lt;code>ssh&lt;/code> commands&lt;/h1>
&lt;p>Sometimes I want to run complicated shell commands on remote servers, with globs and pipelines and quoted arguments and all sorts of things, without actually starting up an interactive session on that server.&lt;/p>
&lt;p>&lt;code>ssh&lt;/code> makes this&amp;hellip; possible. But I find it really difficult to wrangle all the nested quoting that this entails.&lt;/p>
&lt;p>Here&amp;rsquo;s a simple example of what I mean:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">$ ssh user@host &lt;span class="s2">&amp;#34;awk &amp;#39;{print &lt;/span>&lt;span class="nv">$1&lt;/span>&lt;span class="s2">}&amp;#39; file.txt&amp;#34;&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>See the problem? &lt;code>$1&lt;/code> is going to be expanded as a shell variable, and fortunately syntax highlighting makes the mistake obvious. (You do have &lt;a href="https://github.com/zsh-users/zsh-syntax-highlighting">syntax highlighting&lt;/a> in your shell, right?)&lt;/p>
&lt;p>To pass a literal &lt;code>$1&lt;/code> to &lt;code>awk&lt;/code>, you &lt;em>actually&lt;/em> have to write:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">ssh user@host &lt;span class="s1">&amp;#39;awk &amp;#34;{print $1}&amp;#34; file.txt&amp;#39;&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Except, of course, that&amp;rsquo;s still wrong. You&amp;rsquo;ve just moved the problem to the remote server. You &lt;em>actually actually&lt;/em> have to write something like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">$ ssh user@host &lt;span class="s2">&amp;#34;awk &amp;#39;{print \$1}&amp;#39; file.txt&amp;#34;&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>(Or is it &lt;code>\\$1&lt;/code>?)&lt;/p>
&lt;p>But with zsh-autoquoter, you don&amp;rsquo;t have to do any of that. You just type the obvious thing; the same thing you would type if you were running a local command:&lt;/p>
&lt;pre>&lt;code>$ ssh user@host awk '{print $1}' file.txt
&lt;/code>&lt;/pre>
&lt;p>And it will do the thing you want.&lt;/p>
&lt;h1 id="3-todo-lists">3. &lt;code>todo&lt;/code> lists&lt;/h1>
&lt;p>Once upon a time I wrote a little command-line todo list app to help keep track of my stack at work.&lt;/p>
&lt;pre>&lt;code>todo 'write a blog post about zsh-autoquoter'
&lt;/code>&lt;/pre>
&lt;p>I liked the app; I liked the interface; I liked how simple it was. But it suffered from a simple ergonomic problem: English strings are really annoying to type on the command line.&lt;/p>
&lt;pre>&lt;code>todo 'rework dackery'\''s speech about the &amp;quot;hex machine&amp;quot;'
&lt;/code>&lt;/pre>
&lt;p>But now I can add a zsh-autoquoter rule for it, and give it another try.&lt;/p>
&lt;h1 id="4-sd---new">4. &lt;code>sd --new&lt;/code>&lt;/h1>
&lt;p>A neat feature of &lt;a href="https://ianthehenry.com/posts/a-cozy-nest-for-your-scripts/">&lt;code>sd&lt;/code>&lt;/a> is the ability to quickly stash useful oneliners:&lt;/p>
&lt;pre>&lt;code>sd video poster --new 'ffmpeg -i &amp;quot;$1&amp;quot; -vframes 1 &amp;quot;${basename $1}-poster.png&amp;quot; -y'
&lt;/code>&lt;/pre>
&lt;p>That &lt;em>particular&lt;/em> example happened to fit into single quotes. But I would really like to be able to stash arbitrary commands without worrying about how to escape them. Like this one, for example:&lt;/p>
&lt;pre>&lt;code>nix-env -qaA &amp;quot;nixpkgs.$1&amp;quot; --json | jq -r '.[] | .name + &amp;quot; &amp;quot; + .meta.description, &amp;quot;&amp;quot;, (.meta.longDescription | rtrimstr(&amp;quot;\n&amp;quot;))'
&lt;/code>&lt;/pre>
&lt;p>How am I supposed to quote &lt;em>that&lt;/em> monstrosity? Well, with zsh-autoquoter, of course.&lt;/p>
&lt;h1 id="5-notes-to-self">5. &lt;code>note&lt;/code>s to self&lt;/h1>
&lt;p>For many years I have been taking notes in a simple text file, using a Mac GUI application called &lt;a href="https://www.alfredapp.com">Alfred&lt;/a>. I press a shortcut, Alfred pops up, and I type my note:&lt;/p>
&lt;a class="image-container" href="https://ianthehenry.com/posts/zsh-autoquoter/alfred_hu9109df11ae5e20ae44a255e1d26d4b3e_52685_1246x1246_fit_box_3.png">&lt;picture>&lt;source type="image/webp"
srcset="https://ianthehenry.com/posts/zsh-autoquoter/alfred_hu9109df11ae5e20ae44a255e1d26d4b3e_52685_623x623_fit_q75_h1_box_3.webp 623w, https://ianthehenry.com/posts/zsh-autoquoter/alfred_hu9109df11ae5e20ae44a255e1d26d4b3e_52685_1246x1246_fit_q75_h1_box_3.webp 1246w, https://ianthehenry.com/posts/zsh-autoquoter/alfred_hu9109df11ae5e20ae44a255e1d26d4b3e_52685_375x375_fit_q75_h1_box_3.webp 375w, https://ianthehenry.com/posts/zsh-autoquoter/alfred_hu9109df11ae5e20ae44a255e1d26d4b3e_52685_750x750_fit_q75_h1_box_3.webp 750w"
sizes="(max-width: 400px) 375px, 623px">&lt;img
class=""
srcset="https://ianthehenry.com/posts/zsh-autoquoter/alfred_hu9109df11ae5e20ae44a255e1d26d4b3e_52685_623x623_fit_box_3.png 623w, https://ianthehenry.com/posts/zsh-autoquoter/alfred_hu9109df11ae5e20ae44a255e1d26d4b3e_52685_1246x1246_fit_box_3.png 1246w, https://ianthehenry.com/posts/zsh-autoquoter/alfred_hu9109df11ae5e20ae44a255e1d26d4b3e_52685_375x375_fit_box_3.png 375w, https://ianthehenry.com/posts/zsh-autoquoter/alfred_hu9109df11ae5e20ae44a255e1d26d4b3e_52685_750x750_fit_box_3.png 750w"
alt=""
title=""
sizes="(max-width: 400px) 375px, 623px"
width="623"
height="187"
style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAACCAYAAABllJ3tAAAATElEQVR4nGI5f/bCpC8fv1n/&amp;#43;/uP4f///wyCggIMzMwsDCDAxMx4lIWDhevzlZMP33z/8pPhx/fvDAwMjAxs7GxgBXzC3J8BAQAA///VWxiK9PaARwAAAABJRU5ErkJggg==);"
/>&lt;/picture>&lt;/a>
&lt;p>Using Alfred for this is a little silly, and I would &lt;em>like&lt;/em> to replace this &amp;ldquo;workflow&amp;rdquo; with this tiny shell script:&lt;/p>
&lt;pre>&lt;code>$ cat ~/bin/note
&lt;/code>&lt;/pre>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="cp">#!/usr/bin/env bash
&lt;/span>&lt;span class="cp">&lt;/span>
&lt;span class="nb">set&lt;/span> -euo pipefail
&lt;span class="nb">printf&lt;/span> &lt;span class="s1">&amp;#39;%s %s\n&amp;#39;&lt;/span> &lt;span class="k">$(&lt;/span>date &lt;span class="s1">&amp;#39;+%Y-%m-%d&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$1&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &amp;gt;&amp;gt; ~/Dropbox/notes.txt
&lt;/code>&lt;/pre>&lt;/div>&lt;p>But these notes can be long, free-from things. It would have been a nightmare to write something like this, for example:&lt;/p>
&lt;pre tabindex="0">&lt;code>2022-01-08 TIL about SSH &amp;quot;key types&amp;quot; and &amp;quot;signature types&amp;quot; being different. if
i want to use this key i have to configure sshd to accept ssh-rsa-sha2-256, not
just ssh-rsa. the error message says that ssh-rsa isn't supported, but it
actually means this particular signature type
&lt;/code>&lt;/pre>&lt;p>But not anymore!&lt;/p>
&lt;h1 id="those-are-all-of-the-examples">those are all of the examples&lt;/h1>
&lt;p>Tempted? &lt;a href="https://github.com/ianthehenry/zsh-autoquoter">Give it a spin!&lt;/a> The extension should work with any zsh plugin manager, and the readme explains how to configure it and enable the (optional) &lt;a href="https://github.com/zsh-users/zsh-syntax-highlighting">zsh-syntax-highlighting&lt;/a> integration.&lt;/p>
&lt;p>Apparently you&amp;rsquo;re supposed to end posts like this with a prominent call to action, so &lt;a href="https://en.wikipedia.org/wiki/Luna_moth">go check out this crazy moth I found on Wikipedia&lt;/a>.&lt;/p></description><pubDate>Sun, 08 May 2022 00:00:00 +0000</pubDate><link>https://ianthehenry.com/posts/zsh-autoquoter/</link><guid isPermaLink="true">https://ianthehenry.com/posts/zsh-autoquoter/</guid></item><item><title>A cozy nest for your scripts</title><description>&lt;p>&lt;a href="https://github.com/ianthehenry/sd">&lt;code>sd&lt;/code>&lt;/a> is a tool for running scripts in your &lt;code>s&lt;/code>cript &lt;code>d&lt;/code>irectory.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ tree ~/sd
/Users/ian/sd
├── blog
│ ├── edit
│ ├── preview
│ └── publish
├── book
│ ├── open
│ ├── words
│ └── typeset
├── nix
│ ├── diff
│ ├── info
│ ├── install
│ ├── shell
│ └── sync
└── video
└── fix
&lt;/code>&lt;/pre>&lt;p>Directories become command groups; executables become subcommands. Instead of a flat &lt;code>~/bin&lt;/code>, you have a cozy nest for your scripts. And you can type:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ sd blog publish --deploy
&lt;/code>&lt;/pre>&lt;p>To run:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ ~/sd/blog/publish --deploy
&lt;/code>&lt;/pre>&lt;p>Which, okay, sure. That&amp;rsquo;s not that exciting.&lt;/p>
&lt;p>The exciting thing is that &lt;code>sd&lt;/code> supports &lt;em>fancy autocompletion&lt;/em>. Take a look:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ sd nix &amp;lt;TAB&amp;gt;
diff -- prints what will happen if you run sync
info -- &amp;lt;package&amp;gt; prints package description
install -- &amp;lt;package&amp;gt; use --latest to install from nixpkgs-unstable
shell -- add gcroots for shell.nix
sync -- make user environment match ~/dotfiles/user.nix
&lt;/code>&lt;/pre>&lt;p>&lt;code>sd&lt;/code> pulls those descriptions from comments in the scripts themselves:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ sd nix info --cat
&lt;/code>&lt;/pre>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="cp">#!/usr/bin/env bash
&lt;/span>&lt;span class="cp">&lt;/span>
&lt;span class="c1"># &amp;lt;package&amp;gt; prints package description&lt;/span>
&lt;span class="nb">set&lt;/span> -euo pipefail
nix-env -qaA &lt;span class="s2">&amp;#34;nixpkgs.&lt;/span>&lt;span class="nv">$1&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> --json &lt;span class="se">\
&lt;/span>&lt;span class="se">&lt;/span>&lt;span class="p">|&lt;/span> jq -r &lt;span class="s1">&amp;#39;.[] | .name + &amp;#34; &amp;#34; + .meta.description,
&lt;/span>&lt;span class="s1"> &amp;#34;&amp;#34;,
&lt;/span>&lt;span class="s1"> (.meta.longDescription | rtrimstr(&amp;#34;\n&amp;#34;))&amp;#39;&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>So they&amp;rsquo;re very easy to write.&lt;/p>
&lt;p>&lt;code>sd&lt;/code> also makes it easy to create &lt;em>new&lt;/em> scripts:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ sd blog laud --new 'echo &amp;quot;good blog ian&amp;quot;'
$ sd blog laud
good blog ian
&lt;/code>&lt;/pre>&lt;p>So you can easily stash oneliners, and you don&amp;rsquo;t have to remember to &lt;code>chmod +x&lt;/code>. (You can also call &lt;code>--new&lt;/code> without providing an initial script, to go straight into your text editor.)&lt;/p>
&lt;aside>
&lt;p>And if you use &lt;a href="https://github.com/ianthehenry/zsh-autoquoter">zsh-autoquoter&lt;/a>, you can add a rule that lets you just write:&lt;/p>
&lt;pre>&lt;code>sd plugin laud --new echo &amp;quot;good plugin ian&amp;quot;
^^^^^^^^^^^^^^^^^^^^^^
&lt;/code>&lt;/pre>
&lt;p>Which can be helpful for stashing complex commands. Here&amp;rsquo;s the rule I use:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-zsh" data-lang="zsh">&lt;span class="nv">ZAQ_PREFIXES&lt;/span>&lt;span class="o">+=(&lt;/span>&lt;span class="s1">&amp;#39;sd [^ ]* --new&amp;#39;&lt;/span>&lt;span class="o">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;/aside>
&lt;p>You can also modify the template for new scripts, on a per-directory basis. Although new scripts default to a fairly reasonable &lt;code>bash&lt;/code>:&lt;/p>
&lt;pre>&lt;code>$ sd blog laud --edit
&lt;/code>&lt;/pre>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash"> &lt;span class="m">1&lt;/span> &lt;span class="c1">#!/usr/bin/env bash&lt;/span>
&lt;span class="m">2&lt;/span>
&lt;span class="m">3&lt;/span> &lt;span class="nb">set&lt;/span> -euo pipefail
&lt;span class="m">4&lt;/span>
&lt;span class="m">5&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s1">&amp;#39;good blog ian&amp;#39;&lt;/span>
~
~
&lt;span class="s2">&amp;#34;~/sd/blog/laud&amp;#34;&lt;/span> 5L, 61B
&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="who-cares">who cares&lt;/h1>
&lt;p>Take a peek at your &lt;code>~/bin&lt;/code>. How many scripts there have names like &lt;code>process&lt;/code> or &lt;code>run&lt;/code> or &lt;code>deploy&lt;/code>? How many of them have you forgotten ever writing?&lt;/p>
&lt;p>If the answer is &amp;ldquo;none of them,&amp;rdquo; &lt;code>sd&lt;/code> might not be for you. It sounds like you&amp;rsquo;ve got this figured out already. Good work.&lt;/p>
&lt;p>But &lt;code>sd&lt;/code> is for script hoarders, who want to rage against the chaos of their &lt;code>~/bin&lt;/code>. And &lt;code>sd&lt;/code> is for people who spend a lot of time combing through &lt;code>ctrl-r&lt;/code>, looking for that oneliner that they never thought to make into a script, because it was too much of a hassle and who knows if they&amp;rsquo;ll ever run it again.&lt;/p>
&lt;p>Give it a name! Give it a meaningful command path. Hoard more. The cozy nest is never crowded.&lt;/p>
&lt;h1 id="sd-a-history">&lt;code>sd&lt;/code>: a history&lt;/h1>
&lt;p>A year ago I published &lt;a href="https://ianthehenry.com/posts/sd-my-script-directory/">my first post about &lt;code>sd&lt;/code>&lt;/a>.&lt;/p>
&lt;p>At the time, &lt;code>sd&lt;/code> was a bundle of assorted shell scripts that I cobbled together one afternoon in 2018. I only &amp;ldquo;publicized&amp;rdquo; it all because I wanted to refer to it from &lt;a href="https://ianthehenry.com/posts/how-to-learn-nix/switching-from-homebrew-to-nix/">another blog post&lt;/a> I was writing &amp;ndash; I didn&amp;rsquo;t even really launch it in any usable form; I just described the idea and linked to &lt;a href="https://github.com/ianthehenry/dotfiles">my dotfiles&lt;/a>. I didn&amp;rsquo;t even bother giving it its own repo!&lt;/p>
&lt;p>But even in that barely-baked form, the project got a small amount of traction. People reached out to me about it &amp;ndash; people who actually wanted to &lt;em>use&lt;/em> &lt;code>sd&lt;/code>. Which gave me a very good excuse to buckle down and rewrite it, and I&amp;rsquo;m happy to announce that &lt;code>sd&lt;/code> recently reached &amp;ldquo;1.0&amp;rdquo; status. It is officially &lt;em>a thing that exists that you can use&lt;/em>, instead of &amp;ldquo;an idea I had that you can implement.&amp;rdquo;&lt;/p>
&lt;p>And the thing that exists is &lt;em>much better&lt;/em> than the thing I described a year ago. In a few ways:&lt;/p>
&lt;p>First off, autocomplete is &lt;em>much faster&lt;/em> than the original version. It used to run &lt;code>find&lt;/code>, but now it uses some fancy shell globbing instead. There used to be the tinest delay after pressing tab, but now autocomplete feels instantenous. Which is how autocomplete &lt;em>should&lt;/em> feel.&lt;/p>
&lt;p>Second off, &lt;code>sd&lt;/code> now works as a &lt;em>shell function&lt;/em> in addition to working as a standalone executable &amp;ndash; mostly because it&amp;rsquo;s very easy to install it and set up completion and everything with a shell plugin manager. &lt;code>sd&lt;/code> is not officially packaged anywhere that I&amp;rsquo;m aware of, so this is a pretty convenient way to install it, if you already use a zsh plugin manager.&lt;/p>
&lt;p>But the biggest change is an ergonomic one.&lt;/p>
&lt;p>The original &lt;code>sd&lt;/code> didn&amp;rsquo;t support any flags: the &lt;em>only&lt;/em> thing it could do was run scripts in the &lt;code>~/sd&lt;/code> directory. It came with some &amp;ldquo;built-in&amp;rdquo; commands, like &lt;code>sd edit&lt;/code>, but those were just scripts like any other: &lt;code>~/sd/edit&lt;/code>. This was &lt;em>elegant&lt;/em>, I suppose, but I really just did it this way because it was easy to implement and I was lazy.&lt;/p>
&lt;p>There were a lot of downsides to that approach. It made installation nontrivial, because you had to bootstrap your &lt;code>~/sd&lt;/code> directory with these &amp;ldquo;built-in&amp;rdquo; commands. There was no code sharing between these scripts &amp;ndash; except by &lt;em>assuming&lt;/em> that certain scripts existed in &lt;code>~/sd&lt;/code>, and failing horribly if they didn&amp;rsquo;t. There was no autocomplete for the built-in commands, so if you ran &lt;code>sd edit some command&lt;/code> you had to type &lt;code>some command&lt;/code> yourself. And when you were autocompleting a command at the top-level, you had to tab past all of these &amp;ldquo;built-in&amp;rdquo; scripts before you got to the command group that you wanted.&lt;/p>
&lt;p>The right answer was clearly to support &lt;code>sd some command --edit&lt;/code>, but I hesitated adding argument parsing for a while, because I didn&amp;rsquo;t like the idea of hijacking arguments. What if you wrote a script that expected a &lt;code>--help&lt;/code>?&lt;/p>
&lt;p>I got over this, eventually. Mostly because &lt;em>I have never written a script that used &lt;code>--help&lt;/code>&lt;/em> in my &lt;code>~/sd&lt;/code>. Writing a script that parsed its arguments would imply that I was writing something other than a quick bash script, which would imply that &lt;code>sd&lt;/code> might not be the best place for it.&lt;/p>
&lt;p>But I added an escape hatch &lt;em>just in case:&lt;/em> you can run &lt;code>sd some command --help --really&lt;/code>, and &lt;code>sd&lt;/code> will execute &lt;code>~/sd/some/command --help&lt;/code>. (And yes, you can also run &lt;code>sd some command --help --really --really&lt;/code> if your script expects a &lt;code>--really&lt;/code> flag. Have you ever written a script that expects a &lt;code>--really&lt;/code> flag? I want to hear about that. That sounds like a fun script.)&lt;/p>
&lt;h1 id="the-future-of-sd">the future of &lt;code>sd&lt;/code>&lt;/h1>
&lt;p>I dunno, it&amp;rsquo;s just a cute little program? It doesn&amp;rsquo;t have much of a future. Check it out. &lt;a href="https://github.com/ianthehenry/sd">Nourish my fragile ego by lavishing stars upon the repo.&lt;/a>&lt;/p></description><pubDate>Fri, 11 Mar 2022 00:00:00 +0000</pubDate><link>https://ianthehenry.com/posts/a-cozy-nest-for-your-scripts/</link><guid isPermaLink="true">https://ianthehenry.com/posts/a-cozy-nest-for-your-scripts/</guid></item></channel></rss>