<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Elijah Potter's Blog</title>
        <link>https://elijahpotter.dev</link>
        <description>The writings of Elijah Potter</description>
        <lastBuildDate>Wed, 22 Jul 2026 23:05:53 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <ttl>60</ttl>
        <copyright>All rights reserved 2025, Elijah Potter</copyright>
        <item>
            <title><![CDATA[Harper's Testing Strategy]]></title>
            <link>https://elijahpotter.dev/articles/harper's_testing_strategy</link>
            <guid>https://elijahpotter.dev/articles/harper's_testing_strategy</guid>
            <pubDate>Fri, 17 Jul 2026 22:13:54 GMT</pubDate>
            <description><![CDATA[<p>After spend­ing a few days dis­cussing and learn­ing from Automattic’s Chief Quality Ofﬁcer (Lance Willet), I’ve pre­pared a ﬁrst draft of <a href="https://writewithharper.com/docs/contributors/testing-strategy">Harper’s test­ing strat­egy.</a>
I hope you all take a look. I am very in­ter­ested in any and all feed­back you may have.</p>]]></description>
            <content:encoded><![CDATA[<p>After spend­ing a few days dis­cussing and learn­ing from Automattic’s Chief Quality Ofﬁcer (Lance Willet), I’ve pre­pared a ﬁrst draft of <a href="https://writewithharper.com/docs/contributors/testing-strategy">Harper’s test­ing strat­egy.</a>
I hope you all take a look. I am very in­ter­ested in any and all feed­back you may have.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dealing with Flaky Tests]]></title>
            <link>https://elijahpotter.dev/articles/dealing_with_flaky_tests</link>
            <guid>https://elijahpotter.dev/articles/dealing_with_flaky_tests</guid>
            <pubDate>Fri, 10 Jul 2026 20:46:48 GMT</pubDate>
            <description><![CDATA[<h1>Dealing with Flaky Tests</h1>
<p>For the past day or so, I have had my head down, tear­ing my hair out, try­ing to de­bug a sig­nif­i­cant block caused by many en­gi­neers’ arch neme­sis: a ﬂaky test.
In my case, it’s ac­tu­ally been sev­eral ﬂaky tests.</p>
<p>A ﬂaky test is any test for your code that only <em>some­times</em> fails.
It shows up when a part of your test re­lies on some­thing non-de­ter­min­is­tic.
Non-de­ter­min­is­tic com­po­nents can in­clude:</p>
<ul>
<li>Fetching re­sources over a net­work con­nec­tion.</li>
<li>Reading from a ran­dom num­ber gen­er­a­tor.</li>
<li>Using a non-de­ter­min­is­tic asyn­chro­nous sched­uler.</li>
<li>Animations (often use the <span class="small-caps">GPU</span>).</li>
<li>…and many more.</li>
</ul>
<p>If your ap­pli­ca­tion in­cludes code that looks any­thing like these, you are at risk for ﬂaky tests.</p>
<p>My use-case hap­pened to be es­pe­cially vul­ner­a­ble to these, since our in­te­gra­tion tests run against a wide va­ri­ety of text ed­i­tors.
<a href="./integration_testing_thousands_of_sites_with_playwright">See my orig­i­nal blog post for more de­tails.</a></p>
<h2>Flaky Tests <strong><span class="small-caps">SUCK</span></strong></h2>
<p>It’s hard to ex­press how frus­trat­ing a ﬂaky test can be if you have not ex­pe­ri­enced a bad one your­self.
In short, they slow de­vel­op­ment time, cause un­in­ten­tion­ally brit­tle soft­ware, and they can cre­ate a lot of frus­tra­tion for users, since the is­sues they al­low into pro­duc­tion are in­con­sis­tent.
They make all the ef­fort put into pro­duc­ing tests, wasted.</p>
<p>Tests are de­signed to catch bugs be­fore they get into pro­duc­tion.
If a test ex­pects cer­tain be­hav­ior out of non-de­ter­min­is­tic sys­tems, these tests can pass lo­cally and in con­tin­u­ous in­te­gra­tion, but fail on a user’s de­vice.</p>
<p>This is a prob­lem pri­mar­ily for users, but also for de­vel­op­ers.
It’s good if a ﬂaky test fails in CI.
After all, that is how you know it ex­ists.
But it is very bad if a ﬂaky test fails in <span class="small-caps">CI</span> and the orig­i­nal au­thor of the test is no longer around.
In that case, it be­comes dif­ﬁ­cult to de­ter­mine which non-de­ter­min­is­tic sys­tem is caus­ing the fail­ure, and thus how to ﬁx the un­der­ly­ing bug rep­re­sented by the ﬂak­i­ness.</p>
<h2>Prevention</h2>
<p>The best way to get around ﬂaky tests is to make it as dif­ﬁ­cult as pos­si­ble to cre­ate them in the ﬁrst place.</p>
<p>When I ﬁrst started us­ing <a href="./integration_testing_thousands_of_sites_with_playwright">Playwright</a> about a year ago, I did­n’t think too much about its con­ﬁg­u­ra­tion.
Boy was that a mis­take.</p>
<p>I re­ally like <a href="https://en.wikipedia.org/wiki/Omakase">Omakase</a> soft­ware, and I had (mistakely) as­sumed that Playwright was an ex­am­ple of it.
Thus, I as­sumed a de­fault con­ﬁg­u­ra­tion for my pro­ject and let it be.
While Playwright is a phe­nom­e­nal tool, I ended up be­ing wrong.
The de­faults I re­ceived from my pro­ject tem­plate were not op­ti­mal, and they would sub­tly waste a lot of my time over the next year.
You see, there is con­ﬁg­u­ra­tion for Playwright that can pre­vent most ﬂaky tests from en­ter­ing your code, and they aren’t set up by de­fault.</p>
<p>To avoid most is­sues, change these two set­tings:</p>
<pre><code class="hljs language-typescript">export default defineConfig({
    retries: 0, // Was 4 by default.
    repeatEach: 2, // Was 0 by default.
});
</code></pre>
<p>In my pro­ject, I added a small nudge to dis­cour­age peo­ple from re­mov­ing these:</p>
<pre><code class="hljs language-typescript">export default defineConfig({
	/** Extremely important to avoid flaky tests. DO NOT CHANGE or I will kill you. */
	retries: 0,
	/** Extremely important to avoid flaky tests. DO NOT CHANGE or I will kill you. */
	repeatEach: 2,
});
</code></pre>
<p>I jest, of course.</p>
<p>What do they do?
They are pretty self-ex­plana­tory.
<code>retries: 0</code> will con­ﬁg­ure Playwright to <em>never</em> retry a test if it fails.
If it fails, it is re­ported as a fail­ure and your con­tin­u­ous in­te­gra­tion will stop, forc­ing a hu­man to deal with it.
<code>repeatEach: 2</code> will run each test twice. A po­ten­tial ﬂaky test might pass on the ﬁrst round, but is much less likely to pass twice.
Some ﬂaky tests may still sneak through.
You may want to con­ﬁg­ure it more ag­gres­sively in some stages of your CI/CD pipeline.</p>
<p>I have no­ticed that <span class="small-caps">AI</span> agents are es­pe­cially prone to pro­duc­ing ﬂaky tests, since they will of­ten sim­ply run a test again and again un­til it passes.</p>
<h2>Debugging</h2>
<p>As for de­bug­ging, I can pro­vide no bet­ter ad­vice than I typ­i­cally do:</p>
<ul>
<li>Use a de­bug­ger.</li>
<li>Use <code>git bisect</code> if nec­es­sary (or have an agent do it).</li>
<li>Work slowly and me­thod­i­cally. Slow is smooth, smooth is fast.</li>
</ul>]]></description>
            <content:encoded><![CDATA[<h1>Dealing with Flaky Tests</h1>
<p>For the past day or so, I have had my head down, tear­ing my hair out, try­ing to de­bug a sig­nif­i­cant block caused by many en­gi­neers’ arch neme­sis: a ﬂaky test.
In my case, it’s ac­tu­ally been sev­eral ﬂaky tests.</p>
<p>A ﬂaky test is any test for your code that only <em>some­times</em> fails.
It shows up when a part of your test re­lies on some­thing non-de­ter­min­is­tic.
Non-de­ter­min­is­tic com­po­nents can in­clude:</p>
<ul>
<li>Fetching re­sources over a net­work con­nec­tion.</li>
<li>Reading from a ran­dom num­ber gen­er­a­tor.</li>
<li>Using a non-de­ter­min­is­tic asyn­chro­nous sched­uler.</li>
<li>Animations (often use the <span class="small-caps">GPU</span>).</li>
<li>…and many more.</li>
</ul>
<p>If your ap­pli­ca­tion in­cludes code that looks any­thing like these, you are at risk for ﬂaky tests.</p>
<p>My use-case hap­pened to be es­pe­cially vul­ner­a­ble to these, since our in­te­gra­tion tests run against a wide va­ri­ety of text ed­i­tors.
<a href="./integration_testing_thousands_of_sites_with_playwright">See my orig­i­nal blog post for more de­tails.</a></p>
<h2>Flaky Tests <strong><span class="small-caps">SUCK</span></strong></h2>
<p>It’s hard to ex­press how frus­trat­ing a ﬂaky test can be if you have not ex­pe­ri­enced a bad one your­self.
In short, they slow de­vel­op­ment time, cause un­in­ten­tion­ally brit­tle soft­ware, and they can cre­ate a lot of frus­tra­tion for users, since the is­sues they al­low into pro­duc­tion are in­con­sis­tent.
They make all the ef­fort put into pro­duc­ing tests, wasted.</p>
<p>Tests are de­signed to catch bugs be­fore they get into pro­duc­tion.
If a test ex­pects cer­tain be­hav­ior out of non-de­ter­min­is­tic sys­tems, these tests can pass lo­cally and in con­tin­u­ous in­te­gra­tion, but fail on a user’s de­vice.</p>
<p>This is a prob­lem pri­mar­ily for users, but also for de­vel­op­ers.
It’s good if a ﬂaky test fails in CI.
After all, that is how you know it ex­ists.
But it is very bad if a ﬂaky test fails in <span class="small-caps">CI</span> and the orig­i­nal au­thor of the test is no longer around.
In that case, it be­comes dif­ﬁ­cult to de­ter­mine which non-de­ter­min­is­tic sys­tem is caus­ing the fail­ure, and thus how to ﬁx the un­der­ly­ing bug rep­re­sented by the ﬂak­i­ness.</p>
<h2>Prevention</h2>
<p>The best way to get around ﬂaky tests is to make it as dif­ﬁ­cult as pos­si­ble to cre­ate them in the ﬁrst place.</p>
<p>When I ﬁrst started us­ing <a href="./integration_testing_thousands_of_sites_with_playwright">Playwright</a> about a year ago, I did­n’t think too much about its con­ﬁg­u­ra­tion.
Boy was that a mis­take.</p>
<p>I re­ally like <a href="https://en.wikipedia.org/wiki/Omakase">Omakase</a> soft­ware, and I had (mistakely) as­sumed that Playwright was an ex­am­ple of it.
Thus, I as­sumed a de­fault con­ﬁg­u­ra­tion for my pro­ject and let it be.
While Playwright is a phe­nom­e­nal tool, I ended up be­ing wrong.
The de­faults I re­ceived from my pro­ject tem­plate were not op­ti­mal, and they would sub­tly waste a lot of my time over the next year.
You see, there is con­ﬁg­u­ra­tion for Playwright that can pre­vent most ﬂaky tests from en­ter­ing your code, and they aren’t set up by de­fault.</p>
<p>To avoid most is­sues, change these two set­tings:</p>
<pre><code class="hljs language-typescript">export default defineConfig({
    retries: 0, // Was 4 by default.
    repeatEach: 2, // Was 0 by default.
});
</code></pre>
<p>In my pro­ject, I added a small nudge to dis­cour­age peo­ple from re­mov­ing these:</p>
<pre><code class="hljs language-typescript">export default defineConfig({
	/** Extremely important to avoid flaky tests. DO NOT CHANGE or I will kill you. */
	retries: 0,
	/** Extremely important to avoid flaky tests. DO NOT CHANGE or I will kill you. */
	repeatEach: 2,
});
</code></pre>
<p>I jest, of course.</p>
<p>What do they do?
They are pretty self-ex­plana­tory.
<code>retries: 0</code> will con­ﬁg­ure Playwright to <em>never</em> retry a test if it fails.
If it fails, it is re­ported as a fail­ure and your con­tin­u­ous in­te­gra­tion will stop, forc­ing a hu­man to deal with it.
<code>repeatEach: 2</code> will run each test twice. A po­ten­tial ﬂaky test might pass on the ﬁrst round, but is much less likely to pass twice.
Some ﬂaky tests may still sneak through.
You may want to con­ﬁg­ure it more ag­gres­sively in some stages of your CI/CD pipeline.</p>
<p>I have no­ticed that <span class="small-caps">AI</span> agents are es­pe­cially prone to pro­duc­ing ﬂaky tests, since they will of­ten sim­ply run a test again and again un­til it passes.</p>
<h2>Debugging</h2>
<p>As for de­bug­ging, I can pro­vide no bet­ter ad­vice than I typ­i­cally do:</p>
<ul>
<li>Use a de­bug­ger.</li>
<li>Use <code>git bisect</code> if nec­es­sary (or have an agent do it).</li>
<li>Work slowly and me­thod­i­cally. Slow is smooth, smooth is fast.</li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Neoengineers]]></title>
            <link>https://elijahpotter.dev/articles/neoengineers</link>
            <guid>https://elijahpotter.dev/articles/neoengineers</guid>
            <pubDate>Fri, 03 Jul 2026 21:17:22 GMT</pubDate>
            <description><![CDATA[<h1>Neoengineers</h1>
<blockquote>
<p><a href="https://lukaswerner.com/post/2026-05-27@genz-neoengineer">neo­engi­neer - noun</a></p>
<p>An in­di­vid­ual, part of a resur­gence, who em­braces and re­spects the craft of en­gi­neer­ing. Often en­joys solv­ing hard prob­lems.</p>
</blockquote>
<p>I was re­cently in­tro­duced to the term <a href="https://lukaswerner.com/post/2026-05-27@genz-neoengineer"><span class="push-double"></span><span class="pull-double">“</span>neoengineer”</a>, whose de­f­i­n­i­tion you will ﬁnd above and in the at­tached blog post.
It tick­led my brain in a rare way.</p>
<p>For sev­eral years in a row, my New Year’s res­o­lu­tion was to be­come<span class="push-double"></span> <span class="pull-double">“</span>the most ef­fec­tive per­son pos­si­ble”.
In some ways, that re­mains my pri­mary res­o­lu­tion.
To put it an­other way: I seek to be­come the most ef­fec­tive <strong>en­gi­neer</strong> pos­si­ble.</p>
<p>I think what makes the term<span class="push-double"></span> <span class="pull-double">“</span>neoengineer” so tick­lish to my gray mat­ter is that it draws a line be­tween those who care about the craft and those who do not.
What’s more: the term does not make a lack of care seem like a bad thing.
It is merely a dif­fer­ence in pri­or­i­ties.</p>
<h2>You Can Choose to Be a Neoengineer</h2>
<p>In the dis­course, both within Automattic and at large, we con­stantly re­fer to some un­quan­tiﬁ­able met­ric known as<span class="push-double"></span> <span class="pull-double">“</span>productivity”.
We seek to op­ti­mize it and in­crease it to what­ever ex­tent pos­si­ble.
Why?
Be­cause we be­lieve that it will make us more money, or bring us more pres­tige, or some­how even bring us hap­pi­ness.
I think we are fo­cused on the wrong thing.</p>
<p>When we fo­cus on<span class="push-double"></span> <span class="pull-double">“</span>productivity” we are fo­cus­ing on our out­put.
But be­cause it is in­her­ently un­quan­tiﬁ­able, we are un­able to ob­jec­tively mon­i­tor that out­put.
Be­cause there is no way to quan­tify it, it’s im­pos­si­ble to know if we have im­proved our pro­duc­tiv­ity or not, which re­sults in one of two things.</p>
<p>In one sit­u­a­tion, a per­son can be­lieve an ac­tion, habit, or tool is <strong>more</strong> help­ful than it re­ally is.
You see this all the time with vibe-coders.
They ﬁnd that they are able to <em>pro­duce</em> a ton of stuff, but that stuff has zero to neg­a­tive value, and thus no one con­sumes it.
In this way, they waste a lot of time do­ing some­thing that amounts to very lit­tle.</p>
<p>That is not a knock on vibe-cod­ing.
It’s a knock on a fail­ure to con­sider the value of an ac­tion prop­erly.</p>
<p>In an­other sit­u­a­tion, a per­son can be­lieve an ac­tion, habit, or tool is <strong>less</strong> help­ful than it re­ally is.
I see this of­ten with un­der-plan­ners.
A per­son un­der­es­ti­mates the value of do­ing re­search or pro­duc­ing a plan.
They think,<span class="push-double"></span> <span class="pull-double">“</span>I will just get started and pivot later if nec­es­sary”.
In this way, they spend less time do­ing some­thing help­ful than they should have.</p>
<p>If we change the frame of ref­er­ence a lit­tle, we earn our­selves ac­cess to a neat men­tal trick.
In­stead of ask­ing the ques­tion<span class="push-double"></span> <span class="pull-double">“</span>does this tool make me more pro­duc­tive?”, ask<span class="push-double"></span> <span class="pull-double">“</span>would an ef­fec­tive en­gi­neer use this tool?”
You may re­place<span class="push-double"></span> <span class="pull-double">“</span>this tool” with<span class="push-double"></span> <span class="pull-double">“</span>this habit” or<span class="push-double"></span> <span class="pull-double">“</span>this ac­tion”.</p>
<p>When I do this, I ﬁnd the an­swer changes dras­ti­cally.</p>
<p>Would an ef­fec­tive en­gi­neer choose to learn a com­plex al­go­rithm deeply, or would they choose the easy route and ask ChatGPT to write the code for them?
They would choose the for­mer, since it in­creases their per­sonal level of skill and im­proves main­tain­abil­ity of the pro­ject long-term.</p>
<p>Would an ef­fec­tive en­gi­neer ap­prove code that is dif­ﬁ­cult to read, so they can go ahead and al­lo­cate their men­tal en­ergy to some­thing more pro­duc­tive?
No.
<a href="./get_rid_of_it">They would give care­ful mea­sured feed­back or none at all.</a>
If some­thing else is more im­por­tant than re­view­ing the code, an ef­fec­tive en­gi­neer would do that other thing.</p>
<p>By choos­ing to adopt the iden­tity of an ef­fec­tive en­gi­neer, I cre­ate a habit of choos­ing to do things an ef­fec­tive en­gi­neer would do.
Even if I am not in fact, an ef­fec­tive en­gi­neer, I can come closer to be­com­ing one by adopt­ing that iden­tity.</p>
<h2>The Term Itself</h2>
<p>The only is­sue I have with the term<span class="push-double"></span> <span class="pull-double">“</span>neoengineer” is that when bro­ken down, it means<span class="push-double"></span> <span class="pull-double">“</span>new en­gi­neer”.
Neo- usu­ally refers to some­thing<span class="push-double"></span> <span class="pull-double">“</span>new” or<span class="push-double"></span> <span class="pull-double">“</span>young”.
So a<span class="push-double"></span> <span class="pull-double">“</span>neoengineer” is a young en­gi­neer, per­haps of the lat­est gen­er­a­tion.</p>
<p>The thing is, I do not think that the lat­est gen­er­a­tion is the only gen­er­a­tion who has cared about the craft of en­gi­neer­ing.
I be­lieve that re­spect for the craft goes back thou­sands of years.
If any­thing, it feels like forces are push­ing the lat­est gen­er­a­tion to care <em>less</em> about the craft than ever be­fore.</p>
<blockquote>
<p>As an aside, I highly rec­om­mend you check out Jonathan Blow’s fab­u­lous talk, <a href="https://www.youtube.com/watch?v=ZSRHeXYDLko"><em>Preventing the Collapse of Civilization</em></a>.
It’s rel­e­vant, I promise.</p>
</blockquote>]]></description>
            <content:encoded><![CDATA[<h1>Neoengineers</h1>
<blockquote>
<p><a href="https://lukaswerner.com/post/2026-05-27@genz-neoengineer">neo­engi­neer - noun</a></p>
<p>An in­di­vid­ual, part of a resur­gence, who em­braces and re­spects the craft of en­gi­neer­ing. Often en­joys solv­ing hard prob­lems.</p>
</blockquote>
<p>I was re­cently in­tro­duced to the term <a href="https://lukaswerner.com/post/2026-05-27@genz-neoengineer"><span class="push-double"></span><span class="pull-double">“</span>neoengineer”</a>, whose de­f­i­n­i­tion you will ﬁnd above and in the at­tached blog post.
It tick­led my brain in a rare way.</p>
<p>For sev­eral years in a row, my New Year’s res­o­lu­tion was to be­come<span class="push-double"></span> <span class="pull-double">“</span>the most ef­fec­tive per­son pos­si­ble”.
In some ways, that re­mains my pri­mary res­o­lu­tion.
To put it an­other way: I seek to be­come the most ef­fec­tive <strong>en­gi­neer</strong> pos­si­ble.</p>
<p>I think what makes the term<span class="push-double"></span> <span class="pull-double">“</span>neoengineer” so tick­lish to my gray mat­ter is that it draws a line be­tween those who care about the craft and those who do not.
What’s more: the term does not make a lack of care seem like a bad thing.
It is merely a dif­fer­ence in pri­or­i­ties.</p>
<h2>You Can Choose to Be a Neoengineer</h2>
<p>In the dis­course, both within Automattic and at large, we con­stantly re­fer to some un­quan­tiﬁ­able met­ric known as<span class="push-double"></span> <span class="pull-double">“</span>productivity”.
We seek to op­ti­mize it and in­crease it to what­ever ex­tent pos­si­ble.
Why?
Be­cause we be­lieve that it will make us more money, or bring us more pres­tige, or some­how even bring us hap­pi­ness.
I think we are fo­cused on the wrong thing.</p>
<p>When we fo­cus on<span class="push-double"></span> <span class="pull-double">“</span>productivity” we are fo­cus­ing on our out­put.
But be­cause it is in­her­ently un­quan­tiﬁ­able, we are un­able to ob­jec­tively mon­i­tor that out­put.
Be­cause there is no way to quan­tify it, it’s im­pos­si­ble to know if we have im­proved our pro­duc­tiv­ity or not, which re­sults in one of two things.</p>
<p>In one sit­u­a­tion, a per­son can be­lieve an ac­tion, habit, or tool is <strong>more</strong> help­ful than it re­ally is.
You see this all the time with vibe-coders.
They ﬁnd that they are able to <em>pro­duce</em> a ton of stuff, but that stuff has zero to neg­a­tive value, and thus no one con­sumes it.
In this way, they waste a lot of time do­ing some­thing that amounts to very lit­tle.</p>
<p>That is not a knock on vibe-cod­ing.
It’s a knock on a fail­ure to con­sider the value of an ac­tion prop­erly.</p>
<p>In an­other sit­u­a­tion, a per­son can be­lieve an ac­tion, habit, or tool is <strong>less</strong> help­ful than it re­ally is.
I see this of­ten with un­der-plan­ners.
A per­son un­der­es­ti­mates the value of do­ing re­search or pro­duc­ing a plan.
They think,<span class="push-double"></span> <span class="pull-double">“</span>I will just get started and pivot later if nec­es­sary”.
In this way, they spend less time do­ing some­thing help­ful than they should have.</p>
<p>If we change the frame of ref­er­ence a lit­tle, we earn our­selves ac­cess to a neat men­tal trick.
In­stead of ask­ing the ques­tion<span class="push-double"></span> <span class="pull-double">“</span>does this tool make me more pro­duc­tive?”, ask<span class="push-double"></span> <span class="pull-double">“</span>would an ef­fec­tive en­gi­neer use this tool?”
You may re­place<span class="push-double"></span> <span class="pull-double">“</span>this tool” with<span class="push-double"></span> <span class="pull-double">“</span>this habit” or<span class="push-double"></span> <span class="pull-double">“</span>this ac­tion”.</p>
<p>When I do this, I ﬁnd the an­swer changes dras­ti­cally.</p>
<p>Would an ef­fec­tive en­gi­neer choose to learn a com­plex al­go­rithm deeply, or would they choose the easy route and ask ChatGPT to write the code for them?
They would choose the for­mer, since it in­creases their per­sonal level of skill and im­proves main­tain­abil­ity of the pro­ject long-term.</p>
<p>Would an ef­fec­tive en­gi­neer ap­prove code that is dif­ﬁ­cult to read, so they can go ahead and al­lo­cate their men­tal en­ergy to some­thing more pro­duc­tive?
No.
<a href="./get_rid_of_it">They would give care­ful mea­sured feed­back or none at all.</a>
If some­thing else is more im­por­tant than re­view­ing the code, an ef­fec­tive en­gi­neer would do that other thing.</p>
<p>By choos­ing to adopt the iden­tity of an ef­fec­tive en­gi­neer, I cre­ate a habit of choos­ing to do things an ef­fec­tive en­gi­neer would do.
Even if I am not in fact, an ef­fec­tive en­gi­neer, I can come closer to be­com­ing one by adopt­ing that iden­tity.</p>
<h2>The Term Itself</h2>
<p>The only is­sue I have with the term<span class="push-double"></span> <span class="pull-double">“</span>neoengineer” is that when bro­ken down, it means<span class="push-double"></span> <span class="pull-double">“</span>new en­gi­neer”.
Neo- usu­ally refers to some­thing<span class="push-double"></span> <span class="pull-double">“</span>new” or<span class="push-double"></span> <span class="pull-double">“</span>young”.
So a<span class="push-double"></span> <span class="pull-double">“</span>neoengineer” is a young en­gi­neer, per­haps of the lat­est gen­er­a­tion.</p>
<p>The thing is, I do not think that the lat­est gen­er­a­tion is the only gen­er­a­tion who has cared about the craft of en­gi­neer­ing.
I be­lieve that re­spect for the craft goes back thou­sands of years.
If any­thing, it feels like forces are push­ing the lat­est gen­er­a­tion to care <em>less</em> about the craft than ever be­fore.</p>
<blockquote>
<p>As an aside, I highly rec­om­mend you check out Jonathan Blow’s fab­u­lous talk, <a href="https://www.youtube.com/watch?v=ZSRHeXYDLko"><em>Preventing the Collapse of Civilization</em></a>.
It’s rel­e­vant, I promise.</p>
</blockquote>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Slop Paralysis]]></title>
            <link>https://elijahpotter.dev/articles/slop_paralysis</link>
            <guid>https://elijahpotter.dev/articles/slop_paralysis</guid>
            <pubDate>Wed, 24 Jun 2026 16:41:44 GMT</pubDate>
            <description><![CDATA[<h1>Slop Paralysis</h1>
<blockquote>
<p><strong>slop paral­y­sis - noun</strong></p>
<p>A com­plete or par­tial loss of func­tion while re­view­ing the out­put of a cod­ing agent.</p>
</blockquote>
<p>Let me paint you a pic­ture.</p>
<p>You have an idea for prod­uct.
It could be any­thing: A mo­bile app, a dash­board, or a script to au­to­mate your work.
So, you sit down with your fa­vorite <span class="small-caps">LLM</span> and de­scribe your idea.
Maybe you have a pretty good un­der­stand­ing of how it should be im­ple­mented.
You might even know the over­all struc­ture of the pro­ject in your mind.
You tell your cod­ing agent all of this.</p>
<p>Then, you let it loose.
It cooks and cleans and im­ple­ments your prod­uct for you.
In the end, you have <em>some</em> idea of how the end re­sult works, but it is not com­plete.
Since this is a pro­ject that is im­por­tant, which you in­tend to main­tain, you go to read the code.
That’s when it hits you.
<strong>Slop paral­y­sis</strong>.</p>
<h2>The Problem</h2>
<p>Slop paral­y­sis is the com­plete lack of de­sire to re­view the out­put of an LLM.
It could show up for any num­ber of rea­sons.
The most com­mon, for me, are:</p>
<ul>
<li>The sheer amount of code de­mand­ing con­sump­tion.</li>
<li>Missing con­text (that the agent was aware of, but not you).</li>
<li>A fear of break­ing some­thing.</li>
</ul>
<p>In the end, slop paral­y­sis slows you down and makes your life more stress­ful.
So, what can we do about it?</p>
<h2>Some Solutions</h2>
<p>I will de­scribe some so­lu­tions that have worked <em>for me</em>.
As al­ways, your mileage may vary.</p>
<p>The ob­vi­ous so­lu­tion is to sim­ply not use an agent.
Some­times cod­ing agents make your job eas­ier.
Some­times they do not.
<a href="./when_to_use_an_agent">Knowing which is which is dif­ﬁ­cult, but ex­tremely valu­able.</a></p>
<p>When you do de­cide to use an agent, you can have the agent sim­ply pro­duce less code.
I have heard of <a href="https://github.com/DietrichGebert/ponytail">pro­jects</a> that help you do this, but I have not ac­tu­ally tried them.
In­stead, I ask the agent to make a plan ﬁrst, then re­work that plan my­self to min­i­mize the num­ber of changes.
In the end, a plan which was orig­i­nally writ­ten by the agent be­comes one that has been mer­ci­lessly edited by my­self.
In this way, I min­i­mize the amount of code which de­mands con­sump­tion.
The side-ef­fect of this is that I then have an ex­cel­lent un­der­stand­ing of what the agent ac­tu­ally does, which helps with the con­text prob­lem.</p>
<p>The ﬁ­nal strat­egy, which I use when all else fails, is to refac­tor the code by hand.
When the agent has proven the tech­ni­cal fea­si­bil­ity of the pro­ject, but the code is sim­ply too ex­pan­sive to prop­erly grok all at once, I sit down with a ﬁne-tooth comb and refac­tor it, mod­ule by mod­ule.
I do not need to care­fully read each func­tion and eval­u­ate its mer­its, but I do force my­self to, at min­i­mum, lay eyes on them all.</p>
<h2>Stay Steady Out There</h2>
<p>I ﬁnd my­self be­com­ing more over­whelmed by the dis­course every sin­gle day.
It’s ex­haust­ing how much is hap­pen­ing in the world, and even worst, how much you are ex­pected to care about it all.
Take some time for your­self.
Save your­self from slop paral­y­sis.</p>]]></description>
            <content:encoded><![CDATA[<h1>Slop Paralysis</h1>
<blockquote>
<p><strong>slop paral­y­sis - noun</strong></p>
<p>A com­plete or par­tial loss of func­tion while re­view­ing the out­put of a cod­ing agent.</p>
</blockquote>
<p>Let me paint you a pic­ture.</p>
<p>You have an idea for prod­uct.
It could be any­thing: A mo­bile app, a dash­board, or a script to au­to­mate your work.
So, you sit down with your fa­vorite <span class="small-caps">LLM</span> and de­scribe your idea.
Maybe you have a pretty good un­der­stand­ing of how it should be im­ple­mented.
You might even know the over­all struc­ture of the pro­ject in your mind.
You tell your cod­ing agent all of this.</p>
<p>Then, you let it loose.
It cooks and cleans and im­ple­ments your prod­uct for you.
In the end, you have <em>some</em> idea of how the end re­sult works, but it is not com­plete.
Since this is a pro­ject that is im­por­tant, which you in­tend to main­tain, you go to read the code.
That’s when it hits you.
<strong>Slop paral­y­sis</strong>.</p>
<h2>The Problem</h2>
<p>Slop paral­y­sis is the com­plete lack of de­sire to re­view the out­put of an LLM.
It could show up for any num­ber of rea­sons.
The most com­mon, for me, are:</p>
<ul>
<li>The sheer amount of code de­mand­ing con­sump­tion.</li>
<li>Missing con­text (that the agent was aware of, but not you).</li>
<li>A fear of break­ing some­thing.</li>
</ul>
<p>In the end, slop paral­y­sis slows you down and makes your life more stress­ful.
So, what can we do about it?</p>
<h2>Some Solutions</h2>
<p>I will de­scribe some so­lu­tions that have worked <em>for me</em>.
As al­ways, your mileage may vary.</p>
<p>The ob­vi­ous so­lu­tion is to sim­ply not use an agent.
Some­times cod­ing agents make your job eas­ier.
Some­times they do not.
<a href="./when_to_use_an_agent">Knowing which is which is dif­ﬁ­cult, but ex­tremely valu­able.</a></p>
<p>When you do de­cide to use an agent, you can have the agent sim­ply pro­duce less code.
I have heard of <a href="https://github.com/DietrichGebert/ponytail">pro­jects</a> that help you do this, but I have not ac­tu­ally tried them.
In­stead, I ask the agent to make a plan ﬁrst, then re­work that plan my­self to min­i­mize the num­ber of changes.
In the end, a plan which was orig­i­nally writ­ten by the agent be­comes one that has been mer­ci­lessly edited by my­self.
In this way, I min­i­mize the amount of code which de­mands con­sump­tion.
The side-ef­fect of this is that I then have an ex­cel­lent un­der­stand­ing of what the agent ac­tu­ally does, which helps with the con­text prob­lem.</p>
<p>The ﬁ­nal strat­egy, which I use when all else fails, is to refac­tor the code by hand.
When the agent has proven the tech­ni­cal fea­si­bil­ity of the pro­ject, but the code is sim­ply too ex­pan­sive to prop­erly grok all at once, I sit down with a ﬁne-tooth comb and refac­tor it, mod­ule by mod­ule.
I do not need to care­fully read each func­tion and eval­u­ate its mer­its, but I do force my­self to, at min­i­mum, lay eyes on them all.</p>
<h2>Stay Steady Out There</h2>
<p>I ﬁnd my­self be­com­ing more over­whelmed by the dis­course every sin­gle day.
It’s ex­haust­ing how much is hap­pen­ing in the world, and even worst, how much you are ex­pected to care about it all.
Take some time for your­self.
Save your­self from slop paral­y­sis.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What Was Radical Speed Month?]]></title>
            <link>https://elijahpotter.dev/articles/what_was_radical_speed_month?</link>
            <guid>https://elijahpotter.dev/articles/what_was_radical_speed_month?</guid>
            <pubDate>Tue, 23 Jun 2026 19:35:02 GMT</pubDate>
            <description><![CDATA[<h1>What Was Radical Speed Month?</h1>
<p>While I was look­ing back through my archive of posts on this blog, I re­al­ized that I never con­cretely de­ﬁned what Radical Speed Month was.
I have talked about it a lot, but there is not any one spot I can point to and say<span class="push-double"></span> <span class="pull-double">“</span>Look there! That’s what <span class="small-caps">RSM</span> was!”</p>
<p>This page will ﬁll that void.</p>
<p>I work at <a href="https://automattic.com/">Automattic</a>.
We run a lot of prod­ucts and host many ser­vices.
I can al­most guar­an­tee that you have used our prod­uct at least once in your life.
There are few peo­ple left on Earth that have not.</p>
<p>We like to ex­per­i­ment with new ways of work­ing.
Our lead­er­ship team is con­stantly try­ing new things and new ways to get ahead of our com­pe­ti­tion.
Rad­i­cal Speed Month, oth­er­wise known as <span class="small-caps">RSM</span>, was one of these ex­per­i­ments.</p>
<p>In short, Radical Speed Month was a month-long sprint where any and all avail­able en­gi­neers were in­structed to stop what­ever they were do­ing and work on some­thing, well, rad­i­cal.
Some­thing new.
Some­thing in­ter­est­ing.
Some­thing no one had ever seen be­fore.</p>
<p>That was the only re­stric­tion, other than work­ing in pairs.
Each en­gi­neer, in­clud­ing my­self, found one other en­gi­neer to work with and they spent a month on a pro­ject of their own choos­ing.
It was a lot like the hackathons I com­peted in dur­ing my more youth­ful years.</p>
<p>For the in­ter­ested, here is a list of links to some of the posts I made dur­ing or re­lat­ing to <span class="small-caps">RSM</span>:</p>
<ul>
<li><a href="./building_a_harper_desktop_app">Announcing work on our <span class="small-caps">RSM</span> pro­ject.</a></li>
<li><a href="./product_product_product">Learning from an ex­pert at prod­uct-ori­ented think­ing.</a></li>
<li><a href="./a_quick_update_on_harper_desktop">A quick up­date on our <span class="small-caps">RSM</span> pro­ject.</a></li>
<li><a href="./good_agents">Thinking about what makes a good agent (in the <span class="small-caps">LLM</span> sense of the word).</a></li>
<li><a href="./wrapping_up_harper_desktop">Reﬂecting on the pro­ject at large.</a></li>
</ul>]]></description>
            <content:encoded><![CDATA[<h1>What Was Radical Speed Month?</h1>
<p>While I was look­ing back through my archive of posts on this blog, I re­al­ized that I never con­cretely de­ﬁned what Radical Speed Month was.
I have talked about it a lot, but there is not any one spot I can point to and say<span class="push-double"></span> <span class="pull-double">“</span>Look there! That’s what <span class="small-caps">RSM</span> was!”</p>
<p>This page will ﬁll that void.</p>
<p>I work at <a href="https://automattic.com/">Automattic</a>.
We run a lot of prod­ucts and host many ser­vices.
I can al­most guar­an­tee that you have used our prod­uct at least once in your life.
There are few peo­ple left on Earth that have not.</p>
<p>We like to ex­per­i­ment with new ways of work­ing.
Our lead­er­ship team is con­stantly try­ing new things and new ways to get ahead of our com­pe­ti­tion.
Rad­i­cal Speed Month, oth­er­wise known as <span class="small-caps">RSM</span>, was one of these ex­per­i­ments.</p>
<p>In short, Radical Speed Month was a month-long sprint where any and all avail­able en­gi­neers were in­structed to stop what­ever they were do­ing and work on some­thing, well, rad­i­cal.
Some­thing new.
Some­thing in­ter­est­ing.
Some­thing no one had ever seen be­fore.</p>
<p>That was the only re­stric­tion, other than work­ing in pairs.
Each en­gi­neer, in­clud­ing my­self, found one other en­gi­neer to work with and they spent a month on a pro­ject of their own choos­ing.
It was a lot like the hackathons I com­peted in dur­ing my more youth­ful years.</p>
<p>For the in­ter­ested, here is a list of links to some of the posts I made dur­ing or re­lat­ing to <span class="small-caps">RSM</span>:</p>
<ul>
<li><a href="./building_a_harper_desktop_app">Announcing work on our <span class="small-caps">RSM</span> pro­ject.</a></li>
<li><a href="./product_product_product">Learning from an ex­pert at prod­uct-ori­ented think­ing.</a></li>
<li><a href="./a_quick_update_on_harper_desktop">A quick up­date on our <span class="small-caps">RSM</span> pro­ject.</a></li>
<li><a href="./good_agents">Thinking about what makes a good agent (in the <span class="small-caps">LLM</span> sense of the word).</a></li>
<li><a href="./wrapping_up_harper_desktop">Reﬂecting on the pro­ject at large.</a></li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Harper's First Committer]]></title>
            <link>https://elijahpotter.dev/articles/harper's_first_committer</link>
            <guid>https://elijahpotter.dev/articles/harper's_first_committer</guid>
            <pubDate>Fri, 19 Jun 2026 21:08:24 GMT</pubDate>
            <description><![CDATA[<h1>Andrew Dunbar Becomes Harper’s First Committer</h1>
<p>I am pleased to say, with much fan­fare, that Andrew Dunbar (known by on­line moniker <a href="https://github.com/hippietrail">hip­pi­etrail</a>) is be­com­ing Harper’s ﬁrst com­mit­ter.</p>
<p>Andrew has been do­ing stel­lar work for the pro­ject for more than a year now.
His work on our gram­mar en­gine has been phe­nom­e­nal.
Odds are, if you have opened a ticket on GitHub or dropped on com­ment on Discord, he was the ﬁrst one you spoke to.</p>
<p>He clearly un­der­stands and em­bod­ies Harper’s mis­sion and has demon­strated re­spon­si­bil­ity for the code he sub­mits.</p>
<p>So to­day, we are mak­ing him Harper’s ﬁrst ex­ter­nal com­mit­ter.
<a href="https://writewithharper.com/docs/contributors/committer">That means a lot of dif­fer­ent things</a>, but pri­mar­ily it means that he has the per­mis­sion to merge his own and oth­er’s PRs.</p>]]></description>
            <content:encoded><![CDATA[<h1>Andrew Dunbar Becomes Harper’s First Committer</h1>
<p>I am pleased to say, with much fan­fare, that Andrew Dunbar (known by on­line moniker <a href="https://github.com/hippietrail">hip­pi­etrail</a>) is be­com­ing Harper’s ﬁrst com­mit­ter.</p>
<p>Andrew has been do­ing stel­lar work for the pro­ject for more than a year now.
His work on our gram­mar en­gine has been phe­nom­e­nal.
Odds are, if you have opened a ticket on GitHub or dropped on com­ment on Discord, he was the ﬁrst one you spoke to.</p>
<p>He clearly un­der­stands and em­bod­ies Harper’s mis­sion and has demon­strated re­spon­si­bil­ity for the code he sub­mits.</p>
<p>So to­day, we are mak­ing him Harper’s ﬁrst ex­ter­nal com­mit­ter.
<a href="https://writewithharper.com/docs/contributors/committer">That means a lot of dif­fer­ent things</a>, but pri­mar­ily it means that he has the per­mis­sion to merge his own and oth­er’s PRs.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Zen and the Art of Open Source Maintenance]]></title>
            <link>https://elijahpotter.dev/articles/zen_and_the_art_of_open_source_maintenance</link>
            <guid>https://elijahpotter.dev/articles/zen_and_the_art_of_open_source_maintenance</guid>
            <pubDate>Fri, 12 Jun 2026 20:22:20 GMT</pubDate>
            <description><![CDATA[<h1>Zen and the Art of Open Source Maintenance</h1>
<p>This blog post will not be as long as I wish it to be.
I do not have the time nor the pa­tience at the mo­ment to dive as deep into this sub­ject as I feel the sub­ject it­self de­serves.
It also will not be as short as I wish it to be.
It takes time to de­ter­mine what is im­por­tant and what is not.
My hope is that by de­clar­ing this es­say<span class="push-double"></span> <span class="pull-double">“</span>incomplete”, you will grant me some grace and un­der­stand­ing.</p>
<p>While read­ing an old — un­pub­lished, for now — es­say of mine, I was re­minded of the won­der­ful book by Robert Persig ti­tled <em>Zen and the Art of Motorcycle Maintenance</em>.
I read it in high-school, and it has served as the foun­da­tion of much of my world­view up to this point.
The book serves to help any­one in any pro­fes­sion, but I think it speaks par­tic­u­larly well to the lives of open source main­tain­ers.
Here, I hope to re­ﬂect on its teach­ings and ﬁnd ways to ap­ply them to the daily rou­tine of open source main­te­nance and de­vel­op­ment.</p>
<p>If you have not read the book yet, please do so now.
I am not kid­ding.
Stop read­ing this blog post, walk down to your lo­cal book­store and pur­chase a copy.
Do not come back un­til you have read it.</p>
<h2>For The Disobedient</h2>
<p>Since I sus­pect many of my read­ers to be rebels, or oth­er­wise feel re­pelled by great lit­er­a­ture, I will pro­vide a brief overview of the book.</p>
<p><em>Zen and the Art of Motorcycle Maintenance</em> is not, as you may have guessed, ac­tu­ally about mo­tor­cy­cle main­te­nance.
Al­though it con­tains some valu­able tips for those will­ing to get a lit­tle greasy, the book is re­ally about how we per­ceive the world and how to nav­i­gate it with­out go­ing in­sane.</p>
<p>Structurally, it fol­lows Persig on his ﬁc­tional mo­tor­cy­cle trip across the coun­try.
Much of the book hap­pens in con­ver­sa­tion with him­self while he rides down var­i­ous roads and high­ways.</p>
<p>While I will save dis­cus­sion of the more im­por­tant mat­ters for a later day, there is one sec­tion that needs ex­pla­na­tion: Persig’s <strong>gump­tion traps</strong>.</p>
<p>What is a gump­tion trap?
A <a href="https://en.wikipedia.org/wiki/Gumption">gump­tion</a> trap is some­thing that saps your mo­ti­va­tion for a pro­ject.
In the book, Persig de­scribed them in the con­text of — you guessed it — mo­tor­cy­cle main­te­nance, but gump­tion traps lie every­where in day to day life, es­pe­cially for open source main­tain­ers.
He de­scribes two types of gump­tion trap: set­backs and hang-ups.</p>
<p>A set­back is any gump­tion trap that is not, for lack of a bet­ter word, your fault.
It’s when some­thing that ex­ists out­side of your con­trol im­pacts your abil­ity to get sh*t done.
For an open source main­tainer, sup­ply chain at­tacks and — god for­bid — GitHub out­ages count as set­backs.
They can of­ten feel out­side of your con­trol.
That feel­ing is what drains your gump­tion.</p>
<p>The good news is re­al­ized that there are things you can do to pre­vent set­backs, like lock­ing your de­pen­den­cies and avoid­ing GitHub.
Planning ahead, even if it’s hard, can pre­serve your gump­tion for the re­ally <strong>fun</strong> parts of open source main­te­nance.</p>
<p>A hang-up on the other hand, is a more com­pli­cated kind of gump­tion trap.
Hang-ups hap­pen in­ter­nally, and are coun­ter­in­tu­itively more dif­ﬁ­cult to move past.
They sap your de­sire to con­tinue work on your pro­ject not be­cause some­thing ter­ri­ble hap­pened, but be­cause there’s some­thing wrong with the way you view the world.
Per­sig says there are three im­por­tant kinds of hang-up: value traps, truth traps, and mus­cle traps.
I do not think truth traps or mus­cle traps are par­tic­u­larly rel­e­vant to my work as an open source main­tainer, so I will be as­sign­ing in­ter­pre­ta­tion of those as home­work for my read­ers.
Value traps, how­ever, are quite rel­e­vant, so I want to go into those in de­tail.</p>
<h3>Value Traps</h3>
<p>A value trap comes up when some as­pect of your value sys­tem comes into con­ﬂict with the pro­ject it­self.</p>
<p>Stubbornness can stop you from see­ing a <strong>good</strong> so­lu­tion to a prob­lem be­cause you are too fo­cused on a <strong>bad</strong> so­lu­tion.
Spend­ing a lot of time work­ing on a bad so­lu­tion only see it as it is retroac­tively is a recipe for frus­tra­tion and, of course, lost gump­tion.
The so­lu­tion here is to slow down.
Spend time look­ing around the pro­ject and make sure all of your as­sump­tions are cor­rect.
For open source main­tain­ers, this sec­ond look does not nec­es­sar­ily have to be at the code.
It can also be a look at the peo­ple around the code.
It’s tempt­ing to be­lieve that you are the only one who can solve each prob­lem.
In re­al­ity, there’s likely a com­mu­nity of peo­ple work­ing with you.
Maybe a mem­ber of that com­mu­nity has in­sight you do not have.
Talk to them.</p>
<p>Boredom can make you sloppy, and sloppy work is bad work, and bad work steeps the gump­tion right out of you.
What’s worse is that bore­dom of­ten comes <strong>af­ter</strong> you have al­ready lost much of your gump­tion.
This vari­ant of value trap is ab­solutely not spe­ciﬁc in any way to open source, so I will not pre­tend to con­nect the two.
Per­sig rec­om­mends tak­ing a rest and get­ting plenty of sleep.
What­ever you do, he says, do not keep work­ing.
Per­son­ally, when I en­counter this gump­tion trap, I try to work on an­other<span class="push-double"></span> <span class="pull-double">“</span>project” within my open source pro­ject.
Maybe I am not in­ter­ested in de­bug­ging at the mo­ment, so I swap over to writ­ing a new mod­ule in­stead.
I can come back to my de­bug­ging task later with a fresh per­spec­tive and a full tank of gump­tion.</p>
<p>Anxiety, per­haps bet­ter char­ac­ter­ized as <em>fear</em>, can stop you from do­ing any­thing at all.
I of­ten get anx­i­ety merg­ing PRs, even af­ter I have done all of my due dili­gence and truly be­lieve them to be ready.
There is some­thing about hit­ting<span class="push-double"></span> <span class="pull-double">“</span>merge” that spikes my adren­a­line.
But, <a href="./code_ages_like_milk">code ages like milk</a>, so I must merge it any­way.
Per­sig sug­gests you do ex­tra due dili­gence if it makes you feel bet­ter.
If that does not help, just man up and do it.</p>
<h2>Conclusion</h2>
<p>The won­der­ful thing about all the gump­tion traps that Persig high­lights is that they are fun­da­men­tally in your con­trol. Some can only be pre­vented, and oth­ers re­quire some level of in­tro­spec­tion, but they all can be man­aged.
Now se­ri­ously, if you did not lis­ten the ﬁrst time: go read the f**king book. Do it.</p>]]></description>
            <content:encoded><![CDATA[<h1>Zen and the Art of Open Source Maintenance</h1>
<p>This blog post will not be as long as I wish it to be.
I do not have the time nor the pa­tience at the mo­ment to dive as deep into this sub­ject as I feel the sub­ject it­self de­serves.
It also will not be as short as I wish it to be.
It takes time to de­ter­mine what is im­por­tant and what is not.
My hope is that by de­clar­ing this es­say<span class="push-double"></span> <span class="pull-double">“</span>incomplete”, you will grant me some grace and un­der­stand­ing.</p>
<p>While read­ing an old — un­pub­lished, for now — es­say of mine, I was re­minded of the won­der­ful book by Robert Persig ti­tled <em>Zen and the Art of Motorcycle Maintenance</em>.
I read it in high-school, and it has served as the foun­da­tion of much of my world­view up to this point.
The book serves to help any­one in any pro­fes­sion, but I think it speaks par­tic­u­larly well to the lives of open source main­tain­ers.
Here, I hope to re­ﬂect on its teach­ings and ﬁnd ways to ap­ply them to the daily rou­tine of open source main­te­nance and de­vel­op­ment.</p>
<p>If you have not read the book yet, please do so now.
I am not kid­ding.
Stop read­ing this blog post, walk down to your lo­cal book­store and pur­chase a copy.
Do not come back un­til you have read it.</p>
<h2>For The Disobedient</h2>
<p>Since I sus­pect many of my read­ers to be rebels, or oth­er­wise feel re­pelled by great lit­er­a­ture, I will pro­vide a brief overview of the book.</p>
<p><em>Zen and the Art of Motorcycle Maintenance</em> is not, as you may have guessed, ac­tu­ally about mo­tor­cy­cle main­te­nance.
Al­though it con­tains some valu­able tips for those will­ing to get a lit­tle greasy, the book is re­ally about how we per­ceive the world and how to nav­i­gate it with­out go­ing in­sane.</p>
<p>Structurally, it fol­lows Persig on his ﬁc­tional mo­tor­cy­cle trip across the coun­try.
Much of the book hap­pens in con­ver­sa­tion with him­self while he rides down var­i­ous roads and high­ways.</p>
<p>While I will save dis­cus­sion of the more im­por­tant mat­ters for a later day, there is one sec­tion that needs ex­pla­na­tion: Persig’s <strong>gump­tion traps</strong>.</p>
<p>What is a gump­tion trap?
A <a href="https://en.wikipedia.org/wiki/Gumption">gump­tion</a> trap is some­thing that saps your mo­ti­va­tion for a pro­ject.
In the book, Persig de­scribed them in the con­text of — you guessed it — mo­tor­cy­cle main­te­nance, but gump­tion traps lie every­where in day to day life, es­pe­cially for open source main­tain­ers.
He de­scribes two types of gump­tion trap: set­backs and hang-ups.</p>
<p>A set­back is any gump­tion trap that is not, for lack of a bet­ter word, your fault.
It’s when some­thing that ex­ists out­side of your con­trol im­pacts your abil­ity to get sh*t done.
For an open source main­tainer, sup­ply chain at­tacks and — god for­bid — GitHub out­ages count as set­backs.
They can of­ten feel out­side of your con­trol.
That feel­ing is what drains your gump­tion.</p>
<p>The good news is re­al­ized that there are things you can do to pre­vent set­backs, like lock­ing your de­pen­den­cies and avoid­ing GitHub.
Planning ahead, even if it’s hard, can pre­serve your gump­tion for the re­ally <strong>fun</strong> parts of open source main­te­nance.</p>
<p>A hang-up on the other hand, is a more com­pli­cated kind of gump­tion trap.
Hang-ups hap­pen in­ter­nally, and are coun­ter­in­tu­itively more dif­ﬁ­cult to move past.
They sap your de­sire to con­tinue work on your pro­ject not be­cause some­thing ter­ri­ble hap­pened, but be­cause there’s some­thing wrong with the way you view the world.
Per­sig says there are three im­por­tant kinds of hang-up: value traps, truth traps, and mus­cle traps.
I do not think truth traps or mus­cle traps are par­tic­u­larly rel­e­vant to my work as an open source main­tainer, so I will be as­sign­ing in­ter­pre­ta­tion of those as home­work for my read­ers.
Value traps, how­ever, are quite rel­e­vant, so I want to go into those in de­tail.</p>
<h3>Value Traps</h3>
<p>A value trap comes up when some as­pect of your value sys­tem comes into con­ﬂict with the pro­ject it­self.</p>
<p>Stubbornness can stop you from see­ing a <strong>good</strong> so­lu­tion to a prob­lem be­cause you are too fo­cused on a <strong>bad</strong> so­lu­tion.
Spend­ing a lot of time work­ing on a bad so­lu­tion only see it as it is retroac­tively is a recipe for frus­tra­tion and, of course, lost gump­tion.
The so­lu­tion here is to slow down.
Spend time look­ing around the pro­ject and make sure all of your as­sump­tions are cor­rect.
For open source main­tain­ers, this sec­ond look does not nec­es­sar­ily have to be at the code.
It can also be a look at the peo­ple around the code.
It’s tempt­ing to be­lieve that you are the only one who can solve each prob­lem.
In re­al­ity, there’s likely a com­mu­nity of peo­ple work­ing with you.
Maybe a mem­ber of that com­mu­nity has in­sight you do not have.
Talk to them.</p>
<p>Boredom can make you sloppy, and sloppy work is bad work, and bad work steeps the gump­tion right out of you.
What’s worse is that bore­dom of­ten comes <strong>af­ter</strong> you have al­ready lost much of your gump­tion.
This vari­ant of value trap is ab­solutely not spe­ciﬁc in any way to open source, so I will not pre­tend to con­nect the two.
Per­sig rec­om­mends tak­ing a rest and get­ting plenty of sleep.
What­ever you do, he says, do not keep work­ing.
Per­son­ally, when I en­counter this gump­tion trap, I try to work on an­other<span class="push-double"></span> <span class="pull-double">“</span>project” within my open source pro­ject.
Maybe I am not in­ter­ested in de­bug­ging at the mo­ment, so I swap over to writ­ing a new mod­ule in­stead.
I can come back to my de­bug­ging task later with a fresh per­spec­tive and a full tank of gump­tion.</p>
<p>Anxiety, per­haps bet­ter char­ac­ter­ized as <em>fear</em>, can stop you from do­ing any­thing at all.
I of­ten get anx­i­ety merg­ing PRs, even af­ter I have done all of my due dili­gence and truly be­lieve them to be ready.
There is some­thing about hit­ting<span class="push-double"></span> <span class="pull-double">“</span>merge” that spikes my adren­a­line.
But, <a href="./code_ages_like_milk">code ages like milk</a>, so I must merge it any­way.
Per­sig sug­gests you do ex­tra due dili­gence if it makes you feel bet­ter.
If that does not help, just man up and do it.</p>
<h2>Conclusion</h2>
<p>The won­der­ful thing about all the gump­tion traps that Persig high­lights is that they are fun­da­men­tally in your con­trol. Some can only be pre­vented, and oth­ers re­quire some level of in­tro­spec­tion, but they all can be man­aged.
Now se­ri­ously, if you did not lis­ten the ﬁrst time: go read the f**king book. Do it.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Get Rid of It]]></title>
            <link>https://elijahpotter.dev/articles/get_rid_of_it</link>
            <guid>https://elijahpotter.dev/articles/get_rid_of_it</guid>
            <pubDate>Fri, 05 Jun 2026 21:15:21 GMT</pubDate>
            <description><![CDATA[<h1>Get Rid of It</h1>
<p>If you have not al­ready, I highly rec­om­mend that you read <a href="https://app.thestorygraph.com/books/2aeb3592-b6ca-449b-ad9e-345f9709dc3b">Walter Isaacson’s bi­og­ra­phy of Elon Musk.</a>
It fol­lows the bil­lion­aire’s stratos­pheric ca­reer from pro­gram­ming video games on an orig­i­nal Commodore VIC-20 to the ac­qui­si­tion of Twitter.
While I do not agree with all of his prac­tices or be­liefs, I do think that some of his philoso­phies are ex­tremely ap­plic­a­ble and largely ig­nored, even by his most ar­dent fol­low­ers.</p>
<blockquote>
<p><span class="pull-double">“</span>Delete any part or process you can. You may have to add them back later. In fact, if you do not end up adding back at least 10% of them, then you did­n’t delete enough.” - Elon</p>
</blockquote>
<p>In the last week, es­pe­cially since we are in hard­en­ing mode, clean­ing up af­ter the wreck­age of <span class="small-caps">RSM</span>, I have been see­ing an enor­mous num­ber of ex­am­ples demon­strat­ing that dele­tion is usu­ally bet­ter than ad­di­tion.
Say­ing<span class="push-double"></span> <span class="pull-double">“</span>no” is more im­por­tant than say­ing<span class="push-double"></span> <span class="pull-double">“</span>yes”.</p>
<h2>Get Rid of Code</h2>
<p>The num­ber of bugs in a pro­gram is roughly pro­por­tional to the num­ber of lines of code.
To a shock­ing de­gree.
Less code means fewer prob­lems.
If you can pro­vide the same ser­vice with less code, pro­vide the same ser­vice with less code.</p>
<p>While this is not a post about cod­ing with <span class="small-caps">AI</span>, I ﬁnd that mak­ing my agen­t’s plans sim­pler with fewer steps al­most al­ways re­sults in bet­ter code.
If most en­gi­neers said<span class="push-double"></span> <span class="pull-double">“</span>no” to the ﬁrst plan an agent pro­vided, the qual­ity of the av­er­age pro­gram on GitHub would sky­rocket.
But peo­ple do not say<span class="push-double"></span> <span class="pull-double">“</span>no”.
Be the ex­cep­tion.</p>
<h2>Get Rid Of Decisions</h2>
<p>When an en­gi­neer needs to make a de­ci­sion for whether to work on some­thing (or oth­er­wise al­lo­cate re­sources to a task), they have three op­tions.</p>
<ol>
<li>They can do the thing.</li>
<li>They can not do the thing.</li>
<li>They can say<span class="push-double"></span> <span class="pull-double">“</span>we will do that later”.</li>
</ol>
<p>Almost al­ways, op­tion three turns into op­tion two.
Peo­ple pro­cras­ti­nate, and there will al­ways be more ur­gent things to at­tend to.
It’s bet­ter to de­cide to do op­tion two in­ten­tion­ally than to let fate de­cide.</p>
<p>So, if you can’t de­cide whether to do some­thing <em>right now</em>, your an­swer should be no.
You are free to change you mind later, but do not pre­tend that you will re­mem­ber to de­cide later.</p>
<h2>Harper Says<span class="push-double"></span> <span class="pull-double">“</span>No”</h2>
<p>Do we train stuff on our user’s data? No.
Do we send data to the cloud? No.
Do we waste <span class="small-caps">CPU</span> cy­cles of friv­o­lous things? No.</p>
<p>People use Harper be­cause we say no.</p>]]></description>
            <content:encoded><![CDATA[<h1>Get Rid of It</h1>
<p>If you have not al­ready, I highly rec­om­mend that you read <a href="https://app.thestorygraph.com/books/2aeb3592-b6ca-449b-ad9e-345f9709dc3b">Walter Isaacson’s bi­og­ra­phy of Elon Musk.</a>
It fol­lows the bil­lion­aire’s stratos­pheric ca­reer from pro­gram­ming video games on an orig­i­nal Commodore VIC-20 to the ac­qui­si­tion of Twitter.
While I do not agree with all of his prac­tices or be­liefs, I do think that some of his philoso­phies are ex­tremely ap­plic­a­ble and largely ig­nored, even by his most ar­dent fol­low­ers.</p>
<blockquote>
<p><span class="pull-double">“</span>Delete any part or process you can. You may have to add them back later. In fact, if you do not end up adding back at least 10% of them, then you did­n’t delete enough.” - Elon</p>
</blockquote>
<p>In the last week, es­pe­cially since we are in hard­en­ing mode, clean­ing up af­ter the wreck­age of <span class="small-caps">RSM</span>, I have been see­ing an enor­mous num­ber of ex­am­ples demon­strat­ing that dele­tion is usu­ally bet­ter than ad­di­tion.
Say­ing<span class="push-double"></span> <span class="pull-double">“</span>no” is more im­por­tant than say­ing<span class="push-double"></span> <span class="pull-double">“</span>yes”.</p>
<h2>Get Rid of Code</h2>
<p>The num­ber of bugs in a pro­gram is roughly pro­por­tional to the num­ber of lines of code.
To a shock­ing de­gree.
Less code means fewer prob­lems.
If you can pro­vide the same ser­vice with less code, pro­vide the same ser­vice with less code.</p>
<p>While this is not a post about cod­ing with <span class="small-caps">AI</span>, I ﬁnd that mak­ing my agen­t’s plans sim­pler with fewer steps al­most al­ways re­sults in bet­ter code.
If most en­gi­neers said<span class="push-double"></span> <span class="pull-double">“</span>no” to the ﬁrst plan an agent pro­vided, the qual­ity of the av­er­age pro­gram on GitHub would sky­rocket.
But peo­ple do not say<span class="push-double"></span> <span class="pull-double">“</span>no”.
Be the ex­cep­tion.</p>
<h2>Get Rid Of Decisions</h2>
<p>When an en­gi­neer needs to make a de­ci­sion for whether to work on some­thing (or oth­er­wise al­lo­cate re­sources to a task), they have three op­tions.</p>
<ol>
<li>They can do the thing.</li>
<li>They can not do the thing.</li>
<li>They can say<span class="push-double"></span> <span class="pull-double">“</span>we will do that later”.</li>
</ol>
<p>Almost al­ways, op­tion three turns into op­tion two.
Peo­ple pro­cras­ti­nate, and there will al­ways be more ur­gent things to at­tend to.
It’s bet­ter to de­cide to do op­tion two in­ten­tion­ally than to let fate de­cide.</p>
<p>So, if you can’t de­cide whether to do some­thing <em>right now</em>, your an­swer should be no.
You are free to change you mind later, but do not pre­tend that you will re­mem­ber to de­cide later.</p>
<h2>Harper Says<span class="push-double"></span> <span class="pull-double">“</span>No”</h2>
<p>Do we train stuff on our user’s data? No.
Do we send data to the cloud? No.
Do we waste <span class="small-caps">CPU</span> cy­cles of friv­o­lous things? No.</p>
<p>People use Harper be­cause we say no.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Hacker News sans AI]]></title>
            <link>https://elijahpotter.dev/articles/hacker_news_sans_AI</link>
            <guid>https://elijahpotter.dev/articles/hacker_news_sans_AI</guid>
            <pubDate>Tue, 02 Jun 2026 23:54:52 GMT</pubDate>
            <description><![CDATA[<h1>Hacker News, sans <span class="small-caps">AI</span></h1>
<p>I like Hacker News, but I do not love that so much of it has turned into dis­cus­sion of a sin­gle topic: <span class="small-caps">AI</span>. So, I have cre­ated my own ver­sion.
A ver­sion of Hacker News, with­out <span class="small-caps">AI</span>.</p>
<p><a href="/hnsansai">HNSansAI</a> pulls from the of­ﬁ­cial Hacker News <span class="small-caps">API</span>, looks through the con­tent of each ar­ti­cle, and dis­cards any that men­tion a term re­lated to<span class="push-double"></span> <span class="pull-double">“</span><span class="small-caps">AI</span><span class="push-double"></span><span class="pull-double">”</span>. That in­cludes stuff like:</p>
<ul>
<li><span class="pull-double">“</span>OpenAI”</li>
<li><span class="pull-double">“</span>Anthropic”</li>
<li><span class="pull-double">“</span>Copilot”</li>
<li>“<span class="small-caps">LLM</span>”</li>
<li>…and many more.</li>
</ul>
<!-- For visibility, I have embedded a version of it below, but you can also visit it (or bookmark it directly) [here](/hnsansai). -->
<!---->
<!-- <div style="position: relative; width: 100%; height: 600px;"> -->
<!--   <a -->
<!--     href="/hnsansai" -->
<!--     style=" -->
<!--       position: absolute; -->
<!--       top: -8px; -->
<!--       right: 8px; -->
<!--       z-index: 10; -->
<!--       padding: 4px 8px; -->
<!--       background: white; -->
<!--       border: 1px solid #ccc; -->
<!--       border-radius: 4px; -->
<!--       text-decoration: none; -->
<!--       color: black; -->
<!--       font-size: 12px; -->
<!--     " -->
<!--   > -->
<!--     Open ↗ -->
<!--   </a> -->
<!---->
<!--   <iframe -->
<!--     src="/hnsansai" -->
<!--     style="width: 100%; height: 100%; border: 0;" -->
<!--     title="HNSansai" -->
<!--   ></iframe> -->
<!-- </div> -->]]></description>
            <content:encoded><![CDATA[<h1>Hacker News, sans <span class="small-caps">AI</span></h1>
<p>I like Hacker News, but I do not love that so much of it has turned into dis­cus­sion of a sin­gle topic: <span class="small-caps">AI</span>. So, I have cre­ated my own ver­sion.
A ver­sion of Hacker News, with­out <span class="small-caps">AI</span>.</p>
<p><a href="/hnsansai">HNSansAI</a> pulls from the of­ﬁ­cial Hacker News <span class="small-caps">API</span>, looks through the con­tent of each ar­ti­cle, and dis­cards any that men­tion a term re­lated to<span class="push-double"></span> <span class="pull-double">“</span><span class="small-caps">AI</span><span class="push-double"></span><span class="pull-double">”</span>. That in­cludes stuff like:</p>
<ul>
<li><span class="pull-double">“</span>OpenAI”</li>
<li><span class="pull-double">“</span>Anthropic”</li>
<li><span class="pull-double">“</span>Copilot”</li>
<li>“<span class="small-caps">LLM</span>”</li>
<li>…and many more.</li>
</ul>
<!-- For visibility, I have embedded a version of it below, but you can also visit it (or bookmark it directly) [here](/hnsansai). -->
<!---->
<!-- <div style="position: relative; width: 100%; height: 600px;"> -->
<!--   <a -->
<!--     href="/hnsansai" -->
<!--     style=" -->
<!--       position: absolute; -->
<!--       top: -8px; -->
<!--       right: 8px; -->
<!--       z-index: 10; -->
<!--       padding: 4px 8px; -->
<!--       background: white; -->
<!--       border: 1px solid #ccc; -->
<!--       border-radius: 4px; -->
<!--       text-decoration: none; -->
<!--       color: black; -->
<!--       font-size: 12px; -->
<!--     " -->
<!--   > -->
<!--     Open ↗ -->
<!--   </a> -->
<!---->
<!--   <iframe -->
<!--     src="/hnsansai" -->
<!--     style="width: 100%; height: 100%; border: 0;" -->
<!--     title="HNSansai" -->
<!--   ></iframe> -->
<!-- </div> -->]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Wrapping up Harper Desktop]]></title>
            <link>https://elijahpotter.dev/articles/wrapping_up_harper_desktop</link>
            <guid>https://elijahpotter.dev/articles/wrapping_up_harper_desktop</guid>
            <pubDate>Thu, 28 May 2026 19:31:33 GMT</pubDate>
            <description><![CDATA[<h1>Wrapping up Our Radical Speed Month</h1>
<p><a href="./harper_desktop">I pre­vi­ously posted</a> about our launch of Harper Desktop.
If you have not read that al­ready, I sug­gest you take a peek be­fore read­ing for­ward here.</p>
<p>For those caught un­aware, Harper is a gram­mar checker de­signed to re­spect the fact that your writ­ing should be yours.
It is pri­vate by de­sign, no mat­ter where you de­cide to use it.
Thou­sands of peo­ple from all walks of life use it to ex­press their thoughts on­line, skip­ping all the fea­tures found in other gram­mar check­ers that sup­press cre­ativ­ity and sell text to the high­est bid­der.</p>
<p>Until re­cently, our most pop­u­lar and ver­sa­tile in­te­gra­tion was through a Chrome or Firefox ex­ten­sion.
It plugged di­rectly into your browser and in­ter­acted with the var­i­ous text ed­i­tors you might en­counter while brows­ing the web.</p>
<p>While this gave Harper the abil­ity to en­hance the writ­ing of peo­ple who spent much of their time on­line, it did not do much for the mak­ers and do­ers that use desk­top ap­pli­ca­tions like Scrivener, Slack, or Discord.
Harper Desktop sought to solve that.</p>
<p>It was build in only a month, with the com­bined ef­fort of just two en­gi­neers, my­self, and <a href="https://jason.blog/">Jason Adams</a>.
It was pos­si­ble though ded­i­ca­tion, hard work, and a will­ing­ness to learn.
<a href="./what_was_radical_speed_month?">Radical Speed Month</a> held an im­pe­tus to ex­per­i­ment, and ex­per­i­ment we did.</p>
<p>In this post, I want to talk about:</p>
<ol>
<li>The ex­per­i­ments Jason and I ran on our­selves.</li>
<li>Which we will keep, and which we will not.</li>
<li>Sandwiches.</li>
</ol>
<p>I will <em>not</em> be talk­ing about:</p>
<ul>
<li>Why Harper Desktop is awe­some.</li>
<li>Why you should down­load and in­stall it.</li>
<li>Pizza.</li>
</ul>
<h2>The Experiments</h2>
<h3>“<span class="small-caps">AI</span>”</h3>
<p><span class="small-caps">AI</span> is a tool, one which I have writ­ten about ex­ten­sively.
Dis­cus­sion of <span class="small-caps">AI</span> is repet­i­tive and hon­estly pretty bor­ing.
Which is why my<span class="push-double"></span> <span class="pull-double">“</span><span class="small-caps">AI</span><span class="push-double"></span><span class="pull-double">”</span> ex­per­i­ment was not re­ally about <span class="small-caps">AI</span> or LLMs.
It was about the kinds of tools I use.</p>
<p>For an ex­ceed­ingly long time, my work­ﬂow has con­sisted of <code>tmux</code>, <code>nvim</code>, and the Rust com­piler.
In the last year, that work­ﬂow has grown to in­clude <a href="https://openai.com/codex/">OpenAI’s Codex</a>.
I found my­self more pro­duc­tive with it be­cause I could del­e­gate oth­er­wise la­bor-in­tense tasks to it to be com­pleted in the back­ground while I worked on some­thing else.</p>
<p>Even with <code>codex</code> avail­able in my <code>$PATH</code>, I would still write most mis­sion-crit­i­cal code my­self.
Part of this was from fear, but there were other <a href="./when_to_use_an_agent">more im­por­tant rea­sons too</a>.</p>
<p>I wanted to see what would hap­pen if I com­pletely changed my work­ﬂow, just for one month.
I would com­mit my­self to spend­ing as much time with new tools as pos­si­ble.
As it would turn out, the only tool I <strong>stopped</strong> us­ing was Codex.</p>
<p>This is <a href="./good_agents">some­thing I spoke about brieﬂy be­fore</a>, but I have de­vel­oped a more com­pre­hen­sive view of the is­sue since, so I’d like to go over it again.</p>
<h4>What I Used Instead</h4>
<p>Instead of del­e­gat­ing tasks to Codex, I started del­e­gat­ing tasks to <a href="https://opencode.ai/">OpenCode</a>.
I found it far more ef­fec­tive.</p>
<p>To me, OpenCode’s stand­out fea­ture was how cen­tral it made<span class="push-double"></span> <span class="pull-double">“</span>plan mode”, which gave it per­mis­sion to view (but not edit) my repos­i­tory while I cre­ated a plan.
It could com­pare its un­der­stand­ing of my plan with the re­al­ity of the ex­ist­ing code, and ask clar­i­fy­ing ques­tions when am­bi­gu­ity arose.
This stood in sharp con­trast to Claude Code and Codex, which would just go and start edit­ing ﬁles, re­gard­less of whether a plan was com­plete.</p>
<p>I liked this, but it still left some things to be de­sired.</p>
<p>OpenCode gave me byte-level con­trol over the code it wrote be­fore it started edit­ing, but it did not give me a whole lot of con­trol over OpenCode it­self.
I found my­self typ­ing the same things over and over again, wish­ing for some kind of higher level macro sys­tem.</p>
<p>That’s when I found Pi, an<span class="push-double"></span> <span class="pull-double">“</span>agent” that in­ten­tion­ally does as lit­tle as pos­si­ble.
In a lot of ways, it’s like <code>neovim</code>.
It does very lit­tle out of the box, but through cus­tomiza­tion it can be­come more use­ful than any­thing else.</p>
<p>Now, I ﬁnd my­self us­ing Pi more of­ten, not least be­cause it gives me every piece of con­trol I al­ways wish I had.
Pi is the only LLM-based tool I have every tried that did not feel like it was ac­tively slow­ing me down.</p>
<p>So in that way, I sup­posed the<span class="push-double"></span> <span class="pull-double">“</span><span class="small-caps">AI</span><span class="push-double"></span><span class="pull-double">”</span> ex­per­i­ment was a suc­cess.
I will be con­tin­u­ing to use Pi.</p>
<h3>Meetups</h3>
<p>Jason and I de­cided to work to­gether for Radical Speed Month in part be­cause we lived in<span class="push-double"></span> <span class="pull-double">“</span>close” prox­im­ity.</p>
<p>You see, Jason and I both work at Automattic, which is <a href="https://distributed.blog/">a dis­trib­uted com­pany</a>.
That means that al­most every­one works re­motely.
There are a num­ber of ben­e­ﬁts to em­pha­siz­ing asyn­chro­nous com­mu­ni­ca­tion, and some down­sides.
We wanted to get a bet­ter un­der­stand­ing of both the pros and cons by tem­porar­ily in­creas­ing the num­ber of days we spent in-per­son work­ing to­gether.</p>
<p>So, twice a week, we would hop in our cars and meet some­where, usu­ally Castle Rock, CO.
We would spend the day talk­ing about big ideas, work­ing on our in­di­vid­ual tasks, and con­sume a likely un­healthy num­ber of sand­wiches.</p>
<p>I per­son­ally found these in-per­son days mo­ti­vat­ing.
I would come home at the end of the day ex­cited to get back to work.
I felt like I had a re­newed sense of pur­pose and drive.
It was in­tox­i­cat­ing.</p>
<p>I think a huge part of this feel­ing was a con­se­quence from Jason’s per­son­al­ity.
We seem to mesh to­gether well, and I have no­ticed that he has a ten­dency to am­plify the best qual­i­ties in the peo­ple he leads.</p>
<p>That said, I do not think that 100% in-of­ﬁce work is sus­tain­able in the long term.
I found that the days we were to­gether, I was not quite as able to<span class="push-double"></span> <span class="pull-double">“</span>lock in”.
We had fan­tas­tic dis­cus­sions and our de­ci­sion mak­ing process was a mil­lion times faster than it would have been oth­er­wise, but it was just that lit­tle harder to turn the talk into ac­tion.
Sim­i­larly, I found it more dif­ﬁ­cult to con­vey cer­tain kinds of tech­ni­cal in­for­ma­tion.
When we needed to ex­plain some­thing in depth, I felt the com­pul­sion (which could be fought, I sup­pose) to send a link later, rather than try to ex­plain it in the mo­ment.</p>
<p>In the end, I think that Matt’s orig­i­nal con­clu­sion on mee­tups is still cor­rect.
He says that in the same way that <a href="https://distributed.blog/2023/05/26/episode-30-meetups/"><span class="push-double"></span><span class="pull-double">“</span>a lit­tle salt makes the dish”</a>, a few mee­tups every so of­ten can bring to­gether the best parts of both in-per­son and re­mote work.</p>
<p>So, I would say that our sec­ond ex­per­i­ment was also a suc­cess.
I per­son­ally feel as though I have earned a bet­ter in­tu­ition for what kinds of work are bet­ter in per­son and what are bet­ter async.</p>
<h2>The Fallout</h2>
<p>Immediately af­ter an­nounc­ing Harper Desktop, I got a ﬂood of mes­sages from peo­ple who had down­loaded it, in­stalled it, and found all the bugs in it.
That was both good news and bad news.</p>
<p>It was good be­cause we could be con­ﬁ­dent that we were in­deed build­ing the right thing.
Peo­ple want a desk­top ver­sion of Harper and they want to help us make it as awe­some as it can be.</p>
<p>The was bad be­cause, well, there were a ton of bugs.
Over the last few days, I have been bak­ing them out of the soft­ware as fast as pos­si­ble.
You should be get­ting a <strong>sig­nif­i­cantly</strong> im­proved piece of soft­ware in the com­ing hours.</p>
<p>Would I have post­poned the launch, had I knew about these is­sues?
Yes.
But I did not, which is why we do soft­ware launches like this.
There’s no way to fully ex­pect every is­sue that comes up, so you need to do a lit­tle ad-hoc user test­ing.
I con­sider this a suc­cess.</p>
<h2>Conclusion</h2>
<p>I re­ally en­joyed Radical Speed Month.
It was a hard stretch of learn­ing a lot about a va­ri­ety of sub­jects, most of which I have not even men­tioned in this post.
I am en­er­gized and ex­cited to ap­ply them to my daily work, and in hard­en­ing Harper Desktop.</p>]]></description>
            <content:encoded><![CDATA[<h1>Wrapping up Our Radical Speed Month</h1>
<p><a href="./harper_desktop">I pre­vi­ously posted</a> about our launch of Harper Desktop.
If you have not read that al­ready, I sug­gest you take a peek be­fore read­ing for­ward here.</p>
<p>For those caught un­aware, Harper is a gram­mar checker de­signed to re­spect the fact that your writ­ing should be yours.
It is pri­vate by de­sign, no mat­ter where you de­cide to use it.
Thou­sands of peo­ple from all walks of life use it to ex­press their thoughts on­line, skip­ping all the fea­tures found in other gram­mar check­ers that sup­press cre­ativ­ity and sell text to the high­est bid­der.</p>
<p>Until re­cently, our most pop­u­lar and ver­sa­tile in­te­gra­tion was through a Chrome or Firefox ex­ten­sion.
It plugged di­rectly into your browser and in­ter­acted with the var­i­ous text ed­i­tors you might en­counter while brows­ing the web.</p>
<p>While this gave Harper the abil­ity to en­hance the writ­ing of peo­ple who spent much of their time on­line, it did not do much for the mak­ers and do­ers that use desk­top ap­pli­ca­tions like Scrivener, Slack, or Discord.
Harper Desktop sought to solve that.</p>
<p>It was build in only a month, with the com­bined ef­fort of just two en­gi­neers, my­self, and <a href="https://jason.blog/">Jason Adams</a>.
It was pos­si­ble though ded­i­ca­tion, hard work, and a will­ing­ness to learn.
<a href="./what_was_radical_speed_month?">Radical Speed Month</a> held an im­pe­tus to ex­per­i­ment, and ex­per­i­ment we did.</p>
<p>In this post, I want to talk about:</p>
<ol>
<li>The ex­per­i­ments Jason and I ran on our­selves.</li>
<li>Which we will keep, and which we will not.</li>
<li>Sandwiches.</li>
</ol>
<p>I will <em>not</em> be talk­ing about:</p>
<ul>
<li>Why Harper Desktop is awe­some.</li>
<li>Why you should down­load and in­stall it.</li>
<li>Pizza.</li>
</ul>
<h2>The Experiments</h2>
<h3>“<span class="small-caps">AI</span>”</h3>
<p><span class="small-caps">AI</span> is a tool, one which I have writ­ten about ex­ten­sively.
Dis­cus­sion of <span class="small-caps">AI</span> is repet­i­tive and hon­estly pretty bor­ing.
Which is why my<span class="push-double"></span> <span class="pull-double">“</span><span class="small-caps">AI</span><span class="push-double"></span><span class="pull-double">”</span> ex­per­i­ment was not re­ally about <span class="small-caps">AI</span> or LLMs.
It was about the kinds of tools I use.</p>
<p>For an ex­ceed­ingly long time, my work­ﬂow has con­sisted of <code>tmux</code>, <code>nvim</code>, and the Rust com­piler.
In the last year, that work­ﬂow has grown to in­clude <a href="https://openai.com/codex/">OpenAI’s Codex</a>.
I found my­self more pro­duc­tive with it be­cause I could del­e­gate oth­er­wise la­bor-in­tense tasks to it to be com­pleted in the back­ground while I worked on some­thing else.</p>
<p>Even with <code>codex</code> avail­able in my <code>$PATH</code>, I would still write most mis­sion-crit­i­cal code my­self.
Part of this was from fear, but there were other <a href="./when_to_use_an_agent">more im­por­tant rea­sons too</a>.</p>
<p>I wanted to see what would hap­pen if I com­pletely changed my work­ﬂow, just for one month.
I would com­mit my­self to spend­ing as much time with new tools as pos­si­ble.
As it would turn out, the only tool I <strong>stopped</strong> us­ing was Codex.</p>
<p>This is <a href="./good_agents">some­thing I spoke about brieﬂy be­fore</a>, but I have de­vel­oped a more com­pre­hen­sive view of the is­sue since, so I’d like to go over it again.</p>
<h4>What I Used Instead</h4>
<p>Instead of del­e­gat­ing tasks to Codex, I started del­e­gat­ing tasks to <a href="https://opencode.ai/">OpenCode</a>.
I found it far more ef­fec­tive.</p>
<p>To me, OpenCode’s stand­out fea­ture was how cen­tral it made<span class="push-double"></span> <span class="pull-double">“</span>plan mode”, which gave it per­mis­sion to view (but not edit) my repos­i­tory while I cre­ated a plan.
It could com­pare its un­der­stand­ing of my plan with the re­al­ity of the ex­ist­ing code, and ask clar­i­fy­ing ques­tions when am­bi­gu­ity arose.
This stood in sharp con­trast to Claude Code and Codex, which would just go and start edit­ing ﬁles, re­gard­less of whether a plan was com­plete.</p>
<p>I liked this, but it still left some things to be de­sired.</p>
<p>OpenCode gave me byte-level con­trol over the code it wrote be­fore it started edit­ing, but it did not give me a whole lot of con­trol over OpenCode it­self.
I found my­self typ­ing the same things over and over again, wish­ing for some kind of higher level macro sys­tem.</p>
<p>That’s when I found Pi, an<span class="push-double"></span> <span class="pull-double">“</span>agent” that in­ten­tion­ally does as lit­tle as pos­si­ble.
In a lot of ways, it’s like <code>neovim</code>.
It does very lit­tle out of the box, but through cus­tomiza­tion it can be­come more use­ful than any­thing else.</p>
<p>Now, I ﬁnd my­self us­ing Pi more of­ten, not least be­cause it gives me every piece of con­trol I al­ways wish I had.
Pi is the only LLM-based tool I have every tried that did not feel like it was ac­tively slow­ing me down.</p>
<p>So in that way, I sup­posed the<span class="push-double"></span> <span class="pull-double">“</span><span class="small-caps">AI</span><span class="push-double"></span><span class="pull-double">”</span> ex­per­i­ment was a suc­cess.
I will be con­tin­u­ing to use Pi.</p>
<h3>Meetups</h3>
<p>Jason and I de­cided to work to­gether for Radical Speed Month in part be­cause we lived in<span class="push-double"></span> <span class="pull-double">“</span>close” prox­im­ity.</p>
<p>You see, Jason and I both work at Automattic, which is <a href="https://distributed.blog/">a dis­trib­uted com­pany</a>.
That means that al­most every­one works re­motely.
There are a num­ber of ben­e­ﬁts to em­pha­siz­ing asyn­chro­nous com­mu­ni­ca­tion, and some down­sides.
We wanted to get a bet­ter un­der­stand­ing of both the pros and cons by tem­porar­ily in­creas­ing the num­ber of days we spent in-per­son work­ing to­gether.</p>
<p>So, twice a week, we would hop in our cars and meet some­where, usu­ally Castle Rock, CO.
We would spend the day talk­ing about big ideas, work­ing on our in­di­vid­ual tasks, and con­sume a likely un­healthy num­ber of sand­wiches.</p>
<p>I per­son­ally found these in-per­son days mo­ti­vat­ing.
I would come home at the end of the day ex­cited to get back to work.
I felt like I had a re­newed sense of pur­pose and drive.
It was in­tox­i­cat­ing.</p>
<p>I think a huge part of this feel­ing was a con­se­quence from Jason’s per­son­al­ity.
We seem to mesh to­gether well, and I have no­ticed that he has a ten­dency to am­plify the best qual­i­ties in the peo­ple he leads.</p>
<p>That said, I do not think that 100% in-of­ﬁce work is sus­tain­able in the long term.
I found that the days we were to­gether, I was not quite as able to<span class="push-double"></span> <span class="pull-double">“</span>lock in”.
We had fan­tas­tic dis­cus­sions and our de­ci­sion mak­ing process was a mil­lion times faster than it would have been oth­er­wise, but it was just that lit­tle harder to turn the talk into ac­tion.
Sim­i­larly, I found it more dif­ﬁ­cult to con­vey cer­tain kinds of tech­ni­cal in­for­ma­tion.
When we needed to ex­plain some­thing in depth, I felt the com­pul­sion (which could be fought, I sup­pose) to send a link later, rather than try to ex­plain it in the mo­ment.</p>
<p>In the end, I think that Matt’s orig­i­nal con­clu­sion on mee­tups is still cor­rect.
He says that in the same way that <a href="https://distributed.blog/2023/05/26/episode-30-meetups/"><span class="push-double"></span><span class="pull-double">“</span>a lit­tle salt makes the dish”</a>, a few mee­tups every so of­ten can bring to­gether the best parts of both in-per­son and re­mote work.</p>
<p>So, I would say that our sec­ond ex­per­i­ment was also a suc­cess.
I per­son­ally feel as though I have earned a bet­ter in­tu­ition for what kinds of work are bet­ter in per­son and what are bet­ter async.</p>
<h2>The Fallout</h2>
<p>Immediately af­ter an­nounc­ing Harper Desktop, I got a ﬂood of mes­sages from peo­ple who had down­loaded it, in­stalled it, and found all the bugs in it.
That was both good news and bad news.</p>
<p>It was good be­cause we could be con­ﬁ­dent that we were in­deed build­ing the right thing.
Peo­ple want a desk­top ver­sion of Harper and they want to help us make it as awe­some as it can be.</p>
<p>The was bad be­cause, well, there were a ton of bugs.
Over the last few days, I have been bak­ing them out of the soft­ware as fast as pos­si­ble.
You should be get­ting a <strong>sig­nif­i­cantly</strong> im­proved piece of soft­ware in the com­ing hours.</p>
<p>Would I have post­poned the launch, had I knew about these is­sues?
Yes.
But I did not, which is why we do soft­ware launches like this.
There’s no way to fully ex­pect every is­sue that comes up, so you need to do a lit­tle ad-hoc user test­ing.
I con­sider this a suc­cess.</p>
<h2>Conclusion</h2>
<p>I re­ally en­joyed Radical Speed Month.
It was a hard stretch of learn­ing a lot about a va­ri­ety of sub­jects, most of which I have not even men­tioned in this post.
I am en­er­gized and ex­cited to ap­ply them to my daily work, and in hard­en­ing Harper Desktop.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Harper Desktop]]></title>
            <link>https://elijahpotter.dev/articles/harper_desktop</link>
            <guid>https://elijahpotter.dev/articles/harper_desktop</guid>
            <pubDate>Fri, 22 May 2026 17:11:41 GMT</pubDate>
            <description><![CDATA[<h1>Harper Desktop Is Available for Download</h1>
<p>Harper Desktop is ﬁ­nally avail­able for down­load.
It’s been about a month since <a href="https://jason.blog/">Jason</a> and I <a href="./building_a_harper_desktop_app">started work on it</a> for <a href="./what_was_radical_speed_month?">Radical Speed Month</a>.
In that time, we have worked hard to cre­ate a mean­ing­fully func­tional ex­pe­ri­ence and <a href="https://writewithharper.com/desktop">it is avail­able for down­load to­day</a>.</p>
<p>That said, it’s still early work.
There are bound to be bugs and edge-cases we have not con­sid­ered.
That’s why we are re­leas­ing it un­der a<span class="push-double"></span> <span class="pull-double">“</span>beta” ﬂag.
It’s avail­able, but do not ex­pect a su­per pol­ished ex­pe­ri­ence yet.
The hope is that we can work with the com­mu­nity to build a su­per pol­ished and com­fort­able ex­pe­ri­ence, but that will take time and, more im­por­tantly, <strong>bug re­ports</strong>.</p>
<p>Alongside sup­port for apps like Slack and Discord, Harper Desktop will bring our phe­nom­e­nal gram­mar check­ing to crit­i­cal apps for long-form au­thors, like <a href="https://www.literatureandlatte.com/scrivener/overview">Scrivener</a> and Microsoft Word.</p>
<h2>What’s Next</h2>
<p>I plan to en­ter a pe­riod of hard­en­ing, both for Harper Desktop and for every­thing else we of­fer.
There’s a back­log of bug re­ports and pull re­quests from the du­ra­tion of Radical Speed Month that needs to be ad­dressed.
If you are one of the many peo­ple who opened one of those things over the last month, know that I am not ig­nor­ing you.
Give me a sec­ond to catch my breath, and I will get to you.</p>
<p>After that, I hope to work on more ad­vanced style sug­ges­tions, po­ten­tially us­ing the greater com­put­ing power avail­able in the Harper Desktop app.
More de­tails to come.</p>
<h2>What I Have Learned</h2>
<p>Building Harper Desktop has given me a bet­ter un­der­stand­ing of what an ef­fec­tive team looks like.
Not just what an ef­fec­tive team does or says or what method­ol­ogy they em­ploy.
What they <strong>look</strong> like.</p>
<p>Harper Desktop was col­lab­o­ra­tion be­tween Jason and my­self.
He has far more ex­pe­ri­ence work­ing in teams than I do.
The chance to ob­serve the kinds of ques­tions he asks and un­der­stand why he asks them was in­cred­i­bly valu­able, and he car­ries a ﬁnely tuned feel­ing of pri­or­i­ti­za­tion that is dif­ﬁ­cult to con­vey over text.
He is a man that knows what is im­por­tant, and what is not.
I learned a lot from work­ing along­side Jason.
I ex­pect to learn more from him in the fu­ture.</p>
<p>With this ex­pe­ri­ence, I in­tend to make some sig­nif­i­cant im­prove­ments to how Harper as an open source com­mu­nity op­er­ates.
You should ex­pect to see me be­come more avail­able, more of the time.
Af­ter all, <a href="./we_are_in_this_together">we are in this to­gether</a></p>]]></description>
            <content:encoded><![CDATA[<h1>Harper Desktop Is Available for Download</h1>
<p>Harper Desktop is ﬁ­nally avail­able for down­load.
It’s been about a month since <a href="https://jason.blog/">Jason</a> and I <a href="./building_a_harper_desktop_app">started work on it</a> for <a href="./what_was_radical_speed_month?">Radical Speed Month</a>.
In that time, we have worked hard to cre­ate a mean­ing­fully func­tional ex­pe­ri­ence and <a href="https://writewithharper.com/desktop">it is avail­able for down­load to­day</a>.</p>
<p>That said, it’s still early work.
There are bound to be bugs and edge-cases we have not con­sid­ered.
That’s why we are re­leas­ing it un­der a<span class="push-double"></span> <span class="pull-double">“</span>beta” ﬂag.
It’s avail­able, but do not ex­pect a su­per pol­ished ex­pe­ri­ence yet.
The hope is that we can work with the com­mu­nity to build a su­per pol­ished and com­fort­able ex­pe­ri­ence, but that will take time and, more im­por­tantly, <strong>bug re­ports</strong>.</p>
<p>Alongside sup­port for apps like Slack and Discord, Harper Desktop will bring our phe­nom­e­nal gram­mar check­ing to crit­i­cal apps for long-form au­thors, like <a href="https://www.literatureandlatte.com/scrivener/overview">Scrivener</a> and Microsoft Word.</p>
<h2>What’s Next</h2>
<p>I plan to en­ter a pe­riod of hard­en­ing, both for Harper Desktop and for every­thing else we of­fer.
There’s a back­log of bug re­ports and pull re­quests from the du­ra­tion of Radical Speed Month that needs to be ad­dressed.
If you are one of the many peo­ple who opened one of those things over the last month, know that I am not ig­nor­ing you.
Give me a sec­ond to catch my breath, and I will get to you.</p>
<p>After that, I hope to work on more ad­vanced style sug­ges­tions, po­ten­tially us­ing the greater com­put­ing power avail­able in the Harper Desktop app.
More de­tails to come.</p>
<h2>What I Have Learned</h2>
<p>Building Harper Desktop has given me a bet­ter un­der­stand­ing of what an ef­fec­tive team looks like.
Not just what an ef­fec­tive team does or says or what method­ol­ogy they em­ploy.
What they <strong>look</strong> like.</p>
<p>Harper Desktop was col­lab­o­ra­tion be­tween Jason and my­self.
He has far more ex­pe­ri­ence work­ing in teams than I do.
The chance to ob­serve the kinds of ques­tions he asks and un­der­stand why he asks them was in­cred­i­bly valu­able, and he car­ries a ﬁnely tuned feel­ing of pri­or­i­ti­za­tion that is dif­ﬁ­cult to con­vey over text.
He is a man that knows what is im­por­tant, and what is not.
I learned a lot from work­ing along­side Jason.
I ex­pect to learn more from him in the fu­ture.</p>
<p>With this ex­pe­ri­ence, I in­tend to make some sig­nif­i­cant im­prove­ments to how Harper as an open source com­mu­nity op­er­ates.
You should ex­pect to see me be­come more avail­able, more of the time.
Af­ter all, <a href="./we_are_in_this_together">we are in this to­gether</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Good Agents]]></title>
            <link>https://elijahpotter.dev/articles/good_agents</link>
            <guid>https://elijahpotter.dev/articles/good_agents</guid>
            <pubDate>Fri, 15 May 2026 19:20:39 GMT</pubDate>
            <description><![CDATA[<h1>Good Agents for Developers</h1>
<p>The spirit of <a href="./what_was_radical_speed_month?">Radical Speed Month</a> in­cludes an im­pe­tus to ex­per­i­ment.
So ex­per­i­ment I have.</p>
<p>In par­tic­u­lar, I have been try­ing two new cod­ing agents: <a href="https://opencode.ai/">OpenCode</a> and <a href="https://pi.dev/">Pi</a>.
Both were in­ter­est­ing and taught me some­thing new, but I only in­tend to con­tinue us­ing one of them.</p>
<h2>OpenCode</h2>
<p>OpenCode caught my eye for the very qual­ity it’s named af­ter: OpenCode is open source.</p>
<p>At ﬁrst, it seemed to me to be an open source knock­off of OpenAI’s Codex.
After a few min­utes of use, though, I dis­cov­ered how it brings<span class="push-double"></span> <span class="pull-double">“</span>plan mode” front and cen­ter.
Claude Code and Codex both in­clude plan mode, but in both cases it feels like an af­ter­thought.
I had tried it a few times, but I was left unim­pressed.
Not so with OpenCode.
It’s a deeply in­te­grated part of the ex­pe­ri­ence.
If you are us­ing OpenCode with­out plan mode, you are hold­ing it wrong.</p>
<p>Overall, I found it a far more pro­duc­tive and en­joy­able ex­pe­ri­ence than Codex.</p>
<h2>Pi</h2>
<p>Pi is amaz­ing.
Sim­i­lar to OpenCode, Pi is an open source agent.
Un­like OpenCode, it is not de­signed to be a clone of Claude Code or Codex.
It is its own thing with an en­tirely sep­a­rate phi­los­o­phy.</p>
<h3>It Is Static</h3>
<p>Pi was born out of a frus­tra­tion with Claude Code: It did too much, and it changed too of­ten.
This is a prob­lem for de­vel­oper tool­ing.
De­vel­op­ers ex­pect the sys­tems un­der­ly­ing their tool­ing to be sta­tic, so the tools them­selves only need to change when the re­quire­ments do.
This sim­ple idea is why we have stan­dards like <a href="https://semver.org/">Semantic Versioning</a>.</p>
<p>The skills, prompts, and tools we write <em>for</em> our agents de­pend on the un­der­ly­ing be­hav­ior of these agent loops to re­main sta­tic.
When Anthropic or OpenAI ad­just the sys­tem prompt or change how the mod­el’s tools are called, the things we build around them break.</p>
<p>Pi is not like that.
Pi is in­tended to be as sim­ple as pos­si­ble, and as ex­ten­si­ble as pos­si­ble.
An en­gi­neer to au­to­mate tasks us­ing Pi and ex­pect those au­toma­tions to con­tinue work­ing for­ever.</p>
<p>This is im­por­tant to me, and if you are an en­gi­neer, it should be im­por­tant to you.</p>
<h3>It Is Extensible</h3>
<p>The en­ergy be­hind Pi feels a lot like the en­ergy be­hind Neovim.
It’s built for and by a com­mu­nity of hack­ers, who each have their own cus­tom setup.</p>
<p>I have only been us­ing it for a few days, but I al­ready feel an in­de­scrib­able sense of own­er­ship over my prompts, skills, and the ex­ten­sions I have in­stalled.
When I feel a de­ﬁ­ciency in my work­ﬂow, I do not feel that I need to wait un­til Anthropic or OpenAI has time to ad­dress it.
I can ad­dress it my­self.</p>
<h2>Think Critically</h2>
<p>If you are not ac­tively push­ing your brain to it’s limit while pro­gram­ming — whether you are us­ing an agent or not — you are not do­ing your best work.
Try­ing both of these agents has taught me that it is en­tirely pos­si­ble to push my brain hard while also us­ing an agent to take away some of the te­dium.
In the end, I get bet­ter soft­ware faster.</p>]]></description>
            <content:encoded><![CDATA[<h1>Good Agents for Developers</h1>
<p>The spirit of <a href="./what_was_radical_speed_month?">Radical Speed Month</a> in­cludes an im­pe­tus to ex­per­i­ment.
So ex­per­i­ment I have.</p>
<p>In par­tic­u­lar, I have been try­ing two new cod­ing agents: <a href="https://opencode.ai/">OpenCode</a> and <a href="https://pi.dev/">Pi</a>.
Both were in­ter­est­ing and taught me some­thing new, but I only in­tend to con­tinue us­ing one of them.</p>
<h2>OpenCode</h2>
<p>OpenCode caught my eye for the very qual­ity it’s named af­ter: OpenCode is open source.</p>
<p>At ﬁrst, it seemed to me to be an open source knock­off of OpenAI’s Codex.
After a few min­utes of use, though, I dis­cov­ered how it brings<span class="push-double"></span> <span class="pull-double">“</span>plan mode” front and cen­ter.
Claude Code and Codex both in­clude plan mode, but in both cases it feels like an af­ter­thought.
I had tried it a few times, but I was left unim­pressed.
Not so with OpenCode.
It’s a deeply in­te­grated part of the ex­pe­ri­ence.
If you are us­ing OpenCode with­out plan mode, you are hold­ing it wrong.</p>
<p>Overall, I found it a far more pro­duc­tive and en­joy­able ex­pe­ri­ence than Codex.</p>
<h2>Pi</h2>
<p>Pi is amaz­ing.
Sim­i­lar to OpenCode, Pi is an open source agent.
Un­like OpenCode, it is not de­signed to be a clone of Claude Code or Codex.
It is its own thing with an en­tirely sep­a­rate phi­los­o­phy.</p>
<h3>It Is Static</h3>
<p>Pi was born out of a frus­tra­tion with Claude Code: It did too much, and it changed too of­ten.
This is a prob­lem for de­vel­oper tool­ing.
De­vel­op­ers ex­pect the sys­tems un­der­ly­ing their tool­ing to be sta­tic, so the tools them­selves only need to change when the re­quire­ments do.
This sim­ple idea is why we have stan­dards like <a href="https://semver.org/">Semantic Versioning</a>.</p>
<p>The skills, prompts, and tools we write <em>for</em> our agents de­pend on the un­der­ly­ing be­hav­ior of these agent loops to re­main sta­tic.
When Anthropic or OpenAI ad­just the sys­tem prompt or change how the mod­el’s tools are called, the things we build around them break.</p>
<p>Pi is not like that.
Pi is in­tended to be as sim­ple as pos­si­ble, and as ex­ten­si­ble as pos­si­ble.
An en­gi­neer to au­to­mate tasks us­ing Pi and ex­pect those au­toma­tions to con­tinue work­ing for­ever.</p>
<p>This is im­por­tant to me, and if you are an en­gi­neer, it should be im­por­tant to you.</p>
<h3>It Is Extensible</h3>
<p>The en­ergy be­hind Pi feels a lot like the en­ergy be­hind Neovim.
It’s built for and by a com­mu­nity of hack­ers, who each have their own cus­tom setup.</p>
<p>I have only been us­ing it for a few days, but I al­ready feel an in­de­scrib­able sense of own­er­ship over my prompts, skills, and the ex­ten­sions I have in­stalled.
When I feel a de­ﬁ­ciency in my work­ﬂow, I do not feel that I need to wait un­til Anthropic or OpenAI has time to ad­dress it.
I can ad­dress it my­self.</p>
<h2>Think Critically</h2>
<p>If you are not ac­tively push­ing your brain to it’s limit while pro­gram­ming — whether you are us­ing an agent or not — you are not do­ing your best work.
Try­ing both of these agents has taught me that it is en­tirely pos­si­ble to push my brain hard while also us­ing an agent to take away some of the te­dium.
In the end, I get bet­ter soft­ware faster.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Quick Update on Harper Desktop]]></title>
            <link>https://elijahpotter.dev/articles/a_quick_update_on_harper_desktop</link>
            <guid>https://elijahpotter.dev/articles/a_quick_update_on_harper_desktop</guid>
            <pubDate>Fri, 08 May 2026 16:08:14 GMT</pubDate>
            <description><![CDATA[<h1>A Quick Update on Harper Desktop</h1>
<p>This week of <a href="./what_was_radical_speed_month?">Radical Speed Month</a> has been ded­i­cated to putting the dis­parate com­po­nents of the Harper Desktop App to­gether.
In other words, we have been in what <a href="https://www.youtube.com/@stuffmadehere">Shane Wighton</a> would call<span class="push-double"></span> <span class="pull-double">“</span>integration hell”.</p>
<p>Due to the con­straints put in place by the op­er­at­ing sys­tem and our de­ci­sion to use Tauri, Harper Desktop needed to be split into three main pieces: the high­lighter process, the main Rust Tauri process, and the Svelte fron­tend.
We use the main Rust Tauri process as the source-of-truth for all user con­ﬁg­u­ra­tion and state, which meant we needed to or­ches­trate a non­triv­ial amount of com­mu­ni­ca­tion be­tween it and the other two com­po­nents.</p>
<p>At the same time, I’ve been in­te­grat­ing every­thing (including <a href="https://github.com/Automattic/harper/pull/3278">Jason’s won­der­ful re­vamp of the Harper ed­i­tor</a>) into the main Harper repos­i­tory.
It’s been a slow go­ing process, and there is still some work to be done with CI.
At the same time, it’s quite sat­is­fy­ing to see every­thing click into place.</p>
<p>Next, we plan to fo­cus our at­ten­tion on the more ﬁt-and-ﬁn­ish pieces of the pro­ject.
That means wrap­ping up the im­ple­men­ta­tion of cer­tain parts of the con­ﬁg­u­ra­tion, build­ing out up­dat­ing logic, and cre­at­ing an ac­tual down­load page on our web­site.</p>]]></description>
            <content:encoded><![CDATA[<h1>A Quick Update on Harper Desktop</h1>
<p>This week of <a href="./what_was_radical_speed_month?">Radical Speed Month</a> has been ded­i­cated to putting the dis­parate com­po­nents of the Harper Desktop App to­gether.
In other words, we have been in what <a href="https://www.youtube.com/@stuffmadehere">Shane Wighton</a> would call<span class="push-double"></span> <span class="pull-double">“</span>integration hell”.</p>
<p>Due to the con­straints put in place by the op­er­at­ing sys­tem and our de­ci­sion to use Tauri, Harper Desktop needed to be split into three main pieces: the high­lighter process, the main Rust Tauri process, and the Svelte fron­tend.
We use the main Rust Tauri process as the source-of-truth for all user con­ﬁg­u­ra­tion and state, which meant we needed to or­ches­trate a non­triv­ial amount of com­mu­ni­ca­tion be­tween it and the other two com­po­nents.</p>
<p>At the same time, I’ve been in­te­grat­ing every­thing (including <a href="https://github.com/Automattic/harper/pull/3278">Jason’s won­der­ful re­vamp of the Harper ed­i­tor</a>) into the main Harper repos­i­tory.
It’s been a slow go­ing process, and there is still some work to be done with CI.
At the same time, it’s quite sat­is­fy­ing to see every­thing click into place.</p>
<p>Next, we plan to fo­cus our at­ten­tion on the more ﬁt-and-ﬁn­ish pieces of the pro­ject.
That means wrap­ping up the im­ple­men­ta­tion of cer­tain parts of the con­ﬁg­u­ra­tion, build­ing out up­dat­ing logic, and cre­at­ing an ac­tual down­load page on our web­site.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Product Product Product]]></title>
            <link>https://elijahpotter.dev/articles/product_product_product</link>
            <guid>https://elijahpotter.dev/articles/product_product_product</guid>
            <pubDate>Fri, 01 May 2026 22:00:42 GMT</pubDate>
            <description><![CDATA[<h1>Product, Product, Product</h1>
<p>Working this past week with Jason Adams has given me a brand-new ap­pre­ci­a­tion for the cat­e­gory of ideas that we en­gi­neers call<span class="push-double"></span> <span class="pull-double">“</span>product.”</p>
<p>For a long time, I’ve dis­cussed the dif­ﬁ­cult-to-de­ﬁne word<span class="push-double"></span> <span class="pull-double">“</span>Quality” and it’s im­por­tance to the suc­cess of soft­ware pro­jects.
To me, cap­i­tal<span class="push-double"></span> <span class="pull-double">“</span>Q” Quality meant that a piece of soft­ware was fast, ex­ten­si­ble, and re­li­able.
It was a check­list and a se­ries of met­rics that could be tracked and op­ti­mized through it­er­a­tion.
I had, and ar­guably still have, an en­gi­neer’s de­f­i­n­i­tion of<span class="push-double"></span> <span class="pull-double">“</span>Quality”.</p>
<p>But the ver­sion of Quality that can be de­ﬁned with num­bers is not com­plete.
A com­plete de­f­i­n­i­tion of Quality in­cludes some­thing that I cur­rently don’t know how to de­ﬁne.
Peo­ple like Jason Adams and Eduardo Villuendas can.</p>
<p>At the end of <a href="./what_was_radical_speed_month?">Radical Speed Month</a>, my goal is to be able to de­scribe it, at least shal­lowly.
Right now, I feel that I un­der­stand some of the base com­po­nents, but not well enough to bring them to­gether into a co­he­sive whole.</p>
<h2>Harper Desktop</h2>
<p>Harper Desktop is com­ing along amaz­ingly!
My goal for this week was to get the full pipeline work­ing.
That means:</p>
<ol>
<li>Reading text from ar­bi­trary text ar­eas on ma­cOS.</li>
<li>Locating er­rors within it us­ing <code>harper-core</code>.</li>
<li>Rendering high­lights over those er­rors.</li>
<li>Showing a sug­ges­tion popup to users so they can re­view po­ten­tial cor­rec­tions.</li>
<li>Applying the cor­rec­tions.</li>
</ol>
<p>This is a pipeline that we’ve suc­cess­fully repli­cated in sev­eral other apps, but this is the ﬁrst time we’ve tried to do it sys­tem-wide.
I’m pleased to say: It’s all work­ing!</p>
<video width="100%" height="400" controls>
    <source src="/videos/macos_harper_desktop_demo.mp4">
</video>
<p>The only thing miss­ing from a<span class="push-double"></span> <span class="pull-double">“</span>complete” Harper Desktop ex­pe­ri­ence is con­ﬁg­u­ra­tion.
Users need to be able to ma­nip­u­late their dic­tio­nary, up­date their rule pref­er­ences, and whitelist (or black­list) apps from be­ing ma­nip­u­lated by Harper.</p>
<p>Fortunately, Jason Adams has been hard at work re­ﬁn­ing what Harper’s set­tings menus look like.
Ac­tu­ally, to say that is <strong>all</strong> he has been do­ing would be disin­gen­u­ous.
In re­al­ity, he has been re­vamp­ing the en­tirety of Harper’s de­sign and look.</p>
<img src="/images/jasons_settings_preview.png" alt="A Preview of Jason&#x27;s Settings Page">
<p>The hope is that much, if not all, of Jason’s work here will be ported back to the Chrome Extension.</p>
<h2>The Magic of Meetups</h2>
<p>I have never felt the magic of mee­tups more than dur­ing the short sin­gle-day mee­tups that Jason and I have been do­ing over the past few days.
I feel more pro­duc­tive, mo­ti­vated, and op­ti­mistic than ever, and I think that is in no small part thanks to mee­tups. I get the hype now.</p>
<img src="/images/jason_elijah_garden_of_the_gods.png" alt="A Quick Jaunt to Garden of the Gods">]]></description>
            <content:encoded><![CDATA[<h1>Product, Product, Product</h1>
<p>Working this past week with Jason Adams has given me a brand-new ap­pre­ci­a­tion for the cat­e­gory of ideas that we en­gi­neers call<span class="push-double"></span> <span class="pull-double">“</span>product.”</p>
<p>For a long time, I’ve dis­cussed the dif­ﬁ­cult-to-de­ﬁne word<span class="push-double"></span> <span class="pull-double">“</span>Quality” and it’s im­por­tance to the suc­cess of soft­ware pro­jects.
To me, cap­i­tal<span class="push-double"></span> <span class="pull-double">“</span>Q” Quality meant that a piece of soft­ware was fast, ex­ten­si­ble, and re­li­able.
It was a check­list and a se­ries of met­rics that could be tracked and op­ti­mized through it­er­a­tion.
I had, and ar­guably still have, an en­gi­neer’s de­f­i­n­i­tion of<span class="push-double"></span> <span class="pull-double">“</span>Quality”.</p>
<p>But the ver­sion of Quality that can be de­ﬁned with num­bers is not com­plete.
A com­plete de­f­i­n­i­tion of Quality in­cludes some­thing that I cur­rently don’t know how to de­ﬁne.
Peo­ple like Jason Adams and Eduardo Villuendas can.</p>
<p>At the end of <a href="./what_was_radical_speed_month?">Radical Speed Month</a>, my goal is to be able to de­scribe it, at least shal­lowly.
Right now, I feel that I un­der­stand some of the base com­po­nents, but not well enough to bring them to­gether into a co­he­sive whole.</p>
<h2>Harper Desktop</h2>
<p>Harper Desktop is com­ing along amaz­ingly!
My goal for this week was to get the full pipeline work­ing.
That means:</p>
<ol>
<li>Reading text from ar­bi­trary text ar­eas on ma­cOS.</li>
<li>Locating er­rors within it us­ing <code>harper-core</code>.</li>
<li>Rendering high­lights over those er­rors.</li>
<li>Showing a sug­ges­tion popup to users so they can re­view po­ten­tial cor­rec­tions.</li>
<li>Applying the cor­rec­tions.</li>
</ol>
<p>This is a pipeline that we’ve suc­cess­fully repli­cated in sev­eral other apps, but this is the ﬁrst time we’ve tried to do it sys­tem-wide.
I’m pleased to say: It’s all work­ing!</p>
<video width="100%" height="400" controls>
    <source src="/videos/macos_harper_desktop_demo.mp4">
</video>
<p>The only thing miss­ing from a<span class="push-double"></span> <span class="pull-double">“</span>complete” Harper Desktop ex­pe­ri­ence is con­ﬁg­u­ra­tion.
Users need to be able to ma­nip­u­late their dic­tio­nary, up­date their rule pref­er­ences, and whitelist (or black­list) apps from be­ing ma­nip­u­lated by Harper.</p>
<p>Fortunately, Jason Adams has been hard at work re­ﬁn­ing what Harper’s set­tings menus look like.
Ac­tu­ally, to say that is <strong>all</strong> he has been do­ing would be disin­gen­u­ous.
In re­al­ity, he has been re­vamp­ing the en­tirety of Harper’s de­sign and look.</p>
<img src="/images/jasons_settings_preview.png" alt="A Preview of Jason&#x27;s Settings Page">
<p>The hope is that much, if not all, of Jason’s work here will be ported back to the Chrome Extension.</p>
<h2>The Magic of Meetups</h2>
<p>I have never felt the magic of mee­tups more than dur­ing the short sin­gle-day mee­tups that Jason and I have been do­ing over the past few days.
I feel more pro­duc­tive, mo­ti­vated, and op­ti­mistic than ever, and I think that is in no small part thanks to mee­tups. I get the hype now.</p>
<img src="/images/jason_elijah_garden_of_the_gods.png" alt="A Quick Jaunt to Garden of the Gods">]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/jason_elijah_garden_of_the_gods.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Building a Harper Desktop App]]></title>
            <link>https://elijahpotter.dev/articles/building_a_harper_desktop_app</link>
            <guid>https://elijahpotter.dev/articles/building_a_harper_desktop_app</guid>
            <pubDate>Wed, 15 Apr 2026 20:21:22 GMT</pubDate>
            <description><![CDATA[<h1>Building a Harper Desktop App</h1>
<p>Harper is a gram­mar checker that makes sure that your writ­ing is ex­actly that: <strong>yours</strong>.
Today, we will be start­ing some new and hope­fully awe­some work to­wards that mis­sion.</p>
<p>Harper has long been avail­able in Chrome, Firefox, Obsidian, and a gazil­lion other places. Knowing what ex­ten­sions to in­stall and how has been a source of con­fu­sion for too many of you. Harper is sup­posed to get out of your way so you can write your best work, and the con­ﬁg­u­ra­tion has been get­ting in the way of that. You’ve been ask­ing for it, and we’re ﬁ­nally de­liv­er­ing.</p>
<p>We’re go­ing to be work­ing in pub­lic as much as we can.
As I said last week: <a href="./we_are_in_this_together">We’re in this to­gether</a>.
That means you’ll be see­ing more posts on this blog and in other places.
I hope to share fun bits about our im­ple­men­ta­tion and an­nounce re­quests for feed­back.</p>
<p>You might be won­der­ing, <em>why is he us­ing the pro­noun<span class="push-double"></span> <span class="pull-double">“</span>we”?</em>
It is be­cause the won­der­ful <a href="https://jason.blog/">Jason Adams</a> has vol­un­teered a sig­nif­i­cant amount of time over the next month to­wards this ef­fort.
He’s an in­cred­i­ble en­gi­neer and I can’t wait to start solv­ing hard prob­lems with him.</p>
<p>If you’d like to have a say in what the app looks like or does, feel free to leave a com­ment here or on <a href="https://github.com/Automattic/harper/discussions/1907">this dis­cus­sion.</a></p>
<p>I imag­ine we’ll be us­ing Tauri and Svelte for con­ﬁg­u­ra­tion and setup screens, as well as for an in-app ed­i­tor.
Be­yond that, I don’t have much more to say now.
Stay tuned!</p>]]></description>
            <content:encoded><![CDATA[<h1>Building a Harper Desktop App</h1>
<p>Harper is a gram­mar checker that makes sure that your writ­ing is ex­actly that: <strong>yours</strong>.
Today, we will be start­ing some new and hope­fully awe­some work to­wards that mis­sion.</p>
<p>Harper has long been avail­able in Chrome, Firefox, Obsidian, and a gazil­lion other places. Knowing what ex­ten­sions to in­stall and how has been a source of con­fu­sion for too many of you. Harper is sup­posed to get out of your way so you can write your best work, and the con­ﬁg­u­ra­tion has been get­ting in the way of that. You’ve been ask­ing for it, and we’re ﬁ­nally de­liv­er­ing.</p>
<p>We’re go­ing to be work­ing in pub­lic as much as we can.
As I said last week: <a href="./we_are_in_this_together">We’re in this to­gether</a>.
That means you’ll be see­ing more posts on this blog and in other places.
I hope to share fun bits about our im­ple­men­ta­tion and an­nounce re­quests for feed­back.</p>
<p>You might be won­der­ing, <em>why is he us­ing the pro­noun<span class="push-double"></span> <span class="pull-double">“</span>we”?</em>
It is be­cause the won­der­ful <a href="https://jason.blog/">Jason Adams</a> has vol­un­teered a sig­nif­i­cant amount of time over the next month to­wards this ef­fort.
He’s an in­cred­i­ble en­gi­neer and I can’t wait to start solv­ing hard prob­lems with him.</p>
<p>If you’d like to have a say in what the app looks like or does, feel free to leave a com­ment here or on <a href="https://github.com/Automattic/harper/discussions/1907">this dis­cus­sion.</a></p>
<p>I imag­ine we’ll be us­ing Tauri and Svelte for con­ﬁg­u­ra­tion and setup screens, as well as for an in-app ed­i­tor.
Be­yond that, I don’t have much more to say now.
Stay tuned!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[We Are in This Together]]></title>
            <link>https://elijahpotter.dev/articles/we_are_in_this_together</link>
            <guid>https://elijahpotter.dev/articles/we_are_in_this_together</guid>
            <pubDate>Mon, 13 Apr 2026 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>We Are in This Together</h1>
<p>Today, Jason Adams joined me and some of the other Automatticians based in the Denver area for a bur­rito.
It’s a tra­di­tion we call Burrito Friday.
Automatticians from a lo­cal area get to­gether for laughs, chats, and bur­ri­tos on some rare Fridays a few times a year.
It’s a great way to stay con­nected in a <a href="https://distributed.blog/podcast/">dis­trib­uted</a> or­ga­ni­za­tion.</p>
<p>Today marked the sec­ond such oc­ca­sion wherein we were joined by Jason, and just like al­ways, he brought with him an un­usual clar­ity of thought.
We dis­cussed a va­ri­ety of sub­jects, but there’s one that rung es­pe­cially true to me.
The im­por­tance of a <strong>mis­sion</strong>.</p>
<h2>The Mission</h2>
<p>Harper’s mis­sion is to en­cour­age and as­sist peo­ple with com­mu­ni­cat­ing their com­plex ideas and emo­tions.
We want to lift the cog­ni­tive load that is gram­mar off the shoul­ders of writ­ers so they can feel free to ex­press them­selves.
At the same time, Harper should­n’t <strong>add</strong> any ad­di­tional cog­ni­tive load.
That means:</p>
<ul>
<li><strong>Harper works fast</strong>. You should­n’t have to think about the gram­mar at all. That means you should­n’t have to think about the gram­mar checker ei­ther.</li>
<li><strong>Harper works pri­vately.</strong> You can trust us not to do any­thing ne­far­i­ous with your writ­ing. We won’t sell it, save it, or train on it. Your writ­ing is yours, no mat­ter what.</li>
</ul>
<h2>The Mission Is an Outcome</h2>
<p><a href="./outcome_over_process">In my post last week</a>, I talked about how fo­cus on the des­ti­na­tion is far more pro­duc­tive than fo­cus on the process of get­ting there.
Harper’s mis­sion is the ul­ti­mate out­come.
Ef­forts to­wards that achiev­ing that grand mis­sion will ul­ti­mately be more suc­cess­ful than ef­forts to­wards per­fect­ing any in­di­vid­ual step along the way.</p>
<p>Jason ex­pressed it quite con­cisely.
So con­cisely in fact, that I won’t try to repli­cate it here.</p>
<p>As it turns out, many peo­ple (including ex-Gram­marly cus­tomers) be­lieve our mis­sion to be im­por­tant.
Part of my job as Harper’s main­tainer is to ﬁnd these peo­ple and demon­strate that we have sim­i­lar goals.</p>
<p>This is some­thing I ad­mit­tedly haven’t done as well as I could.
I’d like to get into real con­ver­sa­tions with real peo­ple who care about the de­struc­tive busi­ness prac­tices em­ployed by Harper’s com­peti­tors.</p>
<p>To that end, I am ex­tend­ing some feel­ers.
I’ll be reach­ing out to some pod­cast­ers and promi­nent mem­bers of the open source com­mu­nity to dis­cuss how we can make writ­ing eas­ier with­out sac­ri­ﬁc­ing its hu­man­ity.
Sim­i­larly, if this is some­thing <strong>you</strong> want to talk with me about, please reach out!</p>]]></description>
            <content:encoded><![CDATA[<h1>We Are in This Together</h1>
<p>Today, Jason Adams joined me and some of the other Automatticians based in the Denver area for a bur­rito.
It’s a tra­di­tion we call Burrito Friday.
Automatticians from a lo­cal area get to­gether for laughs, chats, and bur­ri­tos on some rare Fridays a few times a year.
It’s a great way to stay con­nected in a <a href="https://distributed.blog/podcast/">dis­trib­uted</a> or­ga­ni­za­tion.</p>
<p>Today marked the sec­ond such oc­ca­sion wherein we were joined by Jason, and just like al­ways, he brought with him an un­usual clar­ity of thought.
We dis­cussed a va­ri­ety of sub­jects, but there’s one that rung es­pe­cially true to me.
The im­por­tance of a <strong>mis­sion</strong>.</p>
<h2>The Mission</h2>
<p>Harper’s mis­sion is to en­cour­age and as­sist peo­ple with com­mu­ni­cat­ing their com­plex ideas and emo­tions.
We want to lift the cog­ni­tive load that is gram­mar off the shoul­ders of writ­ers so they can feel free to ex­press them­selves.
At the same time, Harper should­n’t <strong>add</strong> any ad­di­tional cog­ni­tive load.
That means:</p>
<ul>
<li><strong>Harper works fast</strong>. You should­n’t have to think about the gram­mar at all. That means you should­n’t have to think about the gram­mar checker ei­ther.</li>
<li><strong>Harper works pri­vately.</strong> You can trust us not to do any­thing ne­far­i­ous with your writ­ing. We won’t sell it, save it, or train on it. Your writ­ing is yours, no mat­ter what.</li>
</ul>
<h2>The Mission Is an Outcome</h2>
<p><a href="./outcome_over_process">In my post last week</a>, I talked about how fo­cus on the des­ti­na­tion is far more pro­duc­tive than fo­cus on the process of get­ting there.
Harper’s mis­sion is the ul­ti­mate out­come.
Ef­forts to­wards that achiev­ing that grand mis­sion will ul­ti­mately be more suc­cess­ful than ef­forts to­wards per­fect­ing any in­di­vid­ual step along the way.</p>
<p>Jason ex­pressed it quite con­cisely.
So con­cisely in fact, that I won’t try to repli­cate it here.</p>
<p>As it turns out, many peo­ple (including ex-Gram­marly cus­tomers) be­lieve our mis­sion to be im­por­tant.
Part of my job as Harper’s main­tainer is to ﬁnd these peo­ple and demon­strate that we have sim­i­lar goals.</p>
<p>This is some­thing I ad­mit­tedly haven’t done as well as I could.
I’d like to get into real con­ver­sa­tions with real peo­ple who care about the de­struc­tive busi­ness prac­tices em­ployed by Harper’s com­peti­tors.</p>
<p>To that end, I am ex­tend­ing some feel­ers.
I’ll be reach­ing out to some pod­cast­ers and promi­nent mem­bers of the open source com­mu­nity to dis­cuss how we can make writ­ing eas­ier with­out sac­ri­ﬁc­ing its hu­man­ity.
Sim­i­larly, if this is some­thing <strong>you</strong> want to talk with me about, please reach out!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Harper 2.0]]></title>
            <link>https://elijahpotter.dev/articles/harper_2.0</link>
            <guid>https://elijahpotter.dev/articles/harper_2.0</guid>
            <pubDate>Thu, 09 Apr 2026 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Harper 2.0</h1>
<p>Hey all! We’re ﬁ­nally re­leas­ing Harper 2.0. Why?</p>
<p>It is­n’t be­cause we have any new ground­break­ing fea­tures in this re­lease, be­cause we don’t let ma­jor ver­sion­ing stop us from push­ing those out as soon as they’re ready. It’s be­cause we have some break­ing changes.</p>
<p>The biggest one only ap­plies to you if you con­sume <code>harper.js</code>. From now on, in­stead of im­port­ing your bi­nary from the main Harper mod­ule, you’ll im­port it from one of four spe­cial­ized mod­ules.</p>
<p>Previously, you’d im­port the Harper WebAssembly bi­nary with:</p>
<pre><code class="hljs language-javascript"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">LocalLinter</span>, binary } <span class="hljs-keyword">from</span> <span class="hljs-string">"harper.js"</span>;
</code></pre>
<p>As of Harper 2.0, you’ll im­port it like this:</p>
<pre><code class="hljs language-javascript"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">LocalLinter</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"harper.js"</span>;
<span class="hljs-keyword">import</span> { binary } <span class="hljs-keyword">from</span> <span class="hljs-string">"harper.js/binary"</span>; 
</code></pre>
<p>It’s that sim­ple! Thanks to this change, you’ll ﬁnd that your ap­pli­ca­tions are more con­ducive to tree-shak­ing and there­fore might even be a bit smaller!</p>
<p>In ad­di­tion to that large break­ing change, we also have a bunch of smaller im­prove­ments rolling out. I won’t go through them one by one, but you’re free to read through any of the linked pull re­quests in the <a href="https://github.com/automattic/harper/releases/tag/v2.0.0">main re­lease notes</a>.</p>]]></description>
            <content:encoded><![CDATA[<h1>Harper 2.0</h1>
<p>Hey all! We’re ﬁ­nally re­leas­ing Harper 2.0. Why?</p>
<p>It is­n’t be­cause we have any new ground­break­ing fea­tures in this re­lease, be­cause we don’t let ma­jor ver­sion­ing stop us from push­ing those out as soon as they’re ready. It’s be­cause we have some break­ing changes.</p>
<p>The biggest one only ap­plies to you if you con­sume <code>harper.js</code>. From now on, in­stead of im­port­ing your bi­nary from the main Harper mod­ule, you’ll im­port it from one of four spe­cial­ized mod­ules.</p>
<p>Previously, you’d im­port the Harper WebAssembly bi­nary with:</p>
<pre><code class="hljs language-javascript"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">LocalLinter</span>, binary } <span class="hljs-keyword">from</span> <span class="hljs-string">"harper.js"</span>;
</code></pre>
<p>As of Harper 2.0, you’ll im­port it like this:</p>
<pre><code class="hljs language-javascript"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">LocalLinter</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"harper.js"</span>;
<span class="hljs-keyword">import</span> { binary } <span class="hljs-keyword">from</span> <span class="hljs-string">"harper.js/binary"</span>; 
</code></pre>
<p>It’s that sim­ple! Thanks to this change, you’ll ﬁnd that your ap­pli­ca­tions are more con­ducive to tree-shak­ing and there­fore might even be a bit smaller!</p>
<p>In ad­di­tion to that large break­ing change, we also have a bunch of smaller im­prove­ments rolling out. I won’t go through them one by one, but you’re free to read through any of the linked pull re­quests in the <a href="https://github.com/automattic/harper/releases/tag/v2.0.0">main re­lease notes</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Outcome over Process]]></title>
            <link>https://elijahpotter.dev/articles/outcome_over_process</link>
            <guid>https://elijahpotter.dev/articles/outcome_over_process</guid>
            <pubDate>Fri, 03 Apr 2026 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Outcome over Process</h1>
<p>For the past few weeks, I’ve been think­ing about I can be more pro­duc­tive.
In par­tic­u­lar, I’ve been think­ing about how <a href="https://elijahpotter.dev/articles/a-case-for-procrastination">I can elim­i­nate waste</a> to give my­self time to work on things that mat­ter.</p>
<p>These ef­forts have been par­tially suc­cess­ful.
I’ve found ways to speed up code re­view with an agent and im­prove the turn­around time on com­mu­ni­ca­tion.
I’ve been spend­ing less time wait­ing and more time <strong>work­ing.</strong>
But in the end, I don’t feel that these per­for­ma­tive ac­tions have re­sulted in any real pro­duc­tiv­ity gains. I re­al­ize now, that this was be­cause I was fo­cus­ing on the wrong thing.</p>
<p>I was fo­cus­ing on the process of get­ting stuff done, in­stead of fo­cus­ing on how to ac­tu­ally get that same stuff done.</p>
<blockquote>
<p>As an aside: I found <a href="https://mitchellh.com/writing/my-ai-adoption-journey">Mitchell Hashimoto’s re­lated blog post ex­tremely stim­u­lat­ing.</a>
I rec­om­mend it to any­one try­ing to ap­ply these agents to their real-world work.</p>
</blockquote>
<p>For the past few weeks, be­tween read­ing <a href="https://practicaltypography.com/">Butterick’s Practical Typography</a> and get­ting Harper 2.0 out the door, I’ve been read­ing <em>The Effective Executive</em> by Peter Drucker — at <a href="https://ma.tt/">Matt’s</a> rec­om­men­da­tion.</p>
<p>It’s a fan­tas­tic book.
It is well writ­ten, gives thor­ough rea­son­ing, and pro­vides case stud­ies for each of its ma­jor points.
I haven’t quite ﬁn­ished it, but there are some great ideas from the ﬁrst bit.
Im­por­tantly, it has be­gin to re­ori­ent my think­ing to­wards out­comes over process.</p>
<h2>What Is<span class="push-double"></span> <span class="pull-double">“</span>The Process”?</h2>
<p>The<span class="push-double"></span> <span class="pull-double">“</span>process” in­cludes all the minu­tia re­quired to de­liver on the mean­ing­ful pur­pose of your job.
For an ac­coun­tant, that might in­volve tab­u­lat­ing data for the pur­pose of in­creas­ing ef­ﬁ­ciency in the busi­ness.
For me, as the main­tainer of <a href="https://writewithharper.com">Harper</a>, my pur­pose is to help peo­ple com­mu­ni­cate bet­ter us­ing the writ­ten word.</p>
<p>My sec­ondary pur­pose is to prove that the open source method­ol­ogy is su­pe­rior to the com­pe­ti­tion in ac­com­plish­ing my pri­mary ob­jec­tive.</p>
<p>For me, the process in­volves:</p>
<ul>
<li>Producing code.</li>
<li>Reviewing code.</li>
<li>Determining in which di­rec­tion the pro­ject needs to move.</li>
</ul>
<p>The<span class="push-double"></span> <span class="pull-double">“</span>process” will look dif­fer­ent for you.
It is com­posed of the in­di­vid­ual steps needed to de­liver the ﬁ­nal out­come.</p>
<h2>Think About Where You’re Going and Nothing Else</h2>
<p><a href="https://elijahpotter.dev/articles/improving-rust-compile-times-by-71-percent">An oc­ca­sional check-in</a> on what the process it­self looks like can be ben­e­ﬁ­cial, but it should not be the fo­cus of my at­ten­tion.
My at­ten­tion should be on how to get things <strong>done</strong> and more im­por­tantly how to get my work <strong>in the hands of my users</strong> as fast as pos­si­ble.</p>
<p>I’ve caught my­self think­ing about the process more than the out­come too many times.
There are di­min­ish­ing re­turns to that kind of prac­tice, and they are ap­proached fast.</p>
<blockquote>
<p>Yes. I re­al­ize that this post is an ex­am­ple of the kind of think­ing that I am crit­i­ciz­ing.
This will be the last one of its kind for some time.</p>
</blockquote>
<p>According to Drucker, some­thing strange hap­pens in the brain when it fo­cuses on out­comes rather than processes.
In­stead of think­ing about how to make some­thing<span class="push-double"></span> <span class="pull-double">“</span>faster”, it fo­cuses on how to ﬁnd short­cuts or brand-new ap­proaches.
In other words — when ap­plied to out­comes — the brain be­comes more ﬂex­i­ble and there­fore more ef­fec­tive.</p>
<p>I’ve seen this my­self, but I’ve never been able to iden­tify it so suc­cinctly.
It’s why I strive to do <a href="https://elijahpotter.dev/articles/the-one-hard-thing">at least one hard thing each day</a>.</p>
<p>I’m look­ing for­ward to wrap­ping up Drucker’s book and hope­fully ex­tract­ing more of his wis­dom.
In the mean­time, I’m cu­ri­ous: have there been any in­stances where you felt you fo­cused too much on the process or tool­ing and too lit­tle on the ac­tual out­come?</p>]]></description>
            <content:encoded><![CDATA[<h1>Outcome over Process</h1>
<p>For the past few weeks, I’ve been think­ing about I can be more pro­duc­tive.
In par­tic­u­lar, I’ve been think­ing about how <a href="https://elijahpotter.dev/articles/a-case-for-procrastination">I can elim­i­nate waste</a> to give my­self time to work on things that mat­ter.</p>
<p>These ef­forts have been par­tially suc­cess­ful.
I’ve found ways to speed up code re­view with an agent and im­prove the turn­around time on com­mu­ni­ca­tion.
I’ve been spend­ing less time wait­ing and more time <strong>work­ing.</strong>
But in the end, I don’t feel that these per­for­ma­tive ac­tions have re­sulted in any real pro­duc­tiv­ity gains. I re­al­ize now, that this was be­cause I was fo­cus­ing on the wrong thing.</p>
<p>I was fo­cus­ing on the process of get­ting stuff done, in­stead of fo­cus­ing on how to ac­tu­ally get that same stuff done.</p>
<blockquote>
<p>As an aside: I found <a href="https://mitchellh.com/writing/my-ai-adoption-journey">Mitchell Hashimoto’s re­lated blog post ex­tremely stim­u­lat­ing.</a>
I rec­om­mend it to any­one try­ing to ap­ply these agents to their real-world work.</p>
</blockquote>
<p>For the past few weeks, be­tween read­ing <a href="https://practicaltypography.com/">Butterick’s Practical Typography</a> and get­ting Harper 2.0 out the door, I’ve been read­ing <em>The Effective Executive</em> by Peter Drucker — at <a href="https://ma.tt/">Matt’s</a> rec­om­men­da­tion.</p>
<p>It’s a fan­tas­tic book.
It is well writ­ten, gives thor­ough rea­son­ing, and pro­vides case stud­ies for each of its ma­jor points.
I haven’t quite ﬁn­ished it, but there are some great ideas from the ﬁrst bit.
Im­por­tantly, it has be­gin to re­ori­ent my think­ing to­wards out­comes over process.</p>
<h2>What Is<span class="push-double"></span> <span class="pull-double">“</span>The Process”?</h2>
<p>The<span class="push-double"></span> <span class="pull-double">“</span>process” in­cludes all the minu­tia re­quired to de­liver on the mean­ing­ful pur­pose of your job.
For an ac­coun­tant, that might in­volve tab­u­lat­ing data for the pur­pose of in­creas­ing ef­ﬁ­ciency in the busi­ness.
For me, as the main­tainer of <a href="https://writewithharper.com">Harper</a>, my pur­pose is to help peo­ple com­mu­ni­cate bet­ter us­ing the writ­ten word.</p>
<p>My sec­ondary pur­pose is to prove that the open source method­ol­ogy is su­pe­rior to the com­pe­ti­tion in ac­com­plish­ing my pri­mary ob­jec­tive.</p>
<p>For me, the process in­volves:</p>
<ul>
<li>Producing code.</li>
<li>Reviewing code.</li>
<li>Determining in which di­rec­tion the pro­ject needs to move.</li>
</ul>
<p>The<span class="push-double"></span> <span class="pull-double">“</span>process” will look dif­fer­ent for you.
It is com­posed of the in­di­vid­ual steps needed to de­liver the ﬁ­nal out­come.</p>
<h2>Think About Where You’re Going and Nothing Else</h2>
<p><a href="https://elijahpotter.dev/articles/improving-rust-compile-times-by-71-percent">An oc­ca­sional check-in</a> on what the process it­self looks like can be ben­e­ﬁ­cial, but it should not be the fo­cus of my at­ten­tion.
My at­ten­tion should be on how to get things <strong>done</strong> and more im­por­tantly how to get my work <strong>in the hands of my users</strong> as fast as pos­si­ble.</p>
<p>I’ve caught my­self think­ing about the process more than the out­come too many times.
There are di­min­ish­ing re­turns to that kind of prac­tice, and they are ap­proached fast.</p>
<blockquote>
<p>Yes. I re­al­ize that this post is an ex­am­ple of the kind of think­ing that I am crit­i­ciz­ing.
This will be the last one of its kind for some time.</p>
</blockquote>
<p>According to Drucker, some­thing strange hap­pens in the brain when it fo­cuses on out­comes rather than processes.
In­stead of think­ing about how to make some­thing<span class="push-double"></span> <span class="pull-double">“</span>faster”, it fo­cuses on how to ﬁnd short­cuts or brand-new ap­proaches.
In other words — when ap­plied to out­comes — the brain be­comes more ﬂex­i­ble and there­fore more ef­fec­tive.</p>
<p>I’ve seen this my­self, but I’ve never been able to iden­tify it so suc­cinctly.
It’s why I strive to do <a href="https://elijahpotter.dev/articles/the-one-hard-thing">at least one hard thing each day</a>.</p>
<p>I’m look­ing for­ward to wrap­ping up Drucker’s book and hope­fully ex­tract­ing more of his wis­dom.
In the mean­time, I’m cu­ri­ous: have there been any in­stances where you felt you fo­cused too much on the process or tool­ing and too lit­tle on the ac­tual out­come?</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[When to Use an Agent]]></title>
            <link>https://elijahpotter.dev/articles/when_to_use_an_agent</link>
            <guid>https://elijahpotter.dev/articles/when_to_use_an_agent</guid>
            <pubDate>Fri, 27 Mar 2026 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>When (Not) to Use an Agent</h1>
<p>There are times where it is pru­dent to use an <span class="small-caps">AI</span> agent (like Codex) ag­gres­sively. There are other times where they should be avoided like the plague. The ques­tion of into which bucket a par­tic­u­lar en­deavor falls is dif­ﬁ­cult to an­swer.
Any­one who says you should <em>al­ways</em> or <em>never</em> use an <span class="small-caps">LLM</span> is over­sim­pli­fy­ing the prob­lem.</p>
<p>Naturally, any­thing pre­scrip­tivist re­lated to LLMs on­line should be re­garded with a fair amount of skep­ti­cism.
That in­cludes what I have to say.</p>
<h2>There Is a Difference</h2>
<p><span class="small-caps">AI</span> agents can be enor­mously help­ful.
The key word is<span class="push-double"></span> <span class="pull-double">“</span>can”.
It is not al­ways a guar­an­tee.</p>
<p>Indeed, most de­vel­op­ers say that they es­ti­mate their pro­duc­tiv­ity mul­ti­ple to be greater than one when sur­veyed.
In other words, they think that their pro­duc­tiv­ity at least dou­bles when they use ei­ther Claude Code or Codex.
In re­al­ity, agents can of­ten slow you down, even if it feels like they are help­ing.
In­deed, <a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/">an <span class="small-caps">METR</span> study</a> found that they more fre­quently coun­ter­pro­duc­tive than pro­duc­tive, even if the de­vel­oper feels oth­er­wise.</p>
<p>Over the last few months, I have been anec­do­tally track­ing the types of tasks that I’ve found to be em­pir­i­cally bet­ter done with an agent, and the types of tasks that are bet­ter done with only the meat be­tween my ears.
I’ve in­stilled that ex­pe­ri­ence down to a set of prin­ci­ples which, I be­lieve, can be em­ployed to save any soft­ware en­gi­neer a de­cent amount of time.
This post in­cludes a few of those prin­ci­ples.</p>
<h2>Your Mileage May Vary</h2>
<p>Before we get to those prin­ci­ples, how­ever, I would like to make my re­al­ity clear to those read­ing.
I work on highly-tech­ni­cal in­te­grated sys­tems that in­volve a high level of knowl­edge.
Mak­ing spu­ri­ous mod­i­ﬁ­ca­tions to these sys­tems can have out­comes that are dif­ﬁ­cult to pre­dict with­out a good men­tal model of how they work.</p>
<p>That means many bugs can be solved in just a few lines of code, and many fea­tures can be im­ple­mented by sim­ply con­nect­ing two seem­ingly un­re­lated sys­tems in a strate­gic way.</p>
<p>My ex­pe­ri­ence may not match yours, es­pe­cially if you work on sys­tems more loosely in­te­grated.</p>
<h2>Err on the Side of Agent-Free Development</h2>
<blockquote>
<p><span class="pull-double">“</span>Back in my day, we would have just called it<span class="push-single"></span> <span class="pull-single">‘</span>development’.” - Me, af­ter writ­ing the above head­ing.</p>
</blockquote>
<p>If you can­not ar­tic­u­late why an agent would be bet­ter or faster at com­plet­ing a task, it is prob­a­bly be­cause you should do it by hand.</p>
<p>In the early days of my ex­plo­rations into ChatGPT, and later into Codex, I found my­self reach­ing for the shiny new tool every chance I could.
It be­came a habit.
I would sit down at my lap­top, de­cide what I wanted to work on, and the next thing I knew I would be typ­ing a prompt into one <span class="small-caps">LLM</span> or an­other.</p>
<blockquote>
<p><span class="pull-double">“</span>I see why it’s easy to fall in love with these things, be­cause the vari­able pos­i­tive re­in­force­ment slot ma­chine cow­boy hack­ing is hon­estly more fun than if it had just got­ten it right on the ﬁrst try.” - <a href="https://ma.tt/2026/02/claude-sonos/">Matt Mullenweg</a></p>
</blockquote>
<p>Honestly, it does­n’t mat­ter how these habits are built and then re­in­forced.
What mat­ters is how turn­ing use of Codex or Claude Code into a habit re­sults in a lot of wasted time.</p>
<p>Sure, some­times the agent <em>will</em> get it right on the ﬁrst try.
The prob­lem is that if it fails, you of­ten need to start from scratch in or­der to end up with a prod­uct that’s work­able.
I have found my­self spend­ing hours re­ﬁn­ing prompts and go­ing back-and-forth with an agent, only to throw it all out and code a bet­ter so­lu­tion my­self in less than ﬁf­teen min­utes.</p>
<p>The crit­i­cal piece here is that if you roll the dice, the po­ten­tial for lost time is in­ﬁ­nite while the po­ten­tial for time saved is ﬁ­nite and of­ten quan­tiﬁ­able.</p>
<p>So, if you can­not pre­cisely ar­tic­u­late why you be­lieve an agent will be able to do the job in less time than if you did the job your­self, you should at­tack the prob­lem with­out one.
At least at ﬁrst.</p>
<h2>Use an Agent for Large Refactors</h2>
<p>There will al­ways be times where the ded­i­cated tool­ing for your pro­gram­ming lan­guage of choice is­n’t enough.</p>
<p>I have en­coun­tered sit­u­a­tions where I have needed to con­vert an in­ter­face into a class and cases where I needed to change a sta­t­i­cally dis­patched sys­tem into a dy­nam­i­cally dis­patched one.
These are the kinds of prob­lems that are great for agents to han­dle, be­cause the logic is sim­ple to ver­ify and most of the changes are merely syn­tac­tic.</p>
<p>At the time, the ded­i­cated Rust and C++ tool­ing at my dis­posal was not ca­pa­ble of do­ing such things.
When I threw an agent at the prob­lem, it was more than ca­pa­ble of mak­ing the changes and ver­i­fy­ing that they do what I want.
In fact, I sus­pect it made the ex­act ed­its that I would have made.
At the very least, I was saved some typ­ing.</p>
<p>Put an­other way, if the bulk of the prob­lem is <a href="https://en.wikipedia.org/wiki/Busy_work"><span class="push-double"></span><span class="pull-double">“</span>busywork”</a>, let an agent han­dle it so you can fo­cus on the more com­plex is­sues re­main­ing.</p>
<h2>Core Systems Should Not Be Written by an Agent</h2>
<p>Systems core to a piece of soft­ware have two key re­quire­ments that, at the time of writ­ing, can­not be ful­ﬁlled by any agents on the mar­ket.
They need to:</p>
<ol>
<li>Be Flexible.</li>
<li>Aggressively mod­eled to the do­main.</li>
</ol>
<p>Flexibility means that the sys­tem can be ex­tended or mod­i­ﬁed with­out much ef­fort.
That it can be imag­ined as a whole by your­self and any fu­ture con­trib­u­tors, and se­man­tic changes to mi­nor com­po­nents are un­likely to un­ex­pect­edly af­fect other mi­nor com­po­nents.
Right now, no agents are ca­pa­ble of do­ing this. Trust me — I’ve tested them.</p>
<p>Similarly, core sys­tem ar­chi­tec­ture needs to ﬁt the do­main or prob­lem it seeks to solve.
Oth­er­wise, it will take un­due ef­fort to im­ple­ment the small­est bit of func­tion­al­ity.
Just like the in­de­scrib­able qual­ity of ﬂex­i­bil­ity, this trait too is not rep­re­sented by the code gen­er­ated by agents on the mar­ket.
Their so­lu­tions are ei­ther too gen­eral or too di­rect.
In ei­ther case, the code turns out to be far from ex­ten­si­ble.</p>
<h2>Extensions Are Perfect for Agents</h2>
<p>While core sys­tems should be ar­chi­tected with hu­man mus­cle, ex­ten­sions to that sys­tem can be built with agents.</p>
<p>Extensions are usu­ally smaller in scope, re­stricted in ca­pa­bil­ity by their en­vi­ron­ment, and are less likely to af­fect other sys­tems.
That means they are gen­er­ally eas­ier to re­view, less likely to in­tro­duce se­cu­rity vul­ner­a­bil­i­ties, and cre­at­ing them does­n’t re­quire an in-depth men­tal model.</p>
<p>This is why soft­ware like <a href="https://telex.automattic.ai/">Telex</a> is so ef­fec­tive.
It is also why those who work with ver­ti­cally in­te­grated sys­tems are like­lier to run into is­sues when us­ing agents than with hor­i­zon­tally in­te­grated sys­tems.</p>
<h2>What Is Your Experience?</h2>
<p>I’m still learn­ing how and when to in­te­grate agents into my work, as is every­one else.
I’m cu­ri­ous: have you found spe­ciﬁc kinds of is­sues or bugs that are best tack­led with an agent?
Are there spe­ciﬁc mo­ments where you feel more pro­duc­tive <em>with­out</em> one?
I am cer­tain there are nu­ances I’ve missed here.
What are they?</p>]]></description>
            <content:encoded><![CDATA[<h1>When (Not) to Use an Agent</h1>
<p>There are times where it is pru­dent to use an <span class="small-caps">AI</span> agent (like Codex) ag­gres­sively. There are other times where they should be avoided like the plague. The ques­tion of into which bucket a par­tic­u­lar en­deavor falls is dif­ﬁ­cult to an­swer.
Any­one who says you should <em>al­ways</em> or <em>never</em> use an <span class="small-caps">LLM</span> is over­sim­pli­fy­ing the prob­lem.</p>
<p>Naturally, any­thing pre­scrip­tivist re­lated to LLMs on­line should be re­garded with a fair amount of skep­ti­cism.
That in­cludes what I have to say.</p>
<h2>There Is a Difference</h2>
<p><span class="small-caps">AI</span> agents can be enor­mously help­ful.
The key word is<span class="push-double"></span> <span class="pull-double">“</span>can”.
It is not al­ways a guar­an­tee.</p>
<p>Indeed, most de­vel­op­ers say that they es­ti­mate their pro­duc­tiv­ity mul­ti­ple to be greater than one when sur­veyed.
In other words, they think that their pro­duc­tiv­ity at least dou­bles when they use ei­ther Claude Code or Codex.
In re­al­ity, agents can of­ten slow you down, even if it feels like they are help­ing.
In­deed, <a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/">an <span class="small-caps">METR</span> study</a> found that they more fre­quently coun­ter­pro­duc­tive than pro­duc­tive, even if the de­vel­oper feels oth­er­wise.</p>
<p>Over the last few months, I have been anec­do­tally track­ing the types of tasks that I’ve found to be em­pir­i­cally bet­ter done with an agent, and the types of tasks that are bet­ter done with only the meat be­tween my ears.
I’ve in­stilled that ex­pe­ri­ence down to a set of prin­ci­ples which, I be­lieve, can be em­ployed to save any soft­ware en­gi­neer a de­cent amount of time.
This post in­cludes a few of those prin­ci­ples.</p>
<h2>Your Mileage May Vary</h2>
<p>Before we get to those prin­ci­ples, how­ever, I would like to make my re­al­ity clear to those read­ing.
I work on highly-tech­ni­cal in­te­grated sys­tems that in­volve a high level of knowl­edge.
Mak­ing spu­ri­ous mod­i­ﬁ­ca­tions to these sys­tems can have out­comes that are dif­ﬁ­cult to pre­dict with­out a good men­tal model of how they work.</p>
<p>That means many bugs can be solved in just a few lines of code, and many fea­tures can be im­ple­mented by sim­ply con­nect­ing two seem­ingly un­re­lated sys­tems in a strate­gic way.</p>
<p>My ex­pe­ri­ence may not match yours, es­pe­cially if you work on sys­tems more loosely in­te­grated.</p>
<h2>Err on the Side of Agent-Free Development</h2>
<blockquote>
<p><span class="pull-double">“</span>Back in my day, we would have just called it<span class="push-single"></span> <span class="pull-single">‘</span>development’.” - Me, af­ter writ­ing the above head­ing.</p>
</blockquote>
<p>If you can­not ar­tic­u­late why an agent would be bet­ter or faster at com­plet­ing a task, it is prob­a­bly be­cause you should do it by hand.</p>
<p>In the early days of my ex­plo­rations into ChatGPT, and later into Codex, I found my­self reach­ing for the shiny new tool every chance I could.
It be­came a habit.
I would sit down at my lap­top, de­cide what I wanted to work on, and the next thing I knew I would be typ­ing a prompt into one <span class="small-caps">LLM</span> or an­other.</p>
<blockquote>
<p><span class="pull-double">“</span>I see why it’s easy to fall in love with these things, be­cause the vari­able pos­i­tive re­in­force­ment slot ma­chine cow­boy hack­ing is hon­estly more fun than if it had just got­ten it right on the ﬁrst try.” - <a href="https://ma.tt/2026/02/claude-sonos/">Matt Mullenweg</a></p>
</blockquote>
<p>Honestly, it does­n’t mat­ter how these habits are built and then re­in­forced.
What mat­ters is how turn­ing use of Codex or Claude Code into a habit re­sults in a lot of wasted time.</p>
<p>Sure, some­times the agent <em>will</em> get it right on the ﬁrst try.
The prob­lem is that if it fails, you of­ten need to start from scratch in or­der to end up with a prod­uct that’s work­able.
I have found my­self spend­ing hours re­ﬁn­ing prompts and go­ing back-and-forth with an agent, only to throw it all out and code a bet­ter so­lu­tion my­self in less than ﬁf­teen min­utes.</p>
<p>The crit­i­cal piece here is that if you roll the dice, the po­ten­tial for lost time is in­ﬁ­nite while the po­ten­tial for time saved is ﬁ­nite and of­ten quan­tiﬁ­able.</p>
<p>So, if you can­not pre­cisely ar­tic­u­late why you be­lieve an agent will be able to do the job in less time than if you did the job your­self, you should at­tack the prob­lem with­out one.
At least at ﬁrst.</p>
<h2>Use an Agent for Large Refactors</h2>
<p>There will al­ways be times where the ded­i­cated tool­ing for your pro­gram­ming lan­guage of choice is­n’t enough.</p>
<p>I have en­coun­tered sit­u­a­tions where I have needed to con­vert an in­ter­face into a class and cases where I needed to change a sta­t­i­cally dis­patched sys­tem into a dy­nam­i­cally dis­patched one.
These are the kinds of prob­lems that are great for agents to han­dle, be­cause the logic is sim­ple to ver­ify and most of the changes are merely syn­tac­tic.</p>
<p>At the time, the ded­i­cated Rust and C++ tool­ing at my dis­posal was not ca­pa­ble of do­ing such things.
When I threw an agent at the prob­lem, it was more than ca­pa­ble of mak­ing the changes and ver­i­fy­ing that they do what I want.
In fact, I sus­pect it made the ex­act ed­its that I would have made.
At the very least, I was saved some typ­ing.</p>
<p>Put an­other way, if the bulk of the prob­lem is <a href="https://en.wikipedia.org/wiki/Busy_work"><span class="push-double"></span><span class="pull-double">“</span>busywork”</a>, let an agent han­dle it so you can fo­cus on the more com­plex is­sues re­main­ing.</p>
<h2>Core Systems Should Not Be Written by an Agent</h2>
<p>Systems core to a piece of soft­ware have two key re­quire­ments that, at the time of writ­ing, can­not be ful­ﬁlled by any agents on the mar­ket.
They need to:</p>
<ol>
<li>Be Flexible.</li>
<li>Aggressively mod­eled to the do­main.</li>
</ol>
<p>Flexibility means that the sys­tem can be ex­tended or mod­i­ﬁed with­out much ef­fort.
That it can be imag­ined as a whole by your­self and any fu­ture con­trib­u­tors, and se­man­tic changes to mi­nor com­po­nents are un­likely to un­ex­pect­edly af­fect other mi­nor com­po­nents.
Right now, no agents are ca­pa­ble of do­ing this. Trust me — I’ve tested them.</p>
<p>Similarly, core sys­tem ar­chi­tec­ture needs to ﬁt the do­main or prob­lem it seeks to solve.
Oth­er­wise, it will take un­due ef­fort to im­ple­ment the small­est bit of func­tion­al­ity.
Just like the in­de­scrib­able qual­ity of ﬂex­i­bil­ity, this trait too is not rep­re­sented by the code gen­er­ated by agents on the mar­ket.
Their so­lu­tions are ei­ther too gen­eral or too di­rect.
In ei­ther case, the code turns out to be far from ex­ten­si­ble.</p>
<h2>Extensions Are Perfect for Agents</h2>
<p>While core sys­tems should be ar­chi­tected with hu­man mus­cle, ex­ten­sions to that sys­tem can be built with agents.</p>
<p>Extensions are usu­ally smaller in scope, re­stricted in ca­pa­bil­ity by their en­vi­ron­ment, and are less likely to af­fect other sys­tems.
That means they are gen­er­ally eas­ier to re­view, less likely to in­tro­duce se­cu­rity vul­ner­a­bil­i­ties, and cre­at­ing them does­n’t re­quire an in-depth men­tal model.</p>
<p>This is why soft­ware like <a href="https://telex.automattic.ai/">Telex</a> is so ef­fec­tive.
It is also why those who work with ver­ti­cally in­te­grated sys­tems are like­lier to run into is­sues when us­ing agents than with hor­i­zon­tally in­te­grated sys­tems.</p>
<h2>What Is Your Experience?</h2>
<p>I’m still learn­ing how and when to in­te­grate agents into my work, as is every­one else.
I’m cu­ri­ous: have you found spe­ciﬁc kinds of is­sues or bugs that are best tack­led with an agent?
Are there spe­ciﬁc mo­ments where you feel more pro­duc­tive <em>with­out</em> one?
I am cer­tain there are nu­ances I’ve missed here.
What are they?</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Harper's Policy on Agent PRs]]></title>
            <link>https://elijahpotter.dev/articles/harper's_policy_on_agent_PRs</link>
            <guid>https://elijahpotter.dev/articles/harper's_policy_on_agent_PRs</guid>
            <pubDate>Mon, 23 Mar 2026 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Harper’s Policy on PRs Authored by Agents</h1>
<p>A few weeks ago, I was in­vited to speak at my lo­cal chap­ter of the Association for Computing Machinery (<span class="small-caps">ACM</span>). Being on a col­lege cam­pus — in this case, the Colorado School of Mines — much of my au­di­ence were stu­dents. My pre­sen­ta­tion, which was about how open source soft­ware tends to out­per­form pro­pri­etary soft­ware in the mar­ket­place due to the abil­ity for its users to con­tribute back in ways that are gen­uinely valu­able, went well. I hope I am in­vited back.</p>
<p>Not much younger than me, they were nat­u­rally cu­ri­ous about what it is like to pro­duce and main­tain open source soft­ware. One stu­dent, Renn, asked me how I deal with patch re­quests pro­duced by large lan­guage mod­els (LLMs). My un­sat­is­fac­tory an­swer was that I ad­dress them on a case‑by‑case ba­sis.</p>
<p>The goal of this page is to for­mal­ize my an­swer so that we can ju­di­ciously deal with patch re­quests pro­duced by LLMs. That is, to pro­vide a frame­work which re­duces the amount of time that overly com­plex patches re­main idle and make it eas­ier for wield­ers of these mod­els to pro­duce mean­ing­ful changes to Harper’s code.</p>
<h2>The Policy</h2>
<p>We ac­cept pull re­quests from hu­mans wield­ing large lan­guage mod­els and from agents pow­ered by them. They are sub­ject to the same rules and guide­lines we ap­ply to hu­mans work­ing <strong>with­out</strong> a large lan­guage model, with a few ex­tra spices mixed in. Below, we have three main ideas which aim to si­mul­ta­ne­ously re­duce the time be­tween sub­mis­sion of a patch and when it is merged and to re­duce the time and at­ten­tion re­quired to re­view a patch.</p>
<p>That means that it is in your in­ter­est to re­spect these ideas. Good pull re­quests are more likely to be re­viewed in a timely man­ner and there­fore are more likely to be merged.</p>
<p>I could wax po­et­i­cally for hours about all the edge cases not cov­ered here. Instead, I choose to be­lieve the things I don’t in­clude are at least as im­por­tant as the ones I do, so I’ll boil every­thing down into three ideas.</p>
<h3>One: Keep Things Brief</h3>
<p>Try to break down the prob­lem into as many in­di­vid­ual pull re­quests as you can. Smaller pull re­quests are eas­ier to re­view. Larger re­quests are su­per‑lin­early harder to re­view. You can dra­mat­i­cally speed up the process by break­ing your large <span class="small-caps">PR</span> into sev­eral smaller ones, each with their own de­scrip­tion and ex­pla­na­tion. I’ve found this to be es­pe­cially true with code gen­er­ated by an <span class="small-caps">LLM</span>.</p>
<p>Part of the idea here is to catch wasted work be­fore it’s done. If you sub­mit step one of a pro­ject that is­n’t re­ally ap­plic­a­ble to Harper’s mis­sion, we can re­ori­ent it be­fore too much time has been wasted.</p>
<h3>Two: Stay Grounded</h3>
<p>Each<span class="push-double"></span> <span class="pull-double">“</span>project” — or string of pull re­quests — should ex­ist to cre­ate value for a hu­man. I don’t love it when I re­ceive code just for code’s sake. If you have a need and a patch will ﬁll it, say so! If there’s an is­sue in GitHub that you’re ad­dress­ing, link it! This should be pretty easy. If you’ve ded­i­cated time to work­ing on a prob­lem, there’s prob­a­bly a rea­son. I want to hear that rea­son.</p>
<h3>Three: Be Honest</h3>
<p>If you are an agent or used an <span class="small-caps">LLM</span> to pro­duce any part of a <span class="small-caps">PR</span>, please pro­vide a dis­claimer at the top of your <span class="small-caps">PR</span> de­scrip­tion. I am not op­posed to re­view­ing <span class="small-caps">LLM</span> code, but I do ap­pre­ci­ate the hon­esty.</p>
<h2>Talk to Me</h2>
<p>Above all, talk to me. If you have ques­tions or con­cerns about any­thing I’ve dis­cussed here, let me know. The hope is that all of this will im­prove the qual­ity of the Harper pro­ject, and im­prove the abil­ity for peo­ple to ex­press them­selves with the writ­ten word. As al­ways, any and all con­tri­bu­tions are wel­come.</p>]]></description>
            <content:encoded><![CDATA[<h1>Harper’s Policy on PRs Authored by Agents</h1>
<p>A few weeks ago, I was in­vited to speak at my lo­cal chap­ter of the Association for Computing Machinery (<span class="small-caps">ACM</span>). Being on a col­lege cam­pus — in this case, the Colorado School of Mines — much of my au­di­ence were stu­dents. My pre­sen­ta­tion, which was about how open source soft­ware tends to out­per­form pro­pri­etary soft­ware in the mar­ket­place due to the abil­ity for its users to con­tribute back in ways that are gen­uinely valu­able, went well. I hope I am in­vited back.</p>
<p>Not much younger than me, they were nat­u­rally cu­ri­ous about what it is like to pro­duce and main­tain open source soft­ware. One stu­dent, Renn, asked me how I deal with patch re­quests pro­duced by large lan­guage mod­els (LLMs). My un­sat­is­fac­tory an­swer was that I ad­dress them on a case‑by‑case ba­sis.</p>
<p>The goal of this page is to for­mal­ize my an­swer so that we can ju­di­ciously deal with patch re­quests pro­duced by LLMs. That is, to pro­vide a frame­work which re­duces the amount of time that overly com­plex patches re­main idle and make it eas­ier for wield­ers of these mod­els to pro­duce mean­ing­ful changes to Harper’s code.</p>
<h2>The Policy</h2>
<p>We ac­cept pull re­quests from hu­mans wield­ing large lan­guage mod­els and from agents pow­ered by them. They are sub­ject to the same rules and guide­lines we ap­ply to hu­mans work­ing <strong>with­out</strong> a large lan­guage model, with a few ex­tra spices mixed in. Below, we have three main ideas which aim to si­mul­ta­ne­ously re­duce the time be­tween sub­mis­sion of a patch and when it is merged and to re­duce the time and at­ten­tion re­quired to re­view a patch.</p>
<p>That means that it is in your in­ter­est to re­spect these ideas. Good pull re­quests are more likely to be re­viewed in a timely man­ner and there­fore are more likely to be merged.</p>
<p>I could wax po­et­i­cally for hours about all the edge cases not cov­ered here. Instead, I choose to be­lieve the things I don’t in­clude are at least as im­por­tant as the ones I do, so I’ll boil every­thing down into three ideas.</p>
<h3>One: Keep Things Brief</h3>
<p>Try to break down the prob­lem into as many in­di­vid­ual pull re­quests as you can. Smaller pull re­quests are eas­ier to re­view. Larger re­quests are su­per‑lin­early harder to re­view. You can dra­mat­i­cally speed up the process by break­ing your large <span class="small-caps">PR</span> into sev­eral smaller ones, each with their own de­scrip­tion and ex­pla­na­tion. I’ve found this to be es­pe­cially true with code gen­er­ated by an <span class="small-caps">LLM</span>.</p>
<p>Part of the idea here is to catch wasted work be­fore it’s done. If you sub­mit step one of a pro­ject that is­n’t re­ally ap­plic­a­ble to Harper’s mis­sion, we can re­ori­ent it be­fore too much time has been wasted.</p>
<h3>Two: Stay Grounded</h3>
<p>Each<span class="push-double"></span> <span class="pull-double">“</span>project” — or string of pull re­quests — should ex­ist to cre­ate value for a hu­man. I don’t love it when I re­ceive code just for code’s sake. If you have a need and a patch will ﬁll it, say so! If there’s an is­sue in GitHub that you’re ad­dress­ing, link it! This should be pretty easy. If you’ve ded­i­cated time to work­ing on a prob­lem, there’s prob­a­bly a rea­son. I want to hear that rea­son.</p>
<h3>Three: Be Honest</h3>
<p>If you are an agent or used an <span class="small-caps">LLM</span> to pro­duce any part of a <span class="small-caps">PR</span>, please pro­vide a dis­claimer at the top of your <span class="small-caps">PR</span> de­scrip­tion. I am not op­posed to re­view­ing <span class="small-caps">LLM</span> code, but I do ap­pre­ci­ate the hon­esty.</p>
<h2>Talk to Me</h2>
<p>Above all, talk to me. If you have ques­tions or con­cerns about any­thing I’ve dis­cussed here, let me know. The hope is that all of this will im­prove the qual­ity of the Harper pro­ject, and im­prove the abil­ity for peo­ple to ex­press them­selves with the writ­ten word. As al­ways, any and all con­tri­bu­tions are wel­come.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My Superpower]]></title>
            <link>https://elijahpotter.dev/articles/my_superpower</link>
            <guid>https://elijahpotter.dev/articles/my_superpower</guid>
            <pubDate>Sat, 14 Mar 2026 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>My Superpower</h1>
<img src="/images/giraffe.webp" alt="A photo taken by myself of a giraffe resting its head on a pole.">
<p>In high school, I was an in­sa­tiable con­sumer of sci­ence ﬁc­tion.
In hind­sight, it is un­be­liev­able how much time I spent read­ing in­stead of do­ing the other things usu­ally con­sid­ered to be typ­i­cal for teenagers (going out, do­ing home­work, etc.).
I was the clas­sic sci­ence ﬁc­tion nerd.
My live re­volved around the words of Asimov, Weir, Clarke, and prob­a­bly every other sci­ence ﬁc­tion au­thor you could name.</p>
<p>It was not un­til rel­a­tively late in my aca­d­e­mic ca­reer when I re­al­ized my thirst for the writ­ten word had given me a su­per­power.
All of those years slurp­ing up sci­ence ﬁc­tion had ac­ci­den­tally given me an un­usual abil­ity (in this day and age).
An abil­ity that, once upon a time, had been com­mon­place.</p>
<p>Fortunately, this abil­ity can be trained quite quickly.
What had taken me years to learn by ac­ci­dent can be de­vel­oped in just a few weeks (or days, if you’re com­mit­ted).</p>
<p>This blog post will serve as doc­u­men­ta­tion of:</p>
<ol>
<li>What this abil­ity is.</li>
<li>How you can de­velop it.</li>
<li>How it can be ap­plied to the day-to-day work of pro­fes­sional soft­ware en­gi­neers.</li>
</ol>
<h2>The Ability to Read</h2>
<p>In the in­ter­est of avoid­ing click­bait, I’ll just come out and say it plainly: this<span class="push-double"></span> <span class="pull-double">“</span>superpower” is the abil­ity to read full books from start to ﬁn­ish.</p>
<p>Sounds bor­ing, right?</p>
<p>I call it a su­per­power be­cause it is be­com­ing rarer by the day.
Fewer pro­fes­sion­als read any­thing longer than a short blog post or ar­ti­cle to­day than at any point in the last cen­tury.
I won’t get into why, since there is al­ready an abun­dance of <a href="https://www.youtube.com/watch?v=A3wJcF0t0bQ">doc­u­men­ta­tion ex­plain­ing why peo­ple have been read­ing less</a>.
But in or­der for some­thing to be a su­per­power, it needs to be more than just rare.
Why should read­ing count as one?</p>
<p>Reading qual­i­ﬁes be­cause it al­lows one to do some­thing that is oth­er­wise ex­tremely hard: un­der­stand a topic or idea at a level ap­proach­ing an ex­pert.</p>
<p>When I was in school, I re­mem­ber sit­ting in lec­tures sur­rounded by stu­dents who hated our cours­es’ text­books.
My col­leagues called the in­fer­nal blocks of pa­per<span class="push-double"></span> <span class="pull-double">“</span>boring”,<span class="push-double"></span> <span class="pull-double">“</span>repetitive”, and<span class="push-double"></span> <span class="pull-double">“</span>heavy”.
Indeed, they were, but they also con­tained most of the in­for­ma­tion the pro­fes­sor would lec­ture about, and pre­sented it in a for­mat I could con­sume in a frac­tion of the time.</p>
<p>I re­al­ized that I could save my­self hours of te­dious home­work and lec­ture time by sim­ply read­ing ahead in a course’s text­book.</p>
<p>Now that I’ve spent a de­cent chunk of my life re­moved from my stud­ies, I’ve come to re­al­ize that this same phi­los­o­phy can be ap­plied pro­fes­sion­ally in our work as en­gi­neers.
Hun­dreds of hours of in­tel­lec­tual ﬂoun­der­ing can be saved with just a lit­tle bit of read­ing.</p>
<h2>Depth Provides Certainty</h2>
<p>To il­lus­trate why the kind of deep un­der­stand­ing that can be ob­tained through lit­er­a­ture is rel­e­vant, I’ll bring an ex­am­ple from my own re­cent work.</p>
<p>For con­text, I work on Harper, a gram­mar checker for those who care for their pri­vacy.
We’ve re­cently been <a href="./finding_the_active_voice">work­ing to ex­pand our of­fer­ing to com­pete with Grammarly Premium</a>.
This is a com­plex, open-ended prob­lem with no<span class="push-double"></span> <span class="pull-double">“</span>right” an­swer.
I can imag­ine two ap­proaches to it, one good and an­other bad.</p>
<p>I was pretty well versed in the kinds of of­fer­ings that Grammarly pro­vides, and it was pretty ob­vi­ous how many of them work.
Gram­marly takes some <span class="small-caps">LLM</span> provider with an <span class="small-caps">API</span> (like OpenAI or Anthropic), pro­vides a bunch of style guides as con­text and wraps it all in a pretty <span class="small-caps">UI</span> and sells that at a pre­mium.
We could do that same thing, right?</p>
<p>In the bad case, I could just copy what Grammarly is al­ready do­ing and call it day.
Good idea, right?</p>
<p>I did­n’t think so, so I sat down and read through some of the prime-time lit­er­a­ture com­ing out of labs writ­ten by ex­perts in nat­ural lan­guage and the arts.
They pointed me in a com­pletely dif­fer­ent di­rec­tion.</p>
<p>By read­ing deeply, I was able to come to the re­al­iza­tion that many of the fea­tures that Grammarly hides be­hind their pay­wall are rel­a­tively sim­ple to im­ple­ment, if you know what you’re do­ing.
Not only that, but com­bined with the ex­ist­ing in­fra­struc­ture Harper al­ready owns, we could do it cheaply with­out dis­re­spect­ing our cus­tomer’s pri­vacy or copy­right.
In other words, we could dif­fer­en­ti­ate our­selves from the com­pe­ti­tion by tack­ling the prob­lem deeply and ap­proach­ing it from a unique an­gle.</p>
<p>This story has hap­pened over and over again, and I can imag­ine that it ap­plies to most of the peo­ple work­ing as en­gi­neers to­day.
A big open-ended prob­lem needs to be solved.
Rather than ap­proach it in the ob­vi­ous man­ner, you can learn from the ex­perts your­self and pro­duce some­thing bet­ter as a re­sult of their in­sight.</p>
<h2>How to Develop the Skill of Reading</h2>
<p>On oc­ca­sion, I will ﬁnd my­self in a dry spell.
I will go sev­eral weeks or even months with­out read­ing any­thing of much sig­nif­i­cance.
In do­ing so, I will grad­u­ally lose my abil­ity to read with depth for ex­tended pe­ri­ods of time.
My mind’s mus­cle for grap­pling the tough ideas pro­posed in the words of ex­perts will weaken.</p>
<p>When that hap­pens, here are two things that I do to beef it back up.</p>
<h3>Schedule a Block of Time for Depth</h3>
<p>Often, the biggest bar­ri­ers to depth are the sim­ple dis­trac­tions of life.
You have to pick up the kids, cook din­ner, or go shop­ping.
There is al­ways some­thing on the docket that gets in the way.</p>
<p>My so­lu­tion: sched­ule a ses­sion of deep read­ing so that it looks like any other chore on your cal­en­dar.
If I don’t do this and, in­stead, try to ﬁt it in­side the gaps, I ﬁnd that my mind will come up with ex­cuses for do­ing any­thing other than read.</p>
<p>My mind says,<span class="push-double"></span> <span class="pull-double">“</span>But Elijah, you need to do your taxes right now. You should do that in­stead, be­cause read­ing is bor­ing.”
Your mind might say some­thing else.
I hap­pen to en­joy do­ing my taxes.</p>
<p>Schedule time for deep read­ing and you’ll be able to say<span class="push-double"></span> <span class="pull-double">“</span>no” to that <a href="https://youtu.be/arj7oStGLkU?si=tOWjRDYhFoOicay-">pro­cras­ti­na­tion mon­key in your head.</a></p>
<h3>Find a Topic That Is of Immediate Relevance</h3>
<p>I love my job, and I get a huge thrill when I ﬁnd out a way that I can do it bet­ter.
So if I need to ﬁnd some­thing to read, I of­ten start by ask­ing my­self,<span class="push-double"></span> <span class="pull-double">“</span>what is the hard­est part of my job right now?”</p>
<p>The an­swer can be:</p>
<ul>
<li>A spe­ciﬁc prob­lem I’m try­ing to solve (see the sec­tion above ti­tled<span class="push-double"></span> <span class="pull-double">“</span>Depth Provides Certainty”).</li>
<li>A repet­i­tive process that I do reg­u­larly.</li>
<li>Something I am un­sure about which is dis­cour­ag­ing me from work­ing with gusto.</li>
</ul>
<p>From there, I plainly ask ChatGPT:<span class="push-double"></span> <span class="pull-double">“</span>What is a good book that cov­ers X in depth for a per­son with Y level of pre­ex­ist­ing knowl­edge of the sub­ject.”</p>
<p>To de­velop the skill of read­ing deeply, your book does­n’t need to be about a sub­ject re­lated to your job.
That just hap­pens to work well for me.</p>
<h2>Just Start</h2>
<p>A jour­ney of a thou­sand miles be­gins with a sin­gle step.
A book of a thou­sand pages be­gins with a sin­gle para­graph.
The best thing you can do is start.</p>
<p>So, I im­plore you.
If you’re stuck on a prob­lem or an idea, try read­ing a book about it.
I have per­son­ally found an im­mea­sur­able amount of mean­ing through lit­er­a­ture, and it’s un­blocked me on a num­ber of prob­lems in my pro­fes­sional and per­sonal life.
I sus­pect it can do the same for you.</p>]]></description>
            <content:encoded><![CDATA[<h1>My Superpower</h1>
<img src="/images/giraffe.webp" alt="A photo taken by myself of a giraffe resting its head on a pole.">
<p>In high school, I was an in­sa­tiable con­sumer of sci­ence ﬁc­tion.
In hind­sight, it is un­be­liev­able how much time I spent read­ing in­stead of do­ing the other things usu­ally con­sid­ered to be typ­i­cal for teenagers (going out, do­ing home­work, etc.).
I was the clas­sic sci­ence ﬁc­tion nerd.
My live re­volved around the words of Asimov, Weir, Clarke, and prob­a­bly every other sci­ence ﬁc­tion au­thor you could name.</p>
<p>It was not un­til rel­a­tively late in my aca­d­e­mic ca­reer when I re­al­ized my thirst for the writ­ten word had given me a su­per­power.
All of those years slurp­ing up sci­ence ﬁc­tion had ac­ci­den­tally given me an un­usual abil­ity (in this day and age).
An abil­ity that, once upon a time, had been com­mon­place.</p>
<p>Fortunately, this abil­ity can be trained quite quickly.
What had taken me years to learn by ac­ci­dent can be de­vel­oped in just a few weeks (or days, if you’re com­mit­ted).</p>
<p>This blog post will serve as doc­u­men­ta­tion of:</p>
<ol>
<li>What this abil­ity is.</li>
<li>How you can de­velop it.</li>
<li>How it can be ap­plied to the day-to-day work of pro­fes­sional soft­ware en­gi­neers.</li>
</ol>
<h2>The Ability to Read</h2>
<p>In the in­ter­est of avoid­ing click­bait, I’ll just come out and say it plainly: this<span class="push-double"></span> <span class="pull-double">“</span>superpower” is the abil­ity to read full books from start to ﬁn­ish.</p>
<p>Sounds bor­ing, right?</p>
<p>I call it a su­per­power be­cause it is be­com­ing rarer by the day.
Fewer pro­fes­sion­als read any­thing longer than a short blog post or ar­ti­cle to­day than at any point in the last cen­tury.
I won’t get into why, since there is al­ready an abun­dance of <a href="https://www.youtube.com/watch?v=A3wJcF0t0bQ">doc­u­men­ta­tion ex­plain­ing why peo­ple have been read­ing less</a>.
But in or­der for some­thing to be a su­per­power, it needs to be more than just rare.
Why should read­ing count as one?</p>
<p>Reading qual­i­ﬁes be­cause it al­lows one to do some­thing that is oth­er­wise ex­tremely hard: un­der­stand a topic or idea at a level ap­proach­ing an ex­pert.</p>
<p>When I was in school, I re­mem­ber sit­ting in lec­tures sur­rounded by stu­dents who hated our cours­es’ text­books.
My col­leagues called the in­fer­nal blocks of pa­per<span class="push-double"></span> <span class="pull-double">“</span>boring”,<span class="push-double"></span> <span class="pull-double">“</span>repetitive”, and<span class="push-double"></span> <span class="pull-double">“</span>heavy”.
Indeed, they were, but they also con­tained most of the in­for­ma­tion the pro­fes­sor would lec­ture about, and pre­sented it in a for­mat I could con­sume in a frac­tion of the time.</p>
<p>I re­al­ized that I could save my­self hours of te­dious home­work and lec­ture time by sim­ply read­ing ahead in a course’s text­book.</p>
<p>Now that I’ve spent a de­cent chunk of my life re­moved from my stud­ies, I’ve come to re­al­ize that this same phi­los­o­phy can be ap­plied pro­fes­sion­ally in our work as en­gi­neers.
Hun­dreds of hours of in­tel­lec­tual ﬂoun­der­ing can be saved with just a lit­tle bit of read­ing.</p>
<h2>Depth Provides Certainty</h2>
<p>To il­lus­trate why the kind of deep un­der­stand­ing that can be ob­tained through lit­er­a­ture is rel­e­vant, I’ll bring an ex­am­ple from my own re­cent work.</p>
<p>For con­text, I work on Harper, a gram­mar checker for those who care for their pri­vacy.
We’ve re­cently been <a href="./finding_the_active_voice">work­ing to ex­pand our of­fer­ing to com­pete with Grammarly Premium</a>.
This is a com­plex, open-ended prob­lem with no<span class="push-double"></span> <span class="pull-double">“</span>right” an­swer.
I can imag­ine two ap­proaches to it, one good and an­other bad.</p>
<p>I was pretty well versed in the kinds of of­fer­ings that Grammarly pro­vides, and it was pretty ob­vi­ous how many of them work.
Gram­marly takes some <span class="small-caps">LLM</span> provider with an <span class="small-caps">API</span> (like OpenAI or Anthropic), pro­vides a bunch of style guides as con­text and wraps it all in a pretty <span class="small-caps">UI</span> and sells that at a pre­mium.
We could do that same thing, right?</p>
<p>In the bad case, I could just copy what Grammarly is al­ready do­ing and call it day.
Good idea, right?</p>
<p>I did­n’t think so, so I sat down and read through some of the prime-time lit­er­a­ture com­ing out of labs writ­ten by ex­perts in nat­ural lan­guage and the arts.
They pointed me in a com­pletely dif­fer­ent di­rec­tion.</p>
<p>By read­ing deeply, I was able to come to the re­al­iza­tion that many of the fea­tures that Grammarly hides be­hind their pay­wall are rel­a­tively sim­ple to im­ple­ment, if you know what you’re do­ing.
Not only that, but com­bined with the ex­ist­ing in­fra­struc­ture Harper al­ready owns, we could do it cheaply with­out dis­re­spect­ing our cus­tomer’s pri­vacy or copy­right.
In other words, we could dif­fer­en­ti­ate our­selves from the com­pe­ti­tion by tack­ling the prob­lem deeply and ap­proach­ing it from a unique an­gle.</p>
<p>This story has hap­pened over and over again, and I can imag­ine that it ap­plies to most of the peo­ple work­ing as en­gi­neers to­day.
A big open-ended prob­lem needs to be solved.
Rather than ap­proach it in the ob­vi­ous man­ner, you can learn from the ex­perts your­self and pro­duce some­thing bet­ter as a re­sult of their in­sight.</p>
<h2>How to Develop the Skill of Reading</h2>
<p>On oc­ca­sion, I will ﬁnd my­self in a dry spell.
I will go sev­eral weeks or even months with­out read­ing any­thing of much sig­nif­i­cance.
In do­ing so, I will grad­u­ally lose my abil­ity to read with depth for ex­tended pe­ri­ods of time.
My mind’s mus­cle for grap­pling the tough ideas pro­posed in the words of ex­perts will weaken.</p>
<p>When that hap­pens, here are two things that I do to beef it back up.</p>
<h3>Schedule a Block of Time for Depth</h3>
<p>Often, the biggest bar­ri­ers to depth are the sim­ple dis­trac­tions of life.
You have to pick up the kids, cook din­ner, or go shop­ping.
There is al­ways some­thing on the docket that gets in the way.</p>
<p>My so­lu­tion: sched­ule a ses­sion of deep read­ing so that it looks like any other chore on your cal­en­dar.
If I don’t do this and, in­stead, try to ﬁt it in­side the gaps, I ﬁnd that my mind will come up with ex­cuses for do­ing any­thing other than read.</p>
<p>My mind says,<span class="push-double"></span> <span class="pull-double">“</span>But Elijah, you need to do your taxes right now. You should do that in­stead, be­cause read­ing is bor­ing.”
Your mind might say some­thing else.
I hap­pen to en­joy do­ing my taxes.</p>
<p>Schedule time for deep read­ing and you’ll be able to say<span class="push-double"></span> <span class="pull-double">“</span>no” to that <a href="https://youtu.be/arj7oStGLkU?si=tOWjRDYhFoOicay-">pro­cras­ti­na­tion mon­key in your head.</a></p>
<h3>Find a Topic That Is of Immediate Relevance</h3>
<p>I love my job, and I get a huge thrill when I ﬁnd out a way that I can do it bet­ter.
So if I need to ﬁnd some­thing to read, I of­ten start by ask­ing my­self,<span class="push-double"></span> <span class="pull-double">“</span>what is the hard­est part of my job right now?”</p>
<p>The an­swer can be:</p>
<ul>
<li>A spe­ciﬁc prob­lem I’m try­ing to solve (see the sec­tion above ti­tled<span class="push-double"></span> <span class="pull-double">“</span>Depth Provides Certainty”).</li>
<li>A repet­i­tive process that I do reg­u­larly.</li>
<li>Something I am un­sure about which is dis­cour­ag­ing me from work­ing with gusto.</li>
</ul>
<p>From there, I plainly ask ChatGPT:<span class="push-double"></span> <span class="pull-double">“</span>What is a good book that cov­ers X in depth for a per­son with Y level of pre­ex­ist­ing knowl­edge of the sub­ject.”</p>
<p>To de­velop the skill of read­ing deeply, your book does­n’t need to be about a sub­ject re­lated to your job.
That just hap­pens to work well for me.</p>
<h2>Just Start</h2>
<p>A jour­ney of a thou­sand miles be­gins with a sin­gle step.
A book of a thou­sand pages be­gins with a sin­gle para­graph.
The best thing you can do is start.</p>
<p>So, I im­plore you.
If you’re stuck on a prob­lem or an idea, try read­ing a book about it.
I have per­son­ally found an im­mea­sur­able amount of mean­ing through lit­er­a­ture, and it’s un­blocked me on a num­ber of prob­lems in my pro­fes­sional and per­sonal life.
I sus­pect it can do the same for you.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/giraffe.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Do Not Write with an LLM]]></title>
            <link>https://elijahpotter.dev/articles/do_not_write_with_an_LLM</link>
            <guid>https://elijahpotter.dev/articles/do_not_write_with_an_LLM</guid>
            <pubDate>Mon, 02 Mar 2026 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Do Not Write with an <span class="small-caps">LLM</span></h1>
<img src="/images/green_bird.webp" alt="A photo taken by myself of a green bird.">
<h2>A Growing Trend</h2>
<p>I have been see­ing an in­creas­ingly preva­lent trend of peo­ple show­ing up in on­line spaces proudly ﬂaunt­ing that they are writ­ing with the as­sis­tance of AI.
I of­ten see them clar­ify that they are go­ing fur­ther than let­ting the model per­form gram­mar or fact-check­ing.
They are de­scrib­ing an idea to an <span class="small-caps">LLM</span>, and giv­ing it com­plete con­trol over the ﬁ­nal writ­ten doc­u­ment.
They seem to be proud of this. They should­n’t be.</p>
<h2>What <strong>Does Not</strong> Work</h2>
<p>Writing seems to be a shadow of a more fun­da­men­tal and use­ful process we like to call<span class="push-double"></span> <span class="pull-double">“</span>thinking”.
There is a rea­son why LLMs, which are the clos­est thing we cur­rently have to<span class="push-double"></span> <span class="pull-double">“</span>software that thinks” is trained to think through writ­ing.
It seems log­i­cal, then, that we can use this<span class="push-double"></span> <span class="pull-double">“</span>software that writes” to help along our own think­ing process, right? Wrong.</p>
<p>Writing is one of the most cere­bral ac­tiv­i­ties that hu­mans do on a reg­u­lar ba­sis.
<a href="https://elijahpotter.dev/articles/why-i-talk-to-myself">I try to sit down and write about a com­plex topic at least once a week.</a>
There are a num­ber of rea­sons I do that per­tain­ing to my job in par­tic­u­lar, but there are more gen­er­ally ap­plic­a­ble rea­sons that can ap­ply to any pro­fes­sion.</p>
<p>When a math­e­mati­cian needs to think crit­i­cally about some the­o­rem or proof, they of­ten turn to a dry-erase board or a chalk­board.
The math­e­mati­cian then writes in clear no­ta­tion what they imag­ine the prob­lem to look like in their heads.
From there, they can ma­nip­u­late that no­ta­tion un­til they reach their de­sired out­come.</p>
<p>The English lan­guage is ex­tremely sim­i­lar, with a key dif­fer­ence.
Math­e­mat­i­cal no­ta­tion is specif­i­cally de­signed for a nar­row do­main.
Nat­ural lan­guages, on the other hand, can be ap­plied to any ﬁeld.
By writ­ing your idea down on pa­per (or in a Google Doc, it does not mat­ter), you give it sub­stance.
This sub­stance can then be shaped it­er­a­tively to­wards a goal of some kind.</p>
<p>No great ideas were writ­ten down on the ﬁrst try.
They started as a vague no­tion, and through writ­ing and rewrit­ing, they be­came some­thing con­crete and valu­able.
You can­not dic­tate a vague no­ta­tion to an <span class="small-caps">LLM</span> and ex­pect it to spit out some­thing tan­gi­bly valu­able.
Any at­tempt to do so is lazy, and it will re­sult in fail­ure.
I have seen it in the wild, and frankly, it is a lit­tle em­bar­rass­ing.</p>
<p><a href="https://ammil.industries/i-know-you-didnt-write-this/">This ar­ti­cle</a> feels es­pe­cially rel­e­vant to­day.</p>
<h2>What <strong>Does</strong> Work</h2>
<p>Using an <span class="small-caps">LLM</span> for re­search can be very ef­fec­tive.
Searches to ChatGPT or Perplexity can sur­face in­for­ma­tion faster than dig­ging around in a book you read three months ago.
For lots of searches, these two sources have com­pletely re­placed Google and DuckDuckGo for me.</p>
<p>They’re also quite ex­cel­lent fact-check­ers.
I’ve seen that plenty of folks have re­ceived quite good re­sults by past­ing their work into an <span class="small-caps">LLM</span> with an in­ter­net tool (like ChatGPT with Thinking en­abled).</p>
<p>Finally, I’ve per­son­ally found that LLMs are quite good at ﬁnd­ing places where I sim­ply for­got to in­sert con­text.
Some­times I for­get to de­ﬁne a term or in­sert a rel­e­vant link.
Even small mod­els like <code>gemma3n:e2b</code> can catch me.</p>
<h2>What <strong>You</strong> Should Be Doing</h2>
<p>When you need to com­mu­ni­cate an idea in your head, sit down with a notepad and a key­board and ex­plain it.
I per­son­ally think you should use a gram­mar checker, since they are far less likely to im­pede your thought process, but if you need to, you can in­struct ChatGPT to cri­tique your work once it is ﬁn­ished.</p>
<p>If an idea de­serves an au­di­ence, you’re ca­pa­ble of writ­ing it out your­self.
In the same vein, if you can vaguely de­scribe it to ChatGPT, you can vaguely de­scribe it to your in­tended au­di­ence.
Do your­self a fa­vor and skip the mid­dle­man.</p>
<h2>What We Should Be Building</h2>
<p>Now, I’d like to en­ter the sec­tion where I talk specif­i­cally about what this all means for peo­ple who cre­ate tools for writ­ers.
That in­cludes both Harper and WordPress.</p>
<p>A tool that helps some­one write is a tool that di­rectly in­ter­faces with how that per­son thinks.
That might in­clude what they think about, but more con­cretely I am re­fer­ring to the ac­tual day-to-day process of think­ing through com­plex ideas.
If you don’t be­lieve me, it’s prob­a­bly be­cause you skipped ahead in­stead of read­ing this post from the be­gin­ning.</p>
<p>Most cru­cially, we can­not al­low our­selves to build tools that dis­suade peo­ple from ex­press­ing their ideas in the way they imag­ine.
This can take the form of fric­tion, like build­ing tools that are so cum­ber­some that one dreads to open them at the start of the work­day.
This can also take the form of a tool that crushes or over­writes an au­thor’s core mes­sage.</p>
<p>A com­mon com­plaint I hear about Grammarly (from peo­ple who have switched to Harper from Grammarly) is that it’s too de­mand­ing. When they write prose in a cre­ative or un­usual fash­ion, their ser­vice of­ten forces them back into a colder and more for­mal way of speak­ing.
We don’t want to be like them.</p>
<p>For Harper, that means we need to be di­verse in our train­ing datasets and mind­ful of feed­back from our users.
For WordPress, it means we need to en­cour­age our <span class="small-caps">LLM</span> in­te­gra­tions to tread lightly and re­spect­fully.
They need to ac­cept when they’re wrong.
Just as any good hu­man should.</p>
<blockquote>
<p>Months af­ter writ­ing this post, I came across an­other.
Au­thor Cynthia Dunlop gives <a href="https://writethatblog.substack.com/p/dev-reaction-to-ai-blog-posts">a great overview of how de­vel­op­ers re­act to blog posts writ­ten by <span class="small-caps">AI</span></a>.
I highly sug­gest you take a look if you have more time.</p>
</blockquote>]]></description>
            <content:encoded><![CDATA[<h1>Do Not Write with an <span class="small-caps">LLM</span></h1>
<img src="/images/green_bird.webp" alt="A photo taken by myself of a green bird.">
<h2>A Growing Trend</h2>
<p>I have been see­ing an in­creas­ingly preva­lent trend of peo­ple show­ing up in on­line spaces proudly ﬂaunt­ing that they are writ­ing with the as­sis­tance of AI.
I of­ten see them clar­ify that they are go­ing fur­ther than let­ting the model per­form gram­mar or fact-check­ing.
They are de­scrib­ing an idea to an <span class="small-caps">LLM</span>, and giv­ing it com­plete con­trol over the ﬁ­nal writ­ten doc­u­ment.
They seem to be proud of this. They should­n’t be.</p>
<h2>What <strong>Does Not</strong> Work</h2>
<p>Writing seems to be a shadow of a more fun­da­men­tal and use­ful process we like to call<span class="push-double"></span> <span class="pull-double">“</span>thinking”.
There is a rea­son why LLMs, which are the clos­est thing we cur­rently have to<span class="push-double"></span> <span class="pull-double">“</span>software that thinks” is trained to think through writ­ing.
It seems log­i­cal, then, that we can use this<span class="push-double"></span> <span class="pull-double">“</span>software that writes” to help along our own think­ing process, right? Wrong.</p>
<p>Writing is one of the most cere­bral ac­tiv­i­ties that hu­mans do on a reg­u­lar ba­sis.
<a href="https://elijahpotter.dev/articles/why-i-talk-to-myself">I try to sit down and write about a com­plex topic at least once a week.</a>
There are a num­ber of rea­sons I do that per­tain­ing to my job in par­tic­u­lar, but there are more gen­er­ally ap­plic­a­ble rea­sons that can ap­ply to any pro­fes­sion.</p>
<p>When a math­e­mati­cian needs to think crit­i­cally about some the­o­rem or proof, they of­ten turn to a dry-erase board or a chalk­board.
The math­e­mati­cian then writes in clear no­ta­tion what they imag­ine the prob­lem to look like in their heads.
From there, they can ma­nip­u­late that no­ta­tion un­til they reach their de­sired out­come.</p>
<p>The English lan­guage is ex­tremely sim­i­lar, with a key dif­fer­ence.
Math­e­mat­i­cal no­ta­tion is specif­i­cally de­signed for a nar­row do­main.
Nat­ural lan­guages, on the other hand, can be ap­plied to any ﬁeld.
By writ­ing your idea down on pa­per (or in a Google Doc, it does not mat­ter), you give it sub­stance.
This sub­stance can then be shaped it­er­a­tively to­wards a goal of some kind.</p>
<p>No great ideas were writ­ten down on the ﬁrst try.
They started as a vague no­tion, and through writ­ing and rewrit­ing, they be­came some­thing con­crete and valu­able.
You can­not dic­tate a vague no­ta­tion to an <span class="small-caps">LLM</span> and ex­pect it to spit out some­thing tan­gi­bly valu­able.
Any at­tempt to do so is lazy, and it will re­sult in fail­ure.
I have seen it in the wild, and frankly, it is a lit­tle em­bar­rass­ing.</p>
<p><a href="https://ammil.industries/i-know-you-didnt-write-this/">This ar­ti­cle</a> feels es­pe­cially rel­e­vant to­day.</p>
<h2>What <strong>Does</strong> Work</h2>
<p>Using an <span class="small-caps">LLM</span> for re­search can be very ef­fec­tive.
Searches to ChatGPT or Perplexity can sur­face in­for­ma­tion faster than dig­ging around in a book you read three months ago.
For lots of searches, these two sources have com­pletely re­placed Google and DuckDuckGo for me.</p>
<p>They’re also quite ex­cel­lent fact-check­ers.
I’ve seen that plenty of folks have re­ceived quite good re­sults by past­ing their work into an <span class="small-caps">LLM</span> with an in­ter­net tool (like ChatGPT with Thinking en­abled).</p>
<p>Finally, I’ve per­son­ally found that LLMs are quite good at ﬁnd­ing places where I sim­ply for­got to in­sert con­text.
Some­times I for­get to de­ﬁne a term or in­sert a rel­e­vant link.
Even small mod­els like <code>gemma3n:e2b</code> can catch me.</p>
<h2>What <strong>You</strong> Should Be Doing</h2>
<p>When you need to com­mu­ni­cate an idea in your head, sit down with a notepad and a key­board and ex­plain it.
I per­son­ally think you should use a gram­mar checker, since they are far less likely to im­pede your thought process, but if you need to, you can in­struct ChatGPT to cri­tique your work once it is ﬁn­ished.</p>
<p>If an idea de­serves an au­di­ence, you’re ca­pa­ble of writ­ing it out your­self.
In the same vein, if you can vaguely de­scribe it to ChatGPT, you can vaguely de­scribe it to your in­tended au­di­ence.
Do your­self a fa­vor and skip the mid­dle­man.</p>
<h2>What We Should Be Building</h2>
<p>Now, I’d like to en­ter the sec­tion where I talk specif­i­cally about what this all means for peo­ple who cre­ate tools for writ­ers.
That in­cludes both Harper and WordPress.</p>
<p>A tool that helps some­one write is a tool that di­rectly in­ter­faces with how that per­son thinks.
That might in­clude what they think about, but more con­cretely I am re­fer­ring to the ac­tual day-to-day process of think­ing through com­plex ideas.
If you don’t be­lieve me, it’s prob­a­bly be­cause you skipped ahead in­stead of read­ing this post from the be­gin­ning.</p>
<p>Most cru­cially, we can­not al­low our­selves to build tools that dis­suade peo­ple from ex­press­ing their ideas in the way they imag­ine.
This can take the form of fric­tion, like build­ing tools that are so cum­ber­some that one dreads to open them at the start of the work­day.
This can also take the form of a tool that crushes or over­writes an au­thor’s core mes­sage.</p>
<p>A com­mon com­plaint I hear about Grammarly (from peo­ple who have switched to Harper from Grammarly) is that it’s too de­mand­ing. When they write prose in a cre­ative or un­usual fash­ion, their ser­vice of­ten forces them back into a colder and more for­mal way of speak­ing.
We don’t want to be like them.</p>
<p>For Harper, that means we need to be di­verse in our train­ing datasets and mind­ful of feed­back from our users.
For WordPress, it means we need to en­cour­age our <span class="small-caps">LLM</span> in­te­gra­tions to tread lightly and re­spect­fully.
They need to ac­cept when they’re wrong.
Just as any good hu­man should.</p>
<blockquote>
<p>Months af­ter writ­ing this post, I came across an­other.
Au­thor Cynthia Dunlop gives <a href="https://writethatblog.substack.com/p/dev-reaction-to-ai-blog-posts">a great overview of how de­vel­op­ers re­act to blog posts writ­ten by <span class="small-caps">AI</span></a>.
I highly sug­gest you take a look if you have more time.</p>
</blockquote>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/green_bird.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Refactoring Slop]]></title>
            <link>https://elijahpotter.dev/articles/refactoring_slop</link>
            <guid>https://elijahpotter.dev/articles/refactoring_slop</guid>
            <pubDate>Fri, 20 Feb 2026 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Refactoring Slop</h1>
<p>The term<span class="push-double"></span> <span class="pull-double">“</span>slop” is rapidly en­ter­ing the con­ven­tional English lex­i­con.
In fact, it was Merriam Webster’s Word Of the Year for 2025. It refers to low-qual­ity me­dia gen­er­ated by an LLM.
It can be ap­plied to im­ages, text, mu­sic, or most rel­e­vantly to us, code.</p>
<p>As an open source main­tainer, I fre­quently both re­ceive and cre­ate pull re­quests that could be la­beled as slop.
One of my prin­ci­pal jobs is to stand at the gate to the Harper repos­i­tory and say,<span class="push-double"></span> <span class="pull-double">“</span>thou shalt not pass” to any low-qual­ity or buggy code that wishes to en­ter.
So my ques­tion be­comes, how can I turn <span class="small-caps">AI</span> gen­er­ated code from<span class="push-double"></span> <span class="pull-double">“</span>slop” tier to<span class="push-double"></span> <span class="pull-double">“</span>top tier”?</p>
<p>There are a gazil­lion guides out there about the var­i­ous tech­niques you can use to im­prove the qual­ity of <span class="small-caps">AI</span> gen­er­ated code.
Those tech­niques will change, and I’m sure my read­er­ship al­ready knows all about them.
In­stead, I want to talk about some more gen­er­ally ap­plic­a­ble ideas that I’ve found es­pe­cially use­ful in the age of AI.
I’m some­thing of a con­trar­ian, so ex­pect some dish­ing on con­ven­tional<span class="push-double"></span> <span class="pull-double">“</span>vibe coders”.</p>
<h2>A Good Foundation</h2>
<p>I am of­ten sent sto­ries of de­vel­op­ers dis­cov­er­ing Claude Code for the ﬁrst time and us­ing it to build some sim­ple <span class="small-caps">CRUD</span> app from scratch, with­out ever read­ing or writ­ing a line of code them­selves.
Those are im­pres­sive sto­ries, and they’re a sign of the amaz­ing progress that the na­tion’s fron­tier <span class="small-caps">AI</span> labs have made in the last few months.
They are not, how­ever, ex­am­ples of how an open-source main­tainer should op­er­ate.</p>
<p>As I said be­fore, it’s crit­i­cal that main­tainer act as a gate that al­lows or de­nies code from en­ter­ing the repos­i­tory (and thus be­ing ul­ti­mately de­liv­ered to user’s de­vices).
In or­der to do that, they need to have a good un­der­stand­ing of how the code al­ready works.
Then, and <em>only</em> then, should they al­low mod­i­ﬁ­ca­tions to it.</p>
<p>This should be pretty ob­vi­ous ad­vice to most in our in­dus­try, but ev­i­dently it is­n’t.
I’ve seen sev­eral in­stances re­cently of de­vel­op­ers vibe-cod­ing a new fea­ture with­out some ex­ist­ing foun­da­tional un­der­stand­ing of how the orig­i­nal code worked.
The re­sult: the new fea­ture works well enough, but its im­ple­men­ta­tion breaks some other part of the ap­pli­ca­tion.
That’s not to men­tion that it in­creases the apps com­plex­ity on the whole far more than nec­es­sary.</p>
<p>Having a foun­da­tional un­der­stand­ing from the get-go is an easy way to pre­vent this kind of tragic out­come.
LLMs are not cur­rently ca­pa­ble enough to rec­og­nize when a pat­tern in the code they’re writ­ing al­ready ex­ists in the code­base.
I’ve tried push­ing them in the right di­rec­tion with skills and with an <code>AGENTS.md</code>, and they’ll pick it up given enough mas­sag­ing, but the fact is that they still need to be poked.
If I did­n’t have a good knowl­edge of my pro­jec­t’s in­ter­nals, I would­n’t know to poke them at all.</p>
<p>Understand your pro­ject deeply.
Un­der­stand its code, its val­ues, and its pur­pose.</p>
<h2>Reviewing <span class="small-caps">AI</span> Code</h2>
<p>Now that you’re an ex­pert on how your pro­ject works, you’re ready to start re­view­ing AI-written pull re­quests.
Here’s the thing you need to re­mem­ber: you have bet­ter taste than the clanker does.
You ul­ti­mately get to de­cide what code gets merged.
What should that code look like?
How should it be tested?
Your an­swer to those ques­tions should guide you to­wards prompt­ing a model to re­vise its work or sub­mit­ting feed­back on a pull re­quest opened by a hu­man.</p>
<p>Here’s what your prompt should <em>not</em> look like:</p>
<ul>
<li><span class="pull-double">“</span>Fix it.”</li>
<li><span class="pull-double">“</span>Make it pret­tier.”</li>
<li><span class="pull-double">“</span>I don’t like that.”</li>
</ul>
<p>Obviously, you would­n’t want feed­back like that if you were the one writ­ing the code.
If you’re look­ing at a patch, who­ever wrote it did the best job they could.
Whether it was an <span class="small-caps">LLM</span> or a hu­man does­n’t mat­ter.
If the code is­n’t up to your re­ﬁned taste as a hu­man, you need to give them a nudge in the right di­rec­tion.
Here are some bet­ter ver­sions of the prompts from be­fore:</p>
<ul>
<li><span class="pull-double">“</span>It looks like your mod­i­ﬁ­ca­tion made the app crash on startup. Would you run the tests to make sure you haven’t bro­ken any­thing in the X mod­ule?”</li>
<li><span class="pull-double">“</span>This huge block of code is re­ally hard to read. Would you refac­tor it into in­di­vid­ual com­pos­able com­po­nents?”</li>
<li><span class="pull-double">“</span>It looks like you’re reim­ple­ment­ing a func­tion we al­ready have. Would you use X in­stead?”</li>
</ul>
<p>For those un­aware, you should never say<span class="push-double"></span> <span class="pull-double">“</span>I don’t like that” to hu­man’s work.</p>
<h2>Accept That The Models Are Limited in Intelligence</h2>
<p>It’s easy to get the im­pres­sion that with enough to­kens, any­thing is pos­si­ble.
Maybe that will be true in the fu­ture, but I don’t think that’s true to­day.</p>
<p>LLMs are lim­ited in in­tel­li­gence and ex­pe­ri­ence.
Even when pro­vided ac­cess to the best cod­ing se­tups in the world, they are still in­ca­pable of ship­ping many (most?) fea­tures or ﬁx­ing many (most?) bugs.
The fact that I have to in­clude ques­tion marks is a tes­ta­ment to how far we’ve come, but it’s im­por­tant not to over­state things.
If you’re read­ing this, you’re pretty smart.
It’s ﬁne to let the model try to solve a prob­lem for you once or twice, but if it still fails, you’ll need to get in there and do it your­self.
Don’t be afraid to get your hands dirty.</p>]]></description>
            <content:encoded><![CDATA[<h1>Refactoring Slop</h1>
<p>The term<span class="push-double"></span> <span class="pull-double">“</span>slop” is rapidly en­ter­ing the con­ven­tional English lex­i­con.
In fact, it was Merriam Webster’s Word Of the Year for 2025. It refers to low-qual­ity me­dia gen­er­ated by an LLM.
It can be ap­plied to im­ages, text, mu­sic, or most rel­e­vantly to us, code.</p>
<p>As an open source main­tainer, I fre­quently both re­ceive and cre­ate pull re­quests that could be la­beled as slop.
One of my prin­ci­pal jobs is to stand at the gate to the Harper repos­i­tory and say,<span class="push-double"></span> <span class="pull-double">“</span>thou shalt not pass” to any low-qual­ity or buggy code that wishes to en­ter.
So my ques­tion be­comes, how can I turn <span class="small-caps">AI</span> gen­er­ated code from<span class="push-double"></span> <span class="pull-double">“</span>slop” tier to<span class="push-double"></span> <span class="pull-double">“</span>top tier”?</p>
<p>There are a gazil­lion guides out there about the var­i­ous tech­niques you can use to im­prove the qual­ity of <span class="small-caps">AI</span> gen­er­ated code.
Those tech­niques will change, and I’m sure my read­er­ship al­ready knows all about them.
In­stead, I want to talk about some more gen­er­ally ap­plic­a­ble ideas that I’ve found es­pe­cially use­ful in the age of AI.
I’m some­thing of a con­trar­ian, so ex­pect some dish­ing on con­ven­tional<span class="push-double"></span> <span class="pull-double">“</span>vibe coders”.</p>
<h2>A Good Foundation</h2>
<p>I am of­ten sent sto­ries of de­vel­op­ers dis­cov­er­ing Claude Code for the ﬁrst time and us­ing it to build some sim­ple <span class="small-caps">CRUD</span> app from scratch, with­out ever read­ing or writ­ing a line of code them­selves.
Those are im­pres­sive sto­ries, and they’re a sign of the amaz­ing progress that the na­tion’s fron­tier <span class="small-caps">AI</span> labs have made in the last few months.
They are not, how­ever, ex­am­ples of how an open-source main­tainer should op­er­ate.</p>
<p>As I said be­fore, it’s crit­i­cal that main­tainer act as a gate that al­lows or de­nies code from en­ter­ing the repos­i­tory (and thus be­ing ul­ti­mately de­liv­ered to user’s de­vices).
In or­der to do that, they need to have a good un­der­stand­ing of how the code al­ready works.
Then, and <em>only</em> then, should they al­low mod­i­ﬁ­ca­tions to it.</p>
<p>This should be pretty ob­vi­ous ad­vice to most in our in­dus­try, but ev­i­dently it is­n’t.
I’ve seen sev­eral in­stances re­cently of de­vel­op­ers vibe-cod­ing a new fea­ture with­out some ex­ist­ing foun­da­tional un­der­stand­ing of how the orig­i­nal code worked.
The re­sult: the new fea­ture works well enough, but its im­ple­men­ta­tion breaks some other part of the ap­pli­ca­tion.
That’s not to men­tion that it in­creases the apps com­plex­ity on the whole far more than nec­es­sary.</p>
<p>Having a foun­da­tional un­der­stand­ing from the get-go is an easy way to pre­vent this kind of tragic out­come.
LLMs are not cur­rently ca­pa­ble enough to rec­og­nize when a pat­tern in the code they’re writ­ing al­ready ex­ists in the code­base.
I’ve tried push­ing them in the right di­rec­tion with skills and with an <code>AGENTS.md</code>, and they’ll pick it up given enough mas­sag­ing, but the fact is that they still need to be poked.
If I did­n’t have a good knowl­edge of my pro­jec­t’s in­ter­nals, I would­n’t know to poke them at all.</p>
<p>Understand your pro­ject deeply.
Un­der­stand its code, its val­ues, and its pur­pose.</p>
<h2>Reviewing <span class="small-caps">AI</span> Code</h2>
<p>Now that you’re an ex­pert on how your pro­ject works, you’re ready to start re­view­ing AI-written pull re­quests.
Here’s the thing you need to re­mem­ber: you have bet­ter taste than the clanker does.
You ul­ti­mately get to de­cide what code gets merged.
What should that code look like?
How should it be tested?
Your an­swer to those ques­tions should guide you to­wards prompt­ing a model to re­vise its work or sub­mit­ting feed­back on a pull re­quest opened by a hu­man.</p>
<p>Here’s what your prompt should <em>not</em> look like:</p>
<ul>
<li><span class="pull-double">“</span>Fix it.”</li>
<li><span class="pull-double">“</span>Make it pret­tier.”</li>
<li><span class="pull-double">“</span>I don’t like that.”</li>
</ul>
<p>Obviously, you would­n’t want feed­back like that if you were the one writ­ing the code.
If you’re look­ing at a patch, who­ever wrote it did the best job they could.
Whether it was an <span class="small-caps">LLM</span> or a hu­man does­n’t mat­ter.
If the code is­n’t up to your re­ﬁned taste as a hu­man, you need to give them a nudge in the right di­rec­tion.
Here are some bet­ter ver­sions of the prompts from be­fore:</p>
<ul>
<li><span class="pull-double">“</span>It looks like your mod­i­ﬁ­ca­tion made the app crash on startup. Would you run the tests to make sure you haven’t bro­ken any­thing in the X mod­ule?”</li>
<li><span class="pull-double">“</span>This huge block of code is re­ally hard to read. Would you refac­tor it into in­di­vid­ual com­pos­able com­po­nents?”</li>
<li><span class="pull-double">“</span>It looks like you’re reim­ple­ment­ing a func­tion we al­ready have. Would you use X in­stead?”</li>
</ul>
<p>For those un­aware, you should never say<span class="push-double"></span> <span class="pull-double">“</span>I don’t like that” to hu­man’s work.</p>
<h2>Accept That The Models Are Limited in Intelligence</h2>
<p>It’s easy to get the im­pres­sion that with enough to­kens, any­thing is pos­si­ble.
Maybe that will be true in the fu­ture, but I don’t think that’s true to­day.</p>
<p>LLMs are lim­ited in in­tel­li­gence and ex­pe­ri­ence.
Even when pro­vided ac­cess to the best cod­ing se­tups in the world, they are still in­ca­pable of ship­ping many (most?) fea­tures or ﬁx­ing many (most?) bugs.
The fact that I have to in­clude ques­tion marks is a tes­ta­ment to how far we’ve come, but it’s im­por­tant not to over­state things.
If you’re read­ing this, you’re pretty smart.
It’s ﬁne to let the model try to solve a prob­lem for you once or twice, but if it still fails, you’ll need to get in there and do it your­self.
Don’t be afraid to get your hands dirty.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LaTeX Support Is Coming to Harper]]></title>
            <link>https://elijahpotter.dev/articles/latex_support_is_coming_to_harper</link>
            <guid>https://elijahpotter.dev/articles/latex_support_is_coming_to_harper</guid>
            <pubDate>Wed, 18 Feb 2026 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>LaTeX Support Is Coming to Harper</h1>
<p>It’s been a long time com­ing, which is why I’m thrilled to say that Harper will soon sup­port LaTeX. This is some­thing our users have been plead­ing for since the be­gin­ning of the pro­ject. LaTeX sup­port feels like a fea­ture that is com­par­a­tively low‑ef­fort, but un­locks a mas­sive new con­sumer seg­ment with lit­tle ex­ist­ing com­pe­ti­tion. It has the po­ten­tial to be a huge boon to our user base and our of­fer­ing over­all.</p>
<p>For con­text, <a href="https://en.wikipedia.org/wiki/LaTeX">LaTeX</a> is a doc­u­ment markup lan­guage (similar in soul to Markdown), pri­mar­ily used by <span class="small-caps">STEM</span> pro­fes­sions and stu­dents. It’s ex­tra­or­di­nar­ily good at for­mat­ting and ren­der­ing com­plex math­e­mat­i­cal equa­tions and chem­i­cal for­mu­las.</p>
<h2>The Market</h2>
<p>Neither Grammarly nor LanguageTool sup­port LaTeX doc­u­ments, yet sites like Overleaf (the most pop­u­lar LaTeX ed­i­tor) reg­u­larly <a href="https://www.overleaf.com/blog/nine-years-nine-million-users">see an ex­cess of 400,000 daily ac­tive users</a>. That’s an enor­mous mar­ket of peo­ple who tend to be tech­ni­cal and who care about their pri­vacy. Since the two most pop­u­lar gram­mar check­ers (Grammarly and LanguageTool) don’t sup­port sites like Overleaf in any way, we don’t have much com­pe­ti­tion to worry about.</p>
<p>The story is sim­i­lar (albeit to a lesser scale) for Typst, an­other doc­u­ment markup lan­guage. We haven’t even merged sup­port for Typst yet, and we’re al­ready see­ing peo­ple pick up Harper ex­plic­itly for use in the Typst on­line ed­i­tor.</p>
<h2>Previous Attempts</h2>
<p>On two sep­a­rate oc­ca­sions, mem­bers of the Harper com­mu­nity have opened (and sub­se­quently closed) pull re­quests which tried to add LaTeX sup­port to Harper. I’ve put links to that work be­low for pos­ter­ity.</p>
<ul>
<li><a href="https://github.com/Automattic/harper/pull/100"><span class="small-caps">PR</span> by Grant Lemons</a></li>
<li><a href="https://github.com/Automattic/harper/pull/2073"><span class="small-caps">PR</span> by Shreyas Minocha</a></li>
</ul>
<p>The fun­da­men­tal prob­lem with LaTeX is that its un­der­ly­ing lan­guage, TeX, is not reg­u­lar. In or­der to parse the lan­guage, you need to ac­tu­ally run it. This is why LaTeX doc­u­ments can take as many as sev­eral full min­utes to com­pile and ren­der to a <span class="small-caps">PDF</span>.</p>
<p>Rather than<span class="push-double"></span> <span class="pull-double">“</span>correctly” read and run the TeX code con­tained within LaTeX doc­u­ments, these pre­vi­ous at­tempts (including mine, which we will dis­cuss in a sec­ond) choose to as­sume that LaTeX com­mands are key­words in a more ab­stract lan­guage. Some edge‑cases will nat­u­rally arise when we ig­nore the low­est level of ab­strac­tion, but do­ing so al­lows us to parse most real doc­u­ments with less ef­fort.</p>
<h2>Implementation Details</h2>
<p>It was very im­por­tant to me that our LaTeX sup­port was fast, small, and <strong>could be com­piled to run in the browser</strong>. That last bit is crit­i­cal, be­cause pre­vi­ous at­tempts (by the com­mu­nity) to add LaTeX sup­port tried to use Tree‑sitter, which can­not be eas­ily com­piled to run in the browser. Almost all LaTeX users (which is the vast ma­jor­ity of <span class="small-caps">STEM</span> un­der­grad­u­ate and grad­u­ate stu­dents) use Overleaf, an on­line LaTeX com­piler and ed­i­tor. Developing LaTeX sup­port with­out mak­ing it pos­si­ble to use Overleaf would be like de­vel­op­ing a text ed­i­tor that can’t be used with a key­board. Simply il­log­i­cal.</p>
<p>To make this hap­pen, I wrote my parser from scratch in Rust. Actually, call­ing it a parser would be an in­sult to all of parser‑kind. My so­lu­tion pro­duces a char­ac­ter mask. That is, it iden­ti­ﬁes which parts of the doc­u­ment are real English text, and which parts are LaTeX code. From there, I can just mark all the LaTeX sec­tions as<span class="push-double"></span> <span class="pull-double">“</span>unlintable” and thus un­de­serv­ing of Harper’s dis­cern­ing gaze.</p>
<p>The cu­ri­ous can <a href="https://github.com/Automattic/harper/pull/2689">check out my <span class="small-caps">PR</span></a>.</p>
<h2>What’s Next</h2>
<p>While we’re si­mul­ta­ne­ously work­ing on rolling out our new Weirpack sys­tem and our small lan­guage model, I plan to work with some of our un­der­grad­u­ate <span class="small-caps">STEM</span> stu­dent users. I ﬁnd it quite likely there’s a use‑case for our LaTeX and Typst sup­port that I’m not con­sid­er­ing. It’s a huge mar­ket, and I’m ex­cited to work with those in it.</p>
<h2>A Quick Correction</h2>
<p>A few days af­ter writ­ing this post, I found ev­i­dence that LanguageTool sup­ports an older ver­sion of the Overleaf ed­i­tor.
While it does­n’t seem to sup­port the lat­est ver­sion, this new in­for­ma­tion makes my pre­vi­ous state­ment that it<span class="push-double"></span> <span class="pull-double">“</span>doesn’t sup­port sites like Overleaf in any way” false.
I apol­o­gize for the mis­in­for­ma­tion.</p>]]></description>
            <content:encoded><![CDATA[<h1>LaTeX Support Is Coming to Harper</h1>
<p>It’s been a long time com­ing, which is why I’m thrilled to say that Harper will soon sup­port LaTeX. This is some­thing our users have been plead­ing for since the be­gin­ning of the pro­ject. LaTeX sup­port feels like a fea­ture that is com­par­a­tively low‑ef­fort, but un­locks a mas­sive new con­sumer seg­ment with lit­tle ex­ist­ing com­pe­ti­tion. It has the po­ten­tial to be a huge boon to our user base and our of­fer­ing over­all.</p>
<p>For con­text, <a href="https://en.wikipedia.org/wiki/LaTeX">LaTeX</a> is a doc­u­ment markup lan­guage (similar in soul to Markdown), pri­mar­ily used by <span class="small-caps">STEM</span> pro­fes­sions and stu­dents. It’s ex­tra­or­di­nar­ily good at for­mat­ting and ren­der­ing com­plex math­e­mat­i­cal equa­tions and chem­i­cal for­mu­las.</p>
<h2>The Market</h2>
<p>Neither Grammarly nor LanguageTool sup­port LaTeX doc­u­ments, yet sites like Overleaf (the most pop­u­lar LaTeX ed­i­tor) reg­u­larly <a href="https://www.overleaf.com/blog/nine-years-nine-million-users">see an ex­cess of 400,000 daily ac­tive users</a>. That’s an enor­mous mar­ket of peo­ple who tend to be tech­ni­cal and who care about their pri­vacy. Since the two most pop­u­lar gram­mar check­ers (Grammarly and LanguageTool) don’t sup­port sites like Overleaf in any way, we don’t have much com­pe­ti­tion to worry about.</p>
<p>The story is sim­i­lar (albeit to a lesser scale) for Typst, an­other doc­u­ment markup lan­guage. We haven’t even merged sup­port for Typst yet, and we’re al­ready see­ing peo­ple pick up Harper ex­plic­itly for use in the Typst on­line ed­i­tor.</p>
<h2>Previous Attempts</h2>
<p>On two sep­a­rate oc­ca­sions, mem­bers of the Harper com­mu­nity have opened (and sub­se­quently closed) pull re­quests which tried to add LaTeX sup­port to Harper. I’ve put links to that work be­low for pos­ter­ity.</p>
<ul>
<li><a href="https://github.com/Automattic/harper/pull/100"><span class="small-caps">PR</span> by Grant Lemons</a></li>
<li><a href="https://github.com/Automattic/harper/pull/2073"><span class="small-caps">PR</span> by Shreyas Minocha</a></li>
</ul>
<p>The fun­da­men­tal prob­lem with LaTeX is that its un­der­ly­ing lan­guage, TeX, is not reg­u­lar. In or­der to parse the lan­guage, you need to ac­tu­ally run it. This is why LaTeX doc­u­ments can take as many as sev­eral full min­utes to com­pile and ren­der to a <span class="small-caps">PDF</span>.</p>
<p>Rather than<span class="push-double"></span> <span class="pull-double">“</span>correctly” read and run the TeX code con­tained within LaTeX doc­u­ments, these pre­vi­ous at­tempts (including mine, which we will dis­cuss in a sec­ond) choose to as­sume that LaTeX com­mands are key­words in a more ab­stract lan­guage. Some edge‑cases will nat­u­rally arise when we ig­nore the low­est level of ab­strac­tion, but do­ing so al­lows us to parse most real doc­u­ments with less ef­fort.</p>
<h2>Implementation Details</h2>
<p>It was very im­por­tant to me that our LaTeX sup­port was fast, small, and <strong>could be com­piled to run in the browser</strong>. That last bit is crit­i­cal, be­cause pre­vi­ous at­tempts (by the com­mu­nity) to add LaTeX sup­port tried to use Tree‑sitter, which can­not be eas­ily com­piled to run in the browser. Almost all LaTeX users (which is the vast ma­jor­ity of <span class="small-caps">STEM</span> un­der­grad­u­ate and grad­u­ate stu­dents) use Overleaf, an on­line LaTeX com­piler and ed­i­tor. Developing LaTeX sup­port with­out mak­ing it pos­si­ble to use Overleaf would be like de­vel­op­ing a text ed­i­tor that can’t be used with a key­board. Simply il­log­i­cal.</p>
<p>To make this hap­pen, I wrote my parser from scratch in Rust. Actually, call­ing it a parser would be an in­sult to all of parser‑kind. My so­lu­tion pro­duces a char­ac­ter mask. That is, it iden­ti­ﬁes which parts of the doc­u­ment are real English text, and which parts are LaTeX code. From there, I can just mark all the LaTeX sec­tions as<span class="push-double"></span> <span class="pull-double">“</span>unlintable” and thus un­de­serv­ing of Harper’s dis­cern­ing gaze.</p>
<p>The cu­ri­ous can <a href="https://github.com/Automattic/harper/pull/2689">check out my <span class="small-caps">PR</span></a>.</p>
<h2>What’s Next</h2>
<p>While we’re si­mul­ta­ne­ously work­ing on rolling out our new Weirpack sys­tem and our small lan­guage model, I plan to work with some of our un­der­grad­u­ate <span class="small-caps">STEM</span> stu­dent users. I ﬁnd it quite likely there’s a use‑case for our LaTeX and Typst sup­port that I’m not con­sid­er­ing. It’s a huge mar­ket, and I’m ex­cited to work with those in it.</p>
<h2>A Quick Correction</h2>
<p>A few days af­ter writ­ing this post, I found ev­i­dence that LanguageTool sup­ports an older ver­sion of the Overleaf ed­i­tor.
While it does­n’t seem to sup­port the lat­est ver­sion, this new in­for­ma­tion makes my pre­vi­ous state­ment that it<span class="push-double"></span> <span class="pull-double">“</span>doesn’t sup­port sites like Overleaf in any way” false.
I apol­o­gize for the mis­in­for­ma­tion.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building Software That Is Good for Humans]]></title>
            <link>https://elijahpotter.dev/articles/building_software_that_is_good_for_humans</link>
            <guid>https://elijahpotter.dev/articles/building_software_that_is_good_for_humans</guid>
            <pubDate>Mon, 09 Feb 2026 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Building Software That Is Good for Humans</h1>
<p>Earlier to­day, <a href="https://digital-strategy.ec.europa.eu/en/news/commission-preliminarily-finds-tiktoks-addictive-design-breach-digital-services-act">the European Commission re­leased a re­port</a> de­tail­ing their ini­tial ﬁnd­ings that TikTok’s core rec­om­men­da­tion al­go­rithm is in breach of the Digital Services Act.
This is only the most re­cent episode in the ex­tended saga where west­ern gov­ern­ments try to reg­u­late the Chinese com­pany.
The var­i­ous rea­sons and the meth­ods they em­ploy in this reg­u­la­tion are far out­side of my do­main of ex­per­tise, but the re­port got me think­ing.
Why does so much of the soft­ware we use feel harm­ful?</p>
<p>There are few peo­ple who can re­port that an in­ter­ac­tion with Microsoft Word, Teams, or TikTok has left them feel­ing em­pow­ered, health­ier, or hap­pier.
On the other hand, I can think of plenty of soft­ware that has left me feel­ing great.
What is that?
How can we make sure our soft­ware sits in the lat­ter group?</p>
<h2>Traits of Offensive Software</h2>
<p>There are a few fun­da­men­tal things that I’ve found make us­ing cer­tain soft­ware feel like a chore.
Soft­ware feels<span class="push-double"></span> <span class="pull-double">“</span>icky” when it is some com­bi­na­tion of slow, un­pre­dictable, or when it gets in the way of the mean­ing­ful pur­suits in life.</p>
<h3>It Is Slow</h3>
<p>Computers are fast.
Peo­ple un­der­stand that.
So if they’re left wait­ing for a page to load or a process to ﬁn­ish, they feel frus­trated.</p>
<p>Slow soft­ware makes it dif­ﬁ­cult to it­er­ate and ex­per­i­ment.
Imag­ine if WordPress took 20 min­utes to pub­lish a post, only to dis­cover af­ter­ward that you left a typo in the ti­tle.
That would be pretty frus­trat­ing, no?</p>
<p>Steve Jobs imag­ined Apple Computers to be<span class="push-double"></span> <span class="pull-double">“</span>bicycles for the mind.”
A bi­cy­cle whose chain hitched or whose steer­ing lagged not be very help­ful. It might not get you to your des­ti­na­tion at all.</p>
<p>I think that this idea of slow­ness is es­pe­cially im­por­tant in re­gards to <span class="small-caps">AI</span> agents.
If an as­sis­tant spends even a few sec­onds more than what is crit­i­cally nec­es­sary for the query, and they’re mes­saged fre­quently (say, to it­er­ate on a blog post), users can quickly get tired or<span class="push-double"></span> <span class="pull-double">“</span>brainrotted”, as the kids say.</p>
<h3>It Is Random</h3>
<p>If some soft­ware works <em>some­times</em>, but not al­ways, there’s a good chance it’s ad­dic­tive.
This pre­sents most ob­vi­ously in apps like TikTok, but it also ap­pears in more in­sid­i­ous places.
The in­fer­ence of large lan­guage mod­els in­volves ran­dom­ness in its very na­ture.</p>
<p>Fundamentally, this from of ad­dic­tion comes from a quirk of hu­man psy­chol­ogy.
Ran­domly ap­plied re­wards (the soft­ware <em>some­times</em> works, but not al­ways) is a form of <a href="https://en.wikipedia.org/wiki/Operant_conditioning">op­er­ant con­di­tion­ing</a>.
It feels good to roll the dice, which makes us want to roll it more of­ten.
Over time, rolling the dice starts to be less of a choice, and more of a habit.</p>
<p>When some­one chooses to use soft­ware not be­cause it’s gen­uinely help­ful, but be­cause it feels good to roll the dice, a sense of agency is lost.</p>
<p>In my ex­pe­ri­ence, Microsoft Word is like this, as are most agen­tic cod­ing tools and im­age gen­er­a­tors.</p>
<h3>It Distracts from That Which Makes Life Meaningful</h3>
<p>When soft­ware is slow, or oth­er­wise re­moves your agency with vari­able re­ward sys­tems, it can start to chip away at your abil­ity to make rea­son­able de­ci­sions on where to spend your time.
Peo­ple who use<span class="push-double"></span> <span class="pull-double">“</span>social” me­dia sites like Facebook or Instagram can at­test to this.
They sim­ply don’t feel like they have agency over their own ac­tions any­more.
They can’t stop us­ing soft­ware they ac­tively hate.</p>
<h2>Our Goals</h2>
<p>I think that one of <a href="https://automattic.com/">Automattic’s</a> great­est strengths is that none of our soft­ware does any one of these things.
I re­mem­ber the ﬁrst time I used Pocket Casts: I was thrilled.
It was eas­ily the best pod­cast soft­ware I had ever used.
The same things hap­pened with Day One and WordPress.com.</p>
<p>As we look to build­ing Harper, I think it’s im­por­tant that we re­mem­ber these core prin­ci­ples.
Our soft­ware should be fast, pre­dictable, and sup­port our cus­tomers in ﬁnd­ing mean­ing in their lives.</p>]]></description>
            <content:encoded><![CDATA[<h1>Building Software That Is Good for Humans</h1>
<p>Earlier to­day, <a href="https://digital-strategy.ec.europa.eu/en/news/commission-preliminarily-finds-tiktoks-addictive-design-breach-digital-services-act">the European Commission re­leased a re­port</a> de­tail­ing their ini­tial ﬁnd­ings that TikTok’s core rec­om­men­da­tion al­go­rithm is in breach of the Digital Services Act.
This is only the most re­cent episode in the ex­tended saga where west­ern gov­ern­ments try to reg­u­late the Chinese com­pany.
The var­i­ous rea­sons and the meth­ods they em­ploy in this reg­u­la­tion are far out­side of my do­main of ex­per­tise, but the re­port got me think­ing.
Why does so much of the soft­ware we use feel harm­ful?</p>
<p>There are few peo­ple who can re­port that an in­ter­ac­tion with Microsoft Word, Teams, or TikTok has left them feel­ing em­pow­ered, health­ier, or hap­pier.
On the other hand, I can think of plenty of soft­ware that has left me feel­ing great.
What is that?
How can we make sure our soft­ware sits in the lat­ter group?</p>
<h2>Traits of Offensive Software</h2>
<p>There are a few fun­da­men­tal things that I’ve found make us­ing cer­tain soft­ware feel like a chore.
Soft­ware feels<span class="push-double"></span> <span class="pull-double">“</span>icky” when it is some com­bi­na­tion of slow, un­pre­dictable, or when it gets in the way of the mean­ing­ful pur­suits in life.</p>
<h3>It Is Slow</h3>
<p>Computers are fast.
Peo­ple un­der­stand that.
So if they’re left wait­ing for a page to load or a process to ﬁn­ish, they feel frus­trated.</p>
<p>Slow soft­ware makes it dif­ﬁ­cult to it­er­ate and ex­per­i­ment.
Imag­ine if WordPress took 20 min­utes to pub­lish a post, only to dis­cover af­ter­ward that you left a typo in the ti­tle.
That would be pretty frus­trat­ing, no?</p>
<p>Steve Jobs imag­ined Apple Computers to be<span class="push-double"></span> <span class="pull-double">“</span>bicycles for the mind.”
A bi­cy­cle whose chain hitched or whose steer­ing lagged not be very help­ful. It might not get you to your des­ti­na­tion at all.</p>
<p>I think that this idea of slow­ness is es­pe­cially im­por­tant in re­gards to <span class="small-caps">AI</span> agents.
If an as­sis­tant spends even a few sec­onds more than what is crit­i­cally nec­es­sary for the query, and they’re mes­saged fre­quently (say, to it­er­ate on a blog post), users can quickly get tired or<span class="push-double"></span> <span class="pull-double">“</span>brainrotted”, as the kids say.</p>
<h3>It Is Random</h3>
<p>If some soft­ware works <em>some­times</em>, but not al­ways, there’s a good chance it’s ad­dic­tive.
This pre­sents most ob­vi­ously in apps like TikTok, but it also ap­pears in more in­sid­i­ous places.
The in­fer­ence of large lan­guage mod­els in­volves ran­dom­ness in its very na­ture.</p>
<p>Fundamentally, this from of ad­dic­tion comes from a quirk of hu­man psy­chol­ogy.
Ran­domly ap­plied re­wards (the soft­ware <em>some­times</em> works, but not al­ways) is a form of <a href="https://en.wikipedia.org/wiki/Operant_conditioning">op­er­ant con­di­tion­ing</a>.
It feels good to roll the dice, which makes us want to roll it more of­ten.
Over time, rolling the dice starts to be less of a choice, and more of a habit.</p>
<p>When some­one chooses to use soft­ware not be­cause it’s gen­uinely help­ful, but be­cause it feels good to roll the dice, a sense of agency is lost.</p>
<p>In my ex­pe­ri­ence, Microsoft Word is like this, as are most agen­tic cod­ing tools and im­age gen­er­a­tors.</p>
<h3>It Distracts from That Which Makes Life Meaningful</h3>
<p>When soft­ware is slow, or oth­er­wise re­moves your agency with vari­able re­ward sys­tems, it can start to chip away at your abil­ity to make rea­son­able de­ci­sions on where to spend your time.
Peo­ple who use<span class="push-double"></span> <span class="pull-double">“</span>social” me­dia sites like Facebook or Instagram can at­test to this.
They sim­ply don’t feel like they have agency over their own ac­tions any­more.
They can’t stop us­ing soft­ware they ac­tively hate.</p>
<h2>Our Goals</h2>
<p>I think that one of <a href="https://automattic.com/">Automattic’s</a> great­est strengths is that none of our soft­ware does any one of these things.
I re­mem­ber the ﬁrst time I used Pocket Casts: I was thrilled.
It was eas­ily the best pod­cast soft­ware I had ever used.
The same things hap­pened with Day One and WordPress.com.</p>
<p>As we look to build­ing Harper, I think it’s im­por­tant that we re­mem­ber these core prin­ci­ples.
Our soft­ware should be fast, pre­dictable, and sup­port our cus­tomers in ﬁnd­ing mean­ing in their lives.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Training a Small Language Model]]></title>
            <link>https://elijahpotter.dev/articles/training_a_small_language_model</link>
            <guid>https://elijahpotter.dev/articles/training_a_small_language_model</guid>
            <pubDate>Tue, 03 Feb 2026 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Training a Small Language Model</h1>
<img src="/images/blue_bird.webp" alt="A photo taken by myself of a blue bird.">
<blockquote>
<p>TL;DR: I’ve built and trained an ex­tremely small lan­guage model from scratch, specif­i­cally de­signed for short-form sen­tence rewrit­ing tasks that are highly re­source con­strained.</p>
</blockquote>
<p>In <a href="./finding_the_active_voice">my last post</a>, I dis­cussed how I plan to tackle the dif­ﬁ­cult prob­lem of help­ing <a href="https://writewithharper.com">Harper’s</a> users take ad­van­tage of the ac­tive voice.
The ac­tive voice is seen as more con­ﬁ­dent and au­thor­i­ta­tive, which is of­ten de­sired in pro­fes­sions like cus­tomer ser­vice.
I have ex­pe­ri­ence in that area, which is how I know the ex­tent to which help with the ac­tive voice can make the job eas­ier.</p>
<p>Naturally, we would only want to pro­vide help with the ac­tive voice when specif­i­cally re­quested by the user.
That means it’s an opt-in fea­ture that is­n’t en­abled by de­fault.
If we suc­ceed in im­ple­ment­ing the func­tion­al­ity, and peo­ple seem to like it, the Harper pro­ject can re­con­sider this stance.
For now, the plan is for it to be dis­abled by de­fault.</p>
<p>As I it­er­ated in my last post, the state-of-the-art method for con­vert­ing from the pas­sive voice to the ac­tive voice is to use an au­tore­gres­sive trans­former model.
This is the same fun­da­men­tal tech­nol­ogy be­hind apps like Google Translate and ChatGPT.
While the ac­tual model I in­tend use for this fea­ture is sim­i­lar in na­ture to those used in these apps, my im­ple­men­ta­tion will be en­tirely dif­fer­ent in scale.</p>
<p>You see, since our do­main is so lim­ited in scope, we can use an ex­tremely small model.
Google’s small­est <span class="small-caps">T5</span> model is a great point of ref­er­ence.
A quan­tized copy of that model runs about 60 megabytes.
I be­lieve we can train our own trans­former model from scratch and pack it in an un­quan­tized con­tainer mea­sur­ing no larger than 30 megabytes.</p>
<p>Why do I be­lieve this to be pos­si­ble?
Be­cause I’ve done it.</p>
<h2>Our Model Architecture</h2>
<p>Over the past few days, I’ve im­ple­mented a ba­sic lan­guage model in Rust us­ing Burn.
For the cu­ri­ous, the code is <a href="https://github.com/elijah-potter/lm">open source</a>.
It’s pretty messy, so please don’t look at the com­mit his­tory.
Once I have a solid proof-of-con­cept work­ing, I’ll pol­ish it up and ﬁnd a good home for it in the main Harper repos­i­tory.</p>
<p>The key here is that our use-case only needs a few hun­dred char­ac­ters of con­text.
Since the com­pu­ta­tional cost of an <span class="small-caps">LLM</span> can be ap­prox­i­mated as the square of the con­text size, we can train highly ef­fec­tive mod­els our­selves that are small and can run any­where.
If we want to pro­vide ad­di­tional in­for­ma­tion to the model, such as style or do­main, we can sup­ple­ment this con­text with cus­tom em­bed­dings.</p>
<p>Using a small model al­lows us to main­tain our pri­vacy guar­an­tees with­out sig­nif­i­cant cap­i­tal ex­pense.
In fact, once we’ve trained the mod­els, they’re free to run in­def­i­nitely.</p>
<p>I’ve cho­sen to use a char­ac­ter-level to­k­enizer.
That means that each char­ac­ter is its own to­ken, and the con­text size of the model is equiv­a­lent to the num­ber of char­ac­ters it can read at once.
The hope here is that it al­low us to min­i­mize Harper’s ﬁ­nal bi­nary size and give the model greater free­dom to change the in­ﬂec­tion of words how­ever it pleases.</p>
<p>For now, here are some sim­ple sen­tences gen­er­ated with the ba­sic model I trained for just un­der an hour.
It’s pretty amaz­ing what can be done with just un­der a dol­lar’s worth of com­put­ing power.</p>
<pre><code class="hljs language-plaintext">The forest rested in a speed of the night.
</code></pre>
<p>Feels&hellip; in­tro­spec­tive, no?</p>
<pre><code class="hljs language-plaintext">It was the today of the world.
</code></pre>
<h2>Moving Forward</h2>
<p>My fo­cus this week was to get a ba­sic au­tore­gres­sive model trained on generic English text.
My fo­cus start­ing next week will be to ﬁne-tune this model for our sen­tence rewrit­ing task.
Once we have a very solid base model, I see no rea­son we could­n’t ﬁne-tune sim­i­lar mod­els for other fea­tures, like chang­ing tone or im­prov­ing clar­ity.</p>
<p>My cur­rent im­ple­men­ta­tion, while fast, could also use some im­prove­ment.
To that end, I’d also like to im­ple­ment a <span class="small-caps">KV</span> cache.
That will al­low us to scale up to larger con­text sizes with­out a drop in qual­ity or speed.</p>
<p>P.S. Today is my birth­day! I’m so grate­ful for the years I’ve been on this earth, and I can’t wait for more. Thanks for read­ing! Here’s to 21.</p>]]></description>
            <content:encoded><![CDATA[<h1>Training a Small Language Model</h1>
<img src="/images/blue_bird.webp" alt="A photo taken by myself of a blue bird.">
<blockquote>
<p>TL;DR: I’ve built and trained an ex­tremely small lan­guage model from scratch, specif­i­cally de­signed for short-form sen­tence rewrit­ing tasks that are highly re­source con­strained.</p>
</blockquote>
<p>In <a href="./finding_the_active_voice">my last post</a>, I dis­cussed how I plan to tackle the dif­ﬁ­cult prob­lem of help­ing <a href="https://writewithharper.com">Harper’s</a> users take ad­van­tage of the ac­tive voice.
The ac­tive voice is seen as more con­ﬁ­dent and au­thor­i­ta­tive, which is of­ten de­sired in pro­fes­sions like cus­tomer ser­vice.
I have ex­pe­ri­ence in that area, which is how I know the ex­tent to which help with the ac­tive voice can make the job eas­ier.</p>
<p>Naturally, we would only want to pro­vide help with the ac­tive voice when specif­i­cally re­quested by the user.
That means it’s an opt-in fea­ture that is­n’t en­abled by de­fault.
If we suc­ceed in im­ple­ment­ing the func­tion­al­ity, and peo­ple seem to like it, the Harper pro­ject can re­con­sider this stance.
For now, the plan is for it to be dis­abled by de­fault.</p>
<p>As I it­er­ated in my last post, the state-of-the-art method for con­vert­ing from the pas­sive voice to the ac­tive voice is to use an au­tore­gres­sive trans­former model.
This is the same fun­da­men­tal tech­nol­ogy be­hind apps like Google Translate and ChatGPT.
While the ac­tual model I in­tend use for this fea­ture is sim­i­lar in na­ture to those used in these apps, my im­ple­men­ta­tion will be en­tirely dif­fer­ent in scale.</p>
<p>You see, since our do­main is so lim­ited in scope, we can use an ex­tremely small model.
Google’s small­est <span class="small-caps">T5</span> model is a great point of ref­er­ence.
A quan­tized copy of that model runs about 60 megabytes.
I be­lieve we can train our own trans­former model from scratch and pack it in an un­quan­tized con­tainer mea­sur­ing no larger than 30 megabytes.</p>
<p>Why do I be­lieve this to be pos­si­ble?
Be­cause I’ve done it.</p>
<h2>Our Model Architecture</h2>
<p>Over the past few days, I’ve im­ple­mented a ba­sic lan­guage model in Rust us­ing Burn.
For the cu­ri­ous, the code is <a href="https://github.com/elijah-potter/lm">open source</a>.
It’s pretty messy, so please don’t look at the com­mit his­tory.
Once I have a solid proof-of-con­cept work­ing, I’ll pol­ish it up and ﬁnd a good home for it in the main Harper repos­i­tory.</p>
<p>The key here is that our use-case only needs a few hun­dred char­ac­ters of con­text.
Since the com­pu­ta­tional cost of an <span class="small-caps">LLM</span> can be ap­prox­i­mated as the square of the con­text size, we can train highly ef­fec­tive mod­els our­selves that are small and can run any­where.
If we want to pro­vide ad­di­tional in­for­ma­tion to the model, such as style or do­main, we can sup­ple­ment this con­text with cus­tom em­bed­dings.</p>
<p>Using a small model al­lows us to main­tain our pri­vacy guar­an­tees with­out sig­nif­i­cant cap­i­tal ex­pense.
In fact, once we’ve trained the mod­els, they’re free to run in­def­i­nitely.</p>
<p>I’ve cho­sen to use a char­ac­ter-level to­k­enizer.
That means that each char­ac­ter is its own to­ken, and the con­text size of the model is equiv­a­lent to the num­ber of char­ac­ters it can read at once.
The hope here is that it al­low us to min­i­mize Harper’s ﬁ­nal bi­nary size and give the model greater free­dom to change the in­ﬂec­tion of words how­ever it pleases.</p>
<p>For now, here are some sim­ple sen­tences gen­er­ated with the ba­sic model I trained for just un­der an hour.
It’s pretty amaz­ing what can be done with just un­der a dol­lar’s worth of com­put­ing power.</p>
<pre><code class="hljs language-plaintext">The forest rested in a speed of the night.
</code></pre>
<p>Feels&hellip; in­tro­spec­tive, no?</p>
<pre><code class="hljs language-plaintext">It was the today of the world.
</code></pre>
<h2>Moving Forward</h2>
<p>My fo­cus this week was to get a ba­sic au­tore­gres­sive model trained on generic English text.
My fo­cus start­ing next week will be to ﬁne-tune this model for our sen­tence rewrit­ing task.
Once we have a very solid base model, I see no rea­son we could­n’t ﬁne-tune sim­i­lar mod­els for other fea­tures, like chang­ing tone or im­prov­ing clar­ity.</p>
<p>My cur­rent im­ple­men­ta­tion, while fast, could also use some im­prove­ment.
To that end, I’d also like to im­ple­ment a <span class="small-caps">KV</span> cache.
That will al­low us to scale up to larger con­text sizes with­out a drop in qual­ity or speed.</p>
<p>P.S. Today is my birth­day! I’m so grate­ful for the years I’ve been on this earth, and I can’t wait for more. Thanks for read­ing! Here’s to 21.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/blue_bird.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Writing in Visual Studio Code]]></title>
            <link>https://elijahpotter.dev/articles/writing_in_visual_studio_code</link>
            <guid>https://elijahpotter.dev/articles/writing_in_visual_studio_code</guid>
            <pubDate>Tue, 27 Jan 2026 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Writing in Visual Studio Code</h1>
<p>I <a href="./my_writing_environment_as_a_software_engineer">write this blog in Neovim</a>, and I’m aware of a good num­ber of other nerds who do the same.
I sup­pose I never con­sid­ered that peo­ple might use other code ed­i­tors for writ­ing prose.
In fact, it seems that there are a non­triv­ial num­ber of peo­ple us­ing <a href="https://chriswiegman.com/2026/01/harper-my-new-grammar-checker/">Harper in Visual Studio Code</a>.
They seem to be us­ing Harper mostly for Markdown and com­ments in code, but I’d love to get <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX</mtext></mrow><annotation encoding="application/x-tex">\LaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span> work­ing as well.</p>]]></description>
            <content:encoded><![CDATA[<h1>Writing in Visual Studio Code</h1>
<p>I <a href="./my_writing_environment_as_a_software_engineer">write this blog in Neovim</a>, and I’m aware of a good num­ber of other nerds who do the same.
I sup­pose I never con­sid­ered that peo­ple might use other code ed­i­tors for writ­ing prose.
In fact, it seems that there are a non­triv­ial num­ber of peo­ple us­ing <a href="https://chriswiegman.com/2026/01/harper-my-new-grammar-checker/">Harper in Visual Studio Code</a>.
They seem to be us­ing Harper mostly for Markdown and com­ments in code, but I’d love to get <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX</mtext></mrow><annotation encoding="application/x-tex">\LaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span> work­ing as well.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Finding the Active Voice]]></title>
            <link>https://elijahpotter.dev/articles/finding_the_active_voice</link>
            <guid>https://elijahpotter.dev/articles/finding_the_active_voice</guid>
            <pubDate>Mon, 26 Jan 2026 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Finding the Active Voice</h1>
<p>Last week, Harper hit a stroke of luck.
It was fea­tured on <a href="https://www.makeuseof.com/stopped-paying-for-grammarly-once-found-free-open-source-alternative/">MakeUseOf</a>.
The down­stream so­cial me­dia posts col­lec­tively gar­nered nearly 300,000 views and drove a sig­nif­i­cant amount of traf­ﬁc to our site.
This all hap­pened in the wake of LanguageTool shut­ting down the free edi­tion of their soft­ware.
These two events com­pound to bring more at­ten­tion than ever to Harper — which is amaz­ing.
It means our mis­sion and val­ues are res­onat­ing with peo­ple, which is al­ways a good thing.</p>
<p>Reading the <span class="small-caps">MUO</span> ar­ti­cle, we hear a lot of great things about Harper.
We also hear that<span class="push-double"></span> <span class="pull-double">“</span>it skips many of the pre­mium bells and whis­tles of Grammarly”.
The au­thor goes on to ex­plain that the pri­vacy Harper pro­vides more than makes up for any miss­ing pre­mium fea­tures, but the point is clear: those fea­tures are deeply de­sired.</p>
<p>So, the ques­tion be­comes: Which of Grammarly’s fea­tures should we work on ﬁrst, and how?</p>
<h2>The Active Voice</h2>
<p>I pro­pose that we should fo­cus ﬁrst on help­ing our users ﬁnd <a href="https://en.wikipedia.org/wiki/Active_voice">the ac­tive voice.</a>
For con­text, the ac­tive voice is the style of writ­ing where sub­ject of the verb in a clause is the doer of the ac­tion.
This is in con­trast to the pas­sive voice, which is where the sub­ject is the re­ceiver of the ac­tion.
For ex­am­ple: the sen­tence,<span class="push-double"></span> <span class="pull-double">“</span>The postal car­rier was bit­ten by the dog” is writ­ten in the pas­sive voice, while the equiv­a­lent sen­tence,<span class="push-double"></span> <span class="pull-double">“</span>The dog bit the postal car­rier.” is writ­ten in the ac­tive voice.</p>
<p>Text writ­ten in the ac­tive voice is com­monly viewed to be more au­thor­i­ta­tive, con­ﬁ­dent, and eas­ier to un­der­stand.
Be­ing able to help users use the ac­tive voice is one of the most com­monly re­quested fea­tures in Harper, and in­clud­ing the fea­ture would be a huge step to­wards com­pet­ing di­rectly with Grammarly Premium.</p>
<p>How should we go about help­ing our users with their ac­tive voice?</p>
<h3>How It Could Be Done</h3>
<p>I spoke brieﬂy with <a href="https://ma.tt/">Matt</a>, and we agreed that a two tier so­lu­tion would be best.
A fast al­go­rithm or model would <em>de­tect</em> in­stances of the pas­sive voice, let­ting a larger more com­pu­ta­tion­ally ex­pen­sive model gen­er­ate a mod­i­ﬁ­ca­tion in the ac­tive voice.</p>
<p>Fortunately, there is al­ready ex­ten­sive lit­er­a­ture on the de­tec­tion of the pas­sive voice.
In par­tic­u­lar, I found the <a href="https://psycnet.apa.org/record/2024-13824-002">PassivePy pa­per</a> stim­u­lat­ing.
In fact, we can im­ple­ment their ideas quite eas­ily us­ing the <a href="https://writewithharper.com/docs/weir">Weir lan­guage</a> al­ready baked into Harper.
I have done so in a pri­vate branch. It turned out to be ~20 lines of code.
That is pretty good bang for the buck!</p>
<p>The sec­ond piece, which has to do with the ac­tual sim­pli­ﬁ­ca­tion of text and con­ver­sion from the pas­sive voice to the ac­tive voice is a tad more com­plex.</p>
<p>Matt and I agree that it will re­quire the use of a larger lan­guage model.
The trou­ble is that it can­not be <strong>too</strong> large.
Harper’s shtick is that it is fast, pri­vate, and that every­thing runs di­rectly on our user’s de­vices.
That means whichever model we use for our style trans­fer will need to be rel­a­tively small.</p>
<p>I be­lieve the best so­lu­tion to this prob­lem is to take an off-the-shelf model, like one of Google’s <span class="small-caps">T5</span> mod­els, and ﬁne tune it for the spe­ciﬁc types of style trans­fer we need.
These are rel­a­tively small mod­els (quantized, they can ﬁt into spaces un­der 65 megabytes) and they run quite quickly, even on older hard­ware that does­n’t have ac­cess to ma­trix mul­ti­pli­ca­tion ac­cel­er­a­tors.
There is prior art for run­ning this at 50 tok/​s in Chrome <em>with­out WebGPU</em> on a sin­gle core.
The best part is that they’re un­der the Apache-2.0 li­cense!</p>
<h2>How This Fits in with the Weirpack Project</h2>
<p>These mod­els are small, but they’re not quite small enough to be a part of the stan­dard dis­tri­b­u­tion of Harper.
I be­lieve this should be an opt-in fea­ture, and the best way to do that is to ex­pose the func­tion­al­ity via a Weirpack.
If you don’t know what a Weirpack is, I highly sug­gest you read my <a href="https://elijahpotter.dev/articles/imagine-a-weir-marketplace">pre­vi­ous</a> <a href="https://elijahpotter.dev/articles/imagine-a-weir-studio">blog posts</a> on the sub­ject.</p>
<p>Everyone who wants this ad­di­tional func­tion­al­ity could just en­able it in the mar­ket­place.
This con­tin­ues our goal to make Harper as cus­tomiz­able as our users want, while pro­vid­ing sen­si­ble de­faults.</p>
<h2>What’s Next?</h2>
<p>Once we have the sys­tem in place to de­tect and pro­vide sug­ges­tions for the ac­tive voice, we will be pre­pared to do other kinds of trans­for­ma­tion, like for ad­just­ing for­mal­ity or tone.</p>
<p>I’m re­ally ex­cited about this pro­ject, and I can’t wait to get started.</p>]]></description>
            <content:encoded><![CDATA[<h1>Finding the Active Voice</h1>
<p>Last week, Harper hit a stroke of luck.
It was fea­tured on <a href="https://www.makeuseof.com/stopped-paying-for-grammarly-once-found-free-open-source-alternative/">MakeUseOf</a>.
The down­stream so­cial me­dia posts col­lec­tively gar­nered nearly 300,000 views and drove a sig­nif­i­cant amount of traf­ﬁc to our site.
This all hap­pened in the wake of LanguageTool shut­ting down the free edi­tion of their soft­ware.
These two events com­pound to bring more at­ten­tion than ever to Harper — which is amaz­ing.
It means our mis­sion and val­ues are res­onat­ing with peo­ple, which is al­ways a good thing.</p>
<p>Reading the <span class="small-caps">MUO</span> ar­ti­cle, we hear a lot of great things about Harper.
We also hear that<span class="push-double"></span> <span class="pull-double">“</span>it skips many of the pre­mium bells and whis­tles of Grammarly”.
The au­thor goes on to ex­plain that the pri­vacy Harper pro­vides more than makes up for any miss­ing pre­mium fea­tures, but the point is clear: those fea­tures are deeply de­sired.</p>
<p>So, the ques­tion be­comes: Which of Grammarly’s fea­tures should we work on ﬁrst, and how?</p>
<h2>The Active Voice</h2>
<p>I pro­pose that we should fo­cus ﬁrst on help­ing our users ﬁnd <a href="https://en.wikipedia.org/wiki/Active_voice">the ac­tive voice.</a>
For con­text, the ac­tive voice is the style of writ­ing where sub­ject of the verb in a clause is the doer of the ac­tion.
This is in con­trast to the pas­sive voice, which is where the sub­ject is the re­ceiver of the ac­tion.
For ex­am­ple: the sen­tence,<span class="push-double"></span> <span class="pull-double">“</span>The postal car­rier was bit­ten by the dog” is writ­ten in the pas­sive voice, while the equiv­a­lent sen­tence,<span class="push-double"></span> <span class="pull-double">“</span>The dog bit the postal car­rier.” is writ­ten in the ac­tive voice.</p>
<p>Text writ­ten in the ac­tive voice is com­monly viewed to be more au­thor­i­ta­tive, con­ﬁ­dent, and eas­ier to un­der­stand.
Be­ing able to help users use the ac­tive voice is one of the most com­monly re­quested fea­tures in Harper, and in­clud­ing the fea­ture would be a huge step to­wards com­pet­ing di­rectly with Grammarly Premium.</p>
<p>How should we go about help­ing our users with their ac­tive voice?</p>
<h3>How It Could Be Done</h3>
<p>I spoke brieﬂy with <a href="https://ma.tt/">Matt</a>, and we agreed that a two tier so­lu­tion would be best.
A fast al­go­rithm or model would <em>de­tect</em> in­stances of the pas­sive voice, let­ting a larger more com­pu­ta­tion­ally ex­pen­sive model gen­er­ate a mod­i­ﬁ­ca­tion in the ac­tive voice.</p>
<p>Fortunately, there is al­ready ex­ten­sive lit­er­a­ture on the de­tec­tion of the pas­sive voice.
In par­tic­u­lar, I found the <a href="https://psycnet.apa.org/record/2024-13824-002">PassivePy pa­per</a> stim­u­lat­ing.
In fact, we can im­ple­ment their ideas quite eas­ily us­ing the <a href="https://writewithharper.com/docs/weir">Weir lan­guage</a> al­ready baked into Harper.
I have done so in a pri­vate branch. It turned out to be ~20 lines of code.
That is pretty good bang for the buck!</p>
<p>The sec­ond piece, which has to do with the ac­tual sim­pli­ﬁ­ca­tion of text and con­ver­sion from the pas­sive voice to the ac­tive voice is a tad more com­plex.</p>
<p>Matt and I agree that it will re­quire the use of a larger lan­guage model.
The trou­ble is that it can­not be <strong>too</strong> large.
Harper’s shtick is that it is fast, pri­vate, and that every­thing runs di­rectly on our user’s de­vices.
That means whichever model we use for our style trans­fer will need to be rel­a­tively small.</p>
<p>I be­lieve the best so­lu­tion to this prob­lem is to take an off-the-shelf model, like one of Google’s <span class="small-caps">T5</span> mod­els, and ﬁne tune it for the spe­ciﬁc types of style trans­fer we need.
These are rel­a­tively small mod­els (quantized, they can ﬁt into spaces un­der 65 megabytes) and they run quite quickly, even on older hard­ware that does­n’t have ac­cess to ma­trix mul­ti­pli­ca­tion ac­cel­er­a­tors.
There is prior art for run­ning this at 50 tok/​s in Chrome <em>with­out WebGPU</em> on a sin­gle core.
The best part is that they’re un­der the Apache-2.0 li­cense!</p>
<h2>How This Fits in with the Weirpack Project</h2>
<p>These mod­els are small, but they’re not quite small enough to be a part of the stan­dard dis­tri­b­u­tion of Harper.
I be­lieve this should be an opt-in fea­ture, and the best way to do that is to ex­pose the func­tion­al­ity via a Weirpack.
If you don’t know what a Weirpack is, I highly sug­gest you read my <a href="https://elijahpotter.dev/articles/imagine-a-weir-marketplace">pre­vi­ous</a> <a href="https://elijahpotter.dev/articles/imagine-a-weir-studio">blog posts</a> on the sub­ject.</p>
<p>Everyone who wants this ad­di­tional func­tion­al­ity could just en­able it in the mar­ket­place.
This con­tin­ues our goal to make Harper as cus­tomiz­able as our users want, while pro­vid­ing sen­si­ble de­faults.</p>
<h2>What’s Next?</h2>
<p>Once we have the sys­tem in place to de­tect and pro­vide sug­ges­tions for the ac­tive voice, we will be pre­pared to do other kinds of trans­for­ma­tion, like for ad­just­ing for­mal­ity or tone.</p>
<p>I’m re­ally ex­cited about this pro­ject, and I can’t wait to get started.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Linkarzu: Harper Is Getting Better]]></title>
            <link>https://elijahpotter.dev/articles/linkarzu:_harper_is_getting_better</link>
            <guid>https://elijahpotter.dev/articles/linkarzu:_harper_is_getting_better</guid>
            <pubDate>Fri, 23 Jan 2026 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Linkarzu: Harper Is Getting Better</h1>
<p>Linkarzu’s re­cent video about Harper is a great primer on what it can do if you spend a ton of time in Neovim (like I do). It’s true: Harper is get­ting bet­ter.</p>
<iframe width="100%" height="400" src="https://www.youtube.com/embed/he8V6gj9IG0?si=mbw6q8m3jqi1qhSf" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>]]></description>
            <content:encoded><![CDATA[<h1>Linkarzu: Harper Is Getting Better</h1>
<p>Linkarzu’s re­cent video about Harper is a great primer on what it can do if you spend a ton of time in Neovim (like I do). It’s true: Harper is get­ting bet­ter.</p>
<iframe width="100%" height="400" src="https://www.youtube.com/embed/he8V6gj9IG0?si=mbw6q8m3jqi1qhSf" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Imagine a Weir Studio]]></title>
            <link>https://elijahpotter.dev/articles/imagine_a_weir_studio</link>
            <guid>https://elijahpotter.dev/articles/imagine_a_weir_studio</guid>
            <pubDate>Fri, 16 Jan 2026 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Imagine a Weir Studio</h1>
<p>This week, I re­ceived a mes­sage from some­one work­ing on a learn­ing man­age­ment sys­tem.
Ap­par­ently, they use <a href="https://www.npmjs.com/package/harper.js"><code>harper.js</code></a> to do spell and gram­mar check­ing on in­ter­nal doc­u­ments.
They wanted to be able to con­ﬁg­ure <a href="https://writewithharper.com">Harper</a> to catch a spe­ciﬁc class of mis­takes that are com­mon to their work­ﬂow.
Look­ing through the doc­u­men­ta­tion for <code>harper.js</code>, they could­n’t see a way to do what they wanted.</p>
<p>Naturally, I di­rected them to­ward my re­cent work on Weir, an ex­pres­sive pro­gram­ming lan­guage for lo­cat­ing er­rors in nat­ural lan­guage.
For those just catch­ing up, this post is a part of a se­ries.
You should­n’t need to go back and read my pre­vi­ous posts, but I’ll link them here any­how.</p>
<ul>
<li><a href="./building_the_weir_language">Building The Weir Language</a></li>
<li><a href="./updates_on_the_weir_language">Updates on the Weir Language</a></li>
<li><a href="./generating_weir_code_with_LLMs">Generating Weir Code With LLMs</a></li>
<li><a href="./imagine_a_weir_marketplace">Imagine A Weir Marketplace</a></li>
</ul>
<p>Unfortunately, I had to in­form them that Weir is still an ex­per­i­ment (although it’s in the <code>master branch</code> and thus used by <code>nightly</code> users) and there­fore is­n’t yet avail­able through pub­lic chan­nels.
That said, it’s an ex­per­i­ment I’d like to make avail­able to the pub­lic soon, along­side the Weir Marketplace.
Before I do so, I want to make it as easy as pos­si­ble to au­thor rules us­ing Weir.
That’s why I’m in­tro­duc­ing the Weir Studio.</p>
<h2>The Weir Studio</h2>
<p>The Weir Studio comes from a pretty sim­ple idea. We want to give peo­ple all the tool­ing nec­es­sary to build and dis­trib­ute Weir rules with­out any up­front cost.
That means there should be no tools that need to be in­stalled, no spe­ciﬁc op­er­at­ing sys­tem set up, and no pro­gram­ming ex­pe­ri­ence needed.</p>
<img src="/images/weir_studio_draft.png" alt="The Initial Version of the Weir Studio">
<p>The so­lu­tions to the ﬁrst and sec­ond items are sim­ple:
Just like any other com­po­nent of Harper, we can com­pile Weir to WebAssembly and con­nect it up to a web ap­pli­ca­tion.
Us­ing off-the-shelf com­po­nents for ter­mi­nal em­u­la­tion, text edit­ing, and vir­tual ﬁlesys­tems, we can pre­sent a mean­ing­fully fea­ture-rich de­vel­op­ment en­vi­ron­ment from in­side the user’s browser.</p>
<p>The last item is also straight­for­ward.
I’ve <a href="./generating_weir_code_with_LLMs">demon­strated that LLMs are al­ready ter­ri­bly ef­fec­tive at writ­ing Weir rules</a>.
By mak­ing that sit­u­a­tion ob­vi­ous and ac­ces­si­ble, we can em­power peo­ple to write and dis­trib­ute their own Weirpacks (ﬁles con­tain­ing one or more Weir rules) with­out any prior un­der­stand­ing of the lan­guage or even of ba­sic pro­gram­ming prin­ci­ples.</p>
<p>The Weir Studio is <a href="https://github.com/Automattic/harper/pull/2491">still in progress</a>, but the ini­tial ver­sions are al­ready prov­ing ex­tremely ef­fec­tive for <a href="https://github.com/Automattic/harper/pull/2506">some of my other work.</a></p>
<h2>Putting Power Back into the Hands of Users</h2>
<p>A com­mon frus­tra­tion with tools like Grammarly or QuillBot is that they lack cus­tomiza­tion, and thus are of­ten un­able to di­rectly ad­dress user’s needs.
By al­low­ing users full ac­cess to the in­ter­nals via Weir, we can help them help them­selves while also en­cour­ag­ing peo­ple to con­tribute back to the pro­ject.
It’s a win-win!</p>
<h2>What’s Next?</h2>
<p>I’d like to wire this up to a mar­ket­place as soon as pos­si­ble (see what I did there?) to stream­line the pub­lish­ing process.
More crit­i­cally, I’ve been in­formed that the in­clu­sion of dic­tio­nary ﬁles di­rectly into Weirpacks should be pri­or­ity num­ber one.
I’ll likely start tack­ling that as soon as I have a spare mo­ment.</p>
<p>If you have thoughts on where Harper or Weir is go­ing, please don’t hes­i­tate to voice them.</p>
<hr>
<p>P.S. <a href="https://www.makeuseof.com/stopped-paying-for-grammarly-once-found-free-open-source-alternative/">A great piece was just pub­lished about Harper.</a></p>]]></description>
            <content:encoded><![CDATA[<h1>Imagine a Weir Studio</h1>
<p>This week, I re­ceived a mes­sage from some­one work­ing on a learn­ing man­age­ment sys­tem.
Ap­par­ently, they use <a href="https://www.npmjs.com/package/harper.js"><code>harper.js</code></a> to do spell and gram­mar check­ing on in­ter­nal doc­u­ments.
They wanted to be able to con­ﬁg­ure <a href="https://writewithharper.com">Harper</a> to catch a spe­ciﬁc class of mis­takes that are com­mon to their work­ﬂow.
Look­ing through the doc­u­men­ta­tion for <code>harper.js</code>, they could­n’t see a way to do what they wanted.</p>
<p>Naturally, I di­rected them to­ward my re­cent work on Weir, an ex­pres­sive pro­gram­ming lan­guage for lo­cat­ing er­rors in nat­ural lan­guage.
For those just catch­ing up, this post is a part of a se­ries.
You should­n’t need to go back and read my pre­vi­ous posts, but I’ll link them here any­how.</p>
<ul>
<li><a href="./building_the_weir_language">Building The Weir Language</a></li>
<li><a href="./updates_on_the_weir_language">Updates on the Weir Language</a></li>
<li><a href="./generating_weir_code_with_LLMs">Generating Weir Code With LLMs</a></li>
<li><a href="./imagine_a_weir_marketplace">Imagine A Weir Marketplace</a></li>
</ul>
<p>Unfortunately, I had to in­form them that Weir is still an ex­per­i­ment (although it’s in the <code>master branch</code> and thus used by <code>nightly</code> users) and there­fore is­n’t yet avail­able through pub­lic chan­nels.
That said, it’s an ex­per­i­ment I’d like to make avail­able to the pub­lic soon, along­side the Weir Marketplace.
Before I do so, I want to make it as easy as pos­si­ble to au­thor rules us­ing Weir.
That’s why I’m in­tro­duc­ing the Weir Studio.</p>
<h2>The Weir Studio</h2>
<p>The Weir Studio comes from a pretty sim­ple idea. We want to give peo­ple all the tool­ing nec­es­sary to build and dis­trib­ute Weir rules with­out any up­front cost.
That means there should be no tools that need to be in­stalled, no spe­ciﬁc op­er­at­ing sys­tem set up, and no pro­gram­ming ex­pe­ri­ence needed.</p>
<img src="/images/weir_studio_draft.png" alt="The Initial Version of the Weir Studio">
<p>The so­lu­tions to the ﬁrst and sec­ond items are sim­ple:
Just like any other com­po­nent of Harper, we can com­pile Weir to WebAssembly and con­nect it up to a web ap­pli­ca­tion.
Us­ing off-the-shelf com­po­nents for ter­mi­nal em­u­la­tion, text edit­ing, and vir­tual ﬁlesys­tems, we can pre­sent a mean­ing­fully fea­ture-rich de­vel­op­ment en­vi­ron­ment from in­side the user’s browser.</p>
<p>The last item is also straight­for­ward.
I’ve <a href="./generating_weir_code_with_LLMs">demon­strated that LLMs are al­ready ter­ri­bly ef­fec­tive at writ­ing Weir rules</a>.
By mak­ing that sit­u­a­tion ob­vi­ous and ac­ces­si­ble, we can em­power peo­ple to write and dis­trib­ute their own Weirpacks (ﬁles con­tain­ing one or more Weir rules) with­out any prior un­der­stand­ing of the lan­guage or even of ba­sic pro­gram­ming prin­ci­ples.</p>
<p>The Weir Studio is <a href="https://github.com/Automattic/harper/pull/2491">still in progress</a>, but the ini­tial ver­sions are al­ready prov­ing ex­tremely ef­fec­tive for <a href="https://github.com/Automattic/harper/pull/2506">some of my other work.</a></p>
<h2>Putting Power Back into the Hands of Users</h2>
<p>A com­mon frus­tra­tion with tools like Grammarly or QuillBot is that they lack cus­tomiza­tion, and thus are of­ten un­able to di­rectly ad­dress user’s needs.
By al­low­ing users full ac­cess to the in­ter­nals via Weir, we can help them help them­selves while also en­cour­ag­ing peo­ple to con­tribute back to the pro­ject.
It’s a win-win!</p>
<h2>What’s Next?</h2>
<p>I’d like to wire this up to a mar­ket­place as soon as pos­si­ble (see what I did there?) to stream­line the pub­lish­ing process.
More crit­i­cally, I’ve been in­formed that the in­clu­sion of dic­tio­nary ﬁles di­rectly into Weirpacks should be pri­or­ity num­ber one.
I’ll likely start tack­ling that as soon as I have a spare mo­ment.</p>
<p>If you have thoughts on where Harper or Weir is go­ing, please don’t hes­i­tate to voice them.</p>
<hr>
<p>P.S. <a href="https://www.makeuseof.com/stopped-paying-for-grammarly-once-found-free-open-source-alternative/">A great piece was just pub­lished about Harper.</a></p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/weir_studio_draft.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Imagine a Weir Marketplace]]></title>
            <link>https://elijahpotter.dev/articles/imagine_a_weir_marketplace</link>
            <guid>https://elijahpotter.dev/articles/imagine_a_weir_marketplace</guid>
            <pubDate>Fri, 09 Jan 2026 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Imagine a Weir Marketplace</h1>
<blockquote>
<p>For con­text, I main­tain <a href="https://writewithharper.com/">an open-source gram­mar checker by the name of Harper.</a>
This post is a kind of <span class="small-caps">RFC</span> for a po­ten­tial im­prove­ment to the pro­jec­t’s er­gonom­ics.</p>
</blockquote>
<p>I’ve ob­served a high de­gree of spe­cial­iza­tion of lan­guage be­tween aca­d­e­mic ﬁelds.</p>
<p>Ecologists use bits of lan­guage that com­puter sci­en­tists don’t use, and com­puter sci­en­tists use bits of lan­guage that ecol­o­gists don’t use.
If we em­brace this spe­cial­iza­tion through cus­tomiza­tion, we could gain an ad­van­tage over other gram­mar check­ers.
That’s all to say: We want to en­able au­thors and aca­d­e­mics to cus­tomize Harper to their spe­cial­ized lan­guage.
For ex­am­ple, a chemist might want to en­force a set of rules for en­sur­ing mol­e­cules are named cor­rectly ac­cord­ing to the <span class="small-caps">IUPAC</span> stan­dard, and a sci­ence ﬁc­tion au­thor might want to en­force rules to make sure spe­ciﬁc char­ac­ters are ad­dressed by the proper ti­tles.
Weir is the so­lu­tion to these prob­lems.</p>
<p>In this post, I’ll re­cap what Weir is, and out­line a mech­a­nism for dis­trib­ut­ing Weir rules to in­di­vid­ual users that might take ad­van­tage of them.
I’d love your thoughts on how we can make it as im­pact­ful and in­ter­ac­tive as pos­si­ble.</p>
<p>In <a href="./building_the_weir_language">my ini­tial post about Weir</a>, I dis­cussed how most large or­ga­ni­za­tions have style guides which de­ﬁne spe­ciﬁc rules for their em­ploy­ee’s gram­mar, cap­i­tal­iza­tion, and for­mat­ting.
I pro­posed a pro­gram­ming lan­guage by the name of Weir, which could be used to clearly state these rules to be en­forced by Harper.
I’ve per­son­ally found that LLMs are not great at en­forc­ing strict gram­mat­i­cal guide­lines, but they’re ex­cel­lent at trans­lat­ing them from an ex­ist­ing style guide into the Weir lan­guage.
See <a href="./generating_weir_code_with_LLMs">my re­cent ex­per­i­men­ta­tion for more de­tails</a>.</p>
<p>Now that Weir has been merged, we can move on to the other prob­lems: cre­ation and dis­tri­b­u­tion.
To­day, I’d like to talk about dis­tri­b­u­tion.</p>
<p>I’ve had sev­eral dis­cus­sions in the last week from peo­ple who want to be able to cus­tomize Harper with their own rules.
This in­cludes peo­ple who use Harper as a part of their <a href="https://en.wikipedia.org/wiki/Continuous_integration">con­tin­u­ous in­te­gra­tion</a>, aca­d­e­mics, and some en­gi­neers who work on learn­ing man­age­ment sys­tems.
There is a need and de­sire for this to­day, and I want to ad­dress it as soon as pos­si­ble.</p>
<h2>Distribution</h2>
<p>Once a col­lec­tion of Weir rules has been drafted, how should it be shared?
There is a for­mat­ting piece, as well as an in­fra­struc­ture piece.</p>
<h3>Weirpacks</h3>
<p>I pro­pose a sim­ple, but ef­fec­tive for­mat for shar­ing col­lec­tions of Weir rules.
In­stances of this for­mat should be called<span class="push-double"></span> <span class="pull-double">“</span>Weirpacks” and thus have the ﬁle ex­ten­sion <code>.weirpack</code>.</p>
<p>Right now, the Weir rules baked into Harper are or­ga­nized into a sin­gle di­rec­tory, <code>weir_rules</code>, with each <code>.weir</code> ﬁle pre­ﬁxed with the rule’s name.
For ex­am­ple, the ﬁle con­tain­ing the rule for ﬁx­ing <code>Their</code> to <code>There</code> is <code>TheirToTheir.weir</code>.
There are a vast quan­tity of these<span class="push-double"></span> <span class="pull-double">“</span>curated” rules, and I don’t think they should go any­where.
I just think we can reuse the lay­out.</p>
<p>I be­lieve the best strat­egy for or­ga­niz­ing col­lec­tions of Weir rules is to repli­cate what’s al­ready in the Harper repos­i­tory, but in­side a <a href="https://en.wikipedia.org/wiki/ZIP_(file_format)"><span class="small-caps">ZIP</span> archive</a>.
Even though it is a <span class="small-caps">ZIP</span> ﬁle, which nor­mally have the suf­ﬁx <code>.zip</code>, I think we should give them the <code>.weirpack</code> suf­ﬁx for clar­ity on their pur­pose.
The root di­rec­tory of the archive will con­tain as many <code>.weir</code> ﬁles as is de­sired by the Weirpack au­thor, in ad­di­tion to a <code>.json</code> man­i­fest ﬁle con­tain­ing pack­age meta­data.</p>
<h4>Metadata</h4>
<p>Since <span class="small-caps">JSON</span> is quite ﬂex­i­ble, Weirpack au­thors can in­clude what­ever in­for­ma­tion they want.
I think cer­tain ﬁelds, how­ever, should be re­quired.
Namely:</p>
<ul>
<li>The Author’s Name</li>
<li>The Weirpack’s ver­sion</li>
<li>A de­scrip­tion of the Weirpack’s pur­pose and us­age</li>
<li>The Weirpack’s li­cense</li>
</ul>
<p>I think we can also rec­om­mend, but not re­quire some ad­di­tional help­ful in­for­ma­tion:</p>
<ul>
<li>Keywords rel­e­vant to the rule’s pur­pose</li>
<li>The Author’s Website</li>
</ul>
<h4>Loading Weirpacks</h4>
<p>Support for Weirpacks will be baked di­rectly into <code>harper-core</code>, as well as <code>harper.js</code>.</p>
<p>The for­mer will make it pos­si­ble for our com­mand-line pro­grams, <code>harper-cli</code> and <code>harper-ls</code>, to load them at run­time.
In <code>harper-cli</code>, which is a de­bug­ging tool, it will be suf­ﬁ­cient to ex­pose a ﬂag on our <code>lint</code> com­mand to al­low Weirpacks to be loaded.
In <code>harper-ls</code>, we need to up­date our con­ﬁg to al­low Weirpacks to be loaded from a di­rec­tory on the disk.
The de­fault should be next to where the dic­tio­nar­ies are al­ready loaded.</p>
<p>The lat­ter will make it pos­si­ble for our web in­te­gra­tions to load them when the user de­sires, ei­ther from disk or from our mar­ket­place (more on that in a minute).
For both the Obsidian plu­gin, as well as the Chrome ex­ten­sion, we’ll in­clude a ﬁeld in the set­tings menus for users to load Weirpacks from disk and into the lin­ter.
Nat­u­rally, these will per­sist across ses­sions in the same way the other set­tings do.</p>
<p>Being able to load Weirpacks from disk is crit­i­cal to al­low­ing users to re­main com­pletely of­ﬂine if they choose to be.
Ad­di­tion­ally, load­ing cus­tom Weirpacks with­out ap­proval from a mod­er­a­tor is a pre­req­ui­site to hav­ing a quick and en­joy­able it­er­a­tive loop.</p>
<h3>The Weir Marketplace</h3>
<p>When a user in a ﬁeld with spe­cial­ized lan­guage down­loads Harper, we will nat­u­rally pro­vide them with the es­sen­tial rules that are com­mon to the en­tirety of the English lan­guage.
We will also give them the op­tion to down­load ad­di­tional Weirpacks from the Weir Marketplace.
This will be a place, not un­like Obsidian’s Community Plugins Marketplace, where user-sub­mit­ted Weirpacks can be down­loaded and in­stalled quickly and eas­ily.</p>
<img src="/images/obsidian_marketplace.png" alt="The Obsidian Community Plugins Marketplace">
<p>Imagine, if you will, that you’re a JavaScript de­vel­oper who has just down­loaded Harper.
You start writ­ing a blog post about your re­cent ad­ven­tures with the lat­est JavaScript frame­work.
Un­for­tu­nately, Harper starts ﬂag­ging swaths of your work as mis­spelled and ﬁlled with poor gram­mar.</p>
<p>Thankfully, Harper no­tices its mis­take and of­fers to down­load a rel­e­vant Weirpack for pro­gram­mers—one pre­set with a dic­tio­nary ﬁlled with tech­ni­cal lan­guage and rules to en­sure proper use.
You down­load the Weirpack and con­tinue writ­ing with Harper now bet­ter equipped to han­dle the sub­ject at hand.
Cri­sis averted.</p>
<h3>The Long View</h3>
<p>This idea of a self-con­tained Weirpack pro­vid­ing the logic and the datasets nec­es­sary to do gram­mar check­ing for a spe­cial­ized sub­set of English is in­tox­i­cat­ing.
Pro­ject­ing for­ward, I can imag­ine a fu­ture where we can pro­vide sup­port for any num­ber of nat­ural lan­guages, dis­trib­uted via Weirpacks and sup­ported by a com­mu­nity of con­trib­u­tors in a de­cen­tral­ized way, not dis­sim­i­lar to WordPress’ plu­gin mar­ket­place.
That is­n’t for to­day, though.</p>]]></description>
            <content:encoded><![CDATA[<h1>Imagine a Weir Marketplace</h1>
<blockquote>
<p>For con­text, I main­tain <a href="https://writewithharper.com/">an open-source gram­mar checker by the name of Harper.</a>
This post is a kind of <span class="small-caps">RFC</span> for a po­ten­tial im­prove­ment to the pro­jec­t’s er­gonom­ics.</p>
</blockquote>
<p>I’ve ob­served a high de­gree of spe­cial­iza­tion of lan­guage be­tween aca­d­e­mic ﬁelds.</p>
<p>Ecologists use bits of lan­guage that com­puter sci­en­tists don’t use, and com­puter sci­en­tists use bits of lan­guage that ecol­o­gists don’t use.
If we em­brace this spe­cial­iza­tion through cus­tomiza­tion, we could gain an ad­van­tage over other gram­mar check­ers.
That’s all to say: We want to en­able au­thors and aca­d­e­mics to cus­tomize Harper to their spe­cial­ized lan­guage.
For ex­am­ple, a chemist might want to en­force a set of rules for en­sur­ing mol­e­cules are named cor­rectly ac­cord­ing to the <span class="small-caps">IUPAC</span> stan­dard, and a sci­ence ﬁc­tion au­thor might want to en­force rules to make sure spe­ciﬁc char­ac­ters are ad­dressed by the proper ti­tles.
Weir is the so­lu­tion to these prob­lems.</p>
<p>In this post, I’ll re­cap what Weir is, and out­line a mech­a­nism for dis­trib­ut­ing Weir rules to in­di­vid­ual users that might take ad­van­tage of them.
I’d love your thoughts on how we can make it as im­pact­ful and in­ter­ac­tive as pos­si­ble.</p>
<p>In <a href="./building_the_weir_language">my ini­tial post about Weir</a>, I dis­cussed how most large or­ga­ni­za­tions have style guides which de­ﬁne spe­ciﬁc rules for their em­ploy­ee’s gram­mar, cap­i­tal­iza­tion, and for­mat­ting.
I pro­posed a pro­gram­ming lan­guage by the name of Weir, which could be used to clearly state these rules to be en­forced by Harper.
I’ve per­son­ally found that LLMs are not great at en­forc­ing strict gram­mat­i­cal guide­lines, but they’re ex­cel­lent at trans­lat­ing them from an ex­ist­ing style guide into the Weir lan­guage.
See <a href="./generating_weir_code_with_LLMs">my re­cent ex­per­i­men­ta­tion for more de­tails</a>.</p>
<p>Now that Weir has been merged, we can move on to the other prob­lems: cre­ation and dis­tri­b­u­tion.
To­day, I’d like to talk about dis­tri­b­u­tion.</p>
<p>I’ve had sev­eral dis­cus­sions in the last week from peo­ple who want to be able to cus­tomize Harper with their own rules.
This in­cludes peo­ple who use Harper as a part of their <a href="https://en.wikipedia.org/wiki/Continuous_integration">con­tin­u­ous in­te­gra­tion</a>, aca­d­e­mics, and some en­gi­neers who work on learn­ing man­age­ment sys­tems.
There is a need and de­sire for this to­day, and I want to ad­dress it as soon as pos­si­ble.</p>
<h2>Distribution</h2>
<p>Once a col­lec­tion of Weir rules has been drafted, how should it be shared?
There is a for­mat­ting piece, as well as an in­fra­struc­ture piece.</p>
<h3>Weirpacks</h3>
<p>I pro­pose a sim­ple, but ef­fec­tive for­mat for shar­ing col­lec­tions of Weir rules.
In­stances of this for­mat should be called<span class="push-double"></span> <span class="pull-double">“</span>Weirpacks” and thus have the ﬁle ex­ten­sion <code>.weirpack</code>.</p>
<p>Right now, the Weir rules baked into Harper are or­ga­nized into a sin­gle di­rec­tory, <code>weir_rules</code>, with each <code>.weir</code> ﬁle pre­ﬁxed with the rule’s name.
For ex­am­ple, the ﬁle con­tain­ing the rule for ﬁx­ing <code>Their</code> to <code>There</code> is <code>TheirToTheir.weir</code>.
There are a vast quan­tity of these<span class="push-double"></span> <span class="pull-double">“</span>curated” rules, and I don’t think they should go any­where.
I just think we can reuse the lay­out.</p>
<p>I be­lieve the best strat­egy for or­ga­niz­ing col­lec­tions of Weir rules is to repli­cate what’s al­ready in the Harper repos­i­tory, but in­side a <a href="https://en.wikipedia.org/wiki/ZIP_(file_format)"><span class="small-caps">ZIP</span> archive</a>.
Even though it is a <span class="small-caps">ZIP</span> ﬁle, which nor­mally have the suf­ﬁx <code>.zip</code>, I think we should give them the <code>.weirpack</code> suf­ﬁx for clar­ity on their pur­pose.
The root di­rec­tory of the archive will con­tain as many <code>.weir</code> ﬁles as is de­sired by the Weirpack au­thor, in ad­di­tion to a <code>.json</code> man­i­fest ﬁle con­tain­ing pack­age meta­data.</p>
<h4>Metadata</h4>
<p>Since <span class="small-caps">JSON</span> is quite ﬂex­i­ble, Weirpack au­thors can in­clude what­ever in­for­ma­tion they want.
I think cer­tain ﬁelds, how­ever, should be re­quired.
Namely:</p>
<ul>
<li>The Author’s Name</li>
<li>The Weirpack’s ver­sion</li>
<li>A de­scrip­tion of the Weirpack’s pur­pose and us­age</li>
<li>The Weirpack’s li­cense</li>
</ul>
<p>I think we can also rec­om­mend, but not re­quire some ad­di­tional help­ful in­for­ma­tion:</p>
<ul>
<li>Keywords rel­e­vant to the rule’s pur­pose</li>
<li>The Author’s Website</li>
</ul>
<h4>Loading Weirpacks</h4>
<p>Support for Weirpacks will be baked di­rectly into <code>harper-core</code>, as well as <code>harper.js</code>.</p>
<p>The for­mer will make it pos­si­ble for our com­mand-line pro­grams, <code>harper-cli</code> and <code>harper-ls</code>, to load them at run­time.
In <code>harper-cli</code>, which is a de­bug­ging tool, it will be suf­ﬁ­cient to ex­pose a ﬂag on our <code>lint</code> com­mand to al­low Weirpacks to be loaded.
In <code>harper-ls</code>, we need to up­date our con­ﬁg to al­low Weirpacks to be loaded from a di­rec­tory on the disk.
The de­fault should be next to where the dic­tio­nar­ies are al­ready loaded.</p>
<p>The lat­ter will make it pos­si­ble for our web in­te­gra­tions to load them when the user de­sires, ei­ther from disk or from our mar­ket­place (more on that in a minute).
For both the Obsidian plu­gin, as well as the Chrome ex­ten­sion, we’ll in­clude a ﬁeld in the set­tings menus for users to load Weirpacks from disk and into the lin­ter.
Nat­u­rally, these will per­sist across ses­sions in the same way the other set­tings do.</p>
<p>Being able to load Weirpacks from disk is crit­i­cal to al­low­ing users to re­main com­pletely of­ﬂine if they choose to be.
Ad­di­tion­ally, load­ing cus­tom Weirpacks with­out ap­proval from a mod­er­a­tor is a pre­req­ui­site to hav­ing a quick and en­joy­able it­er­a­tive loop.</p>
<h3>The Weir Marketplace</h3>
<p>When a user in a ﬁeld with spe­cial­ized lan­guage down­loads Harper, we will nat­u­rally pro­vide them with the es­sen­tial rules that are com­mon to the en­tirety of the English lan­guage.
We will also give them the op­tion to down­load ad­di­tional Weirpacks from the Weir Marketplace.
This will be a place, not un­like Obsidian’s Community Plugins Marketplace, where user-sub­mit­ted Weirpacks can be down­loaded and in­stalled quickly and eas­ily.</p>
<img src="/images/obsidian_marketplace.png" alt="The Obsidian Community Plugins Marketplace">
<p>Imagine, if you will, that you’re a JavaScript de­vel­oper who has just down­loaded Harper.
You start writ­ing a blog post about your re­cent ad­ven­tures with the lat­est JavaScript frame­work.
Un­for­tu­nately, Harper starts ﬂag­ging swaths of your work as mis­spelled and ﬁlled with poor gram­mar.</p>
<p>Thankfully, Harper no­tices its mis­take and of­fers to down­load a rel­e­vant Weirpack for pro­gram­mers—one pre­set with a dic­tio­nary ﬁlled with tech­ni­cal lan­guage and rules to en­sure proper use.
You down­load the Weirpack and con­tinue writ­ing with Harper now bet­ter equipped to han­dle the sub­ject at hand.
Cri­sis averted.</p>
<h3>The Long View</h3>
<p>This idea of a self-con­tained Weirpack pro­vid­ing the logic and the datasets nec­es­sary to do gram­mar check­ing for a spe­cial­ized sub­set of English is in­tox­i­cat­ing.
Pro­ject­ing for­ward, I can imag­ine a fu­ture where we can pro­vide sup­port for any num­ber of nat­ural lan­guages, dis­trib­uted via Weirpacks and sup­ported by a com­mu­nity of con­trib­u­tors in a de­cen­tral­ized way, not dis­sim­i­lar to WordPress’ plu­gin mar­ket­place.
That is­n’t for to­day, though.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Someone Remixed Tatum]]></title>
            <link>https://elijahpotter.dev/articles/someone_remixed_tatum</link>
            <guid>https://elijahpotter.dev/articles/someone_remixed_tatum</guid>
            <pubDate>Tue, 30 Dec 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Someone Remixed Tatum</h1>
<p>A while back, I wrote a small tool, dubbed<span class="push-double"></span> <span class="pull-double">“</span>Tatum”, for ren­der­ing Markdown to <span class="small-caps">HTML</span> for use along­side Neovim.
I’ve con­tin­ued to use it to this day, with a few tweaks here and there.</p>
<p>It looks like some­one has taken ad­van­tage of Tatum’s open-source li­cense and remixed it into some­thing even more ca­pa­ble.
I highly sug­gest you check out <a href="https://github.com/vihu/penview.nvim/"><code>penview.nvim</code></a>.</p>]]></description>
            <content:encoded><![CDATA[<h1>Someone Remixed Tatum</h1>
<p>A while back, I wrote a small tool, dubbed<span class="push-double"></span> <span class="pull-double">“</span>Tatum”, for ren­der­ing Markdown to <span class="small-caps">HTML</span> for use along­side Neovim.
I’ve con­tin­ued to use it to this day, with a few tweaks here and there.</p>
<p>It looks like some­one has taken ad­van­tage of Tatum’s open-source li­cense and remixed it into some­thing even more ca­pa­ble.
I highly sug­gest you check out <a href="https://github.com/vihu/penview.nvim/"><code>penview.nvim</code></a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Projects Using Harper]]></title>
            <link>https://elijahpotter.dev/articles/projects_using_harper</link>
            <guid>https://elijahpotter.dev/articles/projects_using_harper</guid>
            <pubDate>Tue, 23 Dec 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Some New Projects Are Using Harper</h1>
<p>I re­cently did some snoop­ing (<a href="https://github.blog/news-insights/product-news/keep-all-your-packages-up-to-date-with-dependabot/">through Dependabot</a>) on what open source pro­jects have been us­ing Harper.
Since I last checked, there are some new ones.
I won­der if the re­cent ad­di­tions have any­thing to do with <a href="./harper_turns_1.0_today">Harper go­ing <code>1.0</code></a>.</p>
<ul>
<li><a href="https://stubby.io/">Stubby</a></li>
<li><a href="https://github.com/stencila/stencila">Stencila</a></li>
<li><a href="https://github.com/gherlint/gherlint">Gherlint</a></li>
<li><a href="https://github.com/walletbeat/walletbeat">wal­let­beat</a></li>
<li><a href="https://github.com/samedwardes/fixmyspelling">ﬁxmyspelling</a></li>
</ul>
<p><a href="https://www.reddit.com/r/ObsidianMD/comments/1nfks80/oh_my_god_its_done_an_entire_book_written_in/">Someone also wrote a book us­ing Harper.</a></p>
<blockquote>
<p>This post is now out-of-date. There are even more pro­jects us­ing Harper to­day (as of 6.11.2026). The canon­i­cal list ex­ists in <a href="https://writewithharper.com/docs/about">Harper’s Documentation</a></p>
</blockquote>]]></description>
            <content:encoded><![CDATA[<h1>Some New Projects Are Using Harper</h1>
<p>I re­cently did some snoop­ing (<a href="https://github.blog/news-insights/product-news/keep-all-your-packages-up-to-date-with-dependabot/">through Dependabot</a>) on what open source pro­jects have been us­ing Harper.
Since I last checked, there are some new ones.
I won­der if the re­cent ad­di­tions have any­thing to do with <a href="./harper_turns_1.0_today">Harper go­ing <code>1.0</code></a>.</p>
<ul>
<li><a href="https://stubby.io/">Stubby</a></li>
<li><a href="https://github.com/stencila/stencila">Stencila</a></li>
<li><a href="https://github.com/gherlint/gherlint">Gherlint</a></li>
<li><a href="https://github.com/walletbeat/walletbeat">wal­let­beat</a></li>
<li><a href="https://github.com/samedwardes/fixmyspelling">ﬁxmyspelling</a></li>
</ul>
<p><a href="https://www.reddit.com/r/ObsidianMD/comments/1nfks80/oh_my_god_its_done_an_entire_book_written_in/">Someone also wrote a book us­ing Harper.</a></p>
<blockquote>
<p>This post is now out-of-date. There are even more pro­jects us­ing Harper to­day (as of 6.11.2026). The canon­i­cal list ex­ists in <a href="https://writewithharper.com/docs/about">Harper’s Documentation</a></p>
</blockquote>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Generating Weir Code with LLMs]]></title>
            <link>https://elijahpotter.dev/articles/generating_weir_code_with_LLMs</link>
            <guid>https://elijahpotter.dev/articles/generating_weir_code_with_LLMs</guid>
            <pubDate>Fri, 19 Dec 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Generating Weir Code with LLMs</h1>
<p>As you know, I’ve been work­ing on a small pro­gram­ming lan­guage called Weir for gen­er­at­ing cor­rec­tions to nat­ural lan­guage.
For the cu­ri­ous, I highly sug­gest read­ing my pre­vi­ous blog posts on the sub­ject if you haven’t al­ready.</p>
<ul>
<li><a href="./building_the_weir_language">Building the Weir Language</a></li>
<li><a href="./updates_on_the_weir_language">Updates on the Weir Language</a></li>
</ul>
<p>In one of those pre­vi­ous blog posts, I spec­u­lated that an <span class="small-caps">LLM</span> might be quite ef­fec­tive at gen­er­at­ing these rules, es­pe­cially if they are given ac­cess to tools that can val­i­date and run tests.
To­day, while writ­ing some pub­lic doc­u­men­ta­tion for the lan­guage, I did a bit of ex­per­i­men­ta­tion.
I wanted to know: Can an <span class="small-caps">LLM</span> si­mul­ta­ne­ously han­dle the com­plex chal­lenge of un­der­stand­ing a <a href="https://en.wikipedia.org/wiki/Domain-specific_language"><span class="small-caps">DSL</span><span class="push-single"></span><span class="pull-single">’</span>s</a> syn­tax, the se­man­tics of the English lan­guage, and be able to val­i­date its own work with tests?
More to the point: can it do a bet­ter job with a <span class="small-caps">DSL</span> than an es­tab­lished lan­guage like Rust?</p>
<p>For those who want to fol­low along at home, <a href="https://chatgpt.com/g/g-6945b952be148191a21d0189e0691a97-weir-writer">here’s the <span class="small-caps">GPT</span></a> I’ve been toy­ing around with.</p>
<h3>Some Experimentation</h3>
<p>I of­ten hear that one should avoid novel or ex­ceed­ingly old pro­gram­ming lan­guages when work­ing with LLMs, sim­ply be­cause these lan­guages aren’t well rep­re­sented in their train­ing data.
Based on this hearsay, I did­n’t ex­pect much go­ing into this lit­tle ex­per­i­ment of mine.</p>
<p>First, I won­dered if it could han­dle the triv­ial is­sue of cor­rect­ing<span class="push-double"></span> <span class="pull-double">“</span>as nau­seam” to the cor­rect form,<span class="push-double"></span> <span class="pull-double">“</span>ad nau­seam”.
It’s a com­mon typo, and it should be pretty sim­ple Weir rule.</p>
<p>My min­i­mal <span class="small-caps">GPT</span> spit this out:</p>
<pre><code class="hljs language-weir">expr main (as nauseam)

let message "Did you mean `ad nauseam`?"
let description "Corrects the common typo `as nauseam` to the proper Latin phrase `ad nauseam`."
let kind "Typo"
let becomes "ad nauseam"

test "He repeated the point as nauseam." "He repeated the point ad nauseam."
test "This argument has been made ad nauseam." "This argument has been made ad nauseam."
</code></pre>
<p>That looks pretty good!
All the syn­tax is cor­rect, and it even in­cluded some (passing!) tests.
Claude Code or Codex could do this us­ing our ex­ist­ing Rust in­fra­struc­ture, but it would have taken sev­eral min­utes to churn out a valid re­sult.
GPT 5.2 Instant was able to do this in a mat­ter of sec­onds.
Not to men­tion that it did­n’t need a copy of the en­tire Harper source code.</p>
<p>I wanted to try a prob­lem that was a bit harder to solve, so I asked it to ad­dress the prob­lem of dou­ble-neg­a­tives.
Af­ter think­ing for a few min­utes, it came up with four in­di­vid­ual rules that each ad­dress dif­fer­ent cases.</p>
<pre><code class="hljs language-weir">expr main &#x3C;(
  [(don't), (do not), (doesn't), (does not), (didn't), (did not),
   (can't), (cannot), (couldn't), (could not),
   (won't), (will not), (wouldn't), (would not),
   (shouldn't), (should not),
   (isn't), (is not), (aren't), (are not), (wasn't), (was not), (weren't), (were not),
   (ain't)]
  [$have, $get, $need, $want, $know, $see, $find, $say, $do]
  no
), no>

let message "Avoid a double negative."
let description "If the clause is already negated (e.g., `don't`), prefer `any` over `no` (e.g., `don't have any`)."
let kind "Usage"
let becomes "any"
let strategy "MatchCase"

test "I don't have no idea." "I don't have any idea."
test "She did not get NO response." "She did not get ANY response."
test "I have no idea." "I have no idea."
</code></pre>
<pre><code class="hljs language-weir">expr main &#x3C;(
  [(don't), (do not), (doesn't), (does not), (didn't), (did not),
   (can't), (cannot), (couldn't), (could not),
   (won't), (will not), (wouldn't), (would not),
   (shouldn't), (should not)]
  [$do, $say, $see, $hear, $find, $know, $have, $get]
  nothing
), nothing>

let message "Avoid a double negative."
let description "If the clause is already negated, replace `nothing` with `anything` (e.g., `didn't do anything`)."
let kind "Usage"
let becomes "anything"
let strategy "MatchCase"

test "He didn't do nothing." "He didn't do anything."
test "I can't see NOTHING." "I can't see ANYTHING."
test "I did nothing." "I did nothing."
</code></pre>
<pre><code class="hljs language-weir">expr main &#x3C;(
  [(don't), (do not), (doesn't), (does not), (didn't), (did not),
   (can't), (cannot), (couldn't), (could not),
   (won't), (will not), (wouldn't), (would not)]
  [$tell, $call, $see, $know, $say, $help, $find]
  nobody
), nobody>

let message "Avoid a double negative."
let description "If the clause is already negated, replace `nobody` with `anybody`."
let kind "Usage"
let becomes "anybody"
let strategy "MatchCase"

test "I didn't tell nobody." "I didn't tell anybody."
test "We can't find NOBODY." "We can't find ANYBODY."
test "Nobody called." "Nobody called."
</code></pre>
<pre><code class="hljs language-weir">expr main &#x3C;(
  [(don't), (do not), (doesn't), (does not), (didn't), (did not),
   (can't), (cannot), (couldn't), (could not),
   (won't), (will not)]
  [$go, $run, $walk, $drive, $get, $look]
  nowhere
), nowhere>

let message "Avoid a double negative."
let description "If the clause is already negated, replace `nowhere` with `anywhere`."
let kind "Usage"
let becomes "anywhere"
let strategy "MatchCase"

test "She can't go nowhere tonight." "She can't go anywhere tonight."
test "I did not look NOWHERE else." "I did not look ANYWHERE else."
test "Nowhere is safe." "Nowhere is safe."
</code></pre>
<p>Again, I’m quite im­pressed.
Ob­vi­ously, it’s hard to tell at ﬁrst glance whether these rules would hold up in pro­duc­tion for tens of thou­sands of users.
Nev­er­the­less, these rules look cor­rect, and my ini­tial test­ing shows that they work.
The model was able to suc­cess­fully ap­ply more com­plex logic: the de­riva­tion syn­tax and the ﬁl­ter syn­tax.</p>
<p>In my orig­i­nal an­nounce­ment blog post, I ex­plained that the tar­get au­di­ence for Weir were the mem­bers of large or­ga­ni­za­tions that wish to en­force styl­is­tic con­ven­tions or brand names through­out their work­force.
To that end, I wanted to test an <span class="small-caps">LLM</span> against some of Automattic’s nomen­cla­ture.
Af­ter read­ing though some of our pub­lic doc­u­men­ta­tion, it come up with some ad­mit­tedly use­ful rules:</p>
<pre><code class="hljs language-weir">expr main (wpvip)

let message "Use the official spacing for VIP."
let description "Expands `wpvip` to the official brand forms `WordPress VIP` or `WP VIP`."
let kind "Miscellaneous"
let becomes ["WordPress VIP", "WP VIP"]
let strategy "Exact"

test "This runs on wpvip." "This runs on WordPress VIP."
</code></pre>
<pre><code class="hljs language-weir">expr main (jet[-, ( )]pack)

let message "Use the official spelling `Jetpack`."
let description "Standardizes `jet pack` / `jet-pack` to `Jetpack`."
let kind "Miscellaneous"
let becomes "Jetpack"
let strategy "Exact"

test "Install jet pack for backups." "Install Jetpack for backups."
test "Install jet-pack for backups." "Install Jetpack for backups."
test "Install Jetpack for backups." "Install Jetpack for backups."
</code></pre>
<pre><code class="hljs language-weir">expr main (word[-, ( )]press)

let message "Use the official spelling `WordPress`."
let description "Standardizes `word press` / `word-press` to the product name `WordPress`."
let kind "Miscellaneous"
let becomes "WordPress"
let strategy "Exact"

test "I build sites with word press." "I build sites with WordPress."
test "I build sites with word-press." "I build sites with WordPress."
test "I build sites with WordPress." "I build sites with WordPress."
</code></pre>
<p>Again, I’m quite pleased.
With just a quick search of our web­site, it was able to dis­cern what style we wished to en­force and wrote func­tion­ing Weir rules to do so.
I can see this be­ing help­ful at any num­ber of busi­ness that com­mu­ni­cate reg­u­larly.</p>
<h3>Conclusion</h3>
<p>Overall, I’m quite pleased with how well these LLMs were able to write Weir code.
I’ve yet to ﬁ­nal­ize any doc­u­men­ta­tion on the more com­plex parts of Weir’s syn­tax (like our POS-tagging sys­tem), which means I haven’t yet been able to test them with any LLMs.
Even so, I’m im­pressed.</p>
<p>I even tried it out on <a href="https://docs.mistral.ai/models/ministral-3-3b-25-12">Mistral’s tiny and ul­tra-fast three bil­lion pa­ra­me­ter model</a>.
It per­formed al­most as well as OpenAI’s 5.2 Instant model, al­beit with­out nearly the same level of cre­ativ­ity.
I think this some­what proves that mod­ern LLMs are able to gen­er­al­ize to novel lan­guages, which makes them ex­ceed­ingly use­ful for <a href="https://en.wikipedia.org/wiki/Domain-specific_language">DSLs</a> like Weir.</p>
<p>I’m look­ing for­ward to see­ing how peo­ple end up tak­ing ad­van­tage of this.</p>]]></description>
            <content:encoded><![CDATA[<h1>Generating Weir Code with LLMs</h1>
<p>As you know, I’ve been work­ing on a small pro­gram­ming lan­guage called Weir for gen­er­at­ing cor­rec­tions to nat­ural lan­guage.
For the cu­ri­ous, I highly sug­gest read­ing my pre­vi­ous blog posts on the sub­ject if you haven’t al­ready.</p>
<ul>
<li><a href="./building_the_weir_language">Building the Weir Language</a></li>
<li><a href="./updates_on_the_weir_language">Updates on the Weir Language</a></li>
</ul>
<p>In one of those pre­vi­ous blog posts, I spec­u­lated that an <span class="small-caps">LLM</span> might be quite ef­fec­tive at gen­er­at­ing these rules, es­pe­cially if they are given ac­cess to tools that can val­i­date and run tests.
To­day, while writ­ing some pub­lic doc­u­men­ta­tion for the lan­guage, I did a bit of ex­per­i­men­ta­tion.
I wanted to know: Can an <span class="small-caps">LLM</span> si­mul­ta­ne­ously han­dle the com­plex chal­lenge of un­der­stand­ing a <a href="https://en.wikipedia.org/wiki/Domain-specific_language"><span class="small-caps">DSL</span><span class="push-single"></span><span class="pull-single">’</span>s</a> syn­tax, the se­man­tics of the English lan­guage, and be able to val­i­date its own work with tests?
More to the point: can it do a bet­ter job with a <span class="small-caps">DSL</span> than an es­tab­lished lan­guage like Rust?</p>
<p>For those who want to fol­low along at home, <a href="https://chatgpt.com/g/g-6945b952be148191a21d0189e0691a97-weir-writer">here’s the <span class="small-caps">GPT</span></a> I’ve been toy­ing around with.</p>
<h3>Some Experimentation</h3>
<p>I of­ten hear that one should avoid novel or ex­ceed­ingly old pro­gram­ming lan­guages when work­ing with LLMs, sim­ply be­cause these lan­guages aren’t well rep­re­sented in their train­ing data.
Based on this hearsay, I did­n’t ex­pect much go­ing into this lit­tle ex­per­i­ment of mine.</p>
<p>First, I won­dered if it could han­dle the triv­ial is­sue of cor­rect­ing<span class="push-double"></span> <span class="pull-double">“</span>as nau­seam” to the cor­rect form,<span class="push-double"></span> <span class="pull-double">“</span>ad nau­seam”.
It’s a com­mon typo, and it should be pretty sim­ple Weir rule.</p>
<p>My min­i­mal <span class="small-caps">GPT</span> spit this out:</p>
<pre><code class="hljs language-weir">expr main (as nauseam)

let message "Did you mean `ad nauseam`?"
let description "Corrects the common typo `as nauseam` to the proper Latin phrase `ad nauseam`."
let kind "Typo"
let becomes "ad nauseam"

test "He repeated the point as nauseam." "He repeated the point ad nauseam."
test "This argument has been made ad nauseam." "This argument has been made ad nauseam."
</code></pre>
<p>That looks pretty good!
All the syn­tax is cor­rect, and it even in­cluded some (passing!) tests.
Claude Code or Codex could do this us­ing our ex­ist­ing Rust in­fra­struc­ture, but it would have taken sev­eral min­utes to churn out a valid re­sult.
GPT 5.2 Instant was able to do this in a mat­ter of sec­onds.
Not to men­tion that it did­n’t need a copy of the en­tire Harper source code.</p>
<p>I wanted to try a prob­lem that was a bit harder to solve, so I asked it to ad­dress the prob­lem of dou­ble-neg­a­tives.
Af­ter think­ing for a few min­utes, it came up with four in­di­vid­ual rules that each ad­dress dif­fer­ent cases.</p>
<pre><code class="hljs language-weir">expr main &#x3C;(
  [(don't), (do not), (doesn't), (does not), (didn't), (did not),
   (can't), (cannot), (couldn't), (could not),
   (won't), (will not), (wouldn't), (would not),
   (shouldn't), (should not),
   (isn't), (is not), (aren't), (are not), (wasn't), (was not), (weren't), (were not),
   (ain't)]
  [$have, $get, $need, $want, $know, $see, $find, $say, $do]
  no
), no>

let message "Avoid a double negative."
let description "If the clause is already negated (e.g., `don't`), prefer `any` over `no` (e.g., `don't have any`)."
let kind "Usage"
let becomes "any"
let strategy "MatchCase"

test "I don't have no idea." "I don't have any idea."
test "She did not get NO response." "She did not get ANY response."
test "I have no idea." "I have no idea."
</code></pre>
<pre><code class="hljs language-weir">expr main &#x3C;(
  [(don't), (do not), (doesn't), (does not), (didn't), (did not),
   (can't), (cannot), (couldn't), (could not),
   (won't), (will not), (wouldn't), (would not),
   (shouldn't), (should not)]
  [$do, $say, $see, $hear, $find, $know, $have, $get]
  nothing
), nothing>

let message "Avoid a double negative."
let description "If the clause is already negated, replace `nothing` with `anything` (e.g., `didn't do anything`)."
let kind "Usage"
let becomes "anything"
let strategy "MatchCase"

test "He didn't do nothing." "He didn't do anything."
test "I can't see NOTHING." "I can't see ANYTHING."
test "I did nothing." "I did nothing."
</code></pre>
<pre><code class="hljs language-weir">expr main &#x3C;(
  [(don't), (do not), (doesn't), (does not), (didn't), (did not),
   (can't), (cannot), (couldn't), (could not),
   (won't), (will not), (wouldn't), (would not)]
  [$tell, $call, $see, $know, $say, $help, $find]
  nobody
), nobody>

let message "Avoid a double negative."
let description "If the clause is already negated, replace `nobody` with `anybody`."
let kind "Usage"
let becomes "anybody"
let strategy "MatchCase"

test "I didn't tell nobody." "I didn't tell anybody."
test "We can't find NOBODY." "We can't find ANYBODY."
test "Nobody called." "Nobody called."
</code></pre>
<pre><code class="hljs language-weir">expr main &#x3C;(
  [(don't), (do not), (doesn't), (does not), (didn't), (did not),
   (can't), (cannot), (couldn't), (could not),
   (won't), (will not)]
  [$go, $run, $walk, $drive, $get, $look]
  nowhere
), nowhere>

let message "Avoid a double negative."
let description "If the clause is already negated, replace `nowhere` with `anywhere`."
let kind "Usage"
let becomes "anywhere"
let strategy "MatchCase"

test "She can't go nowhere tonight." "She can't go anywhere tonight."
test "I did not look NOWHERE else." "I did not look ANYWHERE else."
test "Nowhere is safe." "Nowhere is safe."
</code></pre>
<p>Again, I’m quite im­pressed.
Ob­vi­ously, it’s hard to tell at ﬁrst glance whether these rules would hold up in pro­duc­tion for tens of thou­sands of users.
Nev­er­the­less, these rules look cor­rect, and my ini­tial test­ing shows that they work.
The model was able to suc­cess­fully ap­ply more com­plex logic: the de­riva­tion syn­tax and the ﬁl­ter syn­tax.</p>
<p>In my orig­i­nal an­nounce­ment blog post, I ex­plained that the tar­get au­di­ence for Weir were the mem­bers of large or­ga­ni­za­tions that wish to en­force styl­is­tic con­ven­tions or brand names through­out their work­force.
To that end, I wanted to test an <span class="small-caps">LLM</span> against some of Automattic’s nomen­cla­ture.
Af­ter read­ing though some of our pub­lic doc­u­men­ta­tion, it come up with some ad­mit­tedly use­ful rules:</p>
<pre><code class="hljs language-weir">expr main (wpvip)

let message "Use the official spacing for VIP."
let description "Expands `wpvip` to the official brand forms `WordPress VIP` or `WP VIP`."
let kind "Miscellaneous"
let becomes ["WordPress VIP", "WP VIP"]
let strategy "Exact"

test "This runs on wpvip." "This runs on WordPress VIP."
</code></pre>
<pre><code class="hljs language-weir">expr main (jet[-, ( )]pack)

let message "Use the official spelling `Jetpack`."
let description "Standardizes `jet pack` / `jet-pack` to `Jetpack`."
let kind "Miscellaneous"
let becomes "Jetpack"
let strategy "Exact"

test "Install jet pack for backups." "Install Jetpack for backups."
test "Install jet-pack for backups." "Install Jetpack for backups."
test "Install Jetpack for backups." "Install Jetpack for backups."
</code></pre>
<pre><code class="hljs language-weir">expr main (word[-, ( )]press)

let message "Use the official spelling `WordPress`."
let description "Standardizes `word press` / `word-press` to the product name `WordPress`."
let kind "Miscellaneous"
let becomes "WordPress"
let strategy "Exact"

test "I build sites with word press." "I build sites with WordPress."
test "I build sites with word-press." "I build sites with WordPress."
test "I build sites with WordPress." "I build sites with WordPress."
</code></pre>
<p>Again, I’m quite pleased.
With just a quick search of our web­site, it was able to dis­cern what style we wished to en­force and wrote func­tion­ing Weir rules to do so.
I can see this be­ing help­ful at any num­ber of busi­ness that com­mu­ni­cate reg­u­larly.</p>
<h3>Conclusion</h3>
<p>Overall, I’m quite pleased with how well these LLMs were able to write Weir code.
I’ve yet to ﬁ­nal­ize any doc­u­men­ta­tion on the more com­plex parts of Weir’s syn­tax (like our POS-tagging sys­tem), which means I haven’t yet been able to test them with any LLMs.
Even so, I’m im­pressed.</p>
<p>I even tried it out on <a href="https://docs.mistral.ai/models/ministral-3-3b-25-12">Mistral’s tiny and ul­tra-fast three bil­lion pa­ra­me­ter model</a>.
It per­formed al­most as well as OpenAI’s 5.2 Instant model, al­beit with­out nearly the same level of cre­ativ­ity.
I think this some­what proves that mod­ern LLMs are able to gen­er­al­ize to novel lan­guages, which makes them ex­ceed­ingly use­ful for <a href="https://en.wikipedia.org/wiki/Domain-specific_language">DSLs</a> like Weir.</p>
<p>I’m look­ing for­ward to see­ing how peo­ple end up tak­ing ad­van­tage of this.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Updates on the Weir Language]]></title>
            <link>https://elijahpotter.dev/articles/updates_on_the_weir_language</link>
            <guid>https://elijahpotter.dev/articles/updates_on_the_weir_language</guid>
            <pubDate>Wed, 17 Dec 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Updates on the Weir Language</h1>
<p>In <a href="./building_the_weir_language">my last blog post</a>, I de­scribed the why, what, and how of the Weir pro­gram­ming lan­guage.
I sug­gest you read that ﬁrst.</p>
<p>Before sign­ing off for the day, I just wanted to give a few up­dates on the pro­ject.
For those in­ter­ested in the tech­ni­cal side of things, I’ve opened <a href="https://github.com/Automattic/harper/pull/2357">a draft <span class="small-caps">PR</span></a> for your read­ing plea­sure.</p>
<h2>Updated Keywords</h2>
<p>After hav­ing a con­ver­sa­tion with Jason Adams, I came to the re­al­iza­tion that <code>declare</code> and <code>set</code> are pretty sh*tty key­words.
They aren’t self-de­scrib­ing and you’d be hard-pressed to ﬁnd any real pro­gram­ming lan­guage that uses them.</p>
<p>I’ve in­stead re­placed them with <code>let</code> and <code>expr</code>.</p>
<p>Here’s what the rule from my last blog post looks like with these up­dated key­words:</p>
<pre><code class="hljs language-weir">expr main [(G [Suite, Suit]), (Google Apps for Work)]
let message "Use the updated brand."
let description "`G Suite` or `Google Apps for Work` is now called `Google Workspace`"
let kind "Miscellaneous"
let becomes "Google Workspace"
let strategy "Exact"
</code></pre>
<p>Note that I’ve also added the <code>strategy</code> set­ting.
This al­lows rule au­thors to de­scribe which strat­egy Harper will use when ap­ply­ing the re­place­ments.
Right now, the only two op­tions are <code>Exact</code> or <code>MatchCase</code>, which ap­ply ei­ther the ex­act text, or the ex­act text but match­ing the cap­i­tal­iza­tion of the text it re­places.
In the above ex­am­ple, we use <code>Exact</code> be­cause it is a proper noun, and it does­n’t mat­ter what the orig­i­nal text looked like: we want to cap­i­tal­ize it prop­erly.</p>
<h2>Deprecation of the <code>phrase_corrections</code> Module</h2>
<p>I’ve also gone ahead and con­verted the en­tirety of the <code>phrase_corrections</code> mod­ule, as well as all as­so­ci­ated tests, to the Weir lan­guage.
If you’re read­ing this and you’re a con­trib­u­tor, know that we will no longer be adding new rules to that mod­ule once Weir is merged into <code>master</code>.
When the merge hap­pens, I’ll han­dle the con­ver­sion of any lag­ging phrase cor­rec­tions that haven’t al­ready been con­verted.</p>
<p>That un­for­tu­nately means that <a href="./writing_a_phrase_correction_for_harper">my pre­vi­ous guide</a> on the sub­ject is no longer rel­e­vant.</p>
<h2>What’s Next?</h2>
<p>Before Weir gets merged, I plan to sup­port the match­ing of <span class="small-caps">UPOS</span> tags, in ad­di­tional to some other qual­ity of life up­dates.
Ex­pect an­other blog post.</p>
<p>In the mean­time, please let me know if you have any ad­di­tional in­put!</p>]]></description>
            <content:encoded><![CDATA[<h1>Updates on the Weir Language</h1>
<p>In <a href="./building_the_weir_language">my last blog post</a>, I de­scribed the why, what, and how of the Weir pro­gram­ming lan­guage.
I sug­gest you read that ﬁrst.</p>
<p>Before sign­ing off for the day, I just wanted to give a few up­dates on the pro­ject.
For those in­ter­ested in the tech­ni­cal side of things, I’ve opened <a href="https://github.com/Automattic/harper/pull/2357">a draft <span class="small-caps">PR</span></a> for your read­ing plea­sure.</p>
<h2>Updated Keywords</h2>
<p>After hav­ing a con­ver­sa­tion with Jason Adams, I came to the re­al­iza­tion that <code>declare</code> and <code>set</code> are pretty sh*tty key­words.
They aren’t self-de­scrib­ing and you’d be hard-pressed to ﬁnd any real pro­gram­ming lan­guage that uses them.</p>
<p>I’ve in­stead re­placed them with <code>let</code> and <code>expr</code>.</p>
<p>Here’s what the rule from my last blog post looks like with these up­dated key­words:</p>
<pre><code class="hljs language-weir">expr main [(G [Suite, Suit]), (Google Apps for Work)]
let message "Use the updated brand."
let description "`G Suite` or `Google Apps for Work` is now called `Google Workspace`"
let kind "Miscellaneous"
let becomes "Google Workspace"
let strategy "Exact"
</code></pre>
<p>Note that I’ve also added the <code>strategy</code> set­ting.
This al­lows rule au­thors to de­scribe which strat­egy Harper will use when ap­ply­ing the re­place­ments.
Right now, the only two op­tions are <code>Exact</code> or <code>MatchCase</code>, which ap­ply ei­ther the ex­act text, or the ex­act text but match­ing the cap­i­tal­iza­tion of the text it re­places.
In the above ex­am­ple, we use <code>Exact</code> be­cause it is a proper noun, and it does­n’t mat­ter what the orig­i­nal text looked like: we want to cap­i­tal­ize it prop­erly.</p>
<h2>Deprecation of the <code>phrase_corrections</code> Module</h2>
<p>I’ve also gone ahead and con­verted the en­tirety of the <code>phrase_corrections</code> mod­ule, as well as all as­so­ci­ated tests, to the Weir lan­guage.
If you’re read­ing this and you’re a con­trib­u­tor, know that we will no longer be adding new rules to that mod­ule once Weir is merged into <code>master</code>.
When the merge hap­pens, I’ll han­dle the con­ver­sion of any lag­ging phrase cor­rec­tions that haven’t al­ready been con­verted.</p>
<p>That un­for­tu­nately means that <a href="./writing_a_phrase_correction_for_harper">my pre­vi­ous guide</a> on the sub­ject is no longer rel­e­vant.</p>
<h2>What’s Next?</h2>
<p>Before Weir gets merged, I plan to sup­port the match­ing of <span class="small-caps">UPOS</span> tags, in ad­di­tional to some other qual­ity of life up­dates.
Ex­pect an­other blog post.</p>
<p>In the mean­time, please let me know if you have any ad­di­tional in­put!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building the Weir Language]]></title>
            <link>https://elijahpotter.dev/articles/building_the_weir_language</link>
            <guid>https://elijahpotter.dev/articles/building_the_weir_language</guid>
            <pubDate>Thu, 11 Dec 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Building the Weir Language</h1>
<p>Most large or­ga­ni­za­tions have a style guide.
A doc­u­ment that de­cides which ver­sions of a lin­guis­tic rule to use.
That could be whether to use the Oxford comma, or if con­trac­tions are al­lowed.
It could de­clare that a cer­tain word should be cap­i­tal­ized in a spe­ciﬁc con­text.</p>
<p>Harper can cover <em>most</em> of the rules in <em>most</em> style guides, but there will al­ways be out­liers that we can’t sup­port (or sim­ply don’t know about).
That is why it is crit­i­cal that Harper al­low in­di­vid­u­als and or­ga­ni­za­tions to de­ﬁne rules and con­ven­tions for Harper to en­force.</p>
<p>For the last few days, I’ve been pro­to­typ­ing a sim­ple (not Turing-complete) pro­gram­ming lan­guage for de­scrib­ing these con­ven­tions.
I’ve called it Weir.
Weir is sim­ple and thus easy to learn, but has es­cape hatches for in­stances where more com­plex logic is nec­es­sary.</p>
<p>Before I be­gin, a quick dis­claimer: This is early work. The Weir lan­guage will likely change and evolve be­fore it makes it into core.
What you see here is rep­re­sen­ta­tive of the over­all de­sign, but noth­ing is ﬁ­nal yet.</p>
<h2>History</h2>
<p>There are in­evitably go­ing to be a good num­ber of edge cases that need to be ad­dressed when build­ing a gram­mar checker.
For these, Harper has a sim­ple map from prob­lem­atic phrases to the cor­rected ver­sions.
Edge cases that are not sim­ple enough to be de­scribed with a map are im­ple­mented as Rust code.
Here’s what the map looks like:</p>
<pre><code class="hljs language-rust">add_exact_mappings!(group, {
    <span class="hljs-comment">// The name of the rule</span>
    <span class="hljs-string">"ACoupleMore"</span> => (
        <span class="hljs-comment">// The phrase(s) to look for.</span>
        [<span class="hljs-string">"a couple of more"</span>],
        <span class="hljs-comment">// The corrections to provide.</span>
        [<span class="hljs-string">"a couple more"</span>],
        <span class="hljs-comment">// The message to be shown with the error.</span>
        <span class="hljs-string">"The correct wording is `a couple more`, without the `of`."</span>,
        <span class="hljs-comment">// A description of the rule.</span>
        <span class="hljs-string">"Corrects `a couple of more` to `a couple more`."</span>,
        LintKind::Redundancy
    ),
    <span class="hljs-string">"CondenseAllThe"</span> => (
        [<span class="hljs-string">"all of the"</span>],
        [<span class="hljs-string">"all the"</span>],
        <span class="hljs-string">"Consider simplifying to `all the`."</span>,
        <span class="hljs-string">"Suggests removing `of` in `all of the` for a more concise phrase."</span>,
        LintKind::Redundancy
    ),
    <span class="hljs-string">"CoursingThroughVeins"</span> => (
        [<span class="hljs-string">"cursing through veins"</span>],
        [<span class="hljs-string">"coursing through veins"</span>],
        <span class="hljs-string">"In this idiom, blood “courses” (flows) through veins, not “curses”."</span>,
        <span class="hljs-string">"In English idioms, “to course” means to flow rapidly—so avoid the eggcorn `cursing through veins.`"</span>,
        LintKind::Eggcorn
    ),
});
</code></pre>
<p>As for the Rust code, it is of­ten heinous and a chore to re­view.
I won’t do my­self the em­bar­rass­ment of in­clud­ing it here.</p>
<h2>Introducing Weir</h2>
<p>The heart of Weir is an ex­pres­sion lan­guage that mim­ics the pseudocode Harper con­trib­u­tors tend to use when de­scrib­ing the Rust code they in­tend to write.</p>
<p>Imagine you work at Google. You’ve just re­branded the<span class="push-double"></span> <span class="pull-double">“</span>G Suite” col­lec­tion of apps and ser­vices to the new name<span class="push-double"></span> <span class="pull-double">“</span>Google Workspace”.
Before that, they were col­lec­tively named<span class="push-double"></span> <span class="pull-double">“</span>Google Apps for Work”.
Moving for­ward, you don’t want you or your cowork­ers to ac­ci­den­tally write<span class="push-double"></span> <span class="pull-double">“</span>G Suite” on pub­lic doc­u­men­ta­tion, be­cause do­ing so might con­fuse users.
To solve this, you use the fol­low­ing Weir rule:</p>
<pre><code class="hljs language-weir">set main [(G [Suite, Suit]), (Google Apps for Work)]

declare message "Use the updated brand."
declare description "`G Suite` or `Google Apps for Work` is now called `Google Workspace`"
declare kind "Miscellaneous"
declare becomes "Google Workspace"
</code></pre>
<p>The ﬁrst line de­scribes the pat­tern of the prob­lem­atic text.
There are two cases here:</p>
<ol>
<li>The let­ter<span class="push-double"></span> <span class="pull-double">“</span>G” fol­lowed by<span class="push-double"></span> <span class="pull-double">“</span>Suite” or its mis­spelling<span class="push-double"></span> <span class="pull-double">“</span>Suit”</li>
<li>The lit­eral phrase<span class="push-double"></span> <span class="pull-double">“</span>Google Apps for Work”</li>
</ol>
<p>Here is a se­man­ti­cally equiv­a­lent ex­am­ple that I ﬁnd a bit eas­ier to read:</p>
<pre><code class="hljs language-weir">set main [(G Suite), (G Suit), (Google Apps for Work)]
</code></pre>
<p>The re­main­ing lines de­scribe:</p>
<ol>
<li>The mes­sage to be shown to the user when the er­ror in en­coun­tered.</li>
<li>A de­scrip­tion of the rule it­self, ex­plain­ing why it ex­ists.</li>
<li>What kind of rule it is.
I sus­pect most end users will mark it as<span class="push-double"></span> <span class="pull-double">“</span>miscellaneous”.
I’m con­sid­er­ing mak­ing this ﬁeld op­tional.</li>
<li>What cor­rec­tions to pro­vide to the user.</li>
</ol>
<p>One of my goals here was to make the sys­tem sim­ple enough for an end-user to copy and paste an ex­ist­ing rule into a new ﬁle, make some tweaks, and they would be done.
For the more com­plex cases, I think we could set up a cus­tom <span class="small-caps">GPT</span> to write these pretty eas­ily.</p>
<p>Here’s an­other ex­am­ple, one that will ac­tu­ally be a part of the Harper source code.
It uses a ﬁl­ter (the <code>&#x3C;></code> syn­tax) to ﬁrst se­lect the broader phrase, then an­other to se­lect the white­space in-be­tween (that’s the <code>( )</code> part).</p>
<pre><code class="hljs language-weir">set main &#x3C;([right, middle, left] $click), ( )>
declare message "Hyphenate this mouse command"
declare description "Hyphenates right-click style mouse commands."
declare kind "Punctuation"
declare becomes "-"
</code></pre>
<p>This is a great ex­am­ple of the <code>$</code> tag.
In­stead of in­struct­ing Harper to look for the lit­eral word af­ter­ward, it in­stead in­structs it to look for all de­riv­a­tive words.
Those in­clude<span class="push-double"></span> <span class="pull-double">“</span>click”,<span class="push-double"></span> <span class="pull-double">“</span>clicking”,<span class="push-double"></span> <span class="pull-double">“</span>clicked”, etc.</p>
<p>We also have spe­ciﬁc syn­tax that can be used to test the ex­pres­sion in­line.
These as­sert that the left string, af­ter be­ing passed through the rule, be­comes the right string.</p>
<pre><code class="hljs language-weir">test "Right click the icon." "Right-click the icon."
test "Please right click on the link." "Please right-click on the link."
test "They right clicked the submit button." "They right-clicked the submit button."
test "Right clicking the item highlights it." "Right-clicking the item highlights it."
test "Right clicks are tracked in the log." "Right-clicks are tracked in the log."
test "He RIGHT CLICKED the file." "He RIGHT-CLICKED the file."
test "Left click the checkbox." "Left-click the checkbox."
test "Middle click to open in a new tab." "Middle-click to open in a new tab."
</code></pre>
<p>I sus­pect Weir will make it ex­tremely easy for <span class="small-caps">AI</span> agents to it­er­ate quickly on these rules.
There’s no com­pi­la­tion step, and we al­ready have ex­cel­lent pipelines for gen­er­at­ing lists of test cases.</p>
<h2>Why Call It<span class="push-double"></span> <span class="pull-double">“</span>Weir”?</h2>
<p>I am nam­ing Weir af­ter the au­thor of <em>Project Hail Mary</em>, <a href="https://en.wikipedia.org/wiki/Andy_Weir">Andy Weir</a>.
I guess I just like nam­ing things af­ter au­thors.
Af­ter all, Harper it­self was <a href="./naming_harper.md">named af­ter Harper Lee</a>.</p>
<h2>Moving Forward</h2>
<p>Once the syn­tax is ﬁ­nal­ized and a cor­pus of tests have been writ­ten, I sus­pect Weir rules will be­come the de­fault for new gram­mat­i­cal rules in­side the Harper repos­i­tory and for in­di­vid­ual users.
I’d love to cre­ate a mar­ket­place for rule sets cre­ated by and for the com­mu­nity.</p>
<p>If you (the reader) have any thoughts on syn­tax or any­thing else, don’t hes­i­tate to speak your mind.</p>]]></description>
            <content:encoded><![CDATA[<h1>Building the Weir Language</h1>
<p>Most large or­ga­ni­za­tions have a style guide.
A doc­u­ment that de­cides which ver­sions of a lin­guis­tic rule to use.
That could be whether to use the Oxford comma, or if con­trac­tions are al­lowed.
It could de­clare that a cer­tain word should be cap­i­tal­ized in a spe­ciﬁc con­text.</p>
<p>Harper can cover <em>most</em> of the rules in <em>most</em> style guides, but there will al­ways be out­liers that we can’t sup­port (or sim­ply don’t know about).
That is why it is crit­i­cal that Harper al­low in­di­vid­u­als and or­ga­ni­za­tions to de­ﬁne rules and con­ven­tions for Harper to en­force.</p>
<p>For the last few days, I’ve been pro­to­typ­ing a sim­ple (not Turing-complete) pro­gram­ming lan­guage for de­scrib­ing these con­ven­tions.
I’ve called it Weir.
Weir is sim­ple and thus easy to learn, but has es­cape hatches for in­stances where more com­plex logic is nec­es­sary.</p>
<p>Before I be­gin, a quick dis­claimer: This is early work. The Weir lan­guage will likely change and evolve be­fore it makes it into core.
What you see here is rep­re­sen­ta­tive of the over­all de­sign, but noth­ing is ﬁ­nal yet.</p>
<h2>History</h2>
<p>There are in­evitably go­ing to be a good num­ber of edge cases that need to be ad­dressed when build­ing a gram­mar checker.
For these, Harper has a sim­ple map from prob­lem­atic phrases to the cor­rected ver­sions.
Edge cases that are not sim­ple enough to be de­scribed with a map are im­ple­mented as Rust code.
Here’s what the map looks like:</p>
<pre><code class="hljs language-rust">add_exact_mappings!(group, {
    <span class="hljs-comment">// The name of the rule</span>
    <span class="hljs-string">"ACoupleMore"</span> => (
        <span class="hljs-comment">// The phrase(s) to look for.</span>
        [<span class="hljs-string">"a couple of more"</span>],
        <span class="hljs-comment">// The corrections to provide.</span>
        [<span class="hljs-string">"a couple more"</span>],
        <span class="hljs-comment">// The message to be shown with the error.</span>
        <span class="hljs-string">"The correct wording is `a couple more`, without the `of`."</span>,
        <span class="hljs-comment">// A description of the rule.</span>
        <span class="hljs-string">"Corrects `a couple of more` to `a couple more`."</span>,
        LintKind::Redundancy
    ),
    <span class="hljs-string">"CondenseAllThe"</span> => (
        [<span class="hljs-string">"all of the"</span>],
        [<span class="hljs-string">"all the"</span>],
        <span class="hljs-string">"Consider simplifying to `all the`."</span>,
        <span class="hljs-string">"Suggests removing `of` in `all of the` for a more concise phrase."</span>,
        LintKind::Redundancy
    ),
    <span class="hljs-string">"CoursingThroughVeins"</span> => (
        [<span class="hljs-string">"cursing through veins"</span>],
        [<span class="hljs-string">"coursing through veins"</span>],
        <span class="hljs-string">"In this idiom, blood “courses” (flows) through veins, not “curses”."</span>,
        <span class="hljs-string">"In English idioms, “to course” means to flow rapidly—so avoid the eggcorn `cursing through veins.`"</span>,
        LintKind::Eggcorn
    ),
});
</code></pre>
<p>As for the Rust code, it is of­ten heinous and a chore to re­view.
I won’t do my­self the em­bar­rass­ment of in­clud­ing it here.</p>
<h2>Introducing Weir</h2>
<p>The heart of Weir is an ex­pres­sion lan­guage that mim­ics the pseudocode Harper con­trib­u­tors tend to use when de­scrib­ing the Rust code they in­tend to write.</p>
<p>Imagine you work at Google. You’ve just re­branded the<span class="push-double"></span> <span class="pull-double">“</span>G Suite” col­lec­tion of apps and ser­vices to the new name<span class="push-double"></span> <span class="pull-double">“</span>Google Workspace”.
Before that, they were col­lec­tively named<span class="push-double"></span> <span class="pull-double">“</span>Google Apps for Work”.
Moving for­ward, you don’t want you or your cowork­ers to ac­ci­den­tally write<span class="push-double"></span> <span class="pull-double">“</span>G Suite” on pub­lic doc­u­men­ta­tion, be­cause do­ing so might con­fuse users.
To solve this, you use the fol­low­ing Weir rule:</p>
<pre><code class="hljs language-weir">set main [(G [Suite, Suit]), (Google Apps for Work)]

declare message "Use the updated brand."
declare description "`G Suite` or `Google Apps for Work` is now called `Google Workspace`"
declare kind "Miscellaneous"
declare becomes "Google Workspace"
</code></pre>
<p>The ﬁrst line de­scribes the pat­tern of the prob­lem­atic text.
There are two cases here:</p>
<ol>
<li>The let­ter<span class="push-double"></span> <span class="pull-double">“</span>G” fol­lowed by<span class="push-double"></span> <span class="pull-double">“</span>Suite” or its mis­spelling<span class="push-double"></span> <span class="pull-double">“</span>Suit”</li>
<li>The lit­eral phrase<span class="push-double"></span> <span class="pull-double">“</span>Google Apps for Work”</li>
</ol>
<p>Here is a se­man­ti­cally equiv­a­lent ex­am­ple that I ﬁnd a bit eas­ier to read:</p>
<pre><code class="hljs language-weir">set main [(G Suite), (G Suit), (Google Apps for Work)]
</code></pre>
<p>The re­main­ing lines de­scribe:</p>
<ol>
<li>The mes­sage to be shown to the user when the er­ror in en­coun­tered.</li>
<li>A de­scrip­tion of the rule it­self, ex­plain­ing why it ex­ists.</li>
<li>What kind of rule it is.
I sus­pect most end users will mark it as<span class="push-double"></span> <span class="pull-double">“</span>miscellaneous”.
I’m con­sid­er­ing mak­ing this ﬁeld op­tional.</li>
<li>What cor­rec­tions to pro­vide to the user.</li>
</ol>
<p>One of my goals here was to make the sys­tem sim­ple enough for an end-user to copy and paste an ex­ist­ing rule into a new ﬁle, make some tweaks, and they would be done.
For the more com­plex cases, I think we could set up a cus­tom <span class="small-caps">GPT</span> to write these pretty eas­ily.</p>
<p>Here’s an­other ex­am­ple, one that will ac­tu­ally be a part of the Harper source code.
It uses a ﬁl­ter (the <code>&#x3C;></code> syn­tax) to ﬁrst se­lect the broader phrase, then an­other to se­lect the white­space in-be­tween (that’s the <code>( )</code> part).</p>
<pre><code class="hljs language-weir">set main &#x3C;([right, middle, left] $click), ( )>
declare message "Hyphenate this mouse command"
declare description "Hyphenates right-click style mouse commands."
declare kind "Punctuation"
declare becomes "-"
</code></pre>
<p>This is a great ex­am­ple of the <code>$</code> tag.
In­stead of in­struct­ing Harper to look for the lit­eral word af­ter­ward, it in­stead in­structs it to look for all de­riv­a­tive words.
Those in­clude<span class="push-double"></span> <span class="pull-double">“</span>click”,<span class="push-double"></span> <span class="pull-double">“</span>clicking”,<span class="push-double"></span> <span class="pull-double">“</span>clicked”, etc.</p>
<p>We also have spe­ciﬁc syn­tax that can be used to test the ex­pres­sion in­line.
These as­sert that the left string, af­ter be­ing passed through the rule, be­comes the right string.</p>
<pre><code class="hljs language-weir">test "Right click the icon." "Right-click the icon."
test "Please right click on the link." "Please right-click on the link."
test "They right clicked the submit button." "They right-clicked the submit button."
test "Right clicking the item highlights it." "Right-clicking the item highlights it."
test "Right clicks are tracked in the log." "Right-clicks are tracked in the log."
test "He RIGHT CLICKED the file." "He RIGHT-CLICKED the file."
test "Left click the checkbox." "Left-click the checkbox."
test "Middle click to open in a new tab." "Middle-click to open in a new tab."
</code></pre>
<p>I sus­pect Weir will make it ex­tremely easy for <span class="small-caps">AI</span> agents to it­er­ate quickly on these rules.
There’s no com­pi­la­tion step, and we al­ready have ex­cel­lent pipelines for gen­er­at­ing lists of test cases.</p>
<h2>Why Call It<span class="push-double"></span> <span class="pull-double">“</span>Weir”?</h2>
<p>I am nam­ing Weir af­ter the au­thor of <em>Project Hail Mary</em>, <a href="https://en.wikipedia.org/wiki/Andy_Weir">Andy Weir</a>.
I guess I just like nam­ing things af­ter au­thors.
Af­ter all, Harper it­self was <a href="./naming_harper.md">named af­ter Harper Lee</a>.</p>
<h2>Moving Forward</h2>
<p>Once the syn­tax is ﬁ­nal­ized and a cor­pus of tests have been writ­ten, I sus­pect Weir rules will be­come the de­fault for new gram­mat­i­cal rules in­side the Harper repos­i­tory and for in­di­vid­ual users.
I’d love to cre­ate a mar­ket­place for rule sets cre­ated by and for the com­mu­nity.</p>
<p>If you (the reader) have any thoughts on syn­tax or any­thing else, don’t hes­i­tate to speak your mind.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Harper Can Apply Titlecase]]></title>
            <link>https://elijahpotter.dev/articles/harper_can_apply_titlecase</link>
            <guid>https://elijahpotter.dev/articles/harper_can_apply_titlecase</guid>
            <pubDate>Fri, 05 Dec 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Harper Can Apply Title Case</h1>
<p>In case you did­n’t know: Harper can con­vert text to ti­tle case!
This has been around for a long time, but we haven’t re­ally talked about it pub­licly.
Be­ing able to make text ti­tle case (strictly in ac­cor­dance to Chicago Style) is­n’t a ground­break­ing fea­ture, but it is use­ful for cer­tain niche ap­pli­ca­tions.</p>
<p>For ex­am­ple, all the ti­tles in this very blog are forced to ti­tle case on pub­lish, which is­n’t some­thing most <span class="small-caps">CMS</span> sys­tems do.
I’d love to see it be­come more wide­spread.</p>
<p>We have <a href="https://writewithharper.com/titlecase">a live demo</a> where you can try it out. It’s also avail­able <a href="https://writewithharper.com/docs/harperjs/ref/harper.js.binarymodule.totitlecase.html">as part of <code>harper.js</code></a>, our JavaScript pack­age.</p>
<p><a href="https://github.com/Automattic/harper/pull/2297">Soon</a>, this will be­come a part of Harper’s na­tive lint­ing ca­pa­bil­i­ties.</p>]]></description>
            <content:encoded><![CDATA[<h1>Harper Can Apply Title Case</h1>
<p>In case you did­n’t know: Harper can con­vert text to ti­tle case!
This has been around for a long time, but we haven’t re­ally talked about it pub­licly.
Be­ing able to make text ti­tle case (strictly in ac­cor­dance to Chicago Style) is­n’t a ground­break­ing fea­ture, but it is use­ful for cer­tain niche ap­pli­ca­tions.</p>
<p>For ex­am­ple, all the ti­tles in this very blog are forced to ti­tle case on pub­lish, which is­n’t some­thing most <span class="small-caps">CMS</span> sys­tems do.
I’d love to see it be­come more wide­spread.</p>
<p>We have <a href="https://writewithharper.com/titlecase">a live demo</a> where you can try it out. It’s also avail­able <a href="https://writewithharper.com/docs/harperjs/ref/harper.js.binarymodule.totitlecase.html">as part of <code>harper.js</code></a>, our JavaScript pack­age.</p>
<p><a href="https://github.com/Automattic/harper/pull/2297">Soon</a>, this will be­come a part of Harper’s na­tive lint­ing ca­pa­bil­i­ties.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Harper Turns 1.0 Today]]></title>
            <link>https://elijahpotter.dev/articles/harper_turns_1.0_today</link>
            <guid>https://elijahpotter.dev/articles/harper_turns_1.0_today</guid>
            <pubDate>Fri, 28 Nov 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Harper Turns 1.0 Today</h1>
<p>Today, we pub­lished <a href="https://github.com/Automattic/harper/releases/tag/v1.0.0">Harper’s <code>1.0.0</code> re­lease</a>. It’s a huge mile­stone, and in this post I’d like to dis­cuss why it took so long, why we’re do­ing it now, and what’s next for the pro­ject.</p>
<h2>A Round of Applause, Please</h2>
<p>As I men­tioned: This is a huge step for the pro­ject. Before I say any­thing else, I want to take a mo­ment to ap­pre­ci­ate the col­lec­tive work of our many con­trib­u­tors. It has taken many it­er­a­tions to get to where we are to­day, and it would­n’t be pos­si­ble with­out the com­mu­ni­ty’s ef­fort and feed­back.</p>
<figure class="wp-block-image"><img src="https://contrib.rocks/image?repo=automattic/harper" alt></figure>
<h2>Why Now?</h2>
<p>Until to­day, I’ve kept Harper in a pre-1.0 state for one rea­son: I wanted to move fast. More specif­i­cally, I wanted the free­dom to build and break things as many times as I needed to. I knew that it would take many cy­cles of build­ing and re­build­ing ar­rive at a ro­bust sys­tem that ad­dresses the peo­ple’s need for a pri­vate writ­ing tool. I be­lieve that build­ing some­thing good can of­ten mean tak­ing the bad parts out. Once a pro­ject is<span class="push-double"></span> <span class="pull-double">“</span>1.0”, it be­comes much harder to re­move things. Naturally, that pushed me to hold off on<span class="push-double"></span> <span class="pull-double">“</span>going 1.0″.</p>
<p>Of course, that did­n’t stop us from get­ting the soft­ware into the hands of users. Today, tens of thou­sands of peo­ple ben­e­ﬁt from Harper’s fast and pri­vate gram­mar check­ing in Chrome, Obsidian, <span class="small-caps">VS</span> Code, and Neovim (among many oth­ers). We’ve racked up hun­dreds of thou­sands of down­loads be­fore ever slap­ping any­thing other than zero be­fore the ﬁrst dec­i­mal point of our ver­sion num­ber.</p>
<p>I’m sure your think­ing:<span class="push-double"></span> <span class="pull-double">“</span>Elijah! Get to the point!” Fine. The rea­son we’re do­ing this now, rather than ear­lier or later, is be­cause our pri­or­i­ties have just re­cently changed.</p>
<p>For one, Harper’s <span class="small-caps">API</span> has been rock-solid for a few months now, which means we can safely say that our need to move fast in that area has di­min­ished. The op­por­tu­nity cost of abid­ing by a sta­ble <span class="small-caps">API</span> has gone down.</p>
<p>Secondly, I’ve been hear­ing pro­gres­sively more in­ter­est from po­ten­tial con­trib­u­tors and con­sumers who want to put Harper di­rectly into their own apps or ser­vices. They have the de­sire to help make Harper more wide­spread, but they can’t com­mit to it un­less Harper com­mits to a sta­ble <span class="small-caps">API</span>. That’s what we’re do­ing to­day.</p>
<p>I can imag­ine a fu­ture where Harper is na­tively in­te­grated every­where: learn­ing man­age­ment sys­tems, doc­u­ment ed­i­tors, mes­sag­ing plat­forms, or even op­er­at­ing sys­tems. But great things take time. Today, we’re tak­ing a huge step in set­ting Harper up for the long-term.</p>
<h2>What Do I Need to Know?</h2>
<p>As an end-user, not much changes. From here on out, we’ll be push­ing qual­ity-of-life tweaks and bug­ﬁxes at a faster rate, all while im­prov­ing Harper’s ca­pa­bil­i­ties across the board.</p>
<p>As a con­trib­u­tor, the patch re­view process might get a lit­tle bit more strict. We’ll be fo­cus­ing more on im­prov­ing the qual­ity of our code, rather than the amount. If your PRs are likely to re­sult in a break­ing change, ex­pect co­pi­ous notes and pos­si­ble de­lays be­fore we hit<span class="push-double"></span> <span class="pull-double">“</span>merge”.</p>
<p>As an in­te­gra­tor, you win more than any­one. If you’d like to in­clude Harper in your ap­pli­ca­tion, let us know and we’ll do our best to make it easy for you. Take a look at <a href="https://writewithharper.com/docs/about#Versioning-Policy">our ver­sion­ing pol­icy</a> if that sort of thing gives you peace of mind.</p>
<h2>Where Can I Get Further Updates?</h2>
<p>That de­pends on the level of ver­bosity your in­ter­ested in. For those who want to know about every­thing I’m cur­rently work­ing, sub­scribe to <a href="https://elijahpotter.dev/">my blog</a>. For every­one else, our <a href="https://github.com/automattic/harper/releases">patch notes in GitHub</a> should suf­ﬁce.</p>]]></description>
            <content:encoded><![CDATA[<h1>Harper Turns 1.0 Today</h1>
<p>Today, we pub­lished <a href="https://github.com/Automattic/harper/releases/tag/v1.0.0">Harper’s <code>1.0.0</code> re­lease</a>. It’s a huge mile­stone, and in this post I’d like to dis­cuss why it took so long, why we’re do­ing it now, and what’s next for the pro­ject.</p>
<h2>A Round of Applause, Please</h2>
<p>As I men­tioned: This is a huge step for the pro­ject. Before I say any­thing else, I want to take a mo­ment to ap­pre­ci­ate the col­lec­tive work of our many con­trib­u­tors. It has taken many it­er­a­tions to get to where we are to­day, and it would­n’t be pos­si­ble with­out the com­mu­ni­ty’s ef­fort and feed­back.</p>
<figure class="wp-block-image"><img src="https://contrib.rocks/image?repo=automattic/harper" alt></figure>
<h2>Why Now?</h2>
<p>Until to­day, I’ve kept Harper in a pre-1.0 state for one rea­son: I wanted to move fast. More specif­i­cally, I wanted the free­dom to build and break things as many times as I needed to. I knew that it would take many cy­cles of build­ing and re­build­ing ar­rive at a ro­bust sys­tem that ad­dresses the peo­ple’s need for a pri­vate writ­ing tool. I be­lieve that build­ing some­thing good can of­ten mean tak­ing the bad parts out. Once a pro­ject is<span class="push-double"></span> <span class="pull-double">“</span>1.0”, it be­comes much harder to re­move things. Naturally, that pushed me to hold off on<span class="push-double"></span> <span class="pull-double">“</span>going 1.0″.</p>
<p>Of course, that did­n’t stop us from get­ting the soft­ware into the hands of users. Today, tens of thou­sands of peo­ple ben­e­ﬁt from Harper’s fast and pri­vate gram­mar check­ing in Chrome, Obsidian, <span class="small-caps">VS</span> Code, and Neovim (among many oth­ers). We’ve racked up hun­dreds of thou­sands of down­loads be­fore ever slap­ping any­thing other than zero be­fore the ﬁrst dec­i­mal point of our ver­sion num­ber.</p>
<p>I’m sure your think­ing:<span class="push-double"></span> <span class="pull-double">“</span>Elijah! Get to the point!” Fine. The rea­son we’re do­ing this now, rather than ear­lier or later, is be­cause our pri­or­i­ties have just re­cently changed.</p>
<p>For one, Harper’s <span class="small-caps">API</span> has been rock-solid for a few months now, which means we can safely say that our need to move fast in that area has di­min­ished. The op­por­tu­nity cost of abid­ing by a sta­ble <span class="small-caps">API</span> has gone down.</p>
<p>Secondly, I’ve been hear­ing pro­gres­sively more in­ter­est from po­ten­tial con­trib­u­tors and con­sumers who want to put Harper di­rectly into their own apps or ser­vices. They have the de­sire to help make Harper more wide­spread, but they can’t com­mit to it un­less Harper com­mits to a sta­ble <span class="small-caps">API</span>. That’s what we’re do­ing to­day.</p>
<p>I can imag­ine a fu­ture where Harper is na­tively in­te­grated every­where: learn­ing man­age­ment sys­tems, doc­u­ment ed­i­tors, mes­sag­ing plat­forms, or even op­er­at­ing sys­tems. But great things take time. Today, we’re tak­ing a huge step in set­ting Harper up for the long-term.</p>
<h2>What Do I Need to Know?</h2>
<p>As an end-user, not much changes. From here on out, we’ll be push­ing qual­ity-of-life tweaks and bug­ﬁxes at a faster rate, all while im­prov­ing Harper’s ca­pa­bil­i­ties across the board.</p>
<p>As a con­trib­u­tor, the patch re­view process might get a lit­tle bit more strict. We’ll be fo­cus­ing more on im­prov­ing the qual­ity of our code, rather than the amount. If your PRs are likely to re­sult in a break­ing change, ex­pect co­pi­ous notes and pos­si­ble de­lays be­fore we hit<span class="push-double"></span> <span class="pull-double">“</span>merge”.</p>
<p>As an in­te­gra­tor, you win more than any­one. If you’d like to in­clude Harper in your ap­pli­ca­tion, let us know and we’ll do our best to make it easy for you. Take a look at <a href="https://writewithharper.com/docs/about#Versioning-Policy">our ver­sion­ing pol­icy</a> if that sort of thing gives you peace of mind.</p>
<h2>Where Can I Get Further Updates?</h2>
<p>That de­pends on the level of ver­bosity your in­ter­ested in. For those who want to know about every­thing I’m cur­rently work­ing, sub­scribe to <a href="https://elijahpotter.dev/">my blog</a>. For every­one else, our <a href="https://github.com/automattic/harper/releases">patch notes in GitHub</a> should suf­ﬁce.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Quality Requires Visual Design]]></title>
            <link>https://elijahpotter.dev/articles/quality_requires_visual_design</link>
            <guid>https://elijahpotter.dev/articles/quality_requires_visual_design</guid>
            <pubDate>Fri, 21 Nov 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Quality Requires Visual Design</h1>
<p>Earlier this week, I was look­ing through a table of user feed­back about <a href="https://writewithharper.com">Harper</a>.
I be­lieve that soft­ware should be build <em>with</em> the user—not just <em>for</em> the user—so this is a pretty reg­u­lar rit­ual for me.</p>
<p>I was de­lighted.
Many of the usual com­plaints were to­tally ab­sent from the re­port.
Users were en­coun­ter­ing fewer of the make-or-break bugs that would harm their ex­pe­ri­ence.
This meant that my re­cent push to get Harper pre­pared for a <code>1.0</code> re­lease was work­ing.
Great news! Yippee!</p>
<p>Unfortunately, some­thing new emerged from be­neath the pile of bugs: prob­lems with Harper’s vi­sual de­sign.</p>
<p>For quite some time now, I’ve been fo­cused solely on the prac­ti­cal parts of Quality.
I mean bugs and the hard-to-deny use­ful­ness of Harper’s ser­vice.
In that time, I’ve for­got­ten that the vi­sual ap­peal of the soft­ware is at least as im­por­tant.
So, when I saw these com­plaints with Harper’s vi­sual de­sign, I knew I needed to do some­thing about it.</p>
<p>I’ve never claimed to be a de­signer, but I’m also not one to shy away from a chal­lenge.
So, I grabbed a copy of the <a href="https://app.thestorygraph.com/books/cd558dda-0b8d-48ff-9414-62684bc1c47b"><em>Laws of <span class="small-caps">UX</span></em></a> (thanks <a href="https://eduardo.blog/">Eduardo</a>, for the rec­om­men­da­tion) and got to work.</p>
<h2>Bring Everything Together</h2>
<p>Here’s the se­cret to Harper’s de­sign sys­tem: it has­n’t re­ally ex­isted un­til now.
Each of the in­te­gra­tions, from the Chrome ex­ten­sion, to the web­site, and even the Obsidian plu­gin, had their own de­sign sys­tem and ap­pear­ance.
Mostly, this was be­cause I did­n’t care enough about it when ﬁrst craft­ing these pro­jects.
Things have changed, so I’m go­ing to take my time and do a good job in an at­tempt to ser­vice these user com­plaints.</p>
<p>Before I could start tweak­ing <span class="small-caps">CSS</span> styles, I needed a uni­ﬁed color scheme.
Af­ter some re­search and time in the color picker, I ar­rived at this:</p>
<div style="
  --color-primary-50:#fef4e7;--color-primary-100:#fce9cf;--color-primary-200:#f9d49f;--color-primary-300:#f7be6e;--color-primary-400:#f4a83e;--color-primary:#f1920e;--color-primary-600:#c1750b;--color-primary-700:#915808;--color-primary-800:#603b06;--color-primary-900:#301d03;--color-primary-950:#221402;
  --color-accent-50:#fee7e9;--color-accent-100:#fccfd3;--color-accent-200:#f99fa6;--color-accent-300:#f76e7a;--color-accent-400:#f43e4d;--color-accent:#f10e21;--color-accent-600:#c10b1a;--color-accent-700:#910814;--color-accent-800:#60060d;--color-accent-900:#300307;--color-accent-950:#220205;
  --color-cream:#fef4e7;--color-cream-100:#fce9cf;--color-cream-200:#f9d49f;--color-cream-300:#f7be6e;--color-cream-400:#f4a83e;--color-cream-500:#f1920e;--color-cream-600:#c1750b;--color-cream-700:#915808;--color-cream-800:#603b06;--color-cream-900:#301d03;--color-cream-950:#221402;
  --color-white:#fffdfa;--color-white-100:#fceacf;--color-white-200:#fad59e;--color-white-300:#f7c06e;--color-white-400:#f5ab3d;--color-white-500:#f2960d;--color-white-600:#c2780a;--color-white-700:#915a08;--color-white-800:#613c05;--color-white-900:#301e03;--color-white-950:#221502;
  font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  border:1px solid #00000014;border-radius:12px;padding:14px;background:var(--color-white);
">
  <div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;">
    <div style="width:12px;height:12px;border-radius:999px;background:var(--color-primary);"></div>
    <div style="font-weight:700;">Primary — honey bronze</div>
  </div>
  <div style="display:grid;grid-template-columns:repeat(11,minmax(0,1fr));gap:6px;">
    <div style="background:var(--color-primary-50);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">50</div>
    <div style="background:var(--color-primary-100);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">100</div>
    <div style="background:var(--color-primary-200);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">200</div>
    <div style="background:var(--color-primary-300);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">300</div>
    <div style="background:var(--color-primary-400);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">400</div>
    <div style="background:var(--color-primary);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000cc;">500</div>
    <div style="background:var(--color-primary-600);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">600</div>
    <div style="background:var(--color-primary-700);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">700</div>
    <div style="background:var(--color-primary-800);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">800</div>
    <div style="background:var(--color-primary-900);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">900</div>
    <div style="background:var(--color-primary-950);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">950</div>
  </div>
  <hr style="border:none;border-top:1px solid #00000010;margin:14px 0;">
  <div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;">
    <div style="width:12px;height:12px;border-radius:999px;background:var(--color-accent);"></div>
    <div style="font-weight:700;">Accent — hot fuch­sia</div>
  </div>
  <div style="display:grid;grid-template-columns:repeat(11,minmax(0,1fr));gap:6px;">
    <div style="background:var(--color-accent-50);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">50</div>
    <div style="background:var(--color-accent-100);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">100</div>
    <div style="background:var(--color-accent-200);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">200</div>
    <div style="background:var(--color-accent-300);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">300</div>
    <div style="background:var(--color-accent-400);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">400</div>
    <div style="background:var(--color-accent);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">500</div>
    <div style="background:var(--color-accent-600);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">600</div>
    <div style="background:var(--color-accent-700);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">700</div>
    <div style="background:var(--color-accent-800);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">800</div>
    <div style="background:var(--color-accent-900);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">900</div>
    <div style="background:var(--color-accent-950);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">950</div>
  </div>
  <hr style="border:none;border-top:1px solid #00000010;margin:14px 0;">
  <div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;">
    <div style="width:12px;height:12px;border-radius:999px;background:var(--color-cream);"></div>
    <div style="font-weight:700;">Cream — sim­ple cream</div>
  </div>
  <div style="display:grid;grid-template-columns:repeat(11,minmax(0,1fr));gap:6px;">
    <div style="background:var(--color-cream);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">50</div>
    <div style="background:var(--color-cream-100);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">100</div>
    <div style="background:var(--color-cream-200);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">200</div>
    <div style="background:var(--color-cream-300);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">300</div>
    <div style="background:var(--color-cream-400);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">400</div>
    <div style="background:var(--color-cream-500);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000cc;">500</div>
    <div style="background:var(--color-cream-600);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">600</div>
    <div style="background:var(--color-cream-700);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">700</div>
    <div style="background:var(--color-cream-800);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">800</div>
    <div style="background:var(--color-cream-900);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">900</div>
    <div style="background:var(--color-cream-950);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">950</div>
  </div>
  <hr style="border:none;border-top:1px solid #00000010;margin:14px 0;">
  <div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;">
    <div style="width:12px;height:12px;border-radius:999px;background:var(--color-white);border:1px solid #0000001a;"></div>
    <div style="font-weight:700;">White — warm white ramp</div>
  </div>
  <div style="display:grid;grid-template-columns:repeat(11,minmax(0,1fr));gap:6px;">
    <div style="background:var(--color-white);height:48px;border-radius:8px;border:1px solid #00000012;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">base</div>
    <div style="background:var(--color-white-100);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">100</div>
    <div style="background:var(--color-white-200);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">200</div>
    <div style="background:var(--color-white-300);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">300</div>
    <div style="background:var(--color-white-400);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">400</div>
    <div style="background:var(--color-white-500);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000cc;">500</div>
    <div style="background:var(--color-white-600);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">600</div>
    <div style="background:var(--color-white-700);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">700</div>
    <div style="background:var(--color-white-800);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">800</div>
    <div style="background:var(--color-white-900);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">900</div>
    <div style="background:var(--color-white-950);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">950</div>
  </div>
  <div style="margin-top:14px;display:flex;gap:10px;flex-wrap:wrap;">
    <div style="flex:1;min-width:180px;border-radius:10px;padding:10px;background:linear-gradient(90deg,var(--color-primary-50),var(--color-primary));">
      <div style="font-size:12px;font-weight:700;color:#000000c0;">Primary gra­di­ent</div>
      <div style="font-size:11px;color:#000000a0;">50 → 500</div>
    </div>
    <div style="flex:1;min-width:180px;border-radius:10px;padding:10px;background:linear-gradient(90deg,var(--color-accent-50),var(--color-accent));">
      <div style="font-size:12px;font-weight:700;color:#000000c0;">Accent gra­di­ent</div>
      <div style="font-size:11px;color:#000000a0;">50 → 500</div>
    </div>
    <div style="flex:1;min-width:180px;border-radius:10px;padding:10px;background:linear-gradient(90deg,var(--color-cream),var(--color-cream-500));">
      <div style="font-size:12px;font-weight:700;color:#000000c0;">Cream gra­di­ent</div>
      <div style="font-size:11px;color:#000000a0;">50 → 500</div>
    </div>
    <div style="flex:1;min-width:180px;border-radius:10px;padding:10px;background:linear-gradient(90deg,var(--color-white),var(--color-white-500));border:1px solid #00000010;">
      <div style="font-size:12px;font-weight:700;color:#000000c0;">White gra­di­ent</div>
      <div style="font-size:11px;color:#000000a0;">base → 500</div>
    </div>
  </div>
</div>
<p>From there, I built out a com­po­nent li­brary based on <a href="https://flowbite.com/">Flowbite</a> and re­placed all the rel­e­vant com­po­nents in Harper’s in­te­gra­tions with my own.</p>
<p>Next, I wanted to cre­ate a bit more dy­namism with our ty­pog­ra­phy.
Af­ter spend­ing some time in Google Fonts, I de­cided to use Domine, a serif font for head­ings and Atkinson Hyperlegible for every­thing else.
Once I com­bined the duo with the or­ange-ish yel­low from the color palette (which is usu­ally as­so­ci­ated with cre­ative brands), I felt con­ﬁ­dent that I had nailed what I imag­ined Harper’s per­son­al­ity to be.</p>
<h2>Finalizing the Draft</h2>
<p>I feel that Harper’s land­ing page em­bod­ies the de­sign sys­tem well, so I’ve shown it be­low.
It’s the ﬁrst, and of­ten only,  thing a user sees, so I put some ex­tra care into it.
At the time of writ­ing, <a href="https://writewithharper.com">it is live</a>.</p>
<h3>Before My Changes</h3>
<img src="/images/writewithharper_before.png" alt="The Harper website before my changes">
<h3>After My Changes</h3>
<img src="/images/writewithharper_after.png" alt="The Harper website after my changes">
<h2>Where Do I Go From Here?</h2>
<p>As I said, this mini-pro­ject was sim­ply a draft.
I’ll be work­ing with Harper con­trib­u­tors on a more reg­u­lar ba­sis to con­tinue nail­ing it down, slowly im­prov­ing the Quality of the vi­sual de­sign for the en­tire pro­ject.
If you have any feed­back, thoughts, or ideas for how we can make Harper more user-friendly or vi­su­ally ap­peal­ing, let me know!</p>]]></description>
            <content:encoded><![CDATA[<h1>Quality Requires Visual Design</h1>
<p>Earlier this week, I was look­ing through a table of user feed­back about <a href="https://writewithharper.com">Harper</a>.
I be­lieve that soft­ware should be build <em>with</em> the user—not just <em>for</em> the user—so this is a pretty reg­u­lar rit­ual for me.</p>
<p>I was de­lighted.
Many of the usual com­plaints were to­tally ab­sent from the re­port.
Users were en­coun­ter­ing fewer of the make-or-break bugs that would harm their ex­pe­ri­ence.
This meant that my re­cent push to get Harper pre­pared for a <code>1.0</code> re­lease was work­ing.
Great news! Yippee!</p>
<p>Unfortunately, some­thing new emerged from be­neath the pile of bugs: prob­lems with Harper’s vi­sual de­sign.</p>
<p>For quite some time now, I’ve been fo­cused solely on the prac­ti­cal parts of Quality.
I mean bugs and the hard-to-deny use­ful­ness of Harper’s ser­vice.
In that time, I’ve for­got­ten that the vi­sual ap­peal of the soft­ware is at least as im­por­tant.
So, when I saw these com­plaints with Harper’s vi­sual de­sign, I knew I needed to do some­thing about it.</p>
<p>I’ve never claimed to be a de­signer, but I’m also not one to shy away from a chal­lenge.
So, I grabbed a copy of the <a href="https://app.thestorygraph.com/books/cd558dda-0b8d-48ff-9414-62684bc1c47b"><em>Laws of <span class="small-caps">UX</span></em></a> (thanks <a href="https://eduardo.blog/">Eduardo</a>, for the rec­om­men­da­tion) and got to work.</p>
<h2>Bring Everything Together</h2>
<p>Here’s the se­cret to Harper’s de­sign sys­tem: it has­n’t re­ally ex­isted un­til now.
Each of the in­te­gra­tions, from the Chrome ex­ten­sion, to the web­site, and even the Obsidian plu­gin, had their own de­sign sys­tem and ap­pear­ance.
Mostly, this was be­cause I did­n’t care enough about it when ﬁrst craft­ing these pro­jects.
Things have changed, so I’m go­ing to take my time and do a good job in an at­tempt to ser­vice these user com­plaints.</p>
<p>Before I could start tweak­ing <span class="small-caps">CSS</span> styles, I needed a uni­ﬁed color scheme.
Af­ter some re­search and time in the color picker, I ar­rived at this:</p>
<div style="
  --color-primary-50:#fef4e7;--color-primary-100:#fce9cf;--color-primary-200:#f9d49f;--color-primary-300:#f7be6e;--color-primary-400:#f4a83e;--color-primary:#f1920e;--color-primary-600:#c1750b;--color-primary-700:#915808;--color-primary-800:#603b06;--color-primary-900:#301d03;--color-primary-950:#221402;
  --color-accent-50:#fee7e9;--color-accent-100:#fccfd3;--color-accent-200:#f99fa6;--color-accent-300:#f76e7a;--color-accent-400:#f43e4d;--color-accent:#f10e21;--color-accent-600:#c10b1a;--color-accent-700:#910814;--color-accent-800:#60060d;--color-accent-900:#300307;--color-accent-950:#220205;
  --color-cream:#fef4e7;--color-cream-100:#fce9cf;--color-cream-200:#f9d49f;--color-cream-300:#f7be6e;--color-cream-400:#f4a83e;--color-cream-500:#f1920e;--color-cream-600:#c1750b;--color-cream-700:#915808;--color-cream-800:#603b06;--color-cream-900:#301d03;--color-cream-950:#221402;
  --color-white:#fffdfa;--color-white-100:#fceacf;--color-white-200:#fad59e;--color-white-300:#f7c06e;--color-white-400:#f5ab3d;--color-white-500:#f2960d;--color-white-600:#c2780a;--color-white-700:#915a08;--color-white-800:#613c05;--color-white-900:#301e03;--color-white-950:#221502;
  font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  border:1px solid #00000014;border-radius:12px;padding:14px;background:var(--color-white);
">
  <div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;">
    <div style="width:12px;height:12px;border-radius:999px;background:var(--color-primary);"></div>
    <div style="font-weight:700;">Primary — honey bronze</div>
  </div>
  <div style="display:grid;grid-template-columns:repeat(11,minmax(0,1fr));gap:6px;">
    <div style="background:var(--color-primary-50);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">50</div>
    <div style="background:var(--color-primary-100);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">100</div>
    <div style="background:var(--color-primary-200);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">200</div>
    <div style="background:var(--color-primary-300);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">300</div>
    <div style="background:var(--color-primary-400);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">400</div>
    <div style="background:var(--color-primary);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000cc;">500</div>
    <div style="background:var(--color-primary-600);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">600</div>
    <div style="background:var(--color-primary-700);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">700</div>
    <div style="background:var(--color-primary-800);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">800</div>
    <div style="background:var(--color-primary-900);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">900</div>
    <div style="background:var(--color-primary-950);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">950</div>
  </div>
  <hr style="border:none;border-top:1px solid #00000010;margin:14px 0;">
  <div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;">
    <div style="width:12px;height:12px;border-radius:999px;background:var(--color-accent);"></div>
    <div style="font-weight:700;">Accent — hot fuch­sia</div>
  </div>
  <div style="display:grid;grid-template-columns:repeat(11,minmax(0,1fr));gap:6px;">
    <div style="background:var(--color-accent-50);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">50</div>
    <div style="background:var(--color-accent-100);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">100</div>
    <div style="background:var(--color-accent-200);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">200</div>
    <div style="background:var(--color-accent-300);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">300</div>
    <div style="background:var(--color-accent-400);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">400</div>
    <div style="background:var(--color-accent);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">500</div>
    <div style="background:var(--color-accent-600);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">600</div>
    <div style="background:var(--color-accent-700);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">700</div>
    <div style="background:var(--color-accent-800);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">800</div>
    <div style="background:var(--color-accent-900);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">900</div>
    <div style="background:var(--color-accent-950);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">950</div>
  </div>
  <hr style="border:none;border-top:1px solid #00000010;margin:14px 0;">
  <div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;">
    <div style="width:12px;height:12px;border-radius:999px;background:var(--color-cream);"></div>
    <div style="font-weight:700;">Cream — sim­ple cream</div>
  </div>
  <div style="display:grid;grid-template-columns:repeat(11,minmax(0,1fr));gap:6px;">
    <div style="background:var(--color-cream);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">50</div>
    <div style="background:var(--color-cream-100);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">100</div>
    <div style="background:var(--color-cream-200);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">200</div>
    <div style="background:var(--color-cream-300);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">300</div>
    <div style="background:var(--color-cream-400);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">400</div>
    <div style="background:var(--color-cream-500);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000cc;">500</div>
    <div style="background:var(--color-cream-600);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">600</div>
    <div style="background:var(--color-cream-700);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">700</div>
    <div style="background:var(--color-cream-800);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">800</div>
    <div style="background:var(--color-cream-900);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">900</div>
    <div style="background:var(--color-cream-950);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">950</div>
  </div>
  <hr style="border:none;border-top:1px solid #00000010;margin:14px 0;">
  <div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;">
    <div style="width:12px;height:12px;border-radius:999px;background:var(--color-white);border:1px solid #0000001a;"></div>
    <div style="font-weight:700;">White — warm white ramp</div>
  </div>
  <div style="display:grid;grid-template-columns:repeat(11,minmax(0,1fr));gap:6px;">
    <div style="background:var(--color-white);height:48px;border-radius:8px;border:1px solid #00000012;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">base</div>
    <div style="background:var(--color-white-100);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">100</div>
    <div style="background:var(--color-white-200);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">200</div>
    <div style="background:var(--color-white-300);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">300</div>
    <div style="background:var(--color-white-400);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000b3;">400</div>
    <div style="background:var(--color-white-500);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#000000cc;">500</div>
    <div style="background:var(--color-white-600);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">600</div>
    <div style="background:var(--color-white-700);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">700</div>
    <div style="background:var(--color-white-800);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">800</div>
    <div style="background:var(--color-white-900);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">900</div>
    <div style="background:var(--color-white-950);height:48px;border-radius:8px;display:flex;align-items:end;justify-content:center;font-size:11px;padding:6px;color:#fff;">950</div>
  </div>
  <div style="margin-top:14px;display:flex;gap:10px;flex-wrap:wrap;">
    <div style="flex:1;min-width:180px;border-radius:10px;padding:10px;background:linear-gradient(90deg,var(--color-primary-50),var(--color-primary));">
      <div style="font-size:12px;font-weight:700;color:#000000c0;">Primary gra­di­ent</div>
      <div style="font-size:11px;color:#000000a0;">50 → 500</div>
    </div>
    <div style="flex:1;min-width:180px;border-radius:10px;padding:10px;background:linear-gradient(90deg,var(--color-accent-50),var(--color-accent));">
      <div style="font-size:12px;font-weight:700;color:#000000c0;">Accent gra­di­ent</div>
      <div style="font-size:11px;color:#000000a0;">50 → 500</div>
    </div>
    <div style="flex:1;min-width:180px;border-radius:10px;padding:10px;background:linear-gradient(90deg,var(--color-cream),var(--color-cream-500));">
      <div style="font-size:12px;font-weight:700;color:#000000c0;">Cream gra­di­ent</div>
      <div style="font-size:11px;color:#000000a0;">50 → 500</div>
    </div>
    <div style="flex:1;min-width:180px;border-radius:10px;padding:10px;background:linear-gradient(90deg,var(--color-white),var(--color-white-500));border:1px solid #00000010;">
      <div style="font-size:12px;font-weight:700;color:#000000c0;">White gra­di­ent</div>
      <div style="font-size:11px;color:#000000a0;">base → 500</div>
    </div>
  </div>
</div>
<p>From there, I built out a com­po­nent li­brary based on <a href="https://flowbite.com/">Flowbite</a> and re­placed all the rel­e­vant com­po­nents in Harper’s in­te­gra­tions with my own.</p>
<p>Next, I wanted to cre­ate a bit more dy­namism with our ty­pog­ra­phy.
Af­ter spend­ing some time in Google Fonts, I de­cided to use Domine, a serif font for head­ings and Atkinson Hyperlegible for every­thing else.
Once I com­bined the duo with the or­ange-ish yel­low from the color palette (which is usu­ally as­so­ci­ated with cre­ative brands), I felt con­ﬁ­dent that I had nailed what I imag­ined Harper’s per­son­al­ity to be.</p>
<h2>Finalizing the Draft</h2>
<p>I feel that Harper’s land­ing page em­bod­ies the de­sign sys­tem well, so I’ve shown it be­low.
It’s the ﬁrst, and of­ten only,  thing a user sees, so I put some ex­tra care into it.
At the time of writ­ing, <a href="https://writewithharper.com">it is live</a>.</p>
<h3>Before My Changes</h3>
<img src="/images/writewithharper_before.png" alt="The Harper website before my changes">
<h3>After My Changes</h3>
<img src="/images/writewithharper_after.png" alt="The Harper website after my changes">
<h2>Where Do I Go From Here?</h2>
<p>As I said, this mini-pro­ject was sim­ply a draft.
I’ll be work­ing with Harper con­trib­u­tors on a more reg­u­lar ba­sis to con­tinue nail­ing it down, slowly im­prov­ing the Quality of the vi­sual de­sign for the en­tire pro­ject.
If you have any feed­back, thoughts, or ideas for how we can make Harper more user-friendly or vi­su­ally ap­peal­ing, let me know!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Re: Collaboration Sucks]]></title>
            <link>https://elijahpotter.dev/articles/re:_collaboration_sucks</link>
            <guid>https://elijahpotter.dev/articles/re:_collaboration_sucks</guid>
            <pubDate>Tue, 18 Nov 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Re: Collaboration Sucks</h1>
<p>Earlier this week, I came across a re­ally great post from a prod­uct en­gi­neer over at PostHog. If you haven’t al­ready, I highly rec­om­mend <a href="https://newsletter.posthog.com/p/collaboration-sucks">read­ing it</a>. With the ti­tle<span class="push-double"></span> <span class="pull-double">“</span>Collaboration sucks,” I think it makes its mes­sage clear.</p>
<p>If you had shown this ar­ti­cle to the Elijah Potter from a year ago, he would have agreed with most of what it says. I was of the strong opin­ion that col­lab­o­ra­tion was fun­da­men­tally coun­ter­pro­duc­tive to get­ting sh*t done. Since then, I’ve com­pletely changed my tune. While I don’t be­lieve col­lab­o­ra­tion is the se­cret weapon my pro­fes­sors in school made it out to be, I think it’s es­sen­tial to work­ing on am­bi­tious pro­jects for a long time with­out burn­ing out. Furthermore, at least for open source pro­jects, I think it’s crit­i­cal for pri­or­i­ti­za­tion of work.</p>
<p>First, let’s get some­thing out of the way. Although the post ti­tle de­clared that col­lab­o­ra­tion in any form was bad, the meat and pota­toes of the post have a slightly dif­fer­ent tune. He merely said that it is­n’t that col­lab­o­ra­tion is bad, just that <em>too much</em> col­lab­o­ra­tion is bad. I can get be­hind this, but I still think there’s more nu­ance lurk­ing in the shad­ows.</p>
<p>I think Cook’s post out­lines many of the var­i­ous ways col­lab­o­ra­tion can go wrong. I par­tic­u­larly en­joy the red ﬂag ex­am­ples he pro­vides. I’d like to pro­vide some ex­am­ples of col­lab­o­ra­tion go­ing <em>right.</em></p>
<p>Since all of this is strictly re­lated to <a href="https://writewithharper.com/">Harper</a>, it’s pos­si­ble that both these ex­am­ples are only help­ful to open source pro­jects.</p>
<h2>It Boosts Motivation</h2>
<p>When I go a lit­tle bit too long with­out en­gag­ing with a mem­ber of the Harper user base, I start to feel a bit de­ﬂated. I start to for­get who it is all for.</p>
<img src="/images/bluesky_testimonial.png" alt="A recent thread found on Bluesky discussing Harper">
<p>As a part of my <a href="https://elijahpotter.dev/articles/my-writing-environment-as-a-software-engineer">own writ­ing en­vi­ron­ment</a>, Harper ex­ists to make writ­ing more fun and more hu­man. I can think crit­i­cally about my mes­sage while spend­ing less time wor­ry­ing about gram­mar or punc­tu­a­tion. For oth­ers, it poses to do all that with­out vi­o­lat­ing their pri­vacy. It’s great to hear from them, and how we can make our ser­vice even bet­ter.</p>
<img src="/images/discord_testimonial.png" alt="A recent conversation on Discord.">
<p>I know I’m do­ing some­thing right when I’m act­ing as cus­tomer sup­port and read a mes­sage like this. I feel em­pow­ered. Not only do I know <em>what</em> I am do­ing right, I feel highly mo­ti­vated to keep do­ing that thing.</p>
<h2>In Open Source, Your Collaborators Are Also Your Users</h2>
<p>When work­ing on open source soft­ware, es­pe­cially one that val­ues pri­vacy, clearly stat­ing a pol­icy of be­ing <a href="https://elijahpotter.dev/articles/never-wait">friendly to pull re­quests</a> is ob­vi­ously crit­i­cal. For one, it makes it clear that bug ﬁxes are wel­come and will be swiftly re­viewed. Less ob­vi­ous is the feed­back re­sid­ing within the pull re­quest.</p>
<img src="/images/harper_issue_2143.png" alt="Harper Issue #2143">
<p>Here’s <a href="https://github.com/Automattic/harper/pull/2143">an ex­am­ple</a>. A user en­coun­tered an is­sue re­sult­ing from a change in Obsidian’s <span class="small-caps">API</span>. Not only did he take the time to re­port the prob­lem, he also went through the ef­fort of im­ple­ment­ing a ﬁx. What does this tell us?</p>
<p>For one, it gives us an idea of where our doc­u­men­ta­tion is suf­ﬁ­cient. He was able to ﬁnd the in­for­ma­tion needed to build the code and run it on his own ma­chine. In the fu­ture, we don’t need to pri­or­i­tize the Obsidian plug­in’s doc­u­men­ta­tion.</p>
<p>More im­por­tantly, how­ever, is the fact that the fea­ture was valu­able enough for this user to spend time ﬁx­ing it. If it is valu­able for him, we can safely say that it is valu­able for other users. Now that I have suf­ﬁ­cient ev­i­dence, I might con­sider bring­ing sim­i­lar func­tion­al­ity to other plat­forms, like our Chrome ex­ten­sion.</p>
<p>Collaborating in an open source con­text can be hugely in­for­ma­tive, since your col­lab­o­ra­tors are also your users. In my ex­pe­ri­ence, this is sur­pris­ingly un­com­mon, even in com­pa­nies that claim to have a cul­ture of feed­back.</p>
<h2>There Is Still Such a Thing As Too Much</h2>
<p>While I’ve found these par­tic­u­lar cases to be pro­duc­tiv­ity-boost­ers, I un­der­stand that fo­cus­ing too much on any one thing can be detri­men­tal. First and fore­most, the goal should be to ship as high a qual­ity of soft­ware as pos­si­ble, as soon as pos­si­ble.</p>]]></description>
            <content:encoded><![CDATA[<h1>Re: Collaboration Sucks</h1>
<p>Earlier this week, I came across a re­ally great post from a prod­uct en­gi­neer over at PostHog. If you haven’t al­ready, I highly rec­om­mend <a href="https://newsletter.posthog.com/p/collaboration-sucks">read­ing it</a>. With the ti­tle<span class="push-double"></span> <span class="pull-double">“</span>Collaboration sucks,” I think it makes its mes­sage clear.</p>
<p>If you had shown this ar­ti­cle to the Elijah Potter from a year ago, he would have agreed with most of what it says. I was of the strong opin­ion that col­lab­o­ra­tion was fun­da­men­tally coun­ter­pro­duc­tive to get­ting sh*t done. Since then, I’ve com­pletely changed my tune. While I don’t be­lieve col­lab­o­ra­tion is the se­cret weapon my pro­fes­sors in school made it out to be, I think it’s es­sen­tial to work­ing on am­bi­tious pro­jects for a long time with­out burn­ing out. Furthermore, at least for open source pro­jects, I think it’s crit­i­cal for pri­or­i­ti­za­tion of work.</p>
<p>First, let’s get some­thing out of the way. Although the post ti­tle de­clared that col­lab­o­ra­tion in any form was bad, the meat and pota­toes of the post have a slightly dif­fer­ent tune. He merely said that it is­n’t that col­lab­o­ra­tion is bad, just that <em>too much</em> col­lab­o­ra­tion is bad. I can get be­hind this, but I still think there’s more nu­ance lurk­ing in the shad­ows.</p>
<p>I think Cook’s post out­lines many of the var­i­ous ways col­lab­o­ra­tion can go wrong. I par­tic­u­larly en­joy the red ﬂag ex­am­ples he pro­vides. I’d like to pro­vide some ex­am­ples of col­lab­o­ra­tion go­ing <em>right.</em></p>
<p>Since all of this is strictly re­lated to <a href="https://writewithharper.com/">Harper</a>, it’s pos­si­ble that both these ex­am­ples are only help­ful to open source pro­jects.</p>
<h2>It Boosts Motivation</h2>
<p>When I go a lit­tle bit too long with­out en­gag­ing with a mem­ber of the Harper user base, I start to feel a bit de­ﬂated. I start to for­get who it is all for.</p>
<img src="/images/bluesky_testimonial.png" alt="A recent thread found on Bluesky discussing Harper">
<p>As a part of my <a href="https://elijahpotter.dev/articles/my-writing-environment-as-a-software-engineer">own writ­ing en­vi­ron­ment</a>, Harper ex­ists to make writ­ing more fun and more hu­man. I can think crit­i­cally about my mes­sage while spend­ing less time wor­ry­ing about gram­mar or punc­tu­a­tion. For oth­ers, it poses to do all that with­out vi­o­lat­ing their pri­vacy. It’s great to hear from them, and how we can make our ser­vice even bet­ter.</p>
<img src="/images/discord_testimonial.png" alt="A recent conversation on Discord.">
<p>I know I’m do­ing some­thing right when I’m act­ing as cus­tomer sup­port and read a mes­sage like this. I feel em­pow­ered. Not only do I know <em>what</em> I am do­ing right, I feel highly mo­ti­vated to keep do­ing that thing.</p>
<h2>In Open Source, Your Collaborators Are Also Your Users</h2>
<p>When work­ing on open source soft­ware, es­pe­cially one that val­ues pri­vacy, clearly stat­ing a pol­icy of be­ing <a href="https://elijahpotter.dev/articles/never-wait">friendly to pull re­quests</a> is ob­vi­ously crit­i­cal. For one, it makes it clear that bug ﬁxes are wel­come and will be swiftly re­viewed. Less ob­vi­ous is the feed­back re­sid­ing within the pull re­quest.</p>
<img src="/images/harper_issue_2143.png" alt="Harper Issue #2143">
<p>Here’s <a href="https://github.com/Automattic/harper/pull/2143">an ex­am­ple</a>. A user en­coun­tered an is­sue re­sult­ing from a change in Obsidian’s <span class="small-caps">API</span>. Not only did he take the time to re­port the prob­lem, he also went through the ef­fort of im­ple­ment­ing a ﬁx. What does this tell us?</p>
<p>For one, it gives us an idea of where our doc­u­men­ta­tion is suf­ﬁ­cient. He was able to ﬁnd the in­for­ma­tion needed to build the code and run it on his own ma­chine. In the fu­ture, we don’t need to pri­or­i­tize the Obsidian plug­in’s doc­u­men­ta­tion.</p>
<p>More im­por­tantly, how­ever, is the fact that the fea­ture was valu­able enough for this user to spend time ﬁx­ing it. If it is valu­able for him, we can safely say that it is valu­able for other users. Now that I have suf­ﬁ­cient ev­i­dence, I might con­sider bring­ing sim­i­lar func­tion­al­ity to other plat­forms, like our Chrome ex­ten­sion.</p>
<p>Collaborating in an open source con­text can be hugely in­for­ma­tive, since your col­lab­o­ra­tors are also your users. In my ex­pe­ri­ence, this is sur­pris­ingly un­com­mon, even in com­pa­nies that claim to have a cul­ture of feed­back.</p>
<h2>There Is Still Such a Thing As Too Much</h2>
<p>While I’ve found these par­tic­u­lar cases to be pro­duc­tiv­ity-boost­ers, I un­der­stand that fo­cus­ing too much on any one thing can be detri­men­tal. First and fore­most, the goal should be to ship as high a qual­ity of soft­ware as pos­si­ble, as soon as pos­si­ble.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Improving Rust Compile Times by 71 Percent]]></title>
            <link>https://elijahpotter.dev/articles/improving_rust_compile_times_by_71_percent</link>
            <guid>https://elijahpotter.dev/articles/improving_rust_compile_times_by_71_percent</guid>
            <pubDate>Thu, 06 Nov 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Improving Rust Compile Times By 71%</h1>
<p>If you main­tain or work on a pro­ject in any com­piled lan­guage, par­tic­u­larly a lan­guage that is known for hav­ing a slow com­piler, I be­lieve it is crit­i­cal to reg­u­larly sched­ule time to do an analy­sis of your build process and make ad­just­ments if nec­es­sary.</p>
<p>Compile-time in­creases slowly as a pro­ject morphs and grows.
Slow enough that it of­ten goes un­no­ticed.
I call this<span class="push-double"></span> <span class="pull-double">“</span>compiler creep” and be­lieve it to be sim­i­lar in many ways to <a href="https://en.wikipedia.org/wiki/Lifestyle_creep">lifestyle creep</a>.
By sched­ul­ing time to do a full ac­count­ing of where your <span class="small-caps">CPU</span> cy­cles are go­ing, you can pre­emp­tively save your­self (and your con­trib­u­tors) a lot of time.</p>
<p>Yesterday was the big day for me. Over the last few months, <a href="https://github.com/automattic/harper">Harper’s</a> com­pile time from scratch in GitHub ac­tions had bal­looned to a ﬁve full min­utes. On my per­sonal lap­top, it had grown to three min­utes. That may not sound like a lot, but I of­ten need to build from scratch when swap­ping be­tween branches or check­ing out a pull re­quest. Incremental build times had also grown to an un­jus­ti­ﬁ­able du­ra­tion. Iterating upon unit tests was start­ing to be­come quite slow.</p>
<p>Harper’s low la­tency is one of the rea­sons many turn to it over the al­ter­na­tives. In the early days, we ac­com­plished that by crank­ing the <span class="small-caps">LLVM</span> op­ti­mizer to the max, re­duc­ing the num­ber of code­gen units to one and us­ing link-time op­ti­miza­tion to take care of the rest. At the time, I be­lieve this re­sulted in a ~30% per­for­mance up­lift. Not bad!</p>
<p>For the unini­ti­ated, a code­gen unit is the small­est unit of work that is passed to the <span class="small-caps">LLVM</span> com­piler. If you al­low the pro­ject to be split into more than one code­gen unit, you can al­low more than one com­piler to run at a time. This, there­fore, in­creases the num­ber of phys­i­cal cores that can be used at any given mo­ment dur­ing com­pi­la­tion. The down­side is that the com­piler misses out on op­ti­miza­tion op­por­tu­ni­ties at the bound­aries be­tween code­gen units.</p>
<p>So when I sat down yes­ter­day, I was hardly sur­prised to see that code gen­er­a­tion was the bot­tle­neck. The com­piler was spend­ing very lit­tle time type-check­ing and link­ing, and a lot of time gen­er­at­ing and op­ti­miz­ing ma­chine code.</p>
<p>My ini­tial thought was to re­duce the <em>amount</em> of code that needed op­ti­miz­ing.
So I did what any­one else would do and went search­ing for un­nec­es­sary de­pen­den­cies and other kinds of dead code in the repos­i­tory. I found some, but not enough to make a mean­ing­ful dent in the com­pile time.</p>
<p>That’s when I took a look at our build con­ﬁg­u­ra­tion and asked my­self:<span class="push-double"></span> <span class="pull-double">“</span>Do we still need LTO? Could we in­crease our code­gen units?”</p>
<p>In the last few months, I’ve made some sig­nif­i­cant im­prove­ments to caching and pipelin­ing, which means the bot­tle­neck has shifted from com­pile-time op­ti­miza­tion to mem­ory band­width. We sim­ply don’t need the com­piler to do as much work any­more. So, I tried dis­abling <span class="small-caps">LTO</span> and re­vert­ing the code­gen units to their de­fault value. What hap­pened?</p>
<p>Clean builds went from ﬁve min­utes on <span class="small-caps">CI</span> to less than two. All in, that’s an im­prove­ment of 71%. On top of that, since it was the code gen­er­a­tion stage that was slow­ing down in­cre­men­tal builds as well, we saw a sim­i­lar im­prove­ment to test ex­e­cu­tion time. That means there is now less time be­tween mak­ing a change in the code, and know­ing how it af­fects our ex­ist­ing test cases. Since that’s a huge por­tion of a con­trib­u­tor’s time, im­prove­ments to in­cre­men­tal builds can be force-mul­ti­pli­ers for the en­tire pro­ject.</p>
<p>I was sur­prised such a sim­ple change would have such an out­sized im­pact on the de­vel­oper ex­pe­ri­ence. It felt too good to be true, so I re-ran our bench­marks to see if the change would have a neg­a­tive im­pact on Harper’s ac­tual per­for­mance. Our un­cached lint times took three per­cent longer than be­fore. I think that’s ac­cept­able.</p>
<p>Is there a les­son to be learned here? I think so. I im­plore you to take a half-hour this week to mess around with your com­pil­er’s set­tings. Even an im­prove­ment of a few sec­onds per it­er­a­tion can com­pound into hours of time sav­ings, both for you and your fel­low con­trib­u­tors. If you think your com­piler set­tings are al­ready per­fect, do it any­way. Your code or re­quire­ments may have changed.</p>
<p>This is great step in my re­cent work to make peo­ple’s ﬁrst con­tri­bu­tions to Harper as fric­tion­less as pos­si­ble.</p>]]></description>
            <content:encoded><![CDATA[<h1>Improving Rust Compile Times By 71%</h1>
<p>If you main­tain or work on a pro­ject in any com­piled lan­guage, par­tic­u­larly a lan­guage that is known for hav­ing a slow com­piler, I be­lieve it is crit­i­cal to reg­u­larly sched­ule time to do an analy­sis of your build process and make ad­just­ments if nec­es­sary.</p>
<p>Compile-time in­creases slowly as a pro­ject morphs and grows.
Slow enough that it of­ten goes un­no­ticed.
I call this<span class="push-double"></span> <span class="pull-double">“</span>compiler creep” and be­lieve it to be sim­i­lar in many ways to <a href="https://en.wikipedia.org/wiki/Lifestyle_creep">lifestyle creep</a>.
By sched­ul­ing time to do a full ac­count­ing of where your <span class="small-caps">CPU</span> cy­cles are go­ing, you can pre­emp­tively save your­self (and your con­trib­u­tors) a lot of time.</p>
<p>Yesterday was the big day for me. Over the last few months, <a href="https://github.com/automattic/harper">Harper’s</a> com­pile time from scratch in GitHub ac­tions had bal­looned to a ﬁve full min­utes. On my per­sonal lap­top, it had grown to three min­utes. That may not sound like a lot, but I of­ten need to build from scratch when swap­ping be­tween branches or check­ing out a pull re­quest. Incremental build times had also grown to an un­jus­ti­ﬁ­able du­ra­tion. Iterating upon unit tests was start­ing to be­come quite slow.</p>
<p>Harper’s low la­tency is one of the rea­sons many turn to it over the al­ter­na­tives. In the early days, we ac­com­plished that by crank­ing the <span class="small-caps">LLVM</span> op­ti­mizer to the max, re­duc­ing the num­ber of code­gen units to one and us­ing link-time op­ti­miza­tion to take care of the rest. At the time, I be­lieve this re­sulted in a ~30% per­for­mance up­lift. Not bad!</p>
<p>For the unini­ti­ated, a code­gen unit is the small­est unit of work that is passed to the <span class="small-caps">LLVM</span> com­piler. If you al­low the pro­ject to be split into more than one code­gen unit, you can al­low more than one com­piler to run at a time. This, there­fore, in­creases the num­ber of phys­i­cal cores that can be used at any given mo­ment dur­ing com­pi­la­tion. The down­side is that the com­piler misses out on op­ti­miza­tion op­por­tu­ni­ties at the bound­aries be­tween code­gen units.</p>
<p>So when I sat down yes­ter­day, I was hardly sur­prised to see that code gen­er­a­tion was the bot­tle­neck. The com­piler was spend­ing very lit­tle time type-check­ing and link­ing, and a lot of time gen­er­at­ing and op­ti­miz­ing ma­chine code.</p>
<p>My ini­tial thought was to re­duce the <em>amount</em> of code that needed op­ti­miz­ing.
So I did what any­one else would do and went search­ing for un­nec­es­sary de­pen­den­cies and other kinds of dead code in the repos­i­tory. I found some, but not enough to make a mean­ing­ful dent in the com­pile time.</p>
<p>That’s when I took a look at our build con­ﬁg­u­ra­tion and asked my­self:<span class="push-double"></span> <span class="pull-double">“</span>Do we still need LTO? Could we in­crease our code­gen units?”</p>
<p>In the last few months, I’ve made some sig­nif­i­cant im­prove­ments to caching and pipelin­ing, which means the bot­tle­neck has shifted from com­pile-time op­ti­miza­tion to mem­ory band­width. We sim­ply don’t need the com­piler to do as much work any­more. So, I tried dis­abling <span class="small-caps">LTO</span> and re­vert­ing the code­gen units to their de­fault value. What hap­pened?</p>
<p>Clean builds went from ﬁve min­utes on <span class="small-caps">CI</span> to less than two. All in, that’s an im­prove­ment of 71%. On top of that, since it was the code gen­er­a­tion stage that was slow­ing down in­cre­men­tal builds as well, we saw a sim­i­lar im­prove­ment to test ex­e­cu­tion time. That means there is now less time be­tween mak­ing a change in the code, and know­ing how it af­fects our ex­ist­ing test cases. Since that’s a huge por­tion of a con­trib­u­tor’s time, im­prove­ments to in­cre­men­tal builds can be force-mul­ti­pli­ers for the en­tire pro­ject.</p>
<p>I was sur­prised such a sim­ple change would have such an out­sized im­pact on the de­vel­oper ex­pe­ri­ence. It felt too good to be true, so I re-ran our bench­marks to see if the change would have a neg­a­tive im­pact on Harper’s ac­tual per­for­mance. Our un­cached lint times took three per­cent longer than be­fore. I think that’s ac­cept­able.</p>
<p>Is there a les­son to be learned here? I think so. I im­plore you to take a half-hour this week to mess around with your com­pil­er’s set­tings. Even an im­prove­ment of a few sec­onds per it­er­a­tion can com­pound into hours of time sav­ings, both for you and your fel­low con­trib­u­tors. If you think your com­piler set­tings are al­ready per­fect, do it any­way. Your code or re­quire­ments may have changed.</p>
<p>This is great step in my re­cent work to make peo­ple’s ﬁrst con­tri­bu­tions to Harper as fric­tion­less as pos­si­ble.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Finding Signal Through the Noise]]></title>
            <link>https://elijahpotter.dev/articles/finding_signal_through_the_noise</link>
            <guid>https://elijahpotter.dev/articles/finding_signal_through_the_noise</guid>
            <pubDate>Mon, 03 Nov 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Finding Signal Through The Noise</h1>
<p>As the main­tainer of <a href="https://github.com/automattic/harper">Harper</a>, I read through dozens of is­sues and pull re­quests per day and count­less more per week.
There is a con­stant ﬂood of new prob­lems to ﬁx and new code to re­view.
At times, it can get over­whelm­ing.</p>
<p>I’ve been work­ing re­cently to re­duce the amount of noise I en­counter when de­ter­min­ing what to work on.
I’m shar­ing my ﬁnd­ings here to help other novice main­tain­ers han­dle the tor­rent.
Hope­fully, there are some ideas here that you ﬁnd in­sight­ful.</p>
<p>When your open source pro­ject gets big enough, you in­evitably hit a point where the ﬂow of bug re­ports or fea­ture re­quests ex­pands be­yond what you’re in­di­vid­u­ally able to ﬁx or im­ple­ment.
If you’re like <a href="https://www.jeffgeerling.com/">Jeff Geerling</a>, this might hap­pen when you ﬁnd your­self main­tain­ing a larger <strong>num­ber</strong> of pro­jects.
Ei­ther way, it’s clear that there will come a time where you need to start mak­ing hard de­ci­sions on how to spend your time.</p>
<h2>Set A Policy</h2>
<p>Jeff’s so­lu­tion, which I’ve adopted, is to set <a href="https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories">well-de­ﬁned</a> poli­cies for which is­sues are al­lowed to slip through the cracks.</p>
<p>The strat­egy is sim­ple in essence.
The main­tainer, Geerling or I in this case, di­rectly ad­dresses is­sues and PRs which are im­por­tant to the ex­ist­ing func­tion­al­ity of the soft­ware ﬁrst.
New fea­tures or mi­nor bugs come sec­ond.
Fea­ture re­quests that help a sin­gle per­son or a bug re­ports that ad­dress a rare edge case are of low­est pri­or­ity.</p>
<p>If an is­sue or pull re­quest can­not be ad­dressed within 30 days of its open­ing, it is marked stale.
If an­other 30 days pass be­fore it be­comes ac­tive again, it is closed.
Over time, unim­por­tant top­ics are cleared away, leav­ing only the ones crit­i­cal to the pro­jec­t’s mis­sion.</p>
<p>A pol­icy like this sets<span class="push-double"></span> <span class="pull-double">“</span>no” as the de­fault and helps pre­vent run­away code qual­ity de­clines.
I in­sti­tuted it on Monday.
If it needs re­vi­sion, I’m open to com­ments and feed­back.</p>
<h3>Harper’s False Positives</h3>
<p>This pol­icy stands in the face of an al­most over­whelm­ing num­ber of is­sues re­lated to false-pos­i­tives in Harper’s al­go­rithm.
<a href="https://github.com/Automattic/harper/issues/2124">Here’s a rep­re­sen­ta­tive ex­am­ple.</a></p>
<p>Since these false-pos­i­tive re­ports rep­re­sent such a sig­nif­i­cant plu­ral­ity of the is­sues on GitHub, I feel the need to han­dle them sep­a­rately from every­thing else.
They’re usu­ally re­solved quickly but left open out of ne­glect. They’re triv­ial enough for me to ﬁx, but not for me to fol­low-up.
Mov­ing for­ward, if any­one needs to re­port an is­sue re­lated to a false-pos­i­tive, please do so <a href="https://writewithharper.com/report-problematic-lint">here</a>.</p>
<h2>Develop a Sense of Importance</h2>
<p>There is no al­go­rithm to de­ter­mine which is­sues or PRs are the most im­por­tant to a pro­jec­t’s suc­cess.
Typ­i­cally, I’ll read through ﬁve or so, and pick the one that<span class="push-double"></span> <span class="pull-double">“</span>feels” the most im­por­tant to work on.
It’s of­ten a bal­ance of pri­or­i­tiz­ing time-sen­si­tive items, items that could re­sult in pub­lic­ity, and use­ful­ness to my per­sonal work.
It’s hard to put it into words, which is why it must be de­vel­oped, not taught.</p>
<h2>What Do You Think?</h2>
<p>I con­sider my­self a novice main­tainer, so I’d love to hear from more ma­ture main­tain­ers.
How do you ﬁnd sig­nal through the noise?</p>
<hr>
<h2>Additional Reading</h2>
<ul>
<li><a href="https://www.jeffgeerling.com/blog/2016/why-i-close-prs-oss-project-maintainer-notes">Why I close PRs - Jeff Geerling</a></li>
</ul>]]></description>
            <content:encoded><![CDATA[<h1>Finding Signal Through The Noise</h1>
<p>As the main­tainer of <a href="https://github.com/automattic/harper">Harper</a>, I read through dozens of is­sues and pull re­quests per day and count­less more per week.
There is a con­stant ﬂood of new prob­lems to ﬁx and new code to re­view.
At times, it can get over­whelm­ing.</p>
<p>I’ve been work­ing re­cently to re­duce the amount of noise I en­counter when de­ter­min­ing what to work on.
I’m shar­ing my ﬁnd­ings here to help other novice main­tain­ers han­dle the tor­rent.
Hope­fully, there are some ideas here that you ﬁnd in­sight­ful.</p>
<p>When your open source pro­ject gets big enough, you in­evitably hit a point where the ﬂow of bug re­ports or fea­ture re­quests ex­pands be­yond what you’re in­di­vid­u­ally able to ﬁx or im­ple­ment.
If you’re like <a href="https://www.jeffgeerling.com/">Jeff Geerling</a>, this might hap­pen when you ﬁnd your­self main­tain­ing a larger <strong>num­ber</strong> of pro­jects.
Ei­ther way, it’s clear that there will come a time where you need to start mak­ing hard de­ci­sions on how to spend your time.</p>
<h2>Set A Policy</h2>
<p>Jeff’s so­lu­tion, which I’ve adopted, is to set <a href="https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories">well-de­ﬁned</a> poli­cies for which is­sues are al­lowed to slip through the cracks.</p>
<p>The strat­egy is sim­ple in essence.
The main­tainer, Geerling or I in this case, di­rectly ad­dresses is­sues and PRs which are im­por­tant to the ex­ist­ing func­tion­al­ity of the soft­ware ﬁrst.
New fea­tures or mi­nor bugs come sec­ond.
Fea­ture re­quests that help a sin­gle per­son or a bug re­ports that ad­dress a rare edge case are of low­est pri­or­ity.</p>
<p>If an is­sue or pull re­quest can­not be ad­dressed within 30 days of its open­ing, it is marked stale.
If an­other 30 days pass be­fore it be­comes ac­tive again, it is closed.
Over time, unim­por­tant top­ics are cleared away, leav­ing only the ones crit­i­cal to the pro­jec­t’s mis­sion.</p>
<p>A pol­icy like this sets<span class="push-double"></span> <span class="pull-double">“</span>no” as the de­fault and helps pre­vent run­away code qual­ity de­clines.
I in­sti­tuted it on Monday.
If it needs re­vi­sion, I’m open to com­ments and feed­back.</p>
<h3>Harper’s False Positives</h3>
<p>This pol­icy stands in the face of an al­most over­whelm­ing num­ber of is­sues re­lated to false-pos­i­tives in Harper’s al­go­rithm.
<a href="https://github.com/Automattic/harper/issues/2124">Here’s a rep­re­sen­ta­tive ex­am­ple.</a></p>
<p>Since these false-pos­i­tive re­ports rep­re­sent such a sig­nif­i­cant plu­ral­ity of the is­sues on GitHub, I feel the need to han­dle them sep­a­rately from every­thing else.
They’re usu­ally re­solved quickly but left open out of ne­glect. They’re triv­ial enough for me to ﬁx, but not for me to fol­low-up.
Mov­ing for­ward, if any­one needs to re­port an is­sue re­lated to a false-pos­i­tive, please do so <a href="https://writewithharper.com/report-problematic-lint">here</a>.</p>
<h2>Develop a Sense of Importance</h2>
<p>There is no al­go­rithm to de­ter­mine which is­sues or PRs are the most im­por­tant to a pro­jec­t’s suc­cess.
Typ­i­cally, I’ll read through ﬁve or so, and pick the one that<span class="push-double"></span> <span class="pull-double">“</span>feels” the most im­por­tant to work on.
It’s of­ten a bal­ance of pri­or­i­tiz­ing time-sen­si­tive items, items that could re­sult in pub­lic­ity, and use­ful­ness to my per­sonal work.
It’s hard to put it into words, which is why it must be de­vel­oped, not taught.</p>
<h2>What Do You Think?</h2>
<p>I con­sider my­self a novice main­tainer, so I’d love to hear from more ma­ture main­tain­ers.
How do you ﬁnd sig­nal through the noise?</p>
<hr>
<h2>Additional Reading</h2>
<ul>
<li><a href="https://www.jeffgeerling.com/blog/2016/why-i-close-prs-oss-project-maintainer-notes">Why I close PRs - Jeff Geerling</a></li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Avoid Complexity]]></title>
            <link>https://elijahpotter.dev/articles/avoid_complexity</link>
            <guid>https://elijahpotter.dev/articles/avoid_complexity</guid>
            <pubDate>Fri, 17 Oct 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Avoid Complexity</h1>
<p>For as long as I can re­mem­ber, I’ve told peo­ple that the real chal­lenge of soft­ware en­gi­neer­ing is­n’t writ­ing code.
It is­n’t doc­u­men­ta­tion or <span class="small-caps">CI</span> pipelines ei­ther.
No, the biggest chal­lenge in soft­ware en­gi­neer­ing is man­ag­ing com­plex­ity.</p>
<p>Think about it.
The typ­i­cal soft­ware en­gi­neer is a sin­gle per­son on a vast team, writ­ing and edit­ing a sys­tem com­posed of hun­dreds, mil­lions, or even bil­lions of lines of code.
I ﬁnd that in­tim­i­dat­ing.
How in the world are we sup­posed to build ro­bust and re­li­able sys­tems when fac­ing such com­plex odds?</p>
<p>I worry that, in my fas­ci­na­tion in the <a href="./LLM_assisted_fuzzing">novel</a> <a href="more_transformation-based_learning">ap­pli­ca­tions</a> of ma­chine learn­ing we hear about on a day-to-day ba­sis, I’ve got­ten dis­tracted from the beast of com­plex­ity.
I’ve al­lowed the Harper code­base to ac­crue tech debt.</p>
<p>I re­cently watched <a href="https://www.youtube.com/watch?v=gcwzWzC7gUA"><span class="small-caps">DHH</span><span class="push-single"></span><span class="pull-single">’</span>s keynote from Rails World 2025</a>.
While I def­i­nitely do not agree with every­thing he has to say, I be­lieve he makes some poignant points about com­plex­ity.
Some things, he says, which we con­sider nor­mal to­day would have been lu­di­crous ten years ago.
Some­how, with com­puter hard­ware faster than ever, we’ve ar­rived at a point where 20 minute <span class="small-caps">CI</span> runs are ac­cept­able.
Where a de­ploy to pro­duc­tion can take an hour and set­ting up a new en­vi­ron­ment can take days.</p>
<p>I want to change that—at least, for Harper.</p>
<p>While work­ing on sev­eral smaller pro­jects, I’ve been cut­ting the fat out of <a href="https://github.com/automattic/harper">the Harper monorepo.</a>
That means re­mov­ing un­nec­es­sary build steps, par­al­leliz­ing in­di­vid­ual <span class="small-caps">CI</span> jobs, and re­mov­ing op­ti­miza­tion ﬂags that don’t work.</p>
<p>Harper is­n’t at the 2005 stan­dard yet, but I want con­tribut­ing to be as easy as pos­si­ble.
Ide­ally, it should­n’t take more than ﬁve min­utes to go from cloning the repo to com­mit­ting code.
That starts with sim­pli­fy­ing <a href="https://writewithharper.com/docs/contributors/environment">the en­vi­ron­ment</a>.</p>
<h2>Addendum</h2>
<p>I just came across <a href="https://digitalsociety.coop/posts/migrating-to-hetzner-cloud/">this other blog post</a> where they dis­cuss achiev­ing three times the per­for­mance for a quar­ter of the price by switch­ing from
a com­plex <span class="small-caps">AWS</span> setup to a bare metal Hetzner server.
An­other win for sim­pler sys­tems!</p>]]></description>
            <content:encoded><![CDATA[<h1>Avoid Complexity</h1>
<p>For as long as I can re­mem­ber, I’ve told peo­ple that the real chal­lenge of soft­ware en­gi­neer­ing is­n’t writ­ing code.
It is­n’t doc­u­men­ta­tion or <span class="small-caps">CI</span> pipelines ei­ther.
No, the biggest chal­lenge in soft­ware en­gi­neer­ing is man­ag­ing com­plex­ity.</p>
<p>Think about it.
The typ­i­cal soft­ware en­gi­neer is a sin­gle per­son on a vast team, writ­ing and edit­ing a sys­tem com­posed of hun­dreds, mil­lions, or even bil­lions of lines of code.
I ﬁnd that in­tim­i­dat­ing.
How in the world are we sup­posed to build ro­bust and re­li­able sys­tems when fac­ing such com­plex odds?</p>
<p>I worry that, in my fas­ci­na­tion in the <a href="./LLM_assisted_fuzzing">novel</a> <a href="more_transformation-based_learning">ap­pli­ca­tions</a> of ma­chine learn­ing we hear about on a day-to-day ba­sis, I’ve got­ten dis­tracted from the beast of com­plex­ity.
I’ve al­lowed the Harper code­base to ac­crue tech debt.</p>
<p>I re­cently watched <a href="https://www.youtube.com/watch?v=gcwzWzC7gUA"><span class="small-caps">DHH</span><span class="push-single"></span><span class="pull-single">’</span>s keynote from Rails World 2025</a>.
While I def­i­nitely do not agree with every­thing he has to say, I be­lieve he makes some poignant points about com­plex­ity.
Some things, he says, which we con­sider nor­mal to­day would have been lu­di­crous ten years ago.
Some­how, with com­puter hard­ware faster than ever, we’ve ar­rived at a point where 20 minute <span class="small-caps">CI</span> runs are ac­cept­able.
Where a de­ploy to pro­duc­tion can take an hour and set­ting up a new en­vi­ron­ment can take days.</p>
<p>I want to change that—at least, for Harper.</p>
<p>While work­ing on sev­eral smaller pro­jects, I’ve been cut­ting the fat out of <a href="https://github.com/automattic/harper">the Harper monorepo.</a>
That means re­mov­ing un­nec­es­sary build steps, par­al­leliz­ing in­di­vid­ual <span class="small-caps">CI</span> jobs, and re­mov­ing op­ti­miza­tion ﬂags that don’t work.</p>
<p>Harper is­n’t at the 2005 stan­dard yet, but I want con­tribut­ing to be as easy as pos­si­ble.
Ide­ally, it should­n’t take more than ﬁve min­utes to go from cloning the repo to com­mit­ting code.
That starts with sim­pli­fy­ing <a href="https://writewithharper.com/docs/contributors/environment">the en­vi­ron­ment</a>.</p>
<h2>Addendum</h2>
<p>I just came across <a href="https://digitalsociety.coop/posts/migrating-to-hetzner-cloud/">this other blog post</a> where they dis­cuss achiev­ing three times the per­for­mance for a quar­ter of the price by switch­ing from
a com­plex <span class="small-caps">AWS</span> setup to a bare metal Hetzner server.
An­other win for sim­pler sys­tems!</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/cathedral_st_john.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Using Codex Is a Lot like Baking]]></title>
            <link>https://elijahpotter.dev/articles/using_codex_is_a_lot_like_baking</link>
            <guid>https://elijahpotter.dev/articles/using_codex_is_a_lot_like_baking</guid>
            <pubDate>Mon, 06 Oct 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Using Codex is a Lot Like Baking</h1>
<p>The biggest prob­lem with <span class="small-caps">AI</span> pro­gram­ming to­day is not what I ex­pected a few years ago.
I truly did­n’t be­lieve they would be able to do any eco­nom­i­cally valu­able work.</p>
<p>For web de­vel­op­ment, mod­ern pro­gram­ming tools like <a href="https://openai.com/codex/">Codex</a> are quite good.
They are able to un­der­stand a query and iden­tify the rel­e­vant ar­eas in a code­base.
Then, they can make mod­i­ﬁ­ca­tions and it­er­ate on the so­lu­tion un­til it ﬁts the prompt.
Frankly, I never ex­pected them to reach this point.</p>
<p>No, the pre­vail­ing prob­lem I’ve en­coun­tered with these tools is not their abil­ity to do work.
Rather, the prob­lem comes from their in­abil­ity to ask ques­tions up-front and it­er­ate on their men­tal model be­fore start­ing to code.</p>
<p>Here’s what I mean.</p>
<h2>Some Context</h2>
<p><a href="https://writewithharper.com">Harper’s land­ing page</a> is pretty im­por­tant.
It sets ex­pec­ta­tions for po­ten­tial users with a work­ing in-browser demo and re­duces un­pro­duc­tive traf­ﬁc to our is­sue-tracker by an­swer­ing com­mon ques­tions.
In the past, im­prov­ing the cap­i­tal-“Q” Quality of our land­ing page has had a sig­nif­i­cant im­pact on Harper’s growth rate.</p>
<h2>What Did I Do?</h2>
<p>Earlier this week, I wanted to make some sig­nif­i­cant changes to parts of this land­ing page.
Since the com­plex­ity of this part of the code-base is rel­a­tively small, I wanted to try some­thing: a new (to me) way to work with OpenAI’s Codex.</p>
<p>Most of the mar­ket­ing ma­te­ri­als sur­round­ing these tools im­ply that hav­ing a con­ver­sa­tion with the<span class="push-double"></span> <span class="pull-double">“</span>agent” is the in­tended work­ﬂow.
Sam Altman et al. make it seem like I’m sup­posed to boot up Codex and have a dis­cus­sion.
At the end of that dis­cus­sion, the thing in my head is down on pa­per (or code).</p>
<p>I wanted to try some­thing new.
In­stead of a con­ver­sa­tion, I wrote a de­tailed goal doc­u­ment.
I usu­ally would­n’t do such a thing for such a triv­ial task, but as I said: this was an ex­per­i­ment.</p>
<p>To par­al­lel what real pro­ject doc would look like, I in­cluded what, why, and how the changes I wanted should be made.
No­tably, I tried to an­tic­i­pate any pos­si­ble re­vi­sions or mis­in­ter­pre­ta­tions the agent might make, which I would later have to have a con­ver­sa­tion about.
When I was done, I gave it to the agent and went to work on some­thing else.
I came back an hour later, and it was done.</p>
<h2>What’s the Big Deal?</h2>
<p>Imagine this sit­u­a­tion. It may sound fa­mil­iar to you.</p>
<p><em>You sit down to ex­per­i­ment with an <span class="small-caps">LLM</span>. You want to see what all hype around vibe-cod­ing looks like.</em>
<em>You tell the agent what you want. You wait a few min­utes for it to do its thing. The re­sult does­n’t look right, so you prompt it once more. You wait some more. The cy­cle con­tin­ues.</em></p>
<p>This is ex­tra­or­di­nar­ily in­ef­ﬁ­cient.
You’re cos­play­ing as a mi­cro­manag­ing boss.</p>
<p>If I had done used the con­ver­sa­tional mi­cro-man­ag­ing strat­egy when work­ing on the land­ing page, it prob­a­bly would have taken just as long (about an hour).
The down­side, is that I don’t get to go work on some­thing else in the mean­time.</p>
<p>By writ­ing a plan­ning doc­u­ment for your agent, you can give your­self room to breath, sav­ing time and be­ing more pro­duc­tive as a re­sult.
It’s a lot like bak­ing.
You put the in­gre­di­ents to­gether, put them in the oven, then walk away and some­thing else while it cooks.</p>]]></description>
            <content:encoded><![CDATA[<h1>Using Codex is a Lot Like Baking</h1>
<p>The biggest prob­lem with <span class="small-caps">AI</span> pro­gram­ming to­day is not what I ex­pected a few years ago.
I truly did­n’t be­lieve they would be able to do any eco­nom­i­cally valu­able work.</p>
<p>For web de­vel­op­ment, mod­ern pro­gram­ming tools like <a href="https://openai.com/codex/">Codex</a> are quite good.
They are able to un­der­stand a query and iden­tify the rel­e­vant ar­eas in a code­base.
Then, they can make mod­i­ﬁ­ca­tions and it­er­ate on the so­lu­tion un­til it ﬁts the prompt.
Frankly, I never ex­pected them to reach this point.</p>
<p>No, the pre­vail­ing prob­lem I’ve en­coun­tered with these tools is not their abil­ity to do work.
Rather, the prob­lem comes from their in­abil­ity to ask ques­tions up-front and it­er­ate on their men­tal model be­fore start­ing to code.</p>
<p>Here’s what I mean.</p>
<h2>Some Context</h2>
<p><a href="https://writewithharper.com">Harper’s land­ing page</a> is pretty im­por­tant.
It sets ex­pec­ta­tions for po­ten­tial users with a work­ing in-browser demo and re­duces un­pro­duc­tive traf­ﬁc to our is­sue-tracker by an­swer­ing com­mon ques­tions.
In the past, im­prov­ing the cap­i­tal-“Q” Quality of our land­ing page has had a sig­nif­i­cant im­pact on Harper’s growth rate.</p>
<h2>What Did I Do?</h2>
<p>Earlier this week, I wanted to make some sig­nif­i­cant changes to parts of this land­ing page.
Since the com­plex­ity of this part of the code-base is rel­a­tively small, I wanted to try some­thing: a new (to me) way to work with OpenAI’s Codex.</p>
<p>Most of the mar­ket­ing ma­te­ri­als sur­round­ing these tools im­ply that hav­ing a con­ver­sa­tion with the<span class="push-double"></span> <span class="pull-double">“</span>agent” is the in­tended work­ﬂow.
Sam Altman et al. make it seem like I’m sup­posed to boot up Codex and have a dis­cus­sion.
At the end of that dis­cus­sion, the thing in my head is down on pa­per (or code).</p>
<p>I wanted to try some­thing new.
In­stead of a con­ver­sa­tion, I wrote a de­tailed goal doc­u­ment.
I usu­ally would­n’t do such a thing for such a triv­ial task, but as I said: this was an ex­per­i­ment.</p>
<p>To par­al­lel what real pro­ject doc would look like, I in­cluded what, why, and how the changes I wanted should be made.
No­tably, I tried to an­tic­i­pate any pos­si­ble re­vi­sions or mis­in­ter­pre­ta­tions the agent might make, which I would later have to have a con­ver­sa­tion about.
When I was done, I gave it to the agent and went to work on some­thing else.
I came back an hour later, and it was done.</p>
<h2>What’s the Big Deal?</h2>
<p>Imagine this sit­u­a­tion. It may sound fa­mil­iar to you.</p>
<p><em>You sit down to ex­per­i­ment with an <span class="small-caps">LLM</span>. You want to see what all hype around vibe-cod­ing looks like.</em>
<em>You tell the agent what you want. You wait a few min­utes for it to do its thing. The re­sult does­n’t look right, so you prompt it once more. You wait some more. The cy­cle con­tin­ues.</em></p>
<p>This is ex­tra­or­di­nar­ily in­ef­ﬁ­cient.
You’re cos­play­ing as a mi­cro­manag­ing boss.</p>
<p>If I had done used the con­ver­sa­tional mi­cro-man­ag­ing strat­egy when work­ing on the land­ing page, it prob­a­bly would have taken just as long (about an hour).
The down­side, is that I don’t get to go work on some­thing else in the mean­time.</p>
<p>By writ­ing a plan­ning doc­u­ment for your agent, you can give your­self room to breath, sav­ing time and be­ing more pro­duc­tive as a re­sult.
It’s a lot like bak­ing.
You put the in­gre­di­ents to­gether, put them in the oven, then walk away and some­thing else while it cooks.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My Writing Environment as a Software Engineer]]></title>
            <link>https://elijahpotter.dev/articles/my_writing_environment_as_a_software_engineer</link>
            <guid>https://elijahpotter.dev/articles/my_writing_environment_as_a_software_engineer</guid>
            <pubDate>Sun, 05 Oct 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>My Writing Environment As a Software Engineer</h1>
<blockquote>
<p><strong>TL;DR: <em>Get a good text ed­i­tor, and get good at us­ing it.
Keep a notepad by your side.
Find a quiet place to write.</em></strong></p>
</blockquote>
<p>Writing is one of life’s greater joys.
It’s a men­tal work­out that of­ten brings me a level of clar­ity that is hard to ﬁnd else­where.
I have never sat down to write for an ex­tended pe­riod of time with­out com­ing away with a greater un­der­stand­ing of my­self and the uni­verse.</p>
<p>I imag­ine that sounds pretty cheesy.</p>
<p>I just want to im­press upon you the im­por­tance writ­ing has had in my life.
As with any­thing this im­por­tant, I have an in­stinc­tual urge to op­ti­mize it.
As soft­ware en­gi­neers, is­n’t that part of our modus operandi?
To spend an un­nec­es­sary amount of time op­ti­miz­ing things?</p>
<p>Well, that’s ex­actly what I’ve done.
Over the years, I’ve spend a un­ruly amount of time tweak­ing and chang­ing the tools I use to write.
I’d love to talk about<span class="push-single"></span> <span class="pull-single">’</span>em.</p>
<h2>Neovim</h2>
<p>In the past few years, Neovim has the talk of the town.
Its an­ces­tor, <code>vi</code> was borne from the ob­ser­va­tion that most writ­ing is re-writ­ing, and thus it in­cludes a vast ar­ray of spe­ciﬁc tools for edit­ing text, rather than writ­ing it.
<a href="https://www.reddit.com/r/neovim/"><code>r/neovim</code></a> has swelled to more than 110 thou­sand weekly vis­i­tors and thou­sands of plu­g­ins and LSPs to choose from.</p>
<p>Unless you choose a pre-built dis­tro like <a href="https://www.lazyvim.org/">LazyVim</a>, Neovim does very lit­tle out of the box.
It is through re­lent­less cus­tomiza­tion that it be­comes some­thing truly pow­er­ful.
Be­side the var­i­ous key­bind­ings in my con­ﬁg, the im­por­tant plu­g­ins and tools that I use are:</p>
<h3><a href="https://writewithharper.com">Harper</a></h3>
<p>As I’m the chief main­tainer, this one should be of no sur­prise.
Don’t worry, I won’t give you the mar­ket­ing spiel.
I’ll sim­ply say that Harper is a gram­mar checker that hap­pens to work ex­cep­tion­ally well in Neovim.</p>
<h3><a href="https://github.com/folke/zen-mode.nvim">Zen Mode</a></h3>
<img src="/images/zen_mode.png" alt="Zen Mode: Activated">
<p>Plain Neovim or­ga­nizes each ﬁle to cover the max­i­mum amount of space on-screen.
Prac­ti­cally, this means most of my text ends up on the left side, which can hurt my neck.
Zen mode re­stricts the width of the ﬁle and cen­ters it on the screen.</p>
<h3><a href="./the_simplest_neovim_markdown_setup">Tatum</a></h3>
<img src="/images/tatum_screenshot.webp" alt="A screenshot of Tatum at work">
<p>Tatum is a small tool I wrote in a few hours that lets me live-pre­view my Markdown as HTML.
I won’t re­peat my­self.
If you’re cu­ri­ous about Tatum, check out my <a href="./the_simplest_neovim_markdown_setup">pre­vi­ous blog post.</a></p>
<h3>More Details</h3>
<p>If you’re thirsty for <em>all</em> the de­tails of my Neovim setup, take a quick look at the con­ﬁg­u­ra­tion code on <a href="https://github.com/elijah-potter/dots/tree/master/nvim">GitHub</a>.</p>
<h2>ReMarkable 2</h2>
<p>A few years ago, while I was still in school, I splurged and pur­chased a used Remarkable 2.
Even now, there is rarely a day that goes by where I don’t use it.
Sure, every­thing it does could be eas­ily ap­prox­i­mated by pa­per.
On the other hand, the thin de­vice saves my back when­ever I have to travel and there’s a cer­tain joy that comes from us­ing an e-ink de­vice like this.</p>
<img src="/images/remarkable.jpeg" alt="A Recent Brainstorming Session on the ReMarkable">
<p>I mostly use it to read dig­i­tal books and blog posts, but it’s also come in handy for get­ting around writer’s block.
Be­ing forced to slow down and use my hand to write, rather than a key­board, is quite help­ful in some in­ex­plic­a­ble way.</p>
<h2>A Quiet Space</h2>
<p>More im­por­tant than any text ed­i­tor, at least for me, is a quiet space.</p>
<p>As I said be­fore, writ­ing can take real cog­ni­tive ef­fort.
A space with dis­tract­ing noise can make it un­nec­es­sar­ily hard to get words on pa­per.
I’ve found lo­cal li­braries or co-work­ing spaces to be great places for quiet au­thor­ship.</p>
<p>Whatever you do, do not lis­ten to mu­sic with words in it.
Even if it <em>feels</em> more pro­duc­tive, I retroac­tively ﬁnd that it makes me less so.</p>
<h2>Conclusion</h2>
<p>There’s prob­a­bly some more im­por­tant points I’m leav­ing out, but I def­i­nitely be­lieve I’ve hit the big three.
For me, the most im­por­tant parts of a good writ­ing en­vi­ron­ment, as a soft­ware en­gi­neer, are my text ed­i­tor, a notepad, and a quiet space.</p>]]></description>
            <content:encoded><![CDATA[<h1>My Writing Environment As a Software Engineer</h1>
<blockquote>
<p><strong>TL;DR: <em>Get a good text ed­i­tor, and get good at us­ing it.
Keep a notepad by your side.
Find a quiet place to write.</em></strong></p>
</blockquote>
<p>Writing is one of life’s greater joys.
It’s a men­tal work­out that of­ten brings me a level of clar­ity that is hard to ﬁnd else­where.
I have never sat down to write for an ex­tended pe­riod of time with­out com­ing away with a greater un­der­stand­ing of my­self and the uni­verse.</p>
<p>I imag­ine that sounds pretty cheesy.</p>
<p>I just want to im­press upon you the im­por­tance writ­ing has had in my life.
As with any­thing this im­por­tant, I have an in­stinc­tual urge to op­ti­mize it.
As soft­ware en­gi­neers, is­n’t that part of our modus operandi?
To spend an un­nec­es­sary amount of time op­ti­miz­ing things?</p>
<p>Well, that’s ex­actly what I’ve done.
Over the years, I’ve spend a un­ruly amount of time tweak­ing and chang­ing the tools I use to write.
I’d love to talk about<span class="push-single"></span> <span class="pull-single">’</span>em.</p>
<h2>Neovim</h2>
<p>In the past few years, Neovim has the talk of the town.
Its an­ces­tor, <code>vi</code> was borne from the ob­ser­va­tion that most writ­ing is re-writ­ing, and thus it in­cludes a vast ar­ray of spe­ciﬁc tools for edit­ing text, rather than writ­ing it.
<a href="https://www.reddit.com/r/neovim/"><code>r/neovim</code></a> has swelled to more than 110 thou­sand weekly vis­i­tors and thou­sands of plu­g­ins and LSPs to choose from.</p>
<p>Unless you choose a pre-built dis­tro like <a href="https://www.lazyvim.org/">LazyVim</a>, Neovim does very lit­tle out of the box.
It is through re­lent­less cus­tomiza­tion that it be­comes some­thing truly pow­er­ful.
Be­side the var­i­ous key­bind­ings in my con­ﬁg, the im­por­tant plu­g­ins and tools that I use are:</p>
<h3><a href="https://writewithharper.com">Harper</a></h3>
<p>As I’m the chief main­tainer, this one should be of no sur­prise.
Don’t worry, I won’t give you the mar­ket­ing spiel.
I’ll sim­ply say that Harper is a gram­mar checker that hap­pens to work ex­cep­tion­ally well in Neovim.</p>
<h3><a href="https://github.com/folke/zen-mode.nvim">Zen Mode</a></h3>
<img src="/images/zen_mode.png" alt="Zen Mode: Activated">
<p>Plain Neovim or­ga­nizes each ﬁle to cover the max­i­mum amount of space on-screen.
Prac­ti­cally, this means most of my text ends up on the left side, which can hurt my neck.
Zen mode re­stricts the width of the ﬁle and cen­ters it on the screen.</p>
<h3><a href="./the_simplest_neovim_markdown_setup">Tatum</a></h3>
<img src="/images/tatum_screenshot.webp" alt="A screenshot of Tatum at work">
<p>Tatum is a small tool I wrote in a few hours that lets me live-pre­view my Markdown as HTML.
I won’t re­peat my­self.
If you’re cu­ri­ous about Tatum, check out my <a href="./the_simplest_neovim_markdown_setup">pre­vi­ous blog post.</a></p>
<h3>More Details</h3>
<p>If you’re thirsty for <em>all</em> the de­tails of my Neovim setup, take a quick look at the con­ﬁg­u­ra­tion code on <a href="https://github.com/elijah-potter/dots/tree/master/nvim">GitHub</a>.</p>
<h2>ReMarkable 2</h2>
<p>A few years ago, while I was still in school, I splurged and pur­chased a used Remarkable 2.
Even now, there is rarely a day that goes by where I don’t use it.
Sure, every­thing it does could be eas­ily ap­prox­i­mated by pa­per.
On the other hand, the thin de­vice saves my back when­ever I have to travel and there’s a cer­tain joy that comes from us­ing an e-ink de­vice like this.</p>
<img src="/images/remarkable.jpeg" alt="A Recent Brainstorming Session on the ReMarkable">
<p>I mostly use it to read dig­i­tal books and blog posts, but it’s also come in handy for get­ting around writer’s block.
Be­ing forced to slow down and use my hand to write, rather than a key­board, is quite help­ful in some in­ex­plic­a­ble way.</p>
<h2>A Quiet Space</h2>
<p>More im­por­tant than any text ed­i­tor, at least for me, is a quiet space.</p>
<p>As I said be­fore, writ­ing can take real cog­ni­tive ef­fort.
A space with dis­tract­ing noise can make it un­nec­es­sar­ily hard to get words on pa­per.
I’ve found lo­cal li­braries or co-work­ing spaces to be great places for quiet au­thor­ship.</p>
<p>Whatever you do, do not lis­ten to mu­sic with words in it.
Even if it <em>feels</em> more pro­duc­tive, I retroac­tively ﬁnd that it makes me less so.</p>
<h2>Conclusion</h2>
<p>There’s prob­a­bly some more im­por­tant points I’m leav­ing out, but I def­i­nitely be­lieve I’ve hit the big three.
For me, the most im­por­tant parts of a good writ­ing en­vi­ron­ment, as a soft­ware en­gi­neer, are my text ed­i­tor, a notepad, and a quiet space.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/remarkable.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Trying out the Helium Browser]]></title>
            <link>https://elijahpotter.dev/articles/trying_out_the_helium_browser</link>
            <guid>https://elijahpotter.dev/articles/trying_out_the_helium_browser</guid>
            <pubDate>Sat, 04 Oct 2025 20:45:58 GMT</pubDate>
            <description><![CDATA[<h1>Trying out the Helium Browser</h1>
<img src="/images/helium_website.png" alt="The Helium Website">
<p>I’ve de­cided to give the <a href="https://helium.computer/">Helium browser</a> a shot.
So far, I’m im­pressed!</p>
<p>For con­text, I’ve been an <code>ungoogled-chromium</code> user for the past 4-ish years.
Why? I like the pri­vacy guar­an­tees. Plus, I pre­fer to use the same browser en­gine as <a href="https://chromewebstore.google.com/detail/private-grammar-checker-h/lodbfhdipoipcjmlebjbgmmgekckhpfb">my users</a>.</p>
<p>When I heard about Helium, I was in­trigued.
From what I can tell, it’s es­sen­tially a fork of <code>ungoogled-chromium</code>, with some bonus perks.
It ships UBlock Origin out-of-the-box, which means I can keep us­ing the same great ad blocker that Google re­cently killed in its browser.</p>
<p>It looks great, and mi­grat­ing from Chromium was su­per easy.</p>]]></description>
            <content:encoded><![CDATA[<h1>Trying out the Helium Browser</h1>
<img src="/images/helium_website.png" alt="The Helium Website">
<p>I’ve de­cided to give the <a href="https://helium.computer/">Helium browser</a> a shot.
So far, I’m im­pressed!</p>
<p>For con­text, I’ve been an <code>ungoogled-chromium</code> user for the past 4-ish years.
Why? I like the pri­vacy guar­an­tees. Plus, I pre­fer to use the same browser en­gine as <a href="https://chromewebstore.google.com/detail/private-grammar-checker-h/lodbfhdipoipcjmlebjbgmmgekckhpfb">my users</a>.</p>
<p>When I heard about Helium, I was in­trigued.
From what I can tell, it’s es­sen­tially a fork of <code>ungoogled-chromium</code>, with some bonus perks.
It ships UBlock Origin out-of-the-box, which means I can keep us­ing the same great ad blocker that Google re­cently killed in its browser.</p>
<p>It looks great, and mi­grat­ing from Chromium was su­per easy.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/helium_website.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Brainstorming a Harper Service]]></title>
            <link>https://elijahpotter.dev/articles/brainstorming_a_harper_service</link>
            <guid>https://elijahpotter.dev/articles/brainstorming_a_harper_service</guid>
            <pubDate>Mon, 29 Sep 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Brainstorming a Harper Service</h1>
<p>SaaS prod­ucts are all the rage these days.
His­tor­i­cally, Harper has po­si­tioned it­self as <a href="./local-first_software_is_easier_to_scale">lo­cal-ﬁrst</a>, which many of­ten view as anti-SaaS.
That is not the case.</p>
<p>I de­scribe Harper as<span class="push-double"></span> <span class="pull-double">“</span>local-ﬁrst” be­cause it is prin­ci­pally meant to be run lo­cally, mean­ing that your data does­n’t leave your de­vice.
Pri­vacy is one of our core be­liefs, and be­ing<span class="push-double"></span> <span class="pull-double">“</span>local-ﬁrst” is part of how we honor that.</p>
<p>Given Harper’s po­si­tion on pri­vacy, dis­cus­sion of a cen­tral­ized ser­vice (running on a server) might seem strange.
To­day, I want to ex­plain why a cen­tral­ized ser­vice is nec­es­sary, how we in­tend to tackle it as part of the open source pro­ject, and how it’s ex­is­tence will not stand to af­fect our users’ pri­vacy in any way.</p>
<h2>Why We Need It <strong>Now</strong></h2>
<p>User’s de­spise false-pos­i­tives in Harper’s gram­mar en­gine.</p>
<p>We’ll of­ten make a mod­i­ﬁ­ca­tion, test it as much as pos­si­ble in­ter­nally, and re­lease it, only for a user to dis­cover false-pos­i­tives on their col­lege es­say.
We’ve sig­nif­i­cantly re­duced their fre­quency through tech­niques like <a href="./LLM_assisted_fuzzing">LLM-Assisted Fuzzing</a> and <a href="./harper_evolves">ar­ti­ﬁ­cial se­lec­tion</a>, but they per­sist.</p>
<p>Far and wide, most of the time be­tween when we get a bug re­port and when we get a ﬁx into pro­duc­tion is spent do­ing mean­ing­less<span class="push-double"></span> <span class="pull-double">“</span>paperwork”.
Reading through is­sues, writ­ing boil­er­plate code for tests, and prepar­ing pull re­quests take up a lot of time.
I want to up­end this en­tire loop and ﬁnd a sim­pler way to it­er­ate on false-pos­i­tives.</p>
<p>The ﬁrst step in this process is re­duc­ing the over­head for users to re­port them in the ﬁrst place.
In­stead of ﬁl­ing an is­sue, I’d love for user’s to be able to push a but­ton have have all rel­e­vant con­text sent di­rectly to our in­boxes.
Not GitHub ac­count re­quired, no fuss.</p>
<p>Obviously, we won’t be send­ing any in­for­ma­tion with­out clearly dis­clos­ing the pre­cise data be­ing sent from the client.</p>
<p>Since these re­ports will be in a struc­tured for­mat, we can bun­dle ﬁxes into larger batches, re­duc­ing over­head (and po­ten­tially mak­ing things eas­ier for LLMs).
The Harper pro­jec­t’s abil­ity to con­tin­u­ously and sur­gi­cally im­prove our ser­vice is part of what sets us apart from our com­peti­tors.
It’s rare for user feed­back to have such a di­rect im­pact on Grammarly’s core en­gine.</p>
<p>This is just the start. Once we’ve proven that we can main­tain such a sys­tem for our users, we will move on to more am­bi­tious goals.</p>
<h2>How We Plan to Make It Happen</h2>
<p>I’ve had suc­cess with these tech­nolo­gies in the past, and be­lieve they’ll work quite well for our ini­tial use-case, and scale quite nicely as the ser­vice be­comes more ca­pa­ble.</p>
<h3>SvelteKit</h3>
<p>Harper’s web­site is al­ready built us­ing SvelteKit.
It works.
Why mess with suc­cess?</p>
<h3>Database: <a href="https://sqlite.org/index.html">SQLite</a> with <a href="https://orm.drizzle.team/">Drizzle</a></h3>
<p>On mod­ern hard­ware, SQLite can han­dle tens of thou­sands of trans­ac­tions per sec­ond.
Since Harper (as a rule) will place lit­tle load on the ser­vice per-user, I ex­pect SQLite can ser­vice our needs for a long time.
When that time ex­pires, Drizzle (an <span class="small-caps">ORM</span>) makes it triv­ial to move to a more ro­bust data­base down the road (I imag­ine PostgreSQL).</p>
<p>For the mo­ment, it­er­a­tion speed is para­mount.
I want to move fast.
To that end, <a href="https://j.cv/">James</a> and I have dis­cussed host­ing every­thing on a small <span class="small-caps">VPS</span>, and we agree it’s the best choice.</p>
<h3>Auth.JS</h3>
<p>Authentication is not some­thing any­one should roll on their own.
Auth.JS is a proven so­lu­tion that bun­dles well with Drizzle to build­ing out user pro­ﬁles, au­then­ti­ca­tion pages, and every­thing in-be­tween.
The needs of the pro­ject won’t ne­ces­si­tate this right away.
When the time comes, us­ing Auth.JS (from my ex­pe­ri­ence) will shorten the auth prob­lem down from weeks to just hours.</p>
<h2>The Future</h2>
<p>I’m thrilled that we’re ﬁ­nally mov­ing in this di­rec­tion.
Be­ing lo­cal-only has taken us far, but loos­en­ing the reigns to be more sim­ply<span class="push-double"></span> <span class="pull-double">“</span>local-ﬁrst” will al­low us to tackle more in­ter­est­ing prob­lems and pro­vide more value to our users.
I can’t wait to get started.</p>]]></description>
            <content:encoded><![CDATA[<h1>Brainstorming a Harper Service</h1>
<p>SaaS prod­ucts are all the rage these days.
His­tor­i­cally, Harper has po­si­tioned it­self as <a href="./local-first_software_is_easier_to_scale">lo­cal-ﬁrst</a>, which many of­ten view as anti-SaaS.
That is not the case.</p>
<p>I de­scribe Harper as<span class="push-double"></span> <span class="pull-double">“</span>local-ﬁrst” be­cause it is prin­ci­pally meant to be run lo­cally, mean­ing that your data does­n’t leave your de­vice.
Pri­vacy is one of our core be­liefs, and be­ing<span class="push-double"></span> <span class="pull-double">“</span>local-ﬁrst” is part of how we honor that.</p>
<p>Given Harper’s po­si­tion on pri­vacy, dis­cus­sion of a cen­tral­ized ser­vice (running on a server) might seem strange.
To­day, I want to ex­plain why a cen­tral­ized ser­vice is nec­es­sary, how we in­tend to tackle it as part of the open source pro­ject, and how it’s ex­is­tence will not stand to af­fect our users’ pri­vacy in any way.</p>
<h2>Why We Need It <strong>Now</strong></h2>
<p>User’s de­spise false-pos­i­tives in Harper’s gram­mar en­gine.</p>
<p>We’ll of­ten make a mod­i­ﬁ­ca­tion, test it as much as pos­si­ble in­ter­nally, and re­lease it, only for a user to dis­cover false-pos­i­tives on their col­lege es­say.
We’ve sig­nif­i­cantly re­duced their fre­quency through tech­niques like <a href="./LLM_assisted_fuzzing">LLM-Assisted Fuzzing</a> and <a href="./harper_evolves">ar­ti­ﬁ­cial se­lec­tion</a>, but they per­sist.</p>
<p>Far and wide, most of the time be­tween when we get a bug re­port and when we get a ﬁx into pro­duc­tion is spent do­ing mean­ing­less<span class="push-double"></span> <span class="pull-double">“</span>paperwork”.
Reading through is­sues, writ­ing boil­er­plate code for tests, and prepar­ing pull re­quests take up a lot of time.
I want to up­end this en­tire loop and ﬁnd a sim­pler way to it­er­ate on false-pos­i­tives.</p>
<p>The ﬁrst step in this process is re­duc­ing the over­head for users to re­port them in the ﬁrst place.
In­stead of ﬁl­ing an is­sue, I’d love for user’s to be able to push a but­ton have have all rel­e­vant con­text sent di­rectly to our in­boxes.
Not GitHub ac­count re­quired, no fuss.</p>
<p>Obviously, we won’t be send­ing any in­for­ma­tion with­out clearly dis­clos­ing the pre­cise data be­ing sent from the client.</p>
<p>Since these re­ports will be in a struc­tured for­mat, we can bun­dle ﬁxes into larger batches, re­duc­ing over­head (and po­ten­tially mak­ing things eas­ier for LLMs).
The Harper pro­jec­t’s abil­ity to con­tin­u­ously and sur­gi­cally im­prove our ser­vice is part of what sets us apart from our com­peti­tors.
It’s rare for user feed­back to have such a di­rect im­pact on Grammarly’s core en­gine.</p>
<p>This is just the start. Once we’ve proven that we can main­tain such a sys­tem for our users, we will move on to more am­bi­tious goals.</p>
<h2>How We Plan to Make It Happen</h2>
<p>I’ve had suc­cess with these tech­nolo­gies in the past, and be­lieve they’ll work quite well for our ini­tial use-case, and scale quite nicely as the ser­vice be­comes more ca­pa­ble.</p>
<h3>SvelteKit</h3>
<p>Harper’s web­site is al­ready built us­ing SvelteKit.
It works.
Why mess with suc­cess?</p>
<h3>Database: <a href="https://sqlite.org/index.html">SQLite</a> with <a href="https://orm.drizzle.team/">Drizzle</a></h3>
<p>On mod­ern hard­ware, SQLite can han­dle tens of thou­sands of trans­ac­tions per sec­ond.
Since Harper (as a rule) will place lit­tle load on the ser­vice per-user, I ex­pect SQLite can ser­vice our needs for a long time.
When that time ex­pires, Drizzle (an <span class="small-caps">ORM</span>) makes it triv­ial to move to a more ro­bust data­base down the road (I imag­ine PostgreSQL).</p>
<p>For the mo­ment, it­er­a­tion speed is para­mount.
I want to move fast.
To that end, <a href="https://j.cv/">James</a> and I have dis­cussed host­ing every­thing on a small <span class="small-caps">VPS</span>, and we agree it’s the best choice.</p>
<h3>Auth.JS</h3>
<p>Authentication is not some­thing any­one should roll on their own.
Auth.JS is a proven so­lu­tion that bun­dles well with Drizzle to build­ing out user pro­ﬁles, au­then­ti­ca­tion pages, and every­thing in-be­tween.
The needs of the pro­ject won’t ne­ces­si­tate this right away.
When the time comes, us­ing Auth.JS (from my ex­pe­ri­ence) will shorten the auth prob­lem down from weeks to just hours.</p>
<h2>The Future</h2>
<p>I’m thrilled that we’re ﬁ­nally mov­ing in this di­rec­tion.
Be­ing lo­cal-only has taken us far, but loos­en­ing the reigns to be more sim­ply<span class="push-double"></span> <span class="pull-double">“</span>local-ﬁrst” will al­low us to tackle more in­ter­est­ing prob­lems and pro­vide more value to our users.
I can’t wait to get started.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Improving Harper for Old Laptops]]></title>
            <link>https://elijahpotter.dev/articles/improving_harper_for_old_laptops</link>
            <guid>https://elijahpotter.dev/articles/improving_harper_for_old_laptops</guid>
            <pubDate>Fri, 19 Sep 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Improving Harper for Old Laptops</h1>
<p>The most com­mon com­plaint I’ve been hear­ing as of late re­lates to Harper’s per­for­mance. I’ve been told that Harper has got­ten slow.</p>
<p>I was puz­zled. Nothing had sub­stan­tially changed in our core en­gine that could have made it slower. Our bench­marks had­n’t shown any slow-downs. What could be prompt­ing users to com­plain about per­for­mance?</p>
<p>The prob­lem was clearly in the Chrome ex­ten­sion. Those who com­plained were do­ing so with screen­shots from Notion or Ghost. Could re­cent de­vel­op­ments in the <a href="https://webassembly.org/news/2025-09-17-wasm-3.0/">WebAssembly ecosys­tem</a> be the cause? Nope. Could it have some­thing to do with the kinds of ap­pli­ca­tions users were try­ing to use with Harper? Nope. The com­plaints seemed un­teth­ered to a spe­ciﬁc do­main or pro­gram. What could be slow­ing our users’ down?</p>
<h2>The Problem</h2>
<p>As it turns out, the prob­lem was­n’t the ac­tual gram­mar check­ing, it was ren­der­ing the high­lights on the page.</p>
<p>The Chrome ex­ten­sion’s hot path looks some­thing like this:</p>
<ol>
<li>Extract the text from the page</li>
<li>Run <code>harper-core</code> (the gram­mar en­gine) over it to ﬁnd mis­takes</li>
<li>Use <span class="small-caps">DOM</span> APIs to com­pute the bound­ing boxes of those mis­takes on the page. This is im­por­tant: This com­pu­ta­tion re­quires the browser to re­ﬂow the lay­out of the page.</li>
<li>Render ad­di­tional <span class="small-caps">DOM</span> el­e­ments to the screen over these bound­ing boxes. This also re­quires the browser to re­ﬂow the lay­out of the page.</li>
</ol>
<p>Requiring the browser to lay out the page more than once per frame is called <em><span class="push-double"></span><span class="pull-double">“</span>layout thrash”</em>, and it’s con­sid­ered bad prac­tice for per­for­mance rea­sons. It causes more <span class="small-caps">CPU</span> us­age than oth­er­wise, which can slow down com­plex web­sites and re­duce bat­tery life. It is­n’t no­tice­able for newer com­put­ers (4 years old), but it <strong>can</strong> be quite jar­ring for older lap­tops.</p>
<h2>The Solution</h2>
<p>Fortunately, there’s a so­lu­tion. Just re­cently, <span class="small-caps">W3C</span> stan­dard­ized <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API">The Custom Highlight <span class="small-caps">API</span></a>, which al­lows us to ren­der high­lights with­out in­ter­act­ing with the <span class="small-caps">DOM</span> at all. This saves us two full lay­out re­freshes and al­lows the browser to of­ﬂoad more work to the <span class="small-caps">GPU</span>, sav­ing <span class="small-caps">CPU</span> cy­cles and bat­tery life.</p>
<p>It’s not all roses and rain­bows. For one, we can only use this op­ti­miza­tion for text ed­i­tors that use <code>contenteditable</code> el­e­ments. It does­n’t help for <code>&#x3C;textarea /></code> or <code>&#x3C;input /></code> el­e­ments. Firefox also does­n’t sup­port this part of the <span class="small-caps">API</span>, which means we have to fall back to the old high­lighted ren­derer on Gecko-based browsers.</p>
<p>There are also some mi­nor dif­fer­ences be­tween how the new ren­derer and the old ren­derer style their high­lights. This is be­cause high­lights us­ing the Custom Highlight <span class="small-caps">API</span> can’t be styled us­ing any old <span class="small-caps">CSS</span>, but rather a sub­set.</p>]]></description>
            <content:encoded><![CDATA[<h1>Improving Harper for Old Laptops</h1>
<p>The most com­mon com­plaint I’ve been hear­ing as of late re­lates to Harper’s per­for­mance. I’ve been told that Harper has got­ten slow.</p>
<p>I was puz­zled. Nothing had sub­stan­tially changed in our core en­gine that could have made it slower. Our bench­marks had­n’t shown any slow-downs. What could be prompt­ing users to com­plain about per­for­mance?</p>
<p>The prob­lem was clearly in the Chrome ex­ten­sion. Those who com­plained were do­ing so with screen­shots from Notion or Ghost. Could re­cent de­vel­op­ments in the <a href="https://webassembly.org/news/2025-09-17-wasm-3.0/">WebAssembly ecosys­tem</a> be the cause? Nope. Could it have some­thing to do with the kinds of ap­pli­ca­tions users were try­ing to use with Harper? Nope. The com­plaints seemed un­teth­ered to a spe­ciﬁc do­main or pro­gram. What could be slow­ing our users’ down?</p>
<h2>The Problem</h2>
<p>As it turns out, the prob­lem was­n’t the ac­tual gram­mar check­ing, it was ren­der­ing the high­lights on the page.</p>
<p>The Chrome ex­ten­sion’s hot path looks some­thing like this:</p>
<ol>
<li>Extract the text from the page</li>
<li>Run <code>harper-core</code> (the gram­mar en­gine) over it to ﬁnd mis­takes</li>
<li>Use <span class="small-caps">DOM</span> APIs to com­pute the bound­ing boxes of those mis­takes on the page. This is im­por­tant: This com­pu­ta­tion re­quires the browser to re­ﬂow the lay­out of the page.</li>
<li>Render ad­di­tional <span class="small-caps">DOM</span> el­e­ments to the screen over these bound­ing boxes. This also re­quires the browser to re­ﬂow the lay­out of the page.</li>
</ol>
<p>Requiring the browser to lay out the page more than once per frame is called <em><span class="push-double"></span><span class="pull-double">“</span>layout thrash”</em>, and it’s con­sid­ered bad prac­tice for per­for­mance rea­sons. It causes more <span class="small-caps">CPU</span> us­age than oth­er­wise, which can slow down com­plex web­sites and re­duce bat­tery life. It is­n’t no­tice­able for newer com­put­ers (4 years old), but it <strong>can</strong> be quite jar­ring for older lap­tops.</p>
<h2>The Solution</h2>
<p>Fortunately, there’s a so­lu­tion. Just re­cently, <span class="small-caps">W3C</span> stan­dard­ized <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API">The Custom Highlight <span class="small-caps">API</span></a>, which al­lows us to ren­der high­lights with­out in­ter­act­ing with the <span class="small-caps">DOM</span> at all. This saves us two full lay­out re­freshes and al­lows the browser to of­ﬂoad more work to the <span class="small-caps">GPU</span>, sav­ing <span class="small-caps">CPU</span> cy­cles and bat­tery life.</p>
<p>It’s not all roses and rain­bows. For one, we can only use this op­ti­miza­tion for text ed­i­tors that use <code>contenteditable</code> el­e­ments. It does­n’t help for <code>&#x3C;textarea /></code> or <code>&#x3C;input /></code> el­e­ments. Firefox also does­n’t sup­port this part of the <span class="small-caps">API</span>, which means we have to fall back to the old high­lighted ren­derer on Gecko-based browsers.</p>
<p>There are also some mi­nor dif­fer­ences be­tween how the new ren­derer and the old ren­derer style their high­lights. This is be­cause high­lights us­ing the Custom Highlight <span class="small-caps">API</span> can’t be styled us­ing any old <span class="small-caps">CSS</span>, but rather a sub­set.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Demos Make Life Worth Living]]></title>
            <link>https://elijahpotter.dev/articles/demos_make_life_worth_living</link>
            <guid>https://elijahpotter.dev/articles/demos_make_life_worth_living</guid>
            <pubDate>Thu, 18 Sep 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Demos Make Life Worth Living</h1>
<img src="/images/bethesda_fountain.png" alt="The Bethesda Fountain in NYC">
<p>The ini­tial goal of any green­ﬁeld pro­ject of mine is to build a work­ing demo. If pos­si­ble, that demo should run on the web, so I can dis­trib­ute to friends and fam­ily.</p>
<p>One of my ﬁrst posts to hit the front-page of Hacker News rep­re­sented ex­actly this phi­los­o­phy. At the time, I was in high-school, tak­ing an in­de­pen­dent lin­ear al­ge­bra class. I wanted to demon­strate ex­actly how the things I was learn­ing could be ap­plied to real-world prob­lems. My blog post, <a href="./markov_chains_are_the_original_language_models"><span class="push-double"></span><span class="pull-double">“</span>Markov Chains are the Original Language Models”</a> in­cluded an in­ter­ac­tive (small) lan­guage model that read­ers could use to gen­er­ate mean­ing­less text. I gave them the abil­ity to toy around with the train­ing data to see how it could af­fect the ﬁ­nal mod­el’s gen­er­a­tions.</p>
<p>I ended up get­ting an<span class="push-double"></span> <span class="pull-double">“</span>A” in the class and a cou­ple thou­sand hits on­line. More im­por­tantly, I had proven the util­ity of a demo. Since then, <a href="./building_a_software_render_engine_from_scratch">every</a> <a href="https://github.com/elijah-potter/thrax-language">ma­jor</a> <a href="https://writewithharper.com">pro­ject</a> of mine has had some kind of demo. Some of them bet­ter than oth­ers.</p>
<p>The Harper demo, in par­tic­u­lar, was rough to start. It was enough to prove the con­cept and es­tab­lish the pro­ject within a cer­tain ecosys­tem, but it far from per­fect. It had bugs and, un­til now, has­n’t evolved with the pro­ject at all.</p>
<p>For all my pro­jects though, I see the ini­tial demo as crit­i­cal. Beyond prov­ing the con­cept, the demo gives me an enor­mous amount of mo­ti­va­tion. As an en­gi­neer, there are few things more thrilling than to watch some­one toy around with the prod­uct of your work. When there’s a live ver­sion of your prod­uct avail­able, any in­cre­men­tal im­prove­ment comes with a hit of dopamine.</p>
<p>I had a great dis­cus­sion with <a href="https://adamadam.blog/">Adam Zielinski</a> a few weeks ago, when he en­cour­aged me to make the demo on the Harper web­site as close to the ac­tual Harper ex­pe­ri­ence as pos­si­ble. Last week, I did ex­actly that.</p>
<p>Actually, I did more than that. Today, the demo is­n’t just sim­i­lar to the ex­pe­ri­ence of us­ing our Chrome ex­ten­sion, it is ex­actly the same. The DOM-inspecting and ma­nip­u­lat­ing tech­nol­ogy found in the Chrome ex­ten­sion can now be em­bed­ded in any page. That in­cludes the ac­tual Harper web­site, and any other site you might want.</p>
<p>For now, I’m call­ing this the <a href="https://github.com/Automattic/harper/tree/master/packages/lint-framework"><code>lint-framework</code></a>, and it opens the doors to a good many other in­te­gra­tions (should we de­cide to build them). It is es­sen­tially a portable pack­age that in­cludes pretty much every­thing you need to pro­vide Harper to any browser-based text ed­i­tor.</p>
<p>Many ap­pli­ca­tions al­low users to build plu­g­ins into their WebView, but don’t pro­vide the full web ex­ten­sion <span class="small-caps">API</span> (Thunderbird, for ex­am­ple). In these cases, we can pull in the <code>lint-framework</code> and wire up the na­tive plu­gin APIs to build con­ﬁg­u­ra­tion pages. It is­n’t a com­plete so­lu­tion, but it cuts down the work and main­te­nance over­head enor­mously.</p>
<p>As it turns out, the cruft of Harper’s demo was a ma­jor bar­rier to adop­tion. Since pol­ish­ing it up late last week, we’re al­ready see­ing met­rics like bounce rate move in a pos­i­tive di­rec­tion.</p>]]></description>
            <content:encoded><![CDATA[<h1>Demos Make Life Worth Living</h1>
<img src="/images/bethesda_fountain.png" alt="The Bethesda Fountain in NYC">
<p>The ini­tial goal of any green­ﬁeld pro­ject of mine is to build a work­ing demo. If pos­si­ble, that demo should run on the web, so I can dis­trib­ute to friends and fam­ily.</p>
<p>One of my ﬁrst posts to hit the front-page of Hacker News rep­re­sented ex­actly this phi­los­o­phy. At the time, I was in high-school, tak­ing an in­de­pen­dent lin­ear al­ge­bra class. I wanted to demon­strate ex­actly how the things I was learn­ing could be ap­plied to real-world prob­lems. My blog post, <a href="./markov_chains_are_the_original_language_models"><span class="push-double"></span><span class="pull-double">“</span>Markov Chains are the Original Language Models”</a> in­cluded an in­ter­ac­tive (small) lan­guage model that read­ers could use to gen­er­ate mean­ing­less text. I gave them the abil­ity to toy around with the train­ing data to see how it could af­fect the ﬁ­nal mod­el’s gen­er­a­tions.</p>
<p>I ended up get­ting an<span class="push-double"></span> <span class="pull-double">“</span>A” in the class and a cou­ple thou­sand hits on­line. More im­por­tantly, I had proven the util­ity of a demo. Since then, <a href="./building_a_software_render_engine_from_scratch">every</a> <a href="https://github.com/elijah-potter/thrax-language">ma­jor</a> <a href="https://writewithharper.com">pro­ject</a> of mine has had some kind of demo. Some of them bet­ter than oth­ers.</p>
<p>The Harper demo, in par­tic­u­lar, was rough to start. It was enough to prove the con­cept and es­tab­lish the pro­ject within a cer­tain ecosys­tem, but it far from per­fect. It had bugs and, un­til now, has­n’t evolved with the pro­ject at all.</p>
<p>For all my pro­jects though, I see the ini­tial demo as crit­i­cal. Beyond prov­ing the con­cept, the demo gives me an enor­mous amount of mo­ti­va­tion. As an en­gi­neer, there are few things more thrilling than to watch some­one toy around with the prod­uct of your work. When there’s a live ver­sion of your prod­uct avail­able, any in­cre­men­tal im­prove­ment comes with a hit of dopamine.</p>
<p>I had a great dis­cus­sion with <a href="https://adamadam.blog/">Adam Zielinski</a> a few weeks ago, when he en­cour­aged me to make the demo on the Harper web­site as close to the ac­tual Harper ex­pe­ri­ence as pos­si­ble. Last week, I did ex­actly that.</p>
<p>Actually, I did more than that. Today, the demo is­n’t just sim­i­lar to the ex­pe­ri­ence of us­ing our Chrome ex­ten­sion, it is ex­actly the same. The DOM-inspecting and ma­nip­u­lat­ing tech­nol­ogy found in the Chrome ex­ten­sion can now be em­bed­ded in any page. That in­cludes the ac­tual Harper web­site, and any other site you might want.</p>
<p>For now, I’m call­ing this the <a href="https://github.com/Automattic/harper/tree/master/packages/lint-framework"><code>lint-framework</code></a>, and it opens the doors to a good many other in­te­gra­tions (should we de­cide to build them). It is es­sen­tially a portable pack­age that in­cludes pretty much every­thing you need to pro­vide Harper to any browser-based text ed­i­tor.</p>
<p>Many ap­pli­ca­tions al­low users to build plu­g­ins into their WebView, but don’t pro­vide the full web ex­ten­sion <span class="small-caps">API</span> (Thunderbird, for ex­am­ple). In these cases, we can pull in the <code>lint-framework</code> and wire up the na­tive plu­gin APIs to build con­ﬁg­u­ra­tion pages. It is­n’t a com­plete so­lu­tion, but it cuts down the work and main­te­nance over­head enor­mously.</p>
<p>As it turns out, the cruft of Harper’s demo was a ma­jor bar­rier to adop­tion. Since pol­ish­ing it up late last week, we’re al­ready see­ing met­rics like bounce rate move in a pos­i­tive di­rec­tion.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/bethesda_fountain.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[What We Can Learn from New York City]]></title>
            <link>https://elijahpotter.dev/articles/what_we_can_learn_from_new_york_city</link>
            <guid>https://elijahpotter.dev/articles/what_we_can_learn_from_new_york_city</guid>
            <pubDate>Sat, 13 Sep 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h2>What We Can Learn from New York City</h2>
<img src="/images/nyc_from_a8c_noho_office.png" alt="The view from Automattic&#x27;s NoHo Office">
<p>Today marks the end of my ﬁrst week in New York City. It is an ex­cep­tion­ally ac­tive place. Neither the hus­tle nor the bus­tle ever stops. Sirens sound at all hours of the day and you’re con­stantly smelling en­tirely new things. I’ve been told New York is an ac­quired taste. I can see why they might say that.</p>
<p>Back home, it’s rare I would walk down the street and over­hear a ran­dom founder talk about their lat­est Y Combinator fund­ing round. Here, it’s ap­par­ently com­mon enough to oc­cur at least once a week. Everywhere I look, I ﬁnd the head­quar­ters of multi­na­tional com­pa­nies and the of­ﬁces of ma­jor gov­ern­men­tal or­ga­ni­za­tions.</p>
<h3>The City is Practical</h3>
<p>If I were to de­scribe Portland, Oregon (which I vis­ited a few weeks ago to at­tend WordCamp <span class="small-caps">US</span>) in one word, it would be op­ti­mistic. If I were to de­scribe New York in one word, based on my in­cred­i­bly lim­ited ex­pe­ri­ence thus far, it would be prac­ti­cal.</p>
<p>The city and the peo­ple who re­side in­side take very lit­tle ﬂuff. They say things as they are, in the kind­est way pos­si­ble. If they don’t think your pro­posed route to a restau­rant will work, they stop you be­fore you ﬁn­ish ex­plain­ing and put you on the right track. It saves every­one time and en­ergy.</p>
<p>This is some­thing I per­son­ally need to prac­tice more of­ten in my work. It is com­mon for me to avoid pro­duc­tive con­ver­sa­tions or code re­views, only be­cause they might evoke a neg­a­tive emo­tion in the short-term. These same con­ver­sa­tions usu­ally pro­duce pos­i­tive emo­tions in the long term, so it is in my in­ter­est and in my col­league’s in­ter­est to have them. <span class="small-caps">NYC</span> is slowly train­ing a habit to rec­og­nize these sit­u­a­tions more of­ten and make the right de­ci­sion.</p>
<p>I don’t think I’m alone here. I think a plu­ral­ity of peo­ple in the mod­ern world could ben­e­ﬁt from a bolder mind­set that takes on tough con­ver­sa­tions, rather than shy­ing away.</p>
<h3>We’re Not Done Yet</h3>
<p>I’ll be in New York for an­other week. If all goes well, I’ll have more op­por­tu­ni­ties to learn from the other mem­bers of the <span class="small-caps">AI</span> group. It’s been great to hang out with James and the gang. The cul­ture here is in­cred­i­bly invit­ing. I am im­mensely grate­ful to be here.</p>]]></description>
            <content:encoded><![CDATA[<h2>What We Can Learn from New York City</h2>
<img src="/images/nyc_from_a8c_noho_office.png" alt="The view from Automattic&#x27;s NoHo Office">
<p>Today marks the end of my ﬁrst week in New York City. It is an ex­cep­tion­ally ac­tive place. Neither the hus­tle nor the bus­tle ever stops. Sirens sound at all hours of the day and you’re con­stantly smelling en­tirely new things. I’ve been told New York is an ac­quired taste. I can see why they might say that.</p>
<p>Back home, it’s rare I would walk down the street and over­hear a ran­dom founder talk about their lat­est Y Combinator fund­ing round. Here, it’s ap­par­ently com­mon enough to oc­cur at least once a week. Everywhere I look, I ﬁnd the head­quar­ters of multi­na­tional com­pa­nies and the of­ﬁces of ma­jor gov­ern­men­tal or­ga­ni­za­tions.</p>
<h3>The City is Practical</h3>
<p>If I were to de­scribe Portland, Oregon (which I vis­ited a few weeks ago to at­tend WordCamp <span class="small-caps">US</span>) in one word, it would be op­ti­mistic. If I were to de­scribe New York in one word, based on my in­cred­i­bly lim­ited ex­pe­ri­ence thus far, it would be prac­ti­cal.</p>
<p>The city and the peo­ple who re­side in­side take very lit­tle ﬂuff. They say things as they are, in the kind­est way pos­si­ble. If they don’t think your pro­posed route to a restau­rant will work, they stop you be­fore you ﬁn­ish ex­plain­ing and put you on the right track. It saves every­one time and en­ergy.</p>
<p>This is some­thing I per­son­ally need to prac­tice more of­ten in my work. It is com­mon for me to avoid pro­duc­tive con­ver­sa­tions or code re­views, only be­cause they might evoke a neg­a­tive emo­tion in the short-term. These same con­ver­sa­tions usu­ally pro­duce pos­i­tive emo­tions in the long term, so it is in my in­ter­est and in my col­league’s in­ter­est to have them. <span class="small-caps">NYC</span> is slowly train­ing a habit to rec­og­nize these sit­u­a­tions more of­ten and make the right de­ci­sion.</p>
<p>I don’t think I’m alone here. I think a plu­ral­ity of peo­ple in the mod­ern world could ben­e­ﬁt from a bolder mind­set that takes on tough con­ver­sa­tions, rather than shy­ing away.</p>
<h3>We’re Not Done Yet</h3>
<p>I’ll be in New York for an­other week. If all goes well, I’ll have more op­por­tu­ni­ties to learn from the other mem­bers of the <span class="small-caps">AI</span> group. It’s been great to hang out with James and the gang. The cul­ture here is in­cred­i­bly invit­ing. I am im­mensely grate­ful to be here.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/nyc_from_a8c_noho_office.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[The Chrome Extension Supersedes the WordPress Plugin]]></title>
            <link>https://elijahpotter.dev/articles/the_chrome_extension_supersedes_the_wordpress_plugin</link>
            <guid>https://elijahpotter.dev/articles/the_chrome_extension_supersedes_the_wordpress_plugin</guid>
            <pubDate>Wed, 10 Sep 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Chrome Extension Supersedes the WordPress Plugin</h1>
<p>Several months ago, I an­nounced the ini­tial ver­sion of the Harper WordPress plu­gin. In the in­ter­est of those still us­ing it, I’d like to give y’all an up­date.</p>
<p><a href="https://chromewebstore.google.com/detail/private-grammar-checker-h/lodbfhdipoipcjmlebjbgmmgekckhpfb">The Chrome ex­ten­sion</a> is now the rec­om­mended way to use Harper in WordPress. There are sev­eral com­pound­ing rea­sons for this. Most im­por­tantly, it­er­at­ing on a Chrome ex­ten­sion is sig­nif­i­cantly faster than the re­view process for WordPress.com plu­g­ins. In fact, the cur­rent WordPress.com Harper plu­gin, which you may al­ready be us­ing in your P2s, is cur­rently sev­eral months out-of-date. While you may con­tinue to use the WordPress plu­gin, it will no longer re­ceive reg­u­lar up­dates.</p>
<p>If you choose to switch to the Chrome ex­ten­sion, I think you’ll ﬁnd the ex­pe­ri­ence more holis­tic, since it is able to per­sist user data across sites.</p>]]></description>
            <content:encoded><![CDATA[<h1>The Chrome Extension Supersedes the WordPress Plugin</h1>
<p>Several months ago, I an­nounced the ini­tial ver­sion of the Harper WordPress plu­gin. In the in­ter­est of those still us­ing it, I’d like to give y’all an up­date.</p>
<p><a href="https://chromewebstore.google.com/detail/private-grammar-checker-h/lodbfhdipoipcjmlebjbgmmgekckhpfb">The Chrome ex­ten­sion</a> is now the rec­om­mended way to use Harper in WordPress. There are sev­eral com­pound­ing rea­sons for this. Most im­por­tantly, it­er­at­ing on a Chrome ex­ten­sion is sig­nif­i­cantly faster than the re­view process for WordPress.com plu­g­ins. In fact, the cur­rent WordPress.com Harper plu­gin, which you may al­ready be us­ing in your P2s, is cur­rently sev­eral months out-of-date. While you may con­tinue to use the WordPress plu­gin, it will no longer re­ceive reg­u­lar up­dates.</p>
<p>If you choose to switch to the Chrome ex­ten­sion, I think you’ll ﬁnd the ex­pe­ri­ence more holis­tic, since it is able to per­sist user data across sites.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[I Spoke at WordCamp U.S. in 2025]]></title>
            <link>https://elijahpotter.dev/articles/i_spoke_at_wordcamp_u.s._in_2025</link>
            <guid>https://elijahpotter.dev/articles/i_spoke_at_wordcamp_u.s._in_2025</guid>
            <pubDate>Wed, 03 Sep 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>I Spoke at WordCamp U.S. in 2025</h1>
<p>I had the in­cred­i­ble op­por­tu­nity to speak at WordCamp U.S. at <a href="https://ma.tt">Matt</a><span class="push-single"></span><span class="pull-single">’</span>s sug­ges­tion.
I met a num­ber of won­der­ful, kind, and invit­ing peo­ple.
It was my ﬁrst WordCamp and ﬁrst con­fer­ence talk, so I was nat­u­rally ner­vous.
The com­mu­nity made it feel easy.</p>
<iframe width="100%" height="400" src="https://www.youtube-nocookie.com/embed/e-jYf8ZV_SA?si=VDPWlTx0kV8lVJTU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<p>I spoke about how we can use ma­chine learn­ing to im­prove the qual­ity of our ap­pli­ca­tions with­out big cap­i­tal ex­pen­di­tures.
In fact, I ar­gue that cap­i­tal ex­pen­di­tures are of­ten a step in the wrong di­rec­tion.</p>
<p>Fun fact: This talk started out as <a href="./training_a_chunker_with_burn">a blog post</a>.</p>]]></description>
            <content:encoded><![CDATA[<h1>I Spoke at WordCamp U.S. in 2025</h1>
<p>I had the in­cred­i­ble op­por­tu­nity to speak at WordCamp U.S. at <a href="https://ma.tt">Matt</a><span class="push-single"></span><span class="pull-single">’</span>s sug­ges­tion.
I met a num­ber of won­der­ful, kind, and invit­ing peo­ple.
It was my ﬁrst WordCamp and ﬁrst con­fer­ence talk, so I was nat­u­rally ner­vous.
The com­mu­nity made it feel easy.</p>
<iframe width="100%" height="400" src="https://www.youtube-nocookie.com/embed/e-jYf8ZV_SA?si=VDPWlTx0kV8lVJTU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<p>I spoke about how we can use ma­chine learn­ing to im­prove the qual­ity of our ap­pli­ca­tions with­out big cap­i­tal ex­pen­di­tures.
In fact, I ar­gue that cap­i­tal ex­pen­di­tures are of­ten a step in the wrong di­rec­tion.</p>
<p>Fun fact: This talk started out as <a href="./training_a_chunker_with_burn">a blog post</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Books I Have Read Since July 2025]]></title>
            <link>https://elijahpotter.dev/articles/the_books_i_have_read_since_july_2025</link>
            <guid>https://elijahpotter.dev/articles/the_books_i_have_read_since_july_2025</guid>
            <pubDate>Mon, 01 Sep 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Books I Have Read Since July of 2025</h1>
<img src="/images/portland_rose.png" alt="Portland Rose">
<p>Whenever I can, I try to stop and re­ﬂect.
Of­ten it’s about my work and the things I can do to con­tinue be­ing more pro­duc­tive.
But of­ten, I re­ﬂect about the re­cent books I have read and the things I (and you) can learn from them.
That’s what this post will hope­fully turn out to be.</p>
<p>So, with­out fur­ther ado, here are some of the books I have read re­cently.</p>
<h2><em>The Stoic Challenge</em> by William B. Irvine</h2>
<p>At the end of each episode of his pod­cast, Ezra Klein asks his guests the same ques­tion: <em>what three books would you rec­om­mend we read?</em>
Terminating <a href="https://pca.st/q3l0grkv">one par­tic­u­lar episode</a>, au­thor Jonathan Haidt sug­gests <em>The Stoic Challenge</em>.</p>
<p>I was al­ready aware of William B. Irvine as a pro­liﬁc stoic writer.
In fact, I had tried to read an­other of his books on the sub­ject of Stoic phi­los­o­phy.
I had not ﬁn­ished it be­cause, well, I found it some­what dry.</p>
<p><em>The Stoic Challenge</em>, on the other hand, was a de­light.
It feels like Irvine took all the suc­cess­ful parts of the pre­vi­ous book and con­densed them down into some­thing my Gen-Z brain could un­der­stand.</p>
<p>The book’s more im­por­tant take­away is sim­ple.
In life, you and I both en­counter daily strug­gles that im­pede our progress to­ward our goals.</p>
<p>Our ﬂight is run­ning late, so we get to our ho­tel later than we in­tend.
We for­get to pack a lunch, so we must get through our work­day hun­gry.
Our babysit­ter wants a raise, which means we won’t be able to go out as of­ten.</p>
<p><em>The Stoic Challenge</em> en­cour­ages us to frame these set­backs as a test.
A test put upon us by some hid­den force.
To pass the test, we need to act in a ra­tio­nal way, and take steps to avoid ex­ac­er­bat­ing the re­sult­ing neg­a­tive emo­tions.</p>
<p>I highly sug­gest any­one read this book.
I felt en­gaged the en­tire time read­ing it, and its lessons ap­ply to any walk of life.</p>
<h2><em>Slow Productivity</em> by Cal Newport</h2>
<p>Cal Newport is known for many of the most ground­break­ing pro­duc­tiv­ity books of the last decade.
Per­son­ally, <em>Deep Work</em> and <em>How to Be a Straight-A Student</em> have been par­tic­u­larly im­pact­ful.
They’ve en­cour­aged me to cut out the un­nec­es­sar­ily com­plex parts of my work­ing life, and work to ﬁnd ways of do­ing the same for oth­ers.</p>
<p>His lat­est work con­tin­ues from the same ideas he put forth in those books.</p>
<p><em>How to be a Straight-A Student</em> was fo­cused on the idea that the quan­tity of high-qual­ity, pub­lished work made the dif­fer­ence be­tween mediocre and stel­lar aca­d­e­mic ca­reers.
Fol­low­ing on, <em>Deep Work</em> made the case that the best way to achieve high out­put was by cre­at­ing cir­cum­stances that al­lowed for ex­tended pe­ri­ods of in­tense fo­cus.</p>
<p><em>Slow Productivity</em> takes both of those ideas a step fur­ther.
He sug­gests that the key to high-qual­ity out­put, in ad­di­tion to deep fo­cus, re­quires time.
He be­lieve that truly amaz­ing prod­ucts re­quire the brain to be re­laxed, com­fort­able, and un­hur­ried.
It is only then, that it can work out­side of the box.</p>
<p>I’m not en­tirely sure I agree, but the book made for a stim­u­lat­ing read nonethe­less.</p>
<h2><em>The Humans</em> by Matt Haig</h2>
<p><em>The Humans</em> is a beau­ti­ful book that cel­e­brates all the idio­syn­cratic things that make hu­mans spe­cial.
Don’t worry, I won’t spoil it for you
I’ve read a de­cent num­ber of Haig’s books in the past, and I’ve en­joyed each one.
This was no dif­fer­ent.</p>
<p>I’ll spare you the de­tails.</p>
<h2><em>The 22 Immutable Laws of Marketing</em></h2>
<p>I am an en­gi­neer.
I went to an en­gi­neer­ing school.
Since I spend most of my time around peo­ple who work in ﬁelds re­lated to en­gi­neer­ing, I do not spend much time around peo­ple who know a lot about mar­ket­ing.
I wanted to ﬁx that. Which is why I read <em>The 22 Immutable Laws of Marketing.</em></p>
<p>This is a book in­tended for peo­ple who want to un­der­stand the ax­ioms of how brands are per­ceived by the pub­lic.</p>
<h2><em>Sapiens</em> by Yuval Noah Harari</h2>
<p><em>Sapiens</em> is a com­pre­hen­sive his­tory of our species, <em>homo sapi­ens</em>, be­gin­ning with the ﬁrst hu­mans to walk the planet all the way up to the hu­mans of to­day.
Harari takes a unique look at what makes our species spe­cial, and the things that make our fu­ture un­cer­tain.</p>
<p>At times, the writ­ing can be a bit dry and repet­i­tive, but on the whole it is a well-writ­ten book.
I would cer­tainly rec­om­mend it for any­one who seeks an en­tirely new per­spec­tive on the short his­tory of hu­man life.</p>
<h2><em>Your Utopia</em> by Bora Chung</h2>
<p>I went into <em>Your Utopia</em> ex­pect­ing a col­lec­tion of short sto­ries sim­i­lar to those by Ted Chiang.
That is not what I re­ceived.</p>
<p>Instead, Chung pro­vides a some­what eclec­tic col­lec­tion of sto­ries that feel not un­like a fever dream.
Rather than gen­uine, hard sci­ence ﬁc­tion, Chung tells the tales of sen­tient cars, el­e­va­tors that care for the el­derly, and im­mor­tal sec­re­taries.</p>
<p>I don’t re­gret read­ing it, but I also don’t rec­om­mend it to fans of Chiang.</p>]]></description>
            <content:encoded><![CDATA[<h1>The Books I Have Read Since July of 2025</h1>
<img src="/images/portland_rose.png" alt="Portland Rose">
<p>Whenever I can, I try to stop and re­ﬂect.
Of­ten it’s about my work and the things I can do to con­tinue be­ing more pro­duc­tive.
But of­ten, I re­ﬂect about the re­cent books I have read and the things I (and you) can learn from them.
That’s what this post will hope­fully turn out to be.</p>
<p>So, with­out fur­ther ado, here are some of the books I have read re­cently.</p>
<h2><em>The Stoic Challenge</em> by William B. Irvine</h2>
<p>At the end of each episode of his pod­cast, Ezra Klein asks his guests the same ques­tion: <em>what three books would you rec­om­mend we read?</em>
Terminating <a href="https://pca.st/q3l0grkv">one par­tic­u­lar episode</a>, au­thor Jonathan Haidt sug­gests <em>The Stoic Challenge</em>.</p>
<p>I was al­ready aware of William B. Irvine as a pro­liﬁc stoic writer.
In fact, I had tried to read an­other of his books on the sub­ject of Stoic phi­los­o­phy.
I had not ﬁn­ished it be­cause, well, I found it some­what dry.</p>
<p><em>The Stoic Challenge</em>, on the other hand, was a de­light.
It feels like Irvine took all the suc­cess­ful parts of the pre­vi­ous book and con­densed them down into some­thing my Gen-Z brain could un­der­stand.</p>
<p>The book’s more im­por­tant take­away is sim­ple.
In life, you and I both en­counter daily strug­gles that im­pede our progress to­ward our goals.</p>
<p>Our ﬂight is run­ning late, so we get to our ho­tel later than we in­tend.
We for­get to pack a lunch, so we must get through our work­day hun­gry.
Our babysit­ter wants a raise, which means we won’t be able to go out as of­ten.</p>
<p><em>The Stoic Challenge</em> en­cour­ages us to frame these set­backs as a test.
A test put upon us by some hid­den force.
To pass the test, we need to act in a ra­tio­nal way, and take steps to avoid ex­ac­er­bat­ing the re­sult­ing neg­a­tive emo­tions.</p>
<p>I highly sug­gest any­one read this book.
I felt en­gaged the en­tire time read­ing it, and its lessons ap­ply to any walk of life.</p>
<h2><em>Slow Productivity</em> by Cal Newport</h2>
<p>Cal Newport is known for many of the most ground­break­ing pro­duc­tiv­ity books of the last decade.
Per­son­ally, <em>Deep Work</em> and <em>How to Be a Straight-A Student</em> have been par­tic­u­larly im­pact­ful.
They’ve en­cour­aged me to cut out the un­nec­es­sar­ily com­plex parts of my work­ing life, and work to ﬁnd ways of do­ing the same for oth­ers.</p>
<p>His lat­est work con­tin­ues from the same ideas he put forth in those books.</p>
<p><em>How to be a Straight-A Student</em> was fo­cused on the idea that the quan­tity of high-qual­ity, pub­lished work made the dif­fer­ence be­tween mediocre and stel­lar aca­d­e­mic ca­reers.
Fol­low­ing on, <em>Deep Work</em> made the case that the best way to achieve high out­put was by cre­at­ing cir­cum­stances that al­lowed for ex­tended pe­ri­ods of in­tense fo­cus.</p>
<p><em>Slow Productivity</em> takes both of those ideas a step fur­ther.
He sug­gests that the key to high-qual­ity out­put, in ad­di­tion to deep fo­cus, re­quires time.
He be­lieve that truly amaz­ing prod­ucts re­quire the brain to be re­laxed, com­fort­able, and un­hur­ried.
It is only then, that it can work out­side of the box.</p>
<p>I’m not en­tirely sure I agree, but the book made for a stim­u­lat­ing read nonethe­less.</p>
<h2><em>The Humans</em> by Matt Haig</h2>
<p><em>The Humans</em> is a beau­ti­ful book that cel­e­brates all the idio­syn­cratic things that make hu­mans spe­cial.
Don’t worry, I won’t spoil it for you
I’ve read a de­cent num­ber of Haig’s books in the past, and I’ve en­joyed each one.
This was no dif­fer­ent.</p>
<p>I’ll spare you the de­tails.</p>
<h2><em>The 22 Immutable Laws of Marketing</em></h2>
<p>I am an en­gi­neer.
I went to an en­gi­neer­ing school.
Since I spend most of my time around peo­ple who work in ﬁelds re­lated to en­gi­neer­ing, I do not spend much time around peo­ple who know a lot about mar­ket­ing.
I wanted to ﬁx that. Which is why I read <em>The 22 Immutable Laws of Marketing.</em></p>
<p>This is a book in­tended for peo­ple who want to un­der­stand the ax­ioms of how brands are per­ceived by the pub­lic.</p>
<h2><em>Sapiens</em> by Yuval Noah Harari</h2>
<p><em>Sapiens</em> is a com­pre­hen­sive his­tory of our species, <em>homo sapi­ens</em>, be­gin­ning with the ﬁrst hu­mans to walk the planet all the way up to the hu­mans of to­day.
Harari takes a unique look at what makes our species spe­cial, and the things that make our fu­ture un­cer­tain.</p>
<p>At times, the writ­ing can be a bit dry and repet­i­tive, but on the whole it is a well-writ­ten book.
I would cer­tainly rec­om­mend it for any­one who seeks an en­tirely new per­spec­tive on the short his­tory of hu­man life.</p>
<h2><em>Your Utopia</em> by Bora Chung</h2>
<p>I went into <em>Your Utopia</em> ex­pect­ing a col­lec­tion of short sto­ries sim­i­lar to those by Ted Chiang.
That is not what I re­ceived.</p>
<p>Instead, Chung pro­vides a some­what eclec­tic col­lec­tion of sto­ries that feel not un­like a fever dream.
Rather than gen­uine, hard sci­ence ﬁc­tion, Chung tells the tales of sen­tient cars, el­e­va­tors that care for the el­derly, and im­mor­tal sec­re­taries.</p>
<p>I don’t re­gret read­ing it, but I also don’t rec­om­mend it to fans of Chiang.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/portland_rose.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Harper Evolves]]></title>
            <link>https://elijahpotter.dev/articles/harper_evolves</link>
            <guid>https://elijahpotter.dev/articles/harper_evolves</guid>
            <pubDate>Thu, 21 Aug 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Harper Evolves</h1>
<p>I want you to read that ti­tle as lit­er­ally as pos­si­ble.
<a href="https://writewithharper.com">Harper</a> is now ca­pa­ble of evo­lu­tion.</p>
<p>This past week, I’ve been work­ing on a sys­tem that should al­low us to han­dle more com­plex gram­mat­i­cal cases and con­texts, faster.
I be­lieve it will im­prove our abil­ity to add new gram­mat­i­cal rules to Harper by some­where be­tween 500% and 1,000%.</p>
<p>To top it off, this sys­tem does it with­out slow­ing Harper it­self down or in­creas­ing the mem­ory foot­print.</p>
<p><strong>Let’s get into it.</strong></p>
<h2>The Problem</h2>
<p>There are sev­eral unique method­olo­gies at play when Harper goes about gram­mar check­ing.
Which strat­egy de­pends on the gram­mat­i­cal rule in ques­tion.
To­day, we’re in­ter­ested in ex­pres­sion rules.</p>
<p>For the cu­ri­ous, I have re­cently writ­ten <a href="./reflections_on_expression_rules">a re­ﬂec­tion</a> on ex­pres­sion rules, as well as <a href="./writing_an_expression_rule_for_harper">a guide</a> for any­one in­ter­ested in pro­duc­ing them.
This post, how­ever, will not re­count in­for­ma­tion I’ve al­ready writ­ten on this blog.</p>
<p>By count, ex­pres­sion rules make up the ma­jor­ity of gram­mat­i­cal rules Harper is cur­rently ca­pa­ble of de­tect­ing.
This is be­cause they are fast, easy to write, and most im­por­tantly, easy to re­view.</p>
<p>There are, how­ever, oc­ca­sional hic­cups that I en­counter when tack­ling a prob­lem.
The English lan­guage is tricky and of­ten it con­tra­dicts it­self.
I will of­ten try to write a rule which cov­ers a cer­tain case, only to ﬁnd that it does­n’t cover all cases.
I can it­er­ate, but it of­ten be­comes te­dious and time-con­sum­ing.</p>
<h2>The Solution</h2>
<p>Last week, I threw in the towel.
I was tired of it­er­at­ing cease­lessly to­wards a goal, only to have a new one to tackle af­ter that.
So I de­cided I would let the com­puter it­er­ate for me.</p>
<p>Harper’s ex­pres­sions are es­sen­tially small pro­grams which are able to iden­tify the lo­ca­tions of given pat­terns in nat­ural lan­guage.
They are con­structed at run­time, but they run ex­ceed­ingly fast be­cause they tend to be amenable to mod­ern branch pre­dic­tion.
We can use this fact to our ad­van­tage.</p>
<p>When gen­er­at­ing an ex­pres­sion that de­tects a par­tic­u­lar gram­mat­i­cal rule, the new sys­tem (which I’ve called The Ripper) fol­lows three steps.</p>
<ol>
<li>Generate N ran­dom Harper ex­pres­sions</li>
<li>Score the per­for­mance of these ex­pres­sions by test­ing them against a cu­rated dataset.
The dataset con­tains la­beled rows of sen­tences that do and do not con­tain the gram­mat­i­cal rule of in­ter­est.</li>
<li>Take the best K ex­pres­sions and mu­tate them to left with L new<span class="push-double"></span> <span class="pull-double">“</span>child” ex­pres­sions. Go to step 2.</li>
</ol>
<p>That’s it!
We’re es­sen­tially treat­ing ex­pres­sions as liv­ing crea­tures and sub­ject­ing them to ar­ti­ﬁ­cial se­lec­tion.
It works re­mark­ably well.</p>
<p>Since these datasets are hand­crafted (or gen­er­ated by an <span class="small-caps">LLM</span>), they don’t need to be large.
Plus, the ex­pres­sions them­selves are quite fast to gen­er­ate and test, so we can do so at an ex­cep­tional rate.</p>
<p>My lap­top is able to churn through about 90 thou­sand can­di­dates per sec­ond, al­low­ing us to con­verge on an ac­cept­able re­sult in just a few min­utes.
Given more time, it’s able to pro­duce an ex­pres­sion rule that is more ac­cu­rate than what I could write my­self.</p>
<h2>What’s Next?</h2>
<p>I in­tend to spend some time op­ti­miz­ing the process, par­tic­u­lar for the hu­man el­e­ment.
I’d like to be able to cre­ate batches of these datasets and let The Ripper take care of them all at once, overnight or on a beefy server in the cloud.</p>
<p>I’d also like to set up au­to­mated work­ﬂows for pip­ing data from an <span class="small-caps">LLM</span> di­rectly into the Ripper.
Ideally, I want this sys­tem to get to a point where I can feed in­for­ma­tion from a style guide into an <span class="small-caps">LLM</span> and get a guar­an­teed func­tion­ing Harper ex­pres­sion rule out of it.</p>]]></description>
            <content:encoded><![CDATA[<h1>Harper Evolves</h1>
<p>I want you to read that ti­tle as lit­er­ally as pos­si­ble.
<a href="https://writewithharper.com">Harper</a> is now ca­pa­ble of evo­lu­tion.</p>
<p>This past week, I’ve been work­ing on a sys­tem that should al­low us to han­dle more com­plex gram­mat­i­cal cases and con­texts, faster.
I be­lieve it will im­prove our abil­ity to add new gram­mat­i­cal rules to Harper by some­where be­tween 500% and 1,000%.</p>
<p>To top it off, this sys­tem does it with­out slow­ing Harper it­self down or in­creas­ing the mem­ory foot­print.</p>
<p><strong>Let’s get into it.</strong></p>
<h2>The Problem</h2>
<p>There are sev­eral unique method­olo­gies at play when Harper goes about gram­mar check­ing.
Which strat­egy de­pends on the gram­mat­i­cal rule in ques­tion.
To­day, we’re in­ter­ested in ex­pres­sion rules.</p>
<p>For the cu­ri­ous, I have re­cently writ­ten <a href="./reflections_on_expression_rules">a re­ﬂec­tion</a> on ex­pres­sion rules, as well as <a href="./writing_an_expression_rule_for_harper">a guide</a> for any­one in­ter­ested in pro­duc­ing them.
This post, how­ever, will not re­count in­for­ma­tion I’ve al­ready writ­ten on this blog.</p>
<p>By count, ex­pres­sion rules make up the ma­jor­ity of gram­mat­i­cal rules Harper is cur­rently ca­pa­ble of de­tect­ing.
This is be­cause they are fast, easy to write, and most im­por­tantly, easy to re­view.</p>
<p>There are, how­ever, oc­ca­sional hic­cups that I en­counter when tack­ling a prob­lem.
The English lan­guage is tricky and of­ten it con­tra­dicts it­self.
I will of­ten try to write a rule which cov­ers a cer­tain case, only to ﬁnd that it does­n’t cover all cases.
I can it­er­ate, but it of­ten be­comes te­dious and time-con­sum­ing.</p>
<h2>The Solution</h2>
<p>Last week, I threw in the towel.
I was tired of it­er­at­ing cease­lessly to­wards a goal, only to have a new one to tackle af­ter that.
So I de­cided I would let the com­puter it­er­ate for me.</p>
<p>Harper’s ex­pres­sions are es­sen­tially small pro­grams which are able to iden­tify the lo­ca­tions of given pat­terns in nat­ural lan­guage.
They are con­structed at run­time, but they run ex­ceed­ingly fast be­cause they tend to be amenable to mod­ern branch pre­dic­tion.
We can use this fact to our ad­van­tage.</p>
<p>When gen­er­at­ing an ex­pres­sion that de­tects a par­tic­u­lar gram­mat­i­cal rule, the new sys­tem (which I’ve called The Ripper) fol­lows three steps.</p>
<ol>
<li>Generate N ran­dom Harper ex­pres­sions</li>
<li>Score the per­for­mance of these ex­pres­sions by test­ing them against a cu­rated dataset.
The dataset con­tains la­beled rows of sen­tences that do and do not con­tain the gram­mat­i­cal rule of in­ter­est.</li>
<li>Take the best K ex­pres­sions and mu­tate them to left with L new<span class="push-double"></span> <span class="pull-double">“</span>child” ex­pres­sions. Go to step 2.</li>
</ol>
<p>That’s it!
We’re es­sen­tially treat­ing ex­pres­sions as liv­ing crea­tures and sub­ject­ing them to ar­ti­ﬁ­cial se­lec­tion.
It works re­mark­ably well.</p>
<p>Since these datasets are hand­crafted (or gen­er­ated by an <span class="small-caps">LLM</span>), they don’t need to be large.
Plus, the ex­pres­sions them­selves are quite fast to gen­er­ate and test, so we can do so at an ex­cep­tional rate.</p>
<p>My lap­top is able to churn through about 90 thou­sand can­di­dates per sec­ond, al­low­ing us to con­verge on an ac­cept­able re­sult in just a few min­utes.
Given more time, it’s able to pro­duce an ex­pres­sion rule that is more ac­cu­rate than what I could write my­self.</p>
<h2>What’s Next?</h2>
<p>I in­tend to spend some time op­ti­miz­ing the process, par­tic­u­lar for the hu­man el­e­ment.
I’d like to be able to cre­ate batches of these datasets and let The Ripper take care of them all at once, overnight or on a beefy server in the cloud.</p>
<p>I’d also like to set up au­to­mated work­ﬂows for pip­ing data from an <span class="small-caps">LLM</span> di­rectly into the Ripper.
Ideally, I want this sys­tem to get to a point where I can feed in­for­ma­tion from a style guide into an <span class="small-caps">LLM</span> and get a guar­an­teed func­tion­ing Harper ex­pres­sion rule out of it.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Art of the Talk]]></title>
            <link>https://elijahpotter.dev/articles/the_art_of_the_talk</link>
            <guid>https://elijahpotter.dev/articles/the_art_of_the_talk</guid>
            <pubDate>Fri, 15 Aug 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Art of the Talk</h1>
<img src="/images/mic.png" alt="A microphone sitting on a piano.">
<p>I’ve given dozens of talks over the years, yet I still feel like I have much to learn about pub­lic speak­ing.
The act of pre­sent­ing an idea to a large au­di­ence in per­son is in­tim­i­dat­ing for me, as I imag­ine it is for most peo­ple.</p>
<p>Preparing for my up­com­ing talk at WordCamp <span class="small-caps">US</span> has me stop­ping to re­ﬂect on what I al­ready know.
To re­call the ba­sic prin­ci­ples that turn a good talk into great one.</p>
<p>Conveying an idea ef­fec­tively is a pow­er­ful skill. It can help get peo­ple on your side and di­rect them to your point of view.
Hu­mans are be­ings that thrive when they build and main­tain strong re­la­tion­ships with each other.
Good com­mu­ni­ca­tion su­per­charges that process.</p>
<p>Here are my bold ideas for how to pre­sent ideas well, in no par­tic­u­lar or­der.</p>
<h2>Convey Exactly One Idea At a Time</h2>
<p>A com­mon prob­lem peo­ple run into when they draft a pre­sen­ta­tion is not a lack of ideas: It is that they have too many.</p>
<p>People adore talk­ing about them­selves or their ideas.
A nat­ural con­se­quence is that peo­ple have a predilec­tion to stuff a pre­sen­ta­tion with more in­for­ma­tion than they need to make their point.
Make sure to al­lo­cate time to take stuff out.</p>
<p>The more stream­lined and clear you make your point, the more re­cep­tive your au­di­ence will be.</p>
<h2>Use a Story</h2>
<p>The hu­man brain is wired for sto­ries.
We’ve been telling them for thou­sands of years.
Peo­ple re­mem­ber them far bet­ter than they do ar­bi­trary in­for­ma­tion.
In fact, that is ex­actly how com­mu­ni­ties used to do record­keep­ing: by telling sto­ries.</p>
<p>By weav­ing a tale into your pre­sen­ta­tion, you can prime the au­di­ence to re­ceive your idea, and make that idea more mem­o­rable.</p>
<h2>Explain the<span class="push-double"></span> <span class="pull-double">“</span>Why”</h2>
<p>The<span class="push-double"></span> <span class="pull-double">“</span>Why” should be the ﬁrst thing your au­di­ence hears from you.
Why they should lis­ten.
Why your idea is im­por­tant.
Why your in­put can help them in their pro­fes­sional or per­sonal lives.</p>
<p>Chris Anderson, the Head of <span class="small-caps">TED</span>, says,<span class="push-double"></span> <span class="pull-double">“</span>Style with­out sub­stance is aw­ful”.</p>
<p>In other words, you can have the fan­ci­est, most vi­su­ally ap­peal­ing slide deck and all the con­ﬁ­dence in the world, and you can still have a mediocre pre­sen­ta­tion.
Find a topic or idea that you gen­uinely care about, and ex­plain why the au­di­ence should care about it too.</p>]]></description>
            <content:encoded><![CDATA[<h1>The Art of the Talk</h1>
<img src="/images/mic.png" alt="A microphone sitting on a piano.">
<p>I’ve given dozens of talks over the years, yet I still feel like I have much to learn about pub­lic speak­ing.
The act of pre­sent­ing an idea to a large au­di­ence in per­son is in­tim­i­dat­ing for me, as I imag­ine it is for most peo­ple.</p>
<p>Preparing for my up­com­ing talk at WordCamp <span class="small-caps">US</span> has me stop­ping to re­ﬂect on what I al­ready know.
To re­call the ba­sic prin­ci­ples that turn a good talk into great one.</p>
<p>Conveying an idea ef­fec­tively is a pow­er­ful skill. It can help get peo­ple on your side and di­rect them to your point of view.
Hu­mans are be­ings that thrive when they build and main­tain strong re­la­tion­ships with each other.
Good com­mu­ni­ca­tion su­per­charges that process.</p>
<p>Here are my bold ideas for how to pre­sent ideas well, in no par­tic­u­lar or­der.</p>
<h2>Convey Exactly One Idea At a Time</h2>
<p>A com­mon prob­lem peo­ple run into when they draft a pre­sen­ta­tion is not a lack of ideas: It is that they have too many.</p>
<p>People adore talk­ing about them­selves or their ideas.
A nat­ural con­se­quence is that peo­ple have a predilec­tion to stuff a pre­sen­ta­tion with more in­for­ma­tion than they need to make their point.
Make sure to al­lo­cate time to take stuff out.</p>
<p>The more stream­lined and clear you make your point, the more re­cep­tive your au­di­ence will be.</p>
<h2>Use a Story</h2>
<p>The hu­man brain is wired for sto­ries.
We’ve been telling them for thou­sands of years.
Peo­ple re­mem­ber them far bet­ter than they do ar­bi­trary in­for­ma­tion.
In fact, that is ex­actly how com­mu­ni­ties used to do record­keep­ing: by telling sto­ries.</p>
<p>By weav­ing a tale into your pre­sen­ta­tion, you can prime the au­di­ence to re­ceive your idea, and make that idea more mem­o­rable.</p>
<h2>Explain the<span class="push-double"></span> <span class="pull-double">“</span>Why”</h2>
<p>The<span class="push-double"></span> <span class="pull-double">“</span>Why” should be the ﬁrst thing your au­di­ence hears from you.
Why they should lis­ten.
Why your idea is im­por­tant.
Why your in­put can help them in their pro­fes­sional or per­sonal lives.</p>
<p>Chris Anderson, the Head of <span class="small-caps">TED</span>, says,<span class="push-double"></span> <span class="pull-double">“</span>Style with­out sub­stance is aw­ful”.</p>
<p>In other words, you can have the fan­ci­est, most vi­su­ally ap­peal­ing slide deck and all the con­ﬁ­dence in the world, and you can still have a mediocre pre­sen­ta­tion.
Find a topic or idea that you gen­uinely care about, and ex­plain why the au­di­ence should care about it too.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/mic.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Harper in the News]]></title>
            <link>https://elijahpotter.dev/articles/harper_in_the_news</link>
            <guid>https://elijahpotter.dev/articles/harper_in_the_news</guid>
            <pubDate>Tue, 12 Aug 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Harper in the News</h1>
<p>The biggest com­plaint about Harper is that the qual­ity of gram­mar check­ing still needs some work.
I’ve got an ex­cit­ing new pro­ject I’m prepar­ing to dis­cuss later this week (hopefully) in that area.
It should be a step-func­tion im­prove­ment over our pre­vi­ous meth­ods for cre­at­ing new rules.</p>
<p>In the mean­time, I’ve col­lected some re­cent sight­ings of Harper in the wild:</p>
<ul>
<li><a href="https://obsidian.rocks/resource-harper/">Obsidian Rocks</a></li>
<li><a href="https://www.wearefounders.uk/the-grammar-checker-that-actually-gets-developers-meet-harper/">We Are Founders</a></li>
<li><a href="https://itsfoss.com/harper-grammar-checker/">It’s <span class="small-caps">FOSS</span></a></li>
<li><a href="https://www.xda-developers.com/ditched-grammarly-for-this-amazing-open-source-alternative/"><span class="small-caps">XDA</span> Developers</a></li>
<li><a href="https://www.maketecheasier.com/harper-vs-grammarly/">Make Tech Easier</a></li>
</ul>]]></description>
            <content:encoded><![CDATA[<h1>Harper in the News</h1>
<p>The biggest com­plaint about Harper is that the qual­ity of gram­mar check­ing still needs some work.
I’ve got an ex­cit­ing new pro­ject I’m prepar­ing to dis­cuss later this week (hopefully) in that area.
It should be a step-func­tion im­prove­ment over our pre­vi­ous meth­ods for cre­at­ing new rules.</p>
<p>In the mean­time, I’ve col­lected some re­cent sight­ings of Harper in the wild:</p>
<ul>
<li><a href="https://obsidian.rocks/resource-harper/">Obsidian Rocks</a></li>
<li><a href="https://www.wearefounders.uk/the-grammar-checker-that-actually-gets-developers-meet-harper/">We Are Founders</a></li>
<li><a href="https://itsfoss.com/harper-grammar-checker/">It’s <span class="small-caps">FOSS</span></a></li>
<li><a href="https://www.xda-developers.com/ditched-grammarly-for-this-amazing-open-source-alternative/"><span class="small-caps">XDA</span> Developers</a></li>
<li><a href="https://www.maketecheasier.com/harper-vs-grammarly/">Make Tech Easier</a></li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Better Interfaces for Grammar Checking]]></title>
            <link>https://elijahpotter.dev/articles/better_interfaces_for_grammar_checking</link>
            <guid>https://elijahpotter.dev/articles/better_interfaces_for_grammar_checking</guid>
            <pubDate>Mon, 28 Jul 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Better <span class="small-caps">UI</span> for Grammar Checking</h1>
<p>Grammar check­ing can be cum­ber­some, es­pe­cially when its sloth gets in the way of your think­ing. That’s part of why so many pre­fer <a href="https://writewithharper.com">Harper</a> over Grammarly or LanguageTool. Waiting four sec­onds per edit can add up over long days of writ­ing.</p>
<p>But there’s some­thing else that can slow peo­ple down, es­pe­cially power users. It’s the mouse. Moving one’s hand to the point­ing de­vice, mov­ing it, and click­ing can take a while—and that adds up too.</p>
<p>Neovim (and sim­i­lar text ed­i­tors like Obsidian) solve this prob­lem by us­ing an­other in­put de­vice: the key­board. Since you’re us­ing the key­board to write words to the page, why not also use it to cor­rect gram­mat­i­cal mis­takes?</p>
<p>In the lat­est re­lease of Harper’s Chrome ex­ten­sion, that’s ex­actly what I’ve added. While writ­ing, you’re bound to make (at the very least) a few ty­pos. If that’s the case, you can now dou­ble-tap the shift key to open the sug­ges­tion popup from the er­ror clos­est to your caret. From there, you can use tab and en­ter to se­lect and ap­ply a cor­rec­tion.</p>
<p>I’ve per­son­ally found that this sin­gle-hand­edly speeds up my writ­ing process by at least 20%. I’m able to fo­cus less on each word and more on the con­tent.</p>]]></description>
            <content:encoded><![CDATA[<h1>Better <span class="small-caps">UI</span> for Grammar Checking</h1>
<p>Grammar check­ing can be cum­ber­some, es­pe­cially when its sloth gets in the way of your think­ing. That’s part of why so many pre­fer <a href="https://writewithharper.com">Harper</a> over Grammarly or LanguageTool. Waiting four sec­onds per edit can add up over long days of writ­ing.</p>
<p>But there’s some­thing else that can slow peo­ple down, es­pe­cially power users. It’s the mouse. Moving one’s hand to the point­ing de­vice, mov­ing it, and click­ing can take a while—and that adds up too.</p>
<p>Neovim (and sim­i­lar text ed­i­tors like Obsidian) solve this prob­lem by us­ing an­other in­put de­vice: the key­board. Since you’re us­ing the key­board to write words to the page, why not also use it to cor­rect gram­mat­i­cal mis­takes?</p>
<p>In the lat­est re­lease of Harper’s Chrome ex­ten­sion, that’s ex­actly what I’ve added. While writ­ing, you’re bound to make (at the very least) a few ty­pos. If that’s the case, you can now dou­ble-tap the shift key to open the sug­ges­tion popup from the er­ror clos­est to your caret. From there, you can use tab and en­ter to se­lect and ap­ply a cor­rec­tion.</p>
<p>I’ve per­son­ally found that this sin­gle-hand­edly speeds up my writ­ing process by at least 20%. I’m able to fo­cus less on each word and more on the con­tent.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Speaking at WordCamp U.S. in 2025]]></title>
            <link>https://elijahpotter.dev/articles/speaking_at_wordcamp_u.s._in_2025</link>
            <guid>https://elijahpotter.dev/articles/speaking_at_wordcamp_u.s._in_2025</guid>
            <pubDate>Fri, 25 Jul 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Speaking at WordCamp U.S. in August</h1>
<p>I was re­cently en­cour­aged to sub­mit a talk pro­posal to the <span class="small-caps">AI</span> track at WordCamp US.
I was more than happy to oblige, and to my sur­prise, I got in.
See de­scrip­tion be­low for de­tails. I hope to see some of y’all there!</p>
<img src="/images/wordcamp_us_speaker_2025.png" alt="I&#x27;ll be speaking at WordCamp US 2025">
<h2>Description</h2>
<p>Machine learn­ing has been around for decades.
Un­til re­cently, run­ning <span class="small-caps">ML</span> mod­els typ­i­cally re­quired big cen­tral­ized servers due to high com­pu­ta­tional de­mands.
With mod­ern hard­ware and tools like Rust and Burn, we can ﬂip the script.
When run on a user’s de­vice, trained mod­els can be an in­cred­i­ble boost to hu­man cre­ativ­ity and ex­pres­sion.</p>
<p>Harper is a gram­mar checker that is de­signed to im­prove your work with­out de­grad­ing your in­tegrity—or the writ­ing ex­pe­ri­ence it­self.
Our users gen­er­ate and pub­lish novel ideas faster be­cause we fo­cus on what’s im­por­tant: speed, pri­vacy, and ac­cu­racy.</p>
<p>WordPress’ mis­sion is to de­moc­ra­tize pub­lish­ing.
This talk will ex­plore how we can use <span class="small-caps">AI</span> to de­moc­ra­tize writ­ing too.
More im­por­tantly, how you can train and de­ploy spe­cial­ized mod­els at the edge to elim­i­nate in­fer­ence costs, lower la­tency, and im­prove user sat­is­fac­tion.</p>]]></description>
            <content:encoded><![CDATA[<h1>Speaking at WordCamp U.S. in August</h1>
<p>I was re­cently en­cour­aged to sub­mit a talk pro­posal to the <span class="small-caps">AI</span> track at WordCamp US.
I was more than happy to oblige, and to my sur­prise, I got in.
See de­scrip­tion be­low for de­tails. I hope to see some of y’all there!</p>
<img src="/images/wordcamp_us_speaker_2025.png" alt="I&#x27;ll be speaking at WordCamp US 2025">
<h2>Description</h2>
<p>Machine learn­ing has been around for decades.
Un­til re­cently, run­ning <span class="small-caps">ML</span> mod­els typ­i­cally re­quired big cen­tral­ized servers due to high com­pu­ta­tional de­mands.
With mod­ern hard­ware and tools like Rust and Burn, we can ﬂip the script.
When run on a user’s de­vice, trained mod­els can be an in­cred­i­ble boost to hu­man cre­ativ­ity and ex­pres­sion.</p>
<p>Harper is a gram­mar checker that is de­signed to im­prove your work with­out de­grad­ing your in­tegrity—or the writ­ing ex­pe­ri­ence it­self.
Our users gen­er­ate and pub­lish novel ideas faster be­cause we fo­cus on what’s im­por­tant: speed, pri­vacy, and ac­cu­racy.</p>
<p>WordPress’ mis­sion is to de­moc­ra­tize pub­lish­ing.
This talk will ex­plore how we can use <span class="small-caps">AI</span> to de­moc­ra­tize writ­ing too.
More im­por­tantly, how you can train and de­ploy spe­cial­ized mod­els at the edge to elim­i­nate in­fer­ence costs, lower la­tency, and im­prove user sat­is­fac­tion.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/wordcamp_us_speaker_2025.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Why I Talk to Myself]]></title>
            <link>https://elijahpotter.dev/articles/why_i_talk_to_myself</link>
            <guid>https://elijahpotter.dev/articles/why_i_talk_to_myself</guid>
            <pubDate>Mon, 21 Jul 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Why I Talk to Myself</h1>
<img src="/images/bike_sidewalk.webp" alt="My bike broke down. Luckily, I had my camera">
<p>Each week I sit down and re­ﬂect on what I’ve learned. I think about which of my ef­forts have helped the Harper en­deavor, and which have harmed it. It’s not easy, but I think it’s one of the best habits I’ve built. More ac­cu­rately, it’s one of the best habits <a href="https://ma.tt">Matt</a> has en­cour­aged me to build.</p>
<p>When I re­ally think about it, I’m re­minded of the orig­i­nal <a href="https://blog.afterthedeadline.com/">After the Deadline blog</a>, which co­in­ci­den­tally also had a cat­e­gory called<span class="push-double"></span> <span class="pull-double">“</span>Talking to Myself”. I won­der if Raphael Mudge (AtD’s au­thor) had a sim­i­lar mo­ti­va­tion.</p>
<p>I talk to my­self to ﬁnd the weak­nesses in Harper’s mis­sion and fo­cus my at­ten­tion on what mat­ters. It is an ef­ﬁ­cient way to get from <a href="https://ma.tt/2025/05/sharing-levels/">layer one to layer ﬁve</a>. It can help get other main­tain­ers on the same page and build a body of knowl­edge that can be linked to from any­where.</p>
<p>Not wor­ry­ing about keep­ing in­for­ma­tion locked-down is one of the more beau­ti­ful parts of work­ing in pub­lic. Open source code would be noth­ing with­out sim­i­larly open source knowl­edge. I talk to my­self <em>on­line</em> so my users can un­der­stand my in­ten­tions.</p>
<p>In many senses, we are not un­like moths. We are drawn to the lights of dis­trac­tion, hype, and pro­cras­ti­na­tion. For me, set­ting up check­points to stop and re­ﬂect has been an ef­fec­tive de­ter­rent from these lights. In fact, I think each minute for which I stop and think has paid it­self back three times over in time sav­ings. If I can bet­ter al­lo­cate my time and en­ergy, I can avoid do­ing re­dun­dant work pur­su­ing point­less goals.</p>
<h2>Bonus Points</h2>
<p>A <a href="./markov_chains_are_the_original_language_models">few</a> of my <a href="./local-first_software_is_easier_to_scale">ar­ti­cles</a> have be­come pop­u­lar on sites like Hacker News and Reddit. We’ve got­ten quite a few Harper in­stal­la­tions from links on these ar­ti­cles. I would never rely on this kind of blog­ging as a source of ad­ver­tis­ing, but it is nice to see it help spread the word.</p>]]></description>
            <content:encoded><![CDATA[<h1>Why I Talk to Myself</h1>
<img src="/images/bike_sidewalk.webp" alt="My bike broke down. Luckily, I had my camera">
<p>Each week I sit down and re­ﬂect on what I’ve learned. I think about which of my ef­forts have helped the Harper en­deavor, and which have harmed it. It’s not easy, but I think it’s one of the best habits I’ve built. More ac­cu­rately, it’s one of the best habits <a href="https://ma.tt">Matt</a> has en­cour­aged me to build.</p>
<p>When I re­ally think about it, I’m re­minded of the orig­i­nal <a href="https://blog.afterthedeadline.com/">After the Deadline blog</a>, which co­in­ci­den­tally also had a cat­e­gory called<span class="push-double"></span> <span class="pull-double">“</span>Talking to Myself”. I won­der if Raphael Mudge (AtD’s au­thor) had a sim­i­lar mo­ti­va­tion.</p>
<p>I talk to my­self to ﬁnd the weak­nesses in Harper’s mis­sion and fo­cus my at­ten­tion on what mat­ters. It is an ef­ﬁ­cient way to get from <a href="https://ma.tt/2025/05/sharing-levels/">layer one to layer ﬁve</a>. It can help get other main­tain­ers on the same page and build a body of knowl­edge that can be linked to from any­where.</p>
<p>Not wor­ry­ing about keep­ing in­for­ma­tion locked-down is one of the more beau­ti­ful parts of work­ing in pub­lic. Open source code would be noth­ing with­out sim­i­larly open source knowl­edge. I talk to my­self <em>on­line</em> so my users can un­der­stand my in­ten­tions.</p>
<p>In many senses, we are not un­like moths. We are drawn to the lights of dis­trac­tion, hype, and pro­cras­ti­na­tion. For me, set­ting up check­points to stop and re­ﬂect has been an ef­fec­tive de­ter­rent from these lights. In fact, I think each minute for which I stop and think has paid it­self back three times over in time sav­ings. If I can bet­ter al­lo­cate my time and en­ergy, I can avoid do­ing re­dun­dant work pur­su­ing point­less goals.</p>
<h2>Bonus Points</h2>
<p>A <a href="./markov_chains_are_the_original_language_models">few</a> of my <a href="./local-first_software_is_easier_to_scale">ar­ti­cles</a> have be­come pop­u­lar on sites like Hacker News and Reddit. We’ve got­ten quite a few Harper in­stal­la­tions from links on these ar­ti­cles. I would never rely on this kind of blog­ging as a source of ad­ver­tis­ing, but it is nice to see it help spread the word.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/bike_sidewalk.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Reflections on Expression Rules]]></title>
            <link>https://elijahpotter.dev/articles/reflections_on_expression_rules</link>
            <guid>https://elijahpotter.dev/articles/reflections_on_expression_rules</guid>
            <pubDate>Fri, 18 Jul 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Reﬂections on Expression Rules</h1>
<p>Just like gram­mar it­self, Harper is rule-based.
These rules can be writ­ten by hu­man or ma­chine, and usu­ally take the form of a<span class="push-double"></span> <span class="pull-double">“</span>phrase cor­rec­tion” or an ex­pres­sion rule.
You can read more about both in <a href="./writing_a_grammatical_rule_for_harper">my guide</a>.</p>
<p>In essence, an ex­pres­sion rule is a small, de­clar­a­tive pro­gram that seeks out spe­ciﬁc cases Harper can be cer­tain are gram­mat­i­cally in­cor­rect.
For ex­am­ple, we know re­dun­dant modal verbs are al­ways wrong:</p>
<pre><code class="hljs language-plaintext">We could give it try attempt.
</code></pre>
<h2>What’s Great About Expression Rules</h2>
<p>As the main­tainer of the pro­ject, I need to read through a de­cent num­ber of pull re­quests each week.
De­pend­ing on their com­plex­ity, this can be time con­sum­ing.
Be­cause ex­pres­sions are al­most al­ways writ­ten with the same ﬁve prim­i­tives, they make re­views way faster.
For many rules, I only need a quick skim of the code and to con­ﬁrm they don’t cre­ate false-pos­i­tives.</p>
<p>In ad­di­tion to ty­pos, the prim­i­tives un­der­ly­ing ex­pres­sion rules were de­signed to check agree­ment and word-or­der­ing er­rors.
To that end, there are a num­ber of as­sump­tions baked in.
This makes some kinds of rules harder to write, but also makes all ex­pres­sion rules ex­ceed­ingly easy to mem­o­ize.</p>
<h2>What’s Not-So-Great About Expression Rules</h2>
<p>As I said be­fore, ex­pres­sion rules were de­signed for a cer­tain kind of er­ror.
There are many gram­mat­i­cal or lay­out rules which hard dif­ﬁ­cult or down­right im­pos­si­ble to de­ﬁne with an ex­pres­sion.</p>
<p>This nor­mally is­n’t prob­lem, since we can go about them in other ways.
On mul­ti­ple oc­ca­sions, how­ever, I’ve seen po­ten­tial con­trib­u­tors get frus­trated at a per­ceived lack of ﬂex­i­bil­ity.
We did­n’t make it clear enough that there were so­lu­tions <em>other</em> than ex­pres­sions.</p>
<h2>How Can We Improve?</h2>
<p>Applying Amdahl’s law, I think the most ef­fec­tive so­lu­tion is to <a href="./writing_good_documentation">im­prove our doc­u­men­ta­tion</a>, as I have been for the last week or so.</p>
<p>Beyond that, re­mov­ing the need for boil­er­plate code and other causes of men­tal over­head (like reg­is­ter­ing our rules and wait­ing for a slow com­pi­la­tion process) might lower the per­ceived dif­ﬁ­culty of writ­ing new rules.</p>]]></description>
            <content:encoded><![CDATA[<h1>Reﬂections on Expression Rules</h1>
<p>Just like gram­mar it­self, Harper is rule-based.
These rules can be writ­ten by hu­man or ma­chine, and usu­ally take the form of a<span class="push-double"></span> <span class="pull-double">“</span>phrase cor­rec­tion” or an ex­pres­sion rule.
You can read more about both in <a href="./writing_a_grammatical_rule_for_harper">my guide</a>.</p>
<p>In essence, an ex­pres­sion rule is a small, de­clar­a­tive pro­gram that seeks out spe­ciﬁc cases Harper can be cer­tain are gram­mat­i­cally in­cor­rect.
For ex­am­ple, we know re­dun­dant modal verbs are al­ways wrong:</p>
<pre><code class="hljs language-plaintext">We could give it try attempt.
</code></pre>
<h2>What’s Great About Expression Rules</h2>
<p>As the main­tainer of the pro­ject, I need to read through a de­cent num­ber of pull re­quests each week.
De­pend­ing on their com­plex­ity, this can be time con­sum­ing.
Be­cause ex­pres­sions are al­most al­ways writ­ten with the same ﬁve prim­i­tives, they make re­views way faster.
For many rules, I only need a quick skim of the code and to con­ﬁrm they don’t cre­ate false-pos­i­tives.</p>
<p>In ad­di­tion to ty­pos, the prim­i­tives un­der­ly­ing ex­pres­sion rules were de­signed to check agree­ment and word-or­der­ing er­rors.
To that end, there are a num­ber of as­sump­tions baked in.
This makes some kinds of rules harder to write, but also makes all ex­pres­sion rules ex­ceed­ingly easy to mem­o­ize.</p>
<h2>What’s Not-So-Great About Expression Rules</h2>
<p>As I said be­fore, ex­pres­sion rules were de­signed for a cer­tain kind of er­ror.
There are many gram­mat­i­cal or lay­out rules which hard dif­ﬁ­cult or down­right im­pos­si­ble to de­ﬁne with an ex­pres­sion.</p>
<p>This nor­mally is­n’t prob­lem, since we can go about them in other ways.
On mul­ti­ple oc­ca­sions, how­ever, I’ve seen po­ten­tial con­trib­u­tors get frus­trated at a per­ceived lack of ﬂex­i­bil­ity.
We did­n’t make it clear enough that there were so­lu­tions <em>other</em> than ex­pres­sions.</p>
<h2>How Can We Improve?</h2>
<p>Applying Amdahl’s law, I think the most ef­fec­tive so­lu­tion is to <a href="./writing_good_documentation">im­prove our doc­u­men­ta­tion</a>, as I have been for the last week or so.</p>
<p>Beyond that, re­mov­ing the need for boil­er­plate code and other causes of men­tal over­head (like reg­is­ter­ing our rules and wait­ing for a slow com­pi­la­tion process) might lower the per­ceived dif­ﬁ­culty of writ­ing new rules.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Training a Chunker with Burn]]></title>
            <link>https://elijahpotter.dev/articles/training_a_chunker_with_burn</link>
            <guid>https://elijahpotter.dev/articles/training_a_chunker_with_burn</guid>
            <pubDate>Wed, 16 Jul 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Training a Chunker with Burn</h1>
<img src="/images/underpass.webp" alt="Graffiti in an Underpass">
<p>In <a href="./more_transformation-based_learning.md">a pre­vi­ous post</a>, I de­tailed how I im­ple­mented a ba­sic nom­i­nal phrase chun­ker us­ing Transformation-based learn­ing (not to be con­fused with the trans­former class of neural net­work mod­els).
Since then, I’ve taken an­other crack at the prob­lem.
My main goal: im­prove the ac­cu­racy.
The end re­sult is a portable neural net­work model that achieves ~95% ac­cu­racy on gram­mat­i­cally cor­rect text when tasked with iden­ti­fy­ing the nom­i­nal phrases in text.</p>
<h2>Our Goal</h2>
<p>We want Harper to be able to match against sub­jects and ob­jects in sen­tences.
This is a pre­req­ui­site for check­ing a di­verse ar­ray of gram­mat­i­cal rules.
For ex­am­ple, to catch the er­ror in this sen­tence, we need to cor­rectly iden­tify which to­kens rep­re­sent our sub­ject.</p>
<pre><code class="hljs language-plaintext">Neither of the big blue bottle would be broken by the fall.
</code></pre>
<p>In this case, our user has ac­ci­den­tally made the sub­ject sin­gu­lar, while the verb<span class="push-double"></span> <span class="pull-double">“</span>neither” im­plies that the sub­ject should be plural.
We call this an agree­ment er­ror.
Be­cause our sub­ject,<span class="push-double"></span> <span class="pull-double">“</span>big blue bot­tle” con­tains mul­ti­ple to­kens, we need a way to iden­tify sub­jects at a higher level than per-to­ken.
That is what a chun­ker does.</p>
<h2>The Failures of the Brill Chunker</h2>
<p>The Brill chun­ker was by many ac­counts a suc­cess.
It was­n’t, how­ever, a suc­cess in the main way that mat­tered: It was­n’t re­li­able enough to be used in Harper’s gram­mat­i­cal rule sys­tem.
While fast and small, it failed to catch most edge-cases in English text.
In some senses, it over­ﬁt its train­ing dataset.</p>
<h2>Why Train Our Own?</h2>
<p>As our needs con­tinue to ex­pand along­side our user-base, I need the chun­ker to be ﬂex­i­ble.
If its needed ca­pa­bil­i­ties ex­pand, I need to be able to re­train the model to meet them.
That would not be pos­si­ble with­out hav­ing a deep un­der­stand­ing of how the sys­tem works.</p>
<h2>Building a Neural Net</h2>
<p>To build a new chun­ker, I just needed to im­ple­ment the Harper <code>Chunker</code> trait.
Easy enough.</p>
<pre><code class="hljs language-rust"><span class="hljs-comment">/// An implementer of this trait is capable of identifying the noun phrases in a provided sentence.</span>
<span class="hljs-keyword">pub</span> <span class="hljs-keyword">trait</span> <span class="hljs-title class_">Chunker</span> {
    <span class="hljs-comment">/// Iterate over the sentence, identifying the noun phrases contained within.</span>
    <span class="hljs-comment">/// A token marked `true` is a component of a noun phrase.</span>
    <span class="hljs-comment">/// A token marked `false` is not.</span>
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">chunk_sentence</span>(&#x26;<span class="hljs-keyword">self</span>, sentence: &#x26;[<span class="hljs-type">String</span>], tags: &#x26;[<span class="hljs-type">Option</span>&#x3C;UPOS>]) <span class="hljs-punctuation">-></span> <span class="hljs-type">Vec</span>&#x3C;<span class="hljs-type">bool</span>>;
}
</code></pre>
<p>For the nerds in the crowd, I de­cided to use a <code>Word + POS embedding -> BiLSTM -> Linear</code> ar­chi­tec­ture.
To keep things portable and con­sis­tent with the rest of the Harper code­base, I used <a href="https://burn.dev/">Burn</a>, a Rust-native ma­chine learn­ing toolkit.
While I be­lieve the BiLSTM to be good enough for this ap­pli­ca­tion, one ad­van­tage of Burn is the abil­ity to eas­ily swap it out for a trans­former if the need arises.
It also makes it un­be­liev­ably easy to quan­tize mod­els.</p>
<p>This ar­chi­tec­ture gives us some hy­per­pa­ra­me­ters to tune against.
Af­ter dozens of train­ing runs of ex­per­i­men­ta­tion, these worked best:</p>
<table>
<thead>
<tr>
<th align="right">Dropout prob­a­bil­ity</th>
<th>Embedding di­men­sions</th>
<th align="right">Learning rate (I used Adam)</th>
<th>Dataset</th>
</tr>
</thead>
<tbody>
<tr>
<td align="right">30%</td>
<td>16 Word Embeddings + 8 <span class="small-caps">UPOS</span> Embeddings</td>
<td align="right">0.003</td>
<td><span class="small-caps">GUM</span> + <span class="small-caps">EWT</span> + <span class="small-caps">LINES</span></td>
</tr>
</tbody>
</table>
<h2>What’s Next?</h2>
<p>Similar to the Brill Chunker, I’ll be try­ing to use this new sys­tem in our gram­mar checker.
From there, I’ll know what ad­di­tional in­for­ma­tion we’d like for it to in­fer.
Once I’ve got­ten it to re­li­ably work for >= 3 rules, I’ll de­clare it ready to merge.</p>
<h2>Update as of December 2025</h2>
<p>I to­tally for­got to up­date this post.
I sup­pose it is bet­ter late than never.
Back in August I ac­tu­ally ended up giv­ing <a href="./i_spoke_at_wordcamp_u.s._in_2025">a talk about our chun­ker at WordCamp U.S</a>.
It was a great ex­pe­ri­ence, and I highly sug­gest you go watch it if you ﬁnd this stuff in­ter­est­ing.</p>]]></description>
            <content:encoded><![CDATA[<h1>Training a Chunker with Burn</h1>
<img src="/images/underpass.webp" alt="Graffiti in an Underpass">
<p>In <a href="./more_transformation-based_learning.md">a pre­vi­ous post</a>, I de­tailed how I im­ple­mented a ba­sic nom­i­nal phrase chun­ker us­ing Transformation-based learn­ing (not to be con­fused with the trans­former class of neural net­work mod­els).
Since then, I’ve taken an­other crack at the prob­lem.
My main goal: im­prove the ac­cu­racy.
The end re­sult is a portable neural net­work model that achieves ~95% ac­cu­racy on gram­mat­i­cally cor­rect text when tasked with iden­ti­fy­ing the nom­i­nal phrases in text.</p>
<h2>Our Goal</h2>
<p>We want Harper to be able to match against sub­jects and ob­jects in sen­tences.
This is a pre­req­ui­site for check­ing a di­verse ar­ray of gram­mat­i­cal rules.
For ex­am­ple, to catch the er­ror in this sen­tence, we need to cor­rectly iden­tify which to­kens rep­re­sent our sub­ject.</p>
<pre><code class="hljs language-plaintext">Neither of the big blue bottle would be broken by the fall.
</code></pre>
<p>In this case, our user has ac­ci­den­tally made the sub­ject sin­gu­lar, while the verb<span class="push-double"></span> <span class="pull-double">“</span>neither” im­plies that the sub­ject should be plural.
We call this an agree­ment er­ror.
Be­cause our sub­ject,<span class="push-double"></span> <span class="pull-double">“</span>big blue bot­tle” con­tains mul­ti­ple to­kens, we need a way to iden­tify sub­jects at a higher level than per-to­ken.
That is what a chun­ker does.</p>
<h2>The Failures of the Brill Chunker</h2>
<p>The Brill chun­ker was by many ac­counts a suc­cess.
It was­n’t, how­ever, a suc­cess in the main way that mat­tered: It was­n’t re­li­able enough to be used in Harper’s gram­mat­i­cal rule sys­tem.
While fast and small, it failed to catch most edge-cases in English text.
In some senses, it over­ﬁt its train­ing dataset.</p>
<h2>Why Train Our Own?</h2>
<p>As our needs con­tinue to ex­pand along­side our user-base, I need the chun­ker to be ﬂex­i­ble.
If its needed ca­pa­bil­i­ties ex­pand, I need to be able to re­train the model to meet them.
That would not be pos­si­ble with­out hav­ing a deep un­der­stand­ing of how the sys­tem works.</p>
<h2>Building a Neural Net</h2>
<p>To build a new chun­ker, I just needed to im­ple­ment the Harper <code>Chunker</code> trait.
Easy enough.</p>
<pre><code class="hljs language-rust"><span class="hljs-comment">/// An implementer of this trait is capable of identifying the noun phrases in a provided sentence.</span>
<span class="hljs-keyword">pub</span> <span class="hljs-keyword">trait</span> <span class="hljs-title class_">Chunker</span> {
    <span class="hljs-comment">/// Iterate over the sentence, identifying the noun phrases contained within.</span>
    <span class="hljs-comment">/// A token marked `true` is a component of a noun phrase.</span>
    <span class="hljs-comment">/// A token marked `false` is not.</span>
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">chunk_sentence</span>(&#x26;<span class="hljs-keyword">self</span>, sentence: &#x26;[<span class="hljs-type">String</span>], tags: &#x26;[<span class="hljs-type">Option</span>&#x3C;UPOS>]) <span class="hljs-punctuation">-></span> <span class="hljs-type">Vec</span>&#x3C;<span class="hljs-type">bool</span>>;
}
</code></pre>
<p>For the nerds in the crowd, I de­cided to use a <code>Word + POS embedding -> BiLSTM -> Linear</code> ar­chi­tec­ture.
To keep things portable and con­sis­tent with the rest of the Harper code­base, I used <a href="https://burn.dev/">Burn</a>, a Rust-native ma­chine learn­ing toolkit.
While I be­lieve the BiLSTM to be good enough for this ap­pli­ca­tion, one ad­van­tage of Burn is the abil­ity to eas­ily swap it out for a trans­former if the need arises.
It also makes it un­be­liev­ably easy to quan­tize mod­els.</p>
<p>This ar­chi­tec­ture gives us some hy­per­pa­ra­me­ters to tune against.
Af­ter dozens of train­ing runs of ex­per­i­men­ta­tion, these worked best:</p>
<table>
<thead>
<tr>
<th align="right">Dropout prob­a­bil­ity</th>
<th>Embedding di­men­sions</th>
<th align="right">Learning rate (I used Adam)</th>
<th>Dataset</th>
</tr>
</thead>
<tbody>
<tr>
<td align="right">30%</td>
<td>16 Word Embeddings + 8 <span class="small-caps">UPOS</span> Embeddings</td>
<td align="right">0.003</td>
<td><span class="small-caps">GUM</span> + <span class="small-caps">EWT</span> + <span class="small-caps">LINES</span></td>
</tr>
</tbody>
</table>
<h2>What’s Next?</h2>
<p>Similar to the Brill Chunker, I’ll be try­ing to use this new sys­tem in our gram­mar checker.
From there, I’ll know what ad­di­tional in­for­ma­tion we’d like for it to in­fer.
Once I’ve got­ten it to re­li­ably work for >= 3 rules, I’ll de­clare it ready to merge.</p>
<h2>Update as of December 2025</h2>
<p>I to­tally for­got to up­date this post.
I sup­pose it is bet­ter late than never.
Back in August I ac­tu­ally ended up giv­ing <a href="./i_spoke_at_wordcamp_u.s._in_2025">a talk about our chun­ker at WordCamp U.S</a>.
It was a great ex­pe­ri­ence, and I highly sug­gest you go watch it if you ﬁnd this stuff in­ter­est­ing.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing Good Documentation]]></title>
            <link>https://elijahpotter.dev/articles/writing_good_documentation</link>
            <guid>https://elijahpotter.dev/articles/writing_good_documentation</guid>
            <pubDate>Tue, 15 Jul 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Writing Good Documentation</h1>
<p>I be­lieve that good doc­u­men­ta­tion is more im­por­tant than ever. In an age where large lan­guage mod­els have ex­cep­tional con­text win­dows, it’s easy to fall into the trap of be­liev­ing a sys­tem is sim­pler than it is. To test the lim­its of their abil­i­ties, I’ll of­ten feed Harper code in and ask for their ex­pla­na­tion of it’s in­ner work­ings.
They’ve never got­ten it any­where close to cor­rect.</p>
<p>That’s be­cause good doc­u­men­ta­tion is more than the sum of its parts. It should con­tain all the rel­e­vant tech­ni­cal de­tails needed to work on a repos­i­tory, but it should also pro­vide a sense of qual­ity and di­rec­tion. It should dis­till an abil­ity within the reader to tell what good code and con­duct looks like.</p>
<p>I think this is an area Harper could do to im­prove on.</p>
<h2>It’s Possible to Write Too Much</h2>
<p>Too much text can be over­whelm­ing.
I have met many peo­ple (myself in­cluded) who be­lieve that more de­tail is al­ways bet­ter.
That as long as the in­for­ma­tion ex­ists some­where, some­one will be able to ﬁnd it.
In re­al­ity, that’s just not true.</p>
<p>That brings us to our ﬁrst rule: <strong>good doc­u­men­ta­tion should be sim­ple</strong>.</p>
<p>The worst way to de­scribe a com­plex sys­tem is with com­plex lan­guage.
It’s much bet­ter to ﬁrst break it down into sim­pler parts.
From there, you can build it back up again.</p>
<p>I’ve found it ef­fec­tive to start by erring on the side of<span class="push-double"></span> <span class="pull-double">“</span>too much”, only to pare it down later.</p>
<p>Needless to say, it’s also pos­si­ble to write too lit­tle.</p>
<h2>Focus on <em>Why</em></h2>
<p>The code it­self is (or should be) the best de­scrip­tion of <em>how</em> the soft­ware works.
Doc­u­men­ta­tion should com­ple­ment this re­al­ity by de­scrib­ing <em>why</em> the soft­ware works that way.</p>
<p>This might in­clude a lit­tle his­tory.
What steps did the orig­i­nal code’s au­thor take be­fore ar­riv­ing on the ﬁ­nal so­lu­tion?
<a href="https://elijahpotter.dev/">A blog</a> is a great way to an­swer that ques­tion.</p>
<p>When de­scrib­ing a bug rel­e­vant to some code (like in a pull re­quest), a pic­ture is worth a thou­sand words.
It’s much eas­ier to show a prob­lem than to de­scribe it.
That’s why I highly sug­gest you take a screen­shot of the prob­lem.
Even bet­ter: record a video recre­ation of it.</p>]]></description>
            <content:encoded><![CDATA[<h1>Writing Good Documentation</h1>
<p>I be­lieve that good doc­u­men­ta­tion is more im­por­tant than ever. In an age where large lan­guage mod­els have ex­cep­tional con­text win­dows, it’s easy to fall into the trap of be­liev­ing a sys­tem is sim­pler than it is. To test the lim­its of their abil­i­ties, I’ll of­ten feed Harper code in and ask for their ex­pla­na­tion of it’s in­ner work­ings.
They’ve never got­ten it any­where close to cor­rect.</p>
<p>That’s be­cause good doc­u­men­ta­tion is more than the sum of its parts. It should con­tain all the rel­e­vant tech­ni­cal de­tails needed to work on a repos­i­tory, but it should also pro­vide a sense of qual­ity and di­rec­tion. It should dis­till an abil­ity within the reader to tell what good code and con­duct looks like.</p>
<p>I think this is an area Harper could do to im­prove on.</p>
<h2>It’s Possible to Write Too Much</h2>
<p>Too much text can be over­whelm­ing.
I have met many peo­ple (myself in­cluded) who be­lieve that more de­tail is al­ways bet­ter.
That as long as the in­for­ma­tion ex­ists some­where, some­one will be able to ﬁnd it.
In re­al­ity, that’s just not true.</p>
<p>That brings us to our ﬁrst rule: <strong>good doc­u­men­ta­tion should be sim­ple</strong>.</p>
<p>The worst way to de­scribe a com­plex sys­tem is with com­plex lan­guage.
It’s much bet­ter to ﬁrst break it down into sim­pler parts.
From there, you can build it back up again.</p>
<p>I’ve found it ef­fec­tive to start by erring on the side of<span class="push-double"></span> <span class="pull-double">“</span>too much”, only to pare it down later.</p>
<p>Needless to say, it’s also pos­si­ble to write too lit­tle.</p>
<h2>Focus on <em>Why</em></h2>
<p>The code it­self is (or should be) the best de­scrip­tion of <em>how</em> the soft­ware works.
Doc­u­men­ta­tion should com­ple­ment this re­al­ity by de­scrib­ing <em>why</em> the soft­ware works that way.</p>
<p>This might in­clude a lit­tle his­tory.
What steps did the orig­i­nal code’s au­thor take be­fore ar­riv­ing on the ﬁ­nal so­lu­tion?
<a href="https://elijahpotter.dev/">A blog</a> is a great way to an­swer that ques­tion.</p>
<p>When de­scrib­ing a bug rel­e­vant to some code (like in a pull re­quest), a pic­ture is worth a thou­sand words.
It’s much eas­ier to show a prob­lem than to de­scribe it.
That’s why I highly sug­gest you take a screen­shot of the prob­lem.
Even bet­ter: record a video recre­ation of it.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing a Grammatical Rule for Harper]]></title>
            <link>https://elijahpotter.dev/articles/writing_a_grammatical_rule_for_harper</link>
            <guid>https://elijahpotter.dev/articles/writing_a_grammatical_rule_for_harper</guid>
            <pubDate>Wed, 09 Jul 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Writing a Grammatical Rule for Harper</h1>
<p><a href="https://writewithharper.com">Harper</a> is a gram­mar checker that re­lies on con­crete, leg­i­ble gram­mat­i­cal rules.
In do­ing so, we make Harper’s in­ner-work­ings fun­da­men­tally clear, which al­lows us to guar­an­tee pri­vacy, speed, and most im­por­tantly re­main <strong>im­par­tial</strong>.</p>
<p>Writing ad­di­tional rules is one of the best (and <em><strong>eas­i­est</strong></em>) ways you can con­tribute to the open source pro­ject.
Sim­ple rules take just a few min­utes and of­ten don’t re­quire any un­der­stand­ing of Rust at all—a fact I only cite be­cause it is a com­mon point of con­cern.</p>
<p>Instead of throw­ing a wall of text in your face, I’m break­ing this<span class="push-double"></span> <span class="pull-double">“</span>guide” of sorts into three sim­ple sec­tions.
You don’t need to read all three—in fact, I would rec­om­mend against it.</p>
<p>The only thing you need from here is an idea of the gram­mat­i­cal rule you want to add to Harper.
Don’t have one in mind?
Visit our <a href="https://github.com/Automattic/harper/issues?q=is%3Aissue%20state%3Aopen%20label%3Aenhancement%20label%3Aharper-core%20label%3Alinting">is­sue board</a> to ﬁnd a po­ten­tial rule that piques your in­ter­est.</p>
<p>The three paths:</p>
<ol>
<li><a href="./writing_a_phrase_correction_for_harper"><strong>A<span class="push-double"></span> <span class="pull-double">“</span>phrase cor­rec­tion”</strong></a>. These are for the sim­plest gram­mat­i­cal rules. Use one of these in cases where se­man­tic mean­ing and con­text aren’t im­por­tant.</li>
<li><a href="./writing_an_expression_rule_for_harper"><strong>An <code>ExprLinter</code></strong></a>. These are for more com­plex rules. Use one of these in cases where se­man­tic mean­ing or con­text <strong>are</strong> im­por­tant, and you don’t need ac­cess to in­for­ma­tion wider than clause-level. Takes a lit­tle bit to learn, but are ex­tremely pow­er­ful.</li>
<li><strong>A plain <code>Linter</code></strong>. These are of­ten used for rules that in­volve punc­tu­a­tion. It re­quires the most Rust knowl­edge but the least Harper-speciﬁc knowl­edge. I’m go­ing to hold off on writ­ing a guide for these un­til I hear a real de­sire to learn about them.</li>
</ol>
<p>These guides will fo­cus more on the process of writ­ing a rule for Harper, not the tech­ni­cal de­tails of wiring it up.
For the lat­ter, see our <a href="https://writewithharper.com/docs/contributors/author-a-rule">of­ﬁ­cial doc­u­men­ta­tion.</a></p>]]></description>
            <content:encoded><![CDATA[<h1>Writing a Grammatical Rule for Harper</h1>
<p><a href="https://writewithharper.com">Harper</a> is a gram­mar checker that re­lies on con­crete, leg­i­ble gram­mat­i­cal rules.
In do­ing so, we make Harper’s in­ner-work­ings fun­da­men­tally clear, which al­lows us to guar­an­tee pri­vacy, speed, and most im­por­tantly re­main <strong>im­par­tial</strong>.</p>
<p>Writing ad­di­tional rules is one of the best (and <em><strong>eas­i­est</strong></em>) ways you can con­tribute to the open source pro­ject.
Sim­ple rules take just a few min­utes and of­ten don’t re­quire any un­der­stand­ing of Rust at all—a fact I only cite be­cause it is a com­mon point of con­cern.</p>
<p>Instead of throw­ing a wall of text in your face, I’m break­ing this<span class="push-double"></span> <span class="pull-double">“</span>guide” of sorts into three sim­ple sec­tions.
You don’t need to read all three—in fact, I would rec­om­mend against it.</p>
<p>The only thing you need from here is an idea of the gram­mat­i­cal rule you want to add to Harper.
Don’t have one in mind?
Visit our <a href="https://github.com/Automattic/harper/issues?q=is%3Aissue%20state%3Aopen%20label%3Aenhancement%20label%3Aharper-core%20label%3Alinting">is­sue board</a> to ﬁnd a po­ten­tial rule that piques your in­ter­est.</p>
<p>The three paths:</p>
<ol>
<li><a href="./writing_a_phrase_correction_for_harper"><strong>A<span class="push-double"></span> <span class="pull-double">“</span>phrase cor­rec­tion”</strong></a>. These are for the sim­plest gram­mat­i­cal rules. Use one of these in cases where se­man­tic mean­ing and con­text aren’t im­por­tant.</li>
<li><a href="./writing_an_expression_rule_for_harper"><strong>An <code>ExprLinter</code></strong></a>. These are for more com­plex rules. Use one of these in cases where se­man­tic mean­ing or con­text <strong>are</strong> im­por­tant, and you don’t need ac­cess to in­for­ma­tion wider than clause-level. Takes a lit­tle bit to learn, but are ex­tremely pow­er­ful.</li>
<li><strong>A plain <code>Linter</code></strong>. These are of­ten used for rules that in­volve punc­tu­a­tion. It re­quires the most Rust knowl­edge but the least Harper-speciﬁc knowl­edge. I’m go­ing to hold off on writ­ing a guide for these un­til I hear a real de­sire to learn about them.</li>
</ol>
<p>These guides will fo­cus more on the process of writ­ing a rule for Harper, not the tech­ni­cal de­tails of wiring it up.
For the lat­ter, see our <a href="https://writewithharper.com/docs/contributors/author-a-rule">of­ﬁ­cial doc­u­men­ta­tion.</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing a Phrase Correction for Harper]]></title>
            <link>https://elijahpotter.dev/articles/writing_a_phrase_correction_for_harper</link>
            <guid>https://elijahpotter.dev/articles/writing_a_phrase_correction_for_harper</guid>
            <pubDate>Wed, 09 Jul 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Writing a Phrase Correction for Harper</h1>
<p><em>This is part of a se­ries.
<a href="./writing_a_grammatical_rule_for_harper">Go to the start.</a></em></p>
<p>There are sev­eral ways to add a gram­mat­i­cal rule to Harper.
This post aims to out­line the eas­i­est (and most con­strained): a<span class="push-double"></span> <span class="pull-double">“</span>phrase cor­rec­tion”.</p>
<p>Sometimes you’ll see a sim­ple but com­mon gram­mat­i­cal er­ror that does­n’t have much to do with any broader con­text.
For ex­am­ple, I of­ten mistype<span class="push-double"></span> <span class="pull-double">“</span>in the” as<span class="push-double"></span> <span class="pull-double">“</span>int he”.
It hap­pens quite of­ten, and there is­n’t a broad pat­tern at play here.
This is a per­fect can­di­date for a<span class="push-double"></span> <span class="pull-double">“</span>phrase cor­rec­tion”.</p>
<p>From a tech­ni­cal point of view, a<span class="push-double"></span> <span class="pull-double">“</span>phrase cor­rec­tion” is just a map­ping from one or more phrases to an­other set of phrases.
But they’re more than a sim­ple search-and-re­place.
Un­der-the-hood, Harper will ac­count for var­i­ous cap­i­tal­iza­tion and styl­is­tic changes that can be dif­ﬁ­cult to cover man­u­ally.
For­tu­nately, they’re pretty easy to add to Harper.</p>
<p>Before we be­gin, make sure you prop­erly <a href="https://writewithharper.com/docs/contributors/environment">set up your en­vi­ron­ment</a>.</p>
<p>Open up the Harper <a href="https://github.com/automattic/harper">monorepo</a>.
In <code>harper-core/src/linting/phrase_corrections/mod.rs</code>, you’ll ﬁnd a list of en­tries that look some­what like this:</p>
<pre><code class="hljs language-rust"><span class="hljs-string">"GildedAge"</span> => (
    [<span class="hljs-string">"guilded age"</span>],
    [<span class="hljs-string">"Gilded Age"</span>],
    <span class="hljs-string">"The period of economic prosperity is called the `Gilded Age`."</span>,
    <span class="hljs-string">"If referring to the period of economic prosperity, the correct term is `Gilded Age`."</span>
),
<span class="hljs-string">"GoingTo"</span> => (
    <span class="hljs-comment">// The value to map _from_.</span>
    [<span class="hljs-string">"gong to"</span>], 
    <span class="hljs-comment">// The suggestions to present to the user, which replace the problematic text.</span>
    [<span class="hljs-string">"going to"</span>], 
    <span class="hljs-comment">// The message for the user.</span>
    <span class="hljs-string">"Did you mean `going to`?"</span>,
    <span class="hljs-comment">// The rule description to be shown in settings pages.</span>
    <span class="hljs-string">"Corrects `gong to` to the intended phrase `going to`."</span>
),
<span class="hljs-string">"GotRidOff"</span> => (
    [<span class="hljs-string">"got rid off"</span>, <span class="hljs-string">"got ride of"</span>, <span class="hljs-string">"got ride off"</span>],
    [<span class="hljs-string">"got rid of"</span>],
    <span class="hljs-string">"Did you mean `got rid of`?"</span>,
    <span class="hljs-string">"Ensures `got rid of` is used instead of `got rid off`."</span>
),
</code></pre>
<p>Each of these is a<span class="push-double"></span> <span class="pull-double">“</span>phrase cor­rec­tion”.
To add one for the prob­lem I out­lined above, we just need to ap­pend to the end of the list and open <a href="./never_wait">a pull re­quest</a>.</p>
<pre><code class="hljs language-rust"><span class="hljs-string">"InThe"</span> => (
    [<span class="hljs-string">"int he"</span>],
    [<span class="hljs-string">"in the"</span>],
    <span class="hljs-string">"Did you mean `in the`?"</span>,
    <span class="hljs-string">"Detects and corrects a spacing error where `in the` is mistakenly written as `int he`. Proper spacing is essential for readability and grammatical correctness in common phrases."</span>
),
</code></pre>
<p>If you want to go the ex­tra mile, we’d re­ally ap­pre­ci­ate if you added one or two test cases to <code>harper-core/src/linting/phrase_corrections/tests.rs</code> to make sure every­thing works as ex­pected:</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[test]</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">corrects_int_he</span>() {
    <span class="hljs-title function_ invoke__">assert_suggestion_result</span>(
        <span class="hljs-string">"That pizza stayed int he box."</span>,
        <span class="hljs-title function_ invoke__">lint_group</span>(),
        <span class="hljs-string">"That pizza stayed in the box."</span>,
    );
}
</code></pre>]]></description>
            <content:encoded><![CDATA[<h1>Writing a Phrase Correction for Harper</h1>
<p><em>This is part of a se­ries.
<a href="./writing_a_grammatical_rule_for_harper">Go to the start.</a></em></p>
<p>There are sev­eral ways to add a gram­mat­i­cal rule to Harper.
This post aims to out­line the eas­i­est (and most con­strained): a<span class="push-double"></span> <span class="pull-double">“</span>phrase cor­rec­tion”.</p>
<p>Sometimes you’ll see a sim­ple but com­mon gram­mat­i­cal er­ror that does­n’t have much to do with any broader con­text.
For ex­am­ple, I of­ten mistype<span class="push-double"></span> <span class="pull-double">“</span>in the” as<span class="push-double"></span> <span class="pull-double">“</span>int he”.
It hap­pens quite of­ten, and there is­n’t a broad pat­tern at play here.
This is a per­fect can­di­date for a<span class="push-double"></span> <span class="pull-double">“</span>phrase cor­rec­tion”.</p>
<p>From a tech­ni­cal point of view, a<span class="push-double"></span> <span class="pull-double">“</span>phrase cor­rec­tion” is just a map­ping from one or more phrases to an­other set of phrases.
But they’re more than a sim­ple search-and-re­place.
Un­der-the-hood, Harper will ac­count for var­i­ous cap­i­tal­iza­tion and styl­is­tic changes that can be dif­ﬁ­cult to cover man­u­ally.
For­tu­nately, they’re pretty easy to add to Harper.</p>
<p>Before we be­gin, make sure you prop­erly <a href="https://writewithharper.com/docs/contributors/environment">set up your en­vi­ron­ment</a>.</p>
<p>Open up the Harper <a href="https://github.com/automattic/harper">monorepo</a>.
In <code>harper-core/src/linting/phrase_corrections/mod.rs</code>, you’ll ﬁnd a list of en­tries that look some­what like this:</p>
<pre><code class="hljs language-rust"><span class="hljs-string">"GildedAge"</span> => (
    [<span class="hljs-string">"guilded age"</span>],
    [<span class="hljs-string">"Gilded Age"</span>],
    <span class="hljs-string">"The period of economic prosperity is called the `Gilded Age`."</span>,
    <span class="hljs-string">"If referring to the period of economic prosperity, the correct term is `Gilded Age`."</span>
),
<span class="hljs-string">"GoingTo"</span> => (
    <span class="hljs-comment">// The value to map _from_.</span>
    [<span class="hljs-string">"gong to"</span>], 
    <span class="hljs-comment">// The suggestions to present to the user, which replace the problematic text.</span>
    [<span class="hljs-string">"going to"</span>], 
    <span class="hljs-comment">// The message for the user.</span>
    <span class="hljs-string">"Did you mean `going to`?"</span>,
    <span class="hljs-comment">// The rule description to be shown in settings pages.</span>
    <span class="hljs-string">"Corrects `gong to` to the intended phrase `going to`."</span>
),
<span class="hljs-string">"GotRidOff"</span> => (
    [<span class="hljs-string">"got rid off"</span>, <span class="hljs-string">"got ride of"</span>, <span class="hljs-string">"got ride off"</span>],
    [<span class="hljs-string">"got rid of"</span>],
    <span class="hljs-string">"Did you mean `got rid of`?"</span>,
    <span class="hljs-string">"Ensures `got rid of` is used instead of `got rid off`."</span>
),
</code></pre>
<p>Each of these is a<span class="push-double"></span> <span class="pull-double">“</span>phrase cor­rec­tion”.
To add one for the prob­lem I out­lined above, we just need to ap­pend to the end of the list and open <a href="./never_wait">a pull re­quest</a>.</p>
<pre><code class="hljs language-rust"><span class="hljs-string">"InThe"</span> => (
    [<span class="hljs-string">"int he"</span>],
    [<span class="hljs-string">"in the"</span>],
    <span class="hljs-string">"Did you mean `in the`?"</span>,
    <span class="hljs-string">"Detects and corrects a spacing error where `in the` is mistakenly written as `int he`. Proper spacing is essential for readability and grammatical correctness in common phrases."</span>
),
</code></pre>
<p>If you want to go the ex­tra mile, we’d re­ally ap­pre­ci­ate if you added one or two test cases to <code>harper-core/src/linting/phrase_corrections/tests.rs</code> to make sure every­thing works as ex­pected:</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[test]</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">corrects_int_he</span>() {
    <span class="hljs-title function_ invoke__">assert_suggestion_result</span>(
        <span class="hljs-string">"That pizza stayed int he box."</span>,
        <span class="hljs-title function_ invoke__">lint_group</span>(),
        <span class="hljs-string">"That pizza stayed in the box."</span>,
    );
}
</code></pre>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing an Expression Rule for Harper]]></title>
            <link>https://elijahpotter.dev/articles/writing_an_expression_rule_for_harper</link>
            <guid>https://elijahpotter.dev/articles/writing_an_expression_rule_for_harper</guid>
            <pubDate>Wed, 09 Jul 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Writing an Expression Rule for Harper</h1>
<p><em>This is part of a se­ries.
<a href="./writing_a_grammatical_rule_for_harper">Go to the start.</a></em></p>
<p>Expression rules (or more com­monly, <code>ExprLinter</code>s) are Harper rules that use de­clar­a­tive ex­pres­sions to ﬁnd and ﬁx gram­mat­i­cal er­rors.
They’re halfway be­tween a<span class="push-double"></span> <span class="pull-double">“</span>phrase cor­rec­tion” and man­u­ally im­ple­ment­ing <code>Linter</code>.</p>
<p>Make sure you prop­erly <a href="https://writewithharper.com/docs/contributors/environment">set up your en­vi­ron­ment</a>.</p>
<p>Before we get started, let’s take a look at the <code>ExprLinter</code> trait.
Here’s what it looks like at the time of writ­ing this post.</p>
<pre><code class="hljs language-rust"><span class="hljs-comment">/// A trait that searches for tokens that fulfil [`Expr`]s in a [`Document`].</span>
<span class="hljs-comment">///</span>
<span class="hljs-comment">/// Makes use of [`TokenStringExt::iter_chunks`] to avoid matching across sentence or clause</span>
<span class="hljs-comment">/// boundaries.</span>
<span class="hljs-meta">#[blanket(derive(Box))]</span>
<span class="hljs-keyword">pub</span> <span class="hljs-keyword">trait</span> <span class="hljs-title class_">ExprLinter</span>: LSend {
    <span class="hljs-comment">/// A simple getter for the expression you want Harper to search for.</span>
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">expr</span>(&#x26;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &#x26;<span class="hljs-keyword">dyn</span> Expr;
    <span class="hljs-comment">/// If any portions of a [`Document`] match [`Self::expr`], they are passed through [`ExprLinter::match_to_lint`] to be</span>
    <span class="hljs-comment">/// transformed into a [`Lint`] for editor consumption.</span>
    <span class="hljs-comment">///</span>
    <span class="hljs-comment">/// This function may return `None` to elect _not_ to produce a lint.</span>
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">match_to_lint</span>(&#x26;<span class="hljs-keyword">self</span>, matched_tokens: &#x26;[Token], source: &#x26;[<span class="hljs-type">char</span>]) <span class="hljs-punctuation">-></span> <span class="hljs-type">Option</span>&#x3C;Lint>;
    <span class="hljs-comment">/// A user-facing description of what kinds of grammatical errors this rule looks for.</span>
    <span class="hljs-comment">/// It is usually shown in settings menus.</span>
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">description</span>(&#x26;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &#x26;<span class="hljs-type">str</span>;
}
</code></pre>
<p>The struc­ture of the trait re­veals some of the be­hind-the-scenes work Harper is do­ing for you.
There are three phases:</p>
<ol>
<li>You pro­vide Harper an <code>Expr</code>.
It will it­er­ate through the doc­u­ment, look­ing for to­ken se­quences that match your ex­pres­sion.</li>
<li>Any and all matches are passed to <code>match_to_lint</code>.
From there, you can per­form op­tional ad­di­tional val­i­da­tion to con­ﬁrm that the to­kens re­ally do rep­re­sent a gram­mat­i­cal er­ror.
If so, re­turn <code>None</code>.
Otherwise, re­turn a <code>Lint</code> with any sug­ges­tions that may ﬁx the prob­lem.</li>
<li>Harper will han­dle every­thing else. It will show <span class="small-caps">UI</span>, re­for­mat text, and set­tings menus to the user.
It will also per­form ag­gres­sive caching on the ﬁrst two steps, so any mod­i­ﬁ­ca­tions to the doc­u­ment have a neg­li­gi­ble per­for­mance im­pact.</li>
</ol>
<h2>Let’s Get Started</h2>
<p>Now that we’ve re­viewed the es­sen­tials, let’s im­ple­ment an <code>ExprLinter</code>.</p>
<p>Before we can write a sin­gle line of code, we need a gram­mat­i­cal rule of in­ter­est.
I’m go­ing to pay a visit to the Harper <a href="https://github.com/Automattic/harper/issues?q=is%3Aissue%20state%3Aopen%20label%3Aenhancement%20label%3Aharper-core%20label%3Alinting">is­sue board</a>.</p>
<p>After look­ing through a few op­tions, I think <a href="https://github.com/Automattic/harper/issues/1513">#1513</a> is a good can­di­date.
We are look­ing for miss­ing prepo­si­tions be­tween an ad­jec­tive and a sub­ject.</p>
<p>To get started, we’ll cre­ate a ﬁle un­der <code>harper-core/src/linting</code> called <code>missing_preposition.rs</code> and add it to the par­ent Rust mod­ule.
I’ll paste the tem­plate into the ﬁle:</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">MissingPreposition</span> {
    expr: <span class="hljs-type">Box</span>&#x3C;<span class="hljs-keyword">dyn</span> Expr>,
}

<span class="hljs-keyword">impl</span> <span class="hljs-title class_">Default</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">MissingPreposition</span> {
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">default</span>() <span class="hljs-punctuation">-></span> <span class="hljs-keyword">Self</span> {
        <span class="hljs-keyword">let</span> <span class="hljs-variable">expr</span> = todo!();

        <span class="hljs-keyword">Self</span> {
            expr: <span class="hljs-type">Box</span>::<span class="hljs-title function_ invoke__">new</span>(expr),
        }
    }
}


<span class="hljs-keyword">impl</span> <span class="hljs-title class_">ExprLinter</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">MissingPreposition</span> {
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">expr</span>(&#x26;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &#x26;<span class="hljs-keyword">dyn</span> Expr {
        <span class="hljs-keyword">self</span>.expr.<span class="hljs-title function_ invoke__">as_ref</span>()
    }

    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">match_to_lint</span>(&#x26;<span class="hljs-keyword">self</span>, matched_tokens: &#x26;[Token], _source: &#x26;[<span class="hljs-type">char</span>]) <span class="hljs-punctuation">-></span> <span class="hljs-type">Option</span>&#x3C;Lint> {
        <span class="hljs-built_in">unimplemented!</span>()
    }

    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">description</span>(&#x26;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &#x26;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span> {
        <span class="hljs-built_in">unimplemented!</span>()
    }
}
</code></pre>
<p>I like to start by build­ing out a few test cases be­fore work­ing on the ac­tual code.
We get some for free from the GitHub is­sue:</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[test]</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">fixes_issue_1513</span>() {
    <span class="hljs-title function_ invoke__">assert_lint_count</span>(
        <span class="hljs-string">"The city is famous its beaches."</span>,
        MissingPreposition::<span class="hljs-title function_ invoke__">default</span>(),
        <span class="hljs-number">1</span>,
    );
    <span class="hljs-title function_ invoke__">assert_lint_count</span>(
        <span class="hljs-string">"The students are interested learning."</span>,
        MissingPreposition::<span class="hljs-title function_ invoke__">default</span>(),
        <span class="hljs-number">1</span>,
    );
}
</code></pre>
<p>Obviously, these tests will fail if we try to run <code>cargo test</code>, but at this point you should do so any­way to make sure your tool­chain is work­ing.</p>
<h2>Writing our Expression</h2>
<p>The heart of this gram­mat­i­cal rule is the <code>Expr</code> (pronounced <em>ex­pres­sion</em>).
There are a num­ber of ways to go about mak­ing one of these.
The sim­plest (and most com­mon by far) is to put to­gether a <a href="https://docs.rs/harper-core/latest/harper_core/expr/struct.SequenceExpr.html"><code>SequenceExpr</code></a>.</p>
<p>In our case, we’re look­ing for miss­ing prepo­si­tions be­tween an ad­jec­tive and a noun.
A good ex­pres­sion to start with could look like:</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Default</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">MissingPreposition</span> {
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">default</span>() <span class="hljs-punctuation">-></span> <span class="hljs-keyword">Self</span> {
        <span class="hljs-keyword">let</span> <span class="hljs-variable">expr</span> = SequenceExpr::<span class="hljs-title function_ invoke__">default</span>()
            .<span class="hljs-title function_ invoke__">then</span>(UPOSSet::<span class="hljs-title function_ invoke__">new</span>(&#x26;[UPOS::ADJ]))
            .<span class="hljs-title function_ invoke__">t_ws</span>()
            .<span class="hljs-title function_ invoke__">then</span>(UPOSSet::<span class="hljs-title function_ invoke__">new</span>(&#x26;[UPOS::NOUN, UPOS::PRON, UPOS::PROPN]));

        <span class="hljs-keyword">Self</span> {
            expr: <span class="hljs-type">Box</span>::<span class="hljs-title function_ invoke__">new</span>(expr),
        }
    }
}
</code></pre>
<p>We’re us­ing a <code>UPOSSet</code> here, which is an­other kind of <code>Expr</code> that looks for spe­ciﬁc parts of speech.
The name de­rives from the <a href="https://universaldependencies.org/u/pos/index.html">Universal Dependencies tag sys­tem</a>.
Any to­kens tagged with any of the op­tions we’ve pro­vided to the <code>UPOSSet</code> will match.</p>
<p>However, it’s easy to cre­ate an ex­am­ple that this ex­pres­sion matches against, but does­n’t con­tain a gram­mat­i­cal er­ror.
We call this a false pos­i­tive.
Let’s write one and add it to our test suite.</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[test]</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">allows_terrible_stuff</span>() {
    <span class="hljs-title function_ invoke__">assert_no_lints</span>(
        <span class="hljs-string">"Either it was terrible stuff or the whiskey distorted things."</span>,
        MissingPreposition::<span class="hljs-title function_ invoke__">default</span>(),
    );
}
</code></pre>
<p>From here, you should use your brain to con­tin­u­ously re­ﬁne the ex­pres­sion into some­thing that
main­tains a low false-pos­i­tive rate while re­main­ing use­ful.
Here’s what I set­tled on:</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Default</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">MissingPreposition</span> {
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">default</span>() <span class="hljs-punctuation">-></span> <span class="hljs-keyword">Self</span> {
        <span class="hljs-keyword">let</span> <span class="hljs-variable">expr</span> = SequenceExpr::<span class="hljs-title function_ invoke__">default</span>()
            .<span class="hljs-title function_ invoke__">then</span>(
                AnchorStart.<span class="hljs-title function_ invoke__">or</span>(SequenceExpr::<span class="hljs-title function_ invoke__">default</span>()
                    .<span class="hljs-title function_ invoke__">then</span>(UPOSSet::<span class="hljs-title function_ invoke__">new</span>(&#x26;[UPOS::DET]))
                    .<span class="hljs-title function_ invoke__">t_ws</span>()),
            )
            .<span class="hljs-title function_ invoke__">then</span>(UPOSSet::<span class="hljs-title function_ invoke__">new</span>(&#x26;[UPOS::NOUN, UPOS::PRON, UPOS::PROPN]))
            .<span class="hljs-title function_ invoke__">t_ws</span>()
            .<span class="hljs-title function_ invoke__">then</span>(UPOSSet::<span class="hljs-title function_ invoke__">new</span>(&#x26;[UPOS::AUX]))
            .<span class="hljs-title function_ invoke__">t_ws</span>()
            .<span class="hljs-title function_ invoke__">then</span>(UPOSSet::<span class="hljs-title function_ invoke__">new</span>(&#x26;[UPOS::ADJ]))
            .<span class="hljs-title function_ invoke__">t_ws</span>()
            .<span class="hljs-title function_ invoke__">then</span>(UPOSSet::<span class="hljs-title function_ invoke__">new</span>(&#x26;[UPOS::NOUN, UPOS::PRON, UPOS::PROPN]))
            .<span class="hljs-title function_ invoke__">then_optional</span>(AnyPattern)
            .<span class="hljs-title function_ invoke__">then_optional</span>(AnyPattern);

        <span class="hljs-keyword">Self</span> {
            expr: <span class="hljs-type">Box</span>::<span class="hljs-title function_ invoke__">new</span>(expr),
        }
    }
}
</code></pre>
<p>Now that we have an ef­fec­tive ex­pres­sion as a base, let’s ﬁll out the re­main­ing ﬁelds.
I found check­ing for an ad­po­si­tion re­duced the false-pos­i­tive rate, and it was eas­i­est to add it to the <code>match_to_lint</code> func­tion.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">impl</span> <span class="hljs-title class_">ExprLinter</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">MissingPreposition</span> {
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">expr</span>(&#x26;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &#x26;<span class="hljs-keyword">dyn</span> Expr {
        <span class="hljs-keyword">self</span>.expr.<span class="hljs-title function_ invoke__">as_ref</span>()
    }

    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">match_to_lint</span>(&#x26;<span class="hljs-keyword">self</span>, matched_tokens: &#x26;[Token], _source: &#x26;[<span class="hljs-type">char</span>]) <span class="hljs-punctuation">-></span> <span class="hljs-type">Option</span>&#x3C;Lint> {
        <span class="hljs-keyword">if</span> matched_tokens.<span class="hljs-title function_ invoke__">last</span>()?.kind.<span class="hljs-title function_ invoke__">is_upos</span>(UPOS::ADP) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>;
        }

        <span class="hljs-title function_ invoke__">Some</span>({
            Lint {
                span: matched_tokens[<span class="hljs-number">2</span>..<span class="hljs-number">4</span>].<span class="hljs-title function_ invoke__">span</span>()?,
                lint_kind: LintKind::Miscellaneous,
                suggestions: <span class="hljs-built_in">vec!</span>[],
                message: <span class="hljs-string">"You may be missing a preposition here."</span>.<span class="hljs-title function_ invoke__">to_owned</span>(),
                priority: <span class="hljs-number">31</span>,
            }
        })
    }

    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">description</span>(&#x26;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &#x26;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span> {
        <span class="hljs-string">"Locates potentially missing prepositions."</span>
    }
}
</code></pre>
<p>That’s it!
We’ve writ­ten our rule.</p>
<p>Don’t for­get to <a href="https://writewithharper.com/docs/contributors/author-a-rule#Register-Your-Rule">reg­is­ter your rule</a> and add some more tests be­fore open­ing PR.
Make sure you take a look at the <a href="https://github.com/Automattic/harper/pull/1530">pull re­quest</a> to see the ﬁn­ished rule.</p>]]></description>
            <content:encoded><![CDATA[<h1>Writing an Expression Rule for Harper</h1>
<p><em>This is part of a se­ries.
<a href="./writing_a_grammatical_rule_for_harper">Go to the start.</a></em></p>
<p>Expression rules (or more com­monly, <code>ExprLinter</code>s) are Harper rules that use de­clar­a­tive ex­pres­sions to ﬁnd and ﬁx gram­mat­i­cal er­rors.
They’re halfway be­tween a<span class="push-double"></span> <span class="pull-double">“</span>phrase cor­rec­tion” and man­u­ally im­ple­ment­ing <code>Linter</code>.</p>
<p>Make sure you prop­erly <a href="https://writewithharper.com/docs/contributors/environment">set up your en­vi­ron­ment</a>.</p>
<p>Before we get started, let’s take a look at the <code>ExprLinter</code> trait.
Here’s what it looks like at the time of writ­ing this post.</p>
<pre><code class="hljs language-rust"><span class="hljs-comment">/// A trait that searches for tokens that fulfil [`Expr`]s in a [`Document`].</span>
<span class="hljs-comment">///</span>
<span class="hljs-comment">/// Makes use of [`TokenStringExt::iter_chunks`] to avoid matching across sentence or clause</span>
<span class="hljs-comment">/// boundaries.</span>
<span class="hljs-meta">#[blanket(derive(Box))]</span>
<span class="hljs-keyword">pub</span> <span class="hljs-keyword">trait</span> <span class="hljs-title class_">ExprLinter</span>: LSend {
    <span class="hljs-comment">/// A simple getter for the expression you want Harper to search for.</span>
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">expr</span>(&#x26;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &#x26;<span class="hljs-keyword">dyn</span> Expr;
    <span class="hljs-comment">/// If any portions of a [`Document`] match [`Self::expr`], they are passed through [`ExprLinter::match_to_lint`] to be</span>
    <span class="hljs-comment">/// transformed into a [`Lint`] for editor consumption.</span>
    <span class="hljs-comment">///</span>
    <span class="hljs-comment">/// This function may return `None` to elect _not_ to produce a lint.</span>
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">match_to_lint</span>(&#x26;<span class="hljs-keyword">self</span>, matched_tokens: &#x26;[Token], source: &#x26;[<span class="hljs-type">char</span>]) <span class="hljs-punctuation">-></span> <span class="hljs-type">Option</span>&#x3C;Lint>;
    <span class="hljs-comment">/// A user-facing description of what kinds of grammatical errors this rule looks for.</span>
    <span class="hljs-comment">/// It is usually shown in settings menus.</span>
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">description</span>(&#x26;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &#x26;<span class="hljs-type">str</span>;
}
</code></pre>
<p>The struc­ture of the trait re­veals some of the be­hind-the-scenes work Harper is do­ing for you.
There are three phases:</p>
<ol>
<li>You pro­vide Harper an <code>Expr</code>.
It will it­er­ate through the doc­u­ment, look­ing for to­ken se­quences that match your ex­pres­sion.</li>
<li>Any and all matches are passed to <code>match_to_lint</code>.
From there, you can per­form op­tional ad­di­tional val­i­da­tion to con­ﬁrm that the to­kens re­ally do rep­re­sent a gram­mat­i­cal er­ror.
If so, re­turn <code>None</code>.
Otherwise, re­turn a <code>Lint</code> with any sug­ges­tions that may ﬁx the prob­lem.</li>
<li>Harper will han­dle every­thing else. It will show <span class="small-caps">UI</span>, re­for­mat text, and set­tings menus to the user.
It will also per­form ag­gres­sive caching on the ﬁrst two steps, so any mod­i­ﬁ­ca­tions to the doc­u­ment have a neg­li­gi­ble per­for­mance im­pact.</li>
</ol>
<h2>Let’s Get Started</h2>
<p>Now that we’ve re­viewed the es­sen­tials, let’s im­ple­ment an <code>ExprLinter</code>.</p>
<p>Before we can write a sin­gle line of code, we need a gram­mat­i­cal rule of in­ter­est.
I’m go­ing to pay a visit to the Harper <a href="https://github.com/Automattic/harper/issues?q=is%3Aissue%20state%3Aopen%20label%3Aenhancement%20label%3Aharper-core%20label%3Alinting">is­sue board</a>.</p>
<p>After look­ing through a few op­tions, I think <a href="https://github.com/Automattic/harper/issues/1513">#1513</a> is a good can­di­date.
We are look­ing for miss­ing prepo­si­tions be­tween an ad­jec­tive and a sub­ject.</p>
<p>To get started, we’ll cre­ate a ﬁle un­der <code>harper-core/src/linting</code> called <code>missing_preposition.rs</code> and add it to the par­ent Rust mod­ule.
I’ll paste the tem­plate into the ﬁle:</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">MissingPreposition</span> {
    expr: <span class="hljs-type">Box</span>&#x3C;<span class="hljs-keyword">dyn</span> Expr>,
}

<span class="hljs-keyword">impl</span> <span class="hljs-title class_">Default</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">MissingPreposition</span> {
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">default</span>() <span class="hljs-punctuation">-></span> <span class="hljs-keyword">Self</span> {
        <span class="hljs-keyword">let</span> <span class="hljs-variable">expr</span> = todo!();

        <span class="hljs-keyword">Self</span> {
            expr: <span class="hljs-type">Box</span>::<span class="hljs-title function_ invoke__">new</span>(expr),
        }
    }
}


<span class="hljs-keyword">impl</span> <span class="hljs-title class_">ExprLinter</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">MissingPreposition</span> {
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">expr</span>(&#x26;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &#x26;<span class="hljs-keyword">dyn</span> Expr {
        <span class="hljs-keyword">self</span>.expr.<span class="hljs-title function_ invoke__">as_ref</span>()
    }

    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">match_to_lint</span>(&#x26;<span class="hljs-keyword">self</span>, matched_tokens: &#x26;[Token], _source: &#x26;[<span class="hljs-type">char</span>]) <span class="hljs-punctuation">-></span> <span class="hljs-type">Option</span>&#x3C;Lint> {
        <span class="hljs-built_in">unimplemented!</span>()
    }

    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">description</span>(&#x26;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &#x26;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span> {
        <span class="hljs-built_in">unimplemented!</span>()
    }
}
</code></pre>
<p>I like to start by build­ing out a few test cases be­fore work­ing on the ac­tual code.
We get some for free from the GitHub is­sue:</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[test]</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">fixes_issue_1513</span>() {
    <span class="hljs-title function_ invoke__">assert_lint_count</span>(
        <span class="hljs-string">"The city is famous its beaches."</span>,
        MissingPreposition::<span class="hljs-title function_ invoke__">default</span>(),
        <span class="hljs-number">1</span>,
    );
    <span class="hljs-title function_ invoke__">assert_lint_count</span>(
        <span class="hljs-string">"The students are interested learning."</span>,
        MissingPreposition::<span class="hljs-title function_ invoke__">default</span>(),
        <span class="hljs-number">1</span>,
    );
}
</code></pre>
<p>Obviously, these tests will fail if we try to run <code>cargo test</code>, but at this point you should do so any­way to make sure your tool­chain is work­ing.</p>
<h2>Writing our Expression</h2>
<p>The heart of this gram­mat­i­cal rule is the <code>Expr</code> (pronounced <em>ex­pres­sion</em>).
There are a num­ber of ways to go about mak­ing one of these.
The sim­plest (and most com­mon by far) is to put to­gether a <a href="https://docs.rs/harper-core/latest/harper_core/expr/struct.SequenceExpr.html"><code>SequenceExpr</code></a>.</p>
<p>In our case, we’re look­ing for miss­ing prepo­si­tions be­tween an ad­jec­tive and a noun.
A good ex­pres­sion to start with could look like:</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Default</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">MissingPreposition</span> {
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">default</span>() <span class="hljs-punctuation">-></span> <span class="hljs-keyword">Self</span> {
        <span class="hljs-keyword">let</span> <span class="hljs-variable">expr</span> = SequenceExpr::<span class="hljs-title function_ invoke__">default</span>()
            .<span class="hljs-title function_ invoke__">then</span>(UPOSSet::<span class="hljs-title function_ invoke__">new</span>(&#x26;[UPOS::ADJ]))
            .<span class="hljs-title function_ invoke__">t_ws</span>()
            .<span class="hljs-title function_ invoke__">then</span>(UPOSSet::<span class="hljs-title function_ invoke__">new</span>(&#x26;[UPOS::NOUN, UPOS::PRON, UPOS::PROPN]));

        <span class="hljs-keyword">Self</span> {
            expr: <span class="hljs-type">Box</span>::<span class="hljs-title function_ invoke__">new</span>(expr),
        }
    }
}
</code></pre>
<p>We’re us­ing a <code>UPOSSet</code> here, which is an­other kind of <code>Expr</code> that looks for spe­ciﬁc parts of speech.
The name de­rives from the <a href="https://universaldependencies.org/u/pos/index.html">Universal Dependencies tag sys­tem</a>.
Any to­kens tagged with any of the op­tions we’ve pro­vided to the <code>UPOSSet</code> will match.</p>
<p>However, it’s easy to cre­ate an ex­am­ple that this ex­pres­sion matches against, but does­n’t con­tain a gram­mat­i­cal er­ror.
We call this a false pos­i­tive.
Let’s write one and add it to our test suite.</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[test]</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">allows_terrible_stuff</span>() {
    <span class="hljs-title function_ invoke__">assert_no_lints</span>(
        <span class="hljs-string">"Either it was terrible stuff or the whiskey distorted things."</span>,
        MissingPreposition::<span class="hljs-title function_ invoke__">default</span>(),
    );
}
</code></pre>
<p>From here, you should use your brain to con­tin­u­ously re­ﬁne the ex­pres­sion into some­thing that
main­tains a low false-pos­i­tive rate while re­main­ing use­ful.
Here’s what I set­tled on:</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Default</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">MissingPreposition</span> {
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">default</span>() <span class="hljs-punctuation">-></span> <span class="hljs-keyword">Self</span> {
        <span class="hljs-keyword">let</span> <span class="hljs-variable">expr</span> = SequenceExpr::<span class="hljs-title function_ invoke__">default</span>()
            .<span class="hljs-title function_ invoke__">then</span>(
                AnchorStart.<span class="hljs-title function_ invoke__">or</span>(SequenceExpr::<span class="hljs-title function_ invoke__">default</span>()
                    .<span class="hljs-title function_ invoke__">then</span>(UPOSSet::<span class="hljs-title function_ invoke__">new</span>(&#x26;[UPOS::DET]))
                    .<span class="hljs-title function_ invoke__">t_ws</span>()),
            )
            .<span class="hljs-title function_ invoke__">then</span>(UPOSSet::<span class="hljs-title function_ invoke__">new</span>(&#x26;[UPOS::NOUN, UPOS::PRON, UPOS::PROPN]))
            .<span class="hljs-title function_ invoke__">t_ws</span>()
            .<span class="hljs-title function_ invoke__">then</span>(UPOSSet::<span class="hljs-title function_ invoke__">new</span>(&#x26;[UPOS::AUX]))
            .<span class="hljs-title function_ invoke__">t_ws</span>()
            .<span class="hljs-title function_ invoke__">then</span>(UPOSSet::<span class="hljs-title function_ invoke__">new</span>(&#x26;[UPOS::ADJ]))
            .<span class="hljs-title function_ invoke__">t_ws</span>()
            .<span class="hljs-title function_ invoke__">then</span>(UPOSSet::<span class="hljs-title function_ invoke__">new</span>(&#x26;[UPOS::NOUN, UPOS::PRON, UPOS::PROPN]))
            .<span class="hljs-title function_ invoke__">then_optional</span>(AnyPattern)
            .<span class="hljs-title function_ invoke__">then_optional</span>(AnyPattern);

        <span class="hljs-keyword">Self</span> {
            expr: <span class="hljs-type">Box</span>::<span class="hljs-title function_ invoke__">new</span>(expr),
        }
    }
}
</code></pre>
<p>Now that we have an ef­fec­tive ex­pres­sion as a base, let’s ﬁll out the re­main­ing ﬁelds.
I found check­ing for an ad­po­si­tion re­duced the false-pos­i­tive rate, and it was eas­i­est to add it to the <code>match_to_lint</code> func­tion.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">impl</span> <span class="hljs-title class_">ExprLinter</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">MissingPreposition</span> {
    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">expr</span>(&#x26;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &#x26;<span class="hljs-keyword">dyn</span> Expr {
        <span class="hljs-keyword">self</span>.expr.<span class="hljs-title function_ invoke__">as_ref</span>()
    }

    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">match_to_lint</span>(&#x26;<span class="hljs-keyword">self</span>, matched_tokens: &#x26;[Token], _source: &#x26;[<span class="hljs-type">char</span>]) <span class="hljs-punctuation">-></span> <span class="hljs-type">Option</span>&#x3C;Lint> {
        <span class="hljs-keyword">if</span> matched_tokens.<span class="hljs-title function_ invoke__">last</span>()?.kind.<span class="hljs-title function_ invoke__">is_upos</span>(UPOS::ADP) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>;
        }

        <span class="hljs-title function_ invoke__">Some</span>({
            Lint {
                span: matched_tokens[<span class="hljs-number">2</span>..<span class="hljs-number">4</span>].<span class="hljs-title function_ invoke__">span</span>()?,
                lint_kind: LintKind::Miscellaneous,
                suggestions: <span class="hljs-built_in">vec!</span>[],
                message: <span class="hljs-string">"You may be missing a preposition here."</span>.<span class="hljs-title function_ invoke__">to_owned</span>(),
                priority: <span class="hljs-number">31</span>,
            }
        })
    }

    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">description</span>(&#x26;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &#x26;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span> {
        <span class="hljs-string">"Locates potentially missing prepositions."</span>
    }
}
</code></pre>
<p>That’s it!
We’ve writ­ten our rule.</p>
<p>Don’t for­get to <a href="https://writewithharper.com/docs/contributors/author-a-rule#Register-Your-Rule">reg­is­ter your rule</a> and add some more tests be­fore open­ing PR.
Make sure you take a look at the <a href="https://github.com/Automattic/harper/pull/1530">pull re­quest</a> to see the ﬁn­ished rule.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Adding a Programming Language to Harper]]></title>
            <link>https://elijahpotter.dev/articles/adding_a_programming_language_to_harper</link>
            <guid>https://elijahpotter.dev/articles/adding_a_programming_language_to_harper</guid>
            <pubDate>Mon, 07 Jul 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Adding a Programming Language to Harper</h1>
<p>When I started the <a href="https://github.com/automattic/harper">Harper pro­ject</a> I knew I wanted to be able to use it for the com­ments in my code.
First, be­cause I knew these com­ments would be­come part of our of­ﬁ­cial doc­u­men­ta­tion over time, and be­cause I hoped it would en­cour­age me to write more.
Over time, this has be­come one of the most prized fea­tures of the soft­ware, at­tract­ing tens of thou­sands of de­vel­op­ers.</p>
<p>The com­mon prob­lem, how­ever, is that there have al­ways been pro­gram­ming lan­guages that our <span class="small-caps">LSP</span> does­n’t sup­port.
One of the <a href="https://github.com/Automattic/harper/issues/79">old­est is­sues</a> on GitHub is about this.</p>
<p>This post is a guide for adding a new pro­gram­ming lan­guage to the Harper lan­guage server.</p>
<p>Why is­n’t it in the of­ﬁ­cial doc­u­men­ta­tion?
While the in­for­ma­tion con­tained within this guide will re­main rel­e­vant to the pro­ject for a long time, I don’t imag­ine each iden­ti­ﬁer or ﬁle path to re­main the same.
If you think it <em>would</em> bet­ter serve po­ten­tial con­trib­u­tors to place this guide on the <a href="https://writewithharper.com">main site</a>, let me know.</p>
<h2>Introduction to Tree-sitter</h2>
<p><a href="https://tree-sitter.github.io/tree-sitter/">Tree-sitter</a> is fan­tas­tic frame­work for build­ing fault-tol­er­ant lan­guage parsers.
That means it is still able to parse the ma­jor­ity of a doc­u­ment, even if it con­tains por­tions of in­valid syn­tax.</p>
<p>This is im­por­tant for Harper, since we ex­pect peo­ple to use Harper <em>while</em> their pro­gram­ming.
It should be <span class="small-caps">OK</span> if some of their code is in­cor­rect, since we only care about their com­ments.</p>
<p>There are also a wide va­ri­ety of Tree-sitter parsers avail­able on <a href="https://crates.io/">crates.io</a>, ripe for our con­sump­tion.
If you want to add a lan­guage to Harper, this is the eas­i­est way to do so.</p>
<h2>Step 0: Avoid Duplicating Work</h2>
<p>You’re in­ter­ested in adding sup­port for a pro­gram­ming lan­guage.
If that’s the case, it’s pos­si­ble other peo­ple are too.
Make sure no one else has <a href="https://elijahpotter.dev/articles/never_wait">opened a <span class="small-caps">PR</span></a> or <a href="https://writewithharper.com/docs/integrations/language-server#Supported-Languages">has al­ready merged sup­port</a> for the lan­guage you have in mind.</p>
<h2>Step 1: Find a Grammar</h2>
<p>Look for an ex­ist­ing gram­mar on <a href="https://crates.io">crates.io</a>.
By con­ven­tion, they tend to be named <code>tree-sitter-&#x3C;language></code>, where <code>&#x3C;language></code> is the lan­guage you’re look­ing for. For ex­am­ple, <a href="https://crates.io/crates/tree-sitter-java"><code>tree-sitter-java</code></a> is for Java and <a href="https://crates.io/crates/tree-sitter-rust"><code>tree-sitter-rust</code></a> is for Rust.</p>
<p>If you would rather write your own gram­mar, make sure it is even­tu­ally pub­lished on <code>crates.io</code>.
<code>harper-ls</code> bi­na­ries are of­ten con­sumed from <code>crates.io</code>, which re­quires that all up­stream de­pen­den­cies come from the same source.</p>
<h2>Step 2: Import and Wire In</h2>
<p>Harper’s com­ment sup­port lies in the <code>harper-comments</code> crate in <a href="https://github.com/automattic/harper/">the monorepo</a>.
Import the gram­mar’s crate into the pro­ject with Cargo.</p>
<pre><code class="hljs language-bash">cargo add &#x3C;CRATE-NAME>
</code></pre>
<p>Then, add lines to the rel­e­vant func­tions in <code>harper-comments/src/comment_parser.rs</code>.
Make sure you visit the <a href="https://microsoft.github.io/language-server-protocol/">Language Server Protocol Speciﬁcation</a> to ob­tain the cor­rect lan­guage <span class="small-caps">ID</span>.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">new_from_language_id</span>(
    language_id: &#x26;<span class="hljs-type">str</span>,
    markdown_options: MarkdownOptions,
) <span class="hljs-punctuation">-></span> <span class="hljs-type">Option</span>&#x3C;<span class="hljs-keyword">Self</span>> {
    <span class="hljs-keyword">let</span> <span class="hljs-variable">language</span> = <span class="hljs-keyword">match</span> language_id {
        <span class="hljs-string">"cmake"</span> => tree_sitter_cmake::LANGUAGE,
        <span class="hljs-string">"cpp"</span> => tree_sitter_cpp::LANGUAGE,
        <span class="hljs-string">"csharp"</span> => tree_sitter_c_sharp::LANGUAGE,
        <span class="hljs-string">"c"</span> => tree_sitter_c::LANGUAGE,
        <span class="hljs-string">"dart"</span> => harper_tree_sitter_dart::LANGUAGE,
        <span class="hljs-string">"go"</span> => tree_sitter_go::LANGUAGE, <span class="hljs-comment">// Add a line here</span>
</code></pre>
<pre><code class="hljs language-rust"><span class="hljs-comment">/// Convert a provided path to a corresponding Language Server Protocol file</span>
<span class="hljs-comment">/// type.</span>
<span class="hljs-comment">///</span>
<span class="hljs-comment">/// Note to contributors: try to keep this in sync with</span>
<span class="hljs-comment">/// [`Self::new_from_language_id`]</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">filename_to_filetype</span>(path: &#x26;Path) <span class="hljs-punctuation">-></span> <span class="hljs-type">Option</span>&#x3C;&#x26;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span>> {
    <span class="hljs-title function_ invoke__">Some</span>(<span class="hljs-keyword">match</span> path.<span class="hljs-title function_ invoke__">extension</span>()?.<span class="hljs-title function_ invoke__">to_str</span>()? {
        <span class="hljs-string">"bash"</span> => <span class="hljs-string">"shellscript"</span>,
        <span class="hljs-string">"c"</span> => <span class="hljs-string">"c"</span>,
        <span class="hljs-string">"cmake"</span> => <span class="hljs-string">"cmake"</span>,
        <span class="hljs-string">"cpp"</span> => <span class="hljs-string">"cpp"</span>,
        <span class="hljs-string">"cs"</span> => <span class="hljs-string">"csharp"</span>, <span class="hljs-comment">// Add a line here</span>
</code></pre>
<h2>Step 3: Testing</h2>
<p>To make sure every­thing be­haves cor­rectly, we need to add some in­te­gra­tion tests.
You’ll ﬁnd all the ex­ist­ing ones un­der <code>harper-comments/tests/language_support_sources</code>.</p>
<p>Find or write sev­eral new ﬁles un­der this di­rec­tory in the lan­guage you’ve added sup­port for.
Add in­ten­tional gram­mat­i­cal er­rors to these ﬁle in syn­tac­ti­cally rel­e­vant places.
We want to make sure that Harper can de­tect the er­rors we want and will ig­nore the er­rors we do not want.
For ex­am­ple, we might put an er­ror in­side an <code>@param</code> tag in JSDoc.
That way we’ll know if Harper is not prop­erly ig­nor­ing those el­e­ments.</p>
<p>Add new en­tries to the bot­tom of <code>harper-comments/tests/language_support.rs</code>.
The sec­ond pa­ra­me­ter of the <code>create_test!</code> macro is the num­ber of gram­mat­i­cal er­rors that Harper <strong>should</strong> de­tect in that ﬁle.</p>
<pre><code class="hljs language-rust">create_test!(ignore_shebang_3.sh, <span class="hljs-number">0</span>);
create_test!(ignore_shebang_4.sh, <span class="hljs-number">1</span>);
create_test!(common.mill, <span class="hljs-number">1</span>);
create_test!(basic_kotlin.kt, <span class="hljs-number">0</span>); <span class="hljs-comment">// Add a line here</span>
</code></pre>
<p>From there, you can run <code>cargo test</code> to make sure every­thing passes.</p>
<h2>Step 4: Document</h2>
<p>To ad­ver­tise sup­port for the lan­guage, there are a cou­ple ad­di­tion places that need mod­i­ﬁ­ca­tion.
No­tably:</p>
<ul>
<li>The sup­ported lan­guages table in <code>packages/web/src/routes/docs/integrations/language-server/+page.md</code></li>
<li>The <a href="https://github.com/Automattic/harper/issues/79">GitHub Issue</a></li>
<li>The <code>activationEvents</code> key in the <span class="small-caps">VS</span> Code plug­in’s man­i­fest: <code>packages/vscode-plugin/package.json</code></li>
</ul>
<h2>Done!</h2>
<p>That should be every­thing.
Open a draft pull re­quest while you work and ping me (<a href="https://github.com/elijah-potter/">eli­jah-pot­ter</a>) if you have any ques­tions.</p>
<h2>Additional Resources</h2>
<ul>
<li><a href="https://github.com/Automattic/harper/pull/1443"><span class="small-caps">PR</span> for adding Solidity sup­port</a></li>
<li><a href="https://github.com/Automattic/harper/pull/970"><span class="small-caps">PR</span> for adding Scala sup­port</a></li>
</ul>]]></description>
            <content:encoded><![CDATA[<h1>Adding a Programming Language to Harper</h1>
<p>When I started the <a href="https://github.com/automattic/harper">Harper pro­ject</a> I knew I wanted to be able to use it for the com­ments in my code.
First, be­cause I knew these com­ments would be­come part of our of­ﬁ­cial doc­u­men­ta­tion over time, and be­cause I hoped it would en­cour­age me to write more.
Over time, this has be­come one of the most prized fea­tures of the soft­ware, at­tract­ing tens of thou­sands of de­vel­op­ers.</p>
<p>The com­mon prob­lem, how­ever, is that there have al­ways been pro­gram­ming lan­guages that our <span class="small-caps">LSP</span> does­n’t sup­port.
One of the <a href="https://github.com/Automattic/harper/issues/79">old­est is­sues</a> on GitHub is about this.</p>
<p>This post is a guide for adding a new pro­gram­ming lan­guage to the Harper lan­guage server.</p>
<p>Why is­n’t it in the of­ﬁ­cial doc­u­men­ta­tion?
While the in­for­ma­tion con­tained within this guide will re­main rel­e­vant to the pro­ject for a long time, I don’t imag­ine each iden­ti­ﬁer or ﬁle path to re­main the same.
If you think it <em>would</em> bet­ter serve po­ten­tial con­trib­u­tors to place this guide on the <a href="https://writewithharper.com">main site</a>, let me know.</p>
<h2>Introduction to Tree-sitter</h2>
<p><a href="https://tree-sitter.github.io/tree-sitter/">Tree-sitter</a> is fan­tas­tic frame­work for build­ing fault-tol­er­ant lan­guage parsers.
That means it is still able to parse the ma­jor­ity of a doc­u­ment, even if it con­tains por­tions of in­valid syn­tax.</p>
<p>This is im­por­tant for Harper, since we ex­pect peo­ple to use Harper <em>while</em> their pro­gram­ming.
It should be <span class="small-caps">OK</span> if some of their code is in­cor­rect, since we only care about their com­ments.</p>
<p>There are also a wide va­ri­ety of Tree-sitter parsers avail­able on <a href="https://crates.io/">crates.io</a>, ripe for our con­sump­tion.
If you want to add a lan­guage to Harper, this is the eas­i­est way to do so.</p>
<h2>Step 0: Avoid Duplicating Work</h2>
<p>You’re in­ter­ested in adding sup­port for a pro­gram­ming lan­guage.
If that’s the case, it’s pos­si­ble other peo­ple are too.
Make sure no one else has <a href="https://elijahpotter.dev/articles/never_wait">opened a <span class="small-caps">PR</span></a> or <a href="https://writewithharper.com/docs/integrations/language-server#Supported-Languages">has al­ready merged sup­port</a> for the lan­guage you have in mind.</p>
<h2>Step 1: Find a Grammar</h2>
<p>Look for an ex­ist­ing gram­mar on <a href="https://crates.io">crates.io</a>.
By con­ven­tion, they tend to be named <code>tree-sitter-&#x3C;language></code>, where <code>&#x3C;language></code> is the lan­guage you’re look­ing for. For ex­am­ple, <a href="https://crates.io/crates/tree-sitter-java"><code>tree-sitter-java</code></a> is for Java and <a href="https://crates.io/crates/tree-sitter-rust"><code>tree-sitter-rust</code></a> is for Rust.</p>
<p>If you would rather write your own gram­mar, make sure it is even­tu­ally pub­lished on <code>crates.io</code>.
<code>harper-ls</code> bi­na­ries are of­ten con­sumed from <code>crates.io</code>, which re­quires that all up­stream de­pen­den­cies come from the same source.</p>
<h2>Step 2: Import and Wire In</h2>
<p>Harper’s com­ment sup­port lies in the <code>harper-comments</code> crate in <a href="https://github.com/automattic/harper/">the monorepo</a>.
Import the gram­mar’s crate into the pro­ject with Cargo.</p>
<pre><code class="hljs language-bash">cargo add &#x3C;CRATE-NAME>
</code></pre>
<p>Then, add lines to the rel­e­vant func­tions in <code>harper-comments/src/comment_parser.rs</code>.
Make sure you visit the <a href="https://microsoft.github.io/language-server-protocol/">Language Server Protocol Speciﬁcation</a> to ob­tain the cor­rect lan­guage <span class="small-caps">ID</span>.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">new_from_language_id</span>(
    language_id: &#x26;<span class="hljs-type">str</span>,
    markdown_options: MarkdownOptions,
) <span class="hljs-punctuation">-></span> <span class="hljs-type">Option</span>&#x3C;<span class="hljs-keyword">Self</span>> {
    <span class="hljs-keyword">let</span> <span class="hljs-variable">language</span> = <span class="hljs-keyword">match</span> language_id {
        <span class="hljs-string">"cmake"</span> => tree_sitter_cmake::LANGUAGE,
        <span class="hljs-string">"cpp"</span> => tree_sitter_cpp::LANGUAGE,
        <span class="hljs-string">"csharp"</span> => tree_sitter_c_sharp::LANGUAGE,
        <span class="hljs-string">"c"</span> => tree_sitter_c::LANGUAGE,
        <span class="hljs-string">"dart"</span> => harper_tree_sitter_dart::LANGUAGE,
        <span class="hljs-string">"go"</span> => tree_sitter_go::LANGUAGE, <span class="hljs-comment">// Add a line here</span>
</code></pre>
<pre><code class="hljs language-rust"><span class="hljs-comment">/// Convert a provided path to a corresponding Language Server Protocol file</span>
<span class="hljs-comment">/// type.</span>
<span class="hljs-comment">///</span>
<span class="hljs-comment">/// Note to contributors: try to keep this in sync with</span>
<span class="hljs-comment">/// [`Self::new_from_language_id`]</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">filename_to_filetype</span>(path: &#x26;Path) <span class="hljs-punctuation">-></span> <span class="hljs-type">Option</span>&#x3C;&#x26;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span>> {
    <span class="hljs-title function_ invoke__">Some</span>(<span class="hljs-keyword">match</span> path.<span class="hljs-title function_ invoke__">extension</span>()?.<span class="hljs-title function_ invoke__">to_str</span>()? {
        <span class="hljs-string">"bash"</span> => <span class="hljs-string">"shellscript"</span>,
        <span class="hljs-string">"c"</span> => <span class="hljs-string">"c"</span>,
        <span class="hljs-string">"cmake"</span> => <span class="hljs-string">"cmake"</span>,
        <span class="hljs-string">"cpp"</span> => <span class="hljs-string">"cpp"</span>,
        <span class="hljs-string">"cs"</span> => <span class="hljs-string">"csharp"</span>, <span class="hljs-comment">// Add a line here</span>
</code></pre>
<h2>Step 3: Testing</h2>
<p>To make sure every­thing be­haves cor­rectly, we need to add some in­te­gra­tion tests.
You’ll ﬁnd all the ex­ist­ing ones un­der <code>harper-comments/tests/language_support_sources</code>.</p>
<p>Find or write sev­eral new ﬁles un­der this di­rec­tory in the lan­guage you’ve added sup­port for.
Add in­ten­tional gram­mat­i­cal er­rors to these ﬁle in syn­tac­ti­cally rel­e­vant places.
We want to make sure that Harper can de­tect the er­rors we want and will ig­nore the er­rors we do not want.
For ex­am­ple, we might put an er­ror in­side an <code>@param</code> tag in JSDoc.
That way we’ll know if Harper is not prop­erly ig­nor­ing those el­e­ments.</p>
<p>Add new en­tries to the bot­tom of <code>harper-comments/tests/language_support.rs</code>.
The sec­ond pa­ra­me­ter of the <code>create_test!</code> macro is the num­ber of gram­mat­i­cal er­rors that Harper <strong>should</strong> de­tect in that ﬁle.</p>
<pre><code class="hljs language-rust">create_test!(ignore_shebang_3.sh, <span class="hljs-number">0</span>);
create_test!(ignore_shebang_4.sh, <span class="hljs-number">1</span>);
create_test!(common.mill, <span class="hljs-number">1</span>);
create_test!(basic_kotlin.kt, <span class="hljs-number">0</span>); <span class="hljs-comment">// Add a line here</span>
</code></pre>
<p>From there, you can run <code>cargo test</code> to make sure every­thing passes.</p>
<h2>Step 4: Document</h2>
<p>To ad­ver­tise sup­port for the lan­guage, there are a cou­ple ad­di­tion places that need mod­i­ﬁ­ca­tion.
No­tably:</p>
<ul>
<li>The sup­ported lan­guages table in <code>packages/web/src/routes/docs/integrations/language-server/+page.md</code></li>
<li>The <a href="https://github.com/Automattic/harper/issues/79">GitHub Issue</a></li>
<li>The <code>activationEvents</code> key in the <span class="small-caps">VS</span> Code plug­in’s man­i­fest: <code>packages/vscode-plugin/package.json</code></li>
</ul>
<h2>Done!</h2>
<p>That should be every­thing.
Open a draft pull re­quest while you work and ping me (<a href="https://github.com/elijah-potter/">eli­jah-pot­ter</a>) if you have any ques­tions.</p>
<h2>Additional Resources</h2>
<ul>
<li><a href="https://github.com/Automattic/harper/pull/1443"><span class="small-caps">PR</span> for adding Solidity sup­port</a></li>
<li><a href="https://github.com/Automattic/harper/pull/970"><span class="small-caps">PR</span> for adding Scala sup­port</a></li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Local-First Software Is Easier to Scale]]></title>
            <link>https://elijahpotter.dev/articles/local-first_software_is_easier_to_scale</link>
            <guid>https://elijahpotter.dev/articles/local-first_software_is_easier_to_scale</guid>
            <pubDate>Fri, 04 Jul 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Local-First Software is Easier to Scale</h1>
<img src="/images/flatirons.webp" alt="The Flatirons">
<p>The ti­tle of this post is some­what mis­lead­ing. Local-ﬁrst soft­ware rarely needs to be scaled at all.</p>
<p><a href="https://writewithharper.com">Harper</a> re­cently re­ceived a mas­sive in­crease in both traf­ﬁc and user count.
How?
By mak­ing it to the front page of Hacker News.
If you could­n’t tell by the ex­tent to which I talk about this, I’m thrilled.
This amounted to a tremen­dous amount of free ad­ver­tis­ing, if noth­ing else.</p>
<p>I’ve been think­ing a lot about the won­drous ben­e­ﬁts of work­ing at the edge.
That’s just one term of many that peo­ple use to de­scribe Harper:<span class="push-double"></span> <span class="pull-double">“</span>edge-ﬁrst”.
Others in­clude<span class="push-double"></span> <span class="pull-double">“</span>local-ﬁrst” or sim­ply<span class="push-double"></span> <span class="pull-double">“</span>on-device”.
They are all just jar­gon for a sim­ple idea: We run all the rel­e­vant gram­mar check­ing code as phys­i­cally close to the users as pos­si­ble.
That way, we can serve up sug­ges­tions faster than any­one else.
To achieve our goal, we make sure that every­thing is well-op­ti­mized and lean.
Harper barely uses any <span class="small-caps">CPU</span> or mem­ory at all.</p>
<p>What does all this mean in the con­text of the re­cent uptick in user ac­qui­si­tion? Imagine, for a mo­ment, we are LanguageTool.
Our soft­ware is writ­ten in Java, with ques­tion­able at­ten­dance to the ef­ﬁ­ciency of our code.
Since our soft­ware re­quires a big server to run, we have a cer­tain num­ber of servers on standby to han­dle a cer­tain amount of ex­pected load.</p>
<p>If sud­denly the num­ber of re­quests we re­ceived were to dou­ble (as it did for the Chrome ex­ten­sion), we would be big trou­ble.
To avoid in­creas­ing the la­tency on re­quests (or drop­ping re­quests all to­gether), we would need to scale up the num­ber of run­ning servers.
This not only takes hir­ing an ex­pert in cloud ar­chi­tec­ture, but also ad­di­tional fund­ing to pay the <span class="small-caps">AWS</span> (or what­ever) bill at the end of the month.</p>
<p>Because Harper runs at the edge (no server re­quired), we don’t have to worry about that.
In fact, I only no­ticed that our user count had spiked af­ter I vis­ited Hacker News the morn­ing af­ter.
No hic­cups at all.</p>
<p>Lots of cloud providers like to brag about be­ing able to scale with their users. I like to brag about not hav­ing to scale at all.</p>]]></description>
            <content:encoded><![CDATA[<h1>Local-First Software is Easier to Scale</h1>
<img src="/images/flatirons.webp" alt="The Flatirons">
<p>The ti­tle of this post is some­what mis­lead­ing. Local-ﬁrst soft­ware rarely needs to be scaled at all.</p>
<p><a href="https://writewithharper.com">Harper</a> re­cently re­ceived a mas­sive in­crease in both traf­ﬁc and user count.
How?
By mak­ing it to the front page of Hacker News.
If you could­n’t tell by the ex­tent to which I talk about this, I’m thrilled.
This amounted to a tremen­dous amount of free ad­ver­tis­ing, if noth­ing else.</p>
<p>I’ve been think­ing a lot about the won­drous ben­e­ﬁts of work­ing at the edge.
That’s just one term of many that peo­ple use to de­scribe Harper:<span class="push-double"></span> <span class="pull-double">“</span>edge-ﬁrst”.
Others in­clude<span class="push-double"></span> <span class="pull-double">“</span>local-ﬁrst” or sim­ply<span class="push-double"></span> <span class="pull-double">“</span>on-device”.
They are all just jar­gon for a sim­ple idea: We run all the rel­e­vant gram­mar check­ing code as phys­i­cally close to the users as pos­si­ble.
That way, we can serve up sug­ges­tions faster than any­one else.
To achieve our goal, we make sure that every­thing is well-op­ti­mized and lean.
Harper barely uses any <span class="small-caps">CPU</span> or mem­ory at all.</p>
<p>What does all this mean in the con­text of the re­cent uptick in user ac­qui­si­tion? Imagine, for a mo­ment, we are LanguageTool.
Our soft­ware is writ­ten in Java, with ques­tion­able at­ten­dance to the ef­ﬁ­ciency of our code.
Since our soft­ware re­quires a big server to run, we have a cer­tain num­ber of servers on standby to han­dle a cer­tain amount of ex­pected load.</p>
<p>If sud­denly the num­ber of re­quests we re­ceived were to dou­ble (as it did for the Chrome ex­ten­sion), we would be big trou­ble.
To avoid in­creas­ing the la­tency on re­quests (or drop­ping re­quests all to­gether), we would need to scale up the num­ber of run­ning servers.
This not only takes hir­ing an ex­pert in cloud ar­chi­tec­ture, but also ad­di­tional fund­ing to pay the <span class="small-caps">AWS</span> (or what­ever) bill at the end of the month.</p>
<p>Because Harper runs at the edge (no server re­quired), we don’t have to worry about that.
In fact, I only no­ticed that our user count had spiked af­ter I vis­ited Hacker News the morn­ing af­ter.
No hic­cups at all.</p>
<p>Lots of cloud providers like to brag about be­ing able to scale with their users. I like to brag about not hav­ing to scale at all.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/flatirons.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Code Ages like Milk]]></title>
            <link>https://elijahpotter.dev/articles/code_ages_like_milk</link>
            <guid>https://elijahpotter.dev/articles/code_ages_like_milk</guid>
            <pubDate>Fri, 27 Jun 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Code Ages Like Milk</h1>
<img src="/images/front_range.webp" alt="The Front Range in the summer is hard to beat">
<p>A bold ti­tle, no? But it’s true, and it’s some­thing that I (and most other main­tain­ers) have to deal with on a reg­u­lar ba­sis. Failing to ac­count for this re­al­ity can slow down de­vel­op­ment and dis­suade con­trib­u­tors from stick­ing around.</p>
<p>As the chief main­tainer of Harper, one of my main jobs is to act as qual­ity con­trol. I re­ceive <a href="https://github.com/Automattic/harper/pulse/monthly">a num­ber of PRs each week</a>, both from new con­trib­u­tors and old. Given the di­verse pool of con­trib­u­tors, it makes sense that I re­view a di­verse pool of code of vary­ing lev­els of qual­ity.</p>
<p>When I have my head down on tech­ni­cal and at­ten­tion-in­tense pro­jects, I have less time to re­view code. When I have less time, I need to set pri­or­i­ties. The ques­tion be­comes: Would I rather let code from high-qual­ity, re­li­able sources pile up, or code from sources of un­known qual­ity?</p>
<p>I usu­ally end up re­view­ing the high-qual­ity sources ﬁrst, since they’ll likely re­quire fewer re­vi­sions. Fewer re­vi­sions means quicker merge means im­prov­ing the user ex­pe­ri­ence faster. Whether or not this is a good de­ci­sion is some­thing I’ve been think­ing about all week.</p>
<h2>Code Ages</h2>
<p>As code sits stag­nant in a <span class="small-caps">PR</span>, it ages. You might say I’m crazy, that there is no way the code it­self changes as it sits still in an un­merged and un­mod­i­ﬁed <span class="small-caps">PR</span>. I’d say you’re right, ex­cept for one teeny-tiny de­tail: Code only has mean­ing when it sits within a broader con­text. If that broader con­text changes (for ex­am­ple, when other PRs are merged) the mean­ing of the un­mod­i­ﬁed code does too.</p>
<p>Let me put it an­other way. If the patch from a <span class="small-caps">PR</span> re­mains stag­nant, but the code it gets patched onto changes, the ac­tual im­pact of the patch does too. This can cause all sorts of prob­lems, from merge con­ﬂicts to er­ro­neous test fail­ures.</p>
<p>The longer code sits in a <span class="small-caps">PR</span>, the more time it usu­ally ends up tak­ing me to get it merged (when I ﬁ­nally have the time to get to it), just from the merge con­ﬂicts alone. It’s the Lindy ef­fect rear­ing its ugly head once more.</p>
<h2>Features Age Too</h2>
<p>Not only does code age when left un­touched and un­used, so do the fea­tures they rep­re­sent. If a user re­quests for a but­ton that marks some text as bold and they don’t re­ceive that fea­ture promptly, they’ll ﬁnd an al­ter­na­tive so­lu­tion, pos­si­bly from a com­peti­tor.</p>
<p>The rest of the app can evolve too. If you’re an in­di­vid­ual con­trib­u­tor, it’s in your best in­ter­est for your code to make it to mas­ter. If that does­n’t hap­pen quickly enough, an­other con­trib­u­tor (or, if the soft­ware is ex­ten­si­ble, like Obsidian or WordPress) or plu­gin au­thor might beat you to the punch.</p>
<h2>What Can I Do About It?</h2>
<p>I be­lieve the way I’ve been han­dling this un­til now has been en­tirely wrong. I should be al­lo­cat­ing more of my time to train­ing new con­trib­u­tors and ﬁeld­ing PRs from es­tab­lished ones. Open sources is a team ef­fort.</p>
<p>As for you, dear reader, that’s for you to ﬁg­ure out. The worst way code can age is if it stays in your head. Don’t let your ideas go to waste. <a href="./never_wait">Open that <span class="small-caps">PR</span></a>, or re­mind your re­viewer to take an­other look if you haven’t al­ready.</p>]]></description>
            <content:encoded><![CDATA[<h1>Code Ages Like Milk</h1>
<img src="/images/front_range.webp" alt="The Front Range in the summer is hard to beat">
<p>A bold ti­tle, no? But it’s true, and it’s some­thing that I (and most other main­tain­ers) have to deal with on a reg­u­lar ba­sis. Failing to ac­count for this re­al­ity can slow down de­vel­op­ment and dis­suade con­trib­u­tors from stick­ing around.</p>
<p>As the chief main­tainer of Harper, one of my main jobs is to act as qual­ity con­trol. I re­ceive <a href="https://github.com/Automattic/harper/pulse/monthly">a num­ber of PRs each week</a>, both from new con­trib­u­tors and old. Given the di­verse pool of con­trib­u­tors, it makes sense that I re­view a di­verse pool of code of vary­ing lev­els of qual­ity.</p>
<p>When I have my head down on tech­ni­cal and at­ten­tion-in­tense pro­jects, I have less time to re­view code. When I have less time, I need to set pri­or­i­ties. The ques­tion be­comes: Would I rather let code from high-qual­ity, re­li­able sources pile up, or code from sources of un­known qual­ity?</p>
<p>I usu­ally end up re­view­ing the high-qual­ity sources ﬁrst, since they’ll likely re­quire fewer re­vi­sions. Fewer re­vi­sions means quicker merge means im­prov­ing the user ex­pe­ri­ence faster. Whether or not this is a good de­ci­sion is some­thing I’ve been think­ing about all week.</p>
<h2>Code Ages</h2>
<p>As code sits stag­nant in a <span class="small-caps">PR</span>, it ages. You might say I’m crazy, that there is no way the code it­self changes as it sits still in an un­merged and un­mod­i­ﬁed <span class="small-caps">PR</span>. I’d say you’re right, ex­cept for one teeny-tiny de­tail: Code only has mean­ing when it sits within a broader con­text. If that broader con­text changes (for ex­am­ple, when other PRs are merged) the mean­ing of the un­mod­i­ﬁed code does too.</p>
<p>Let me put it an­other way. If the patch from a <span class="small-caps">PR</span> re­mains stag­nant, but the code it gets patched onto changes, the ac­tual im­pact of the patch does too. This can cause all sorts of prob­lems, from merge con­ﬂicts to er­ro­neous test fail­ures.</p>
<p>The longer code sits in a <span class="small-caps">PR</span>, the more time it usu­ally ends up tak­ing me to get it merged (when I ﬁ­nally have the time to get to it), just from the merge con­ﬂicts alone. It’s the Lindy ef­fect rear­ing its ugly head once more.</p>
<h2>Features Age Too</h2>
<p>Not only does code age when left un­touched and un­used, so do the fea­tures they rep­re­sent. If a user re­quests for a but­ton that marks some text as bold and they don’t re­ceive that fea­ture promptly, they’ll ﬁnd an al­ter­na­tive so­lu­tion, pos­si­bly from a com­peti­tor.</p>
<p>The rest of the app can evolve too. If you’re an in­di­vid­ual con­trib­u­tor, it’s in your best in­ter­est for your code to make it to mas­ter. If that does­n’t hap­pen quickly enough, an­other con­trib­u­tor (or, if the soft­ware is ex­ten­si­ble, like Obsidian or WordPress) or plu­gin au­thor might beat you to the punch.</p>
<h2>What Can I Do About It?</h2>
<p>I be­lieve the way I’ve been han­dling this un­til now has been en­tirely wrong. I should be al­lo­cat­ing more of my time to train­ing new con­trib­u­tors and ﬁeld­ing PRs from es­tab­lished ones. Open sources is a team ef­fort.</p>
<p>As for you, dear reader, that’s for you to ﬁg­ure out. The worst way code can age is if it stays in your head. Don’t let your ideas go to waste. <a href="./never_wait">Open that <span class="small-caps">PR</span></a>, or re­mind your re­viewer to take an­other look if you haven’t al­ready.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/front_range.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[The Books I Have Read Since April 2025]]></title>
            <link>https://elijahpotter.dev/articles/the_books_i_have_read_since_april_2025</link>
            <guid>https://elijahpotter.dev/articles/the_books_i_have_read_since_april_2025</guid>
            <pubDate>Sun, 22 Jun 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Books I Have Read Since April 2025</h1>
<p>It has been a slow cou­ple of months for my read­ing habit.
Al­though, now that I’ve said that out loud, I re­al­ize I say that every time.
I never quite read enough books by the time I feel ready to write one of these read­ing re­views.</p>
<p>In the in­ter­est of brevity, let’s jump straight in.</p>
<h2><em>How to Stop Time</em> by Matt Haig</h2>
<p>After read­ing <em>The Midnight Library</em> in <a href="/the_books_i_read_in_january_2025">January</a>, I was thor­oughly im­pressed by Haig’s work.
I wanted more, but was­n’t sure what to read next.
So, the next time I saw a book with an in­ter­est­ing ti­tle which hap­pened to be his, I picked it up.</p>
<p>If <em>The Midnight Library</em> was a lit­er­ary ther­apy ses­sion, <em>How to Stop Time</em> is a his­tory les­son.
It fol­lows a man who ages at one-tenth the rate of a nor­mal hu­man.
De­spite be­ing given the gift of ex­tra life, he reg­u­larly falls into spats of de­pres­sion when his loved ones die be­fore him.</p>
<p>While Haig’s writ­ing con­tin­ues to be sen­ti­men­tal and ap­pre­cia­tive of the small things in life (a trait few seem to pos­sess these days), I ul­ti­mately found the book pre­dictable and un­en­gag­ing.
It was a good book, and I felt sat­is­ﬁed hav­ing read it, I had put it down for a few days at a time more than once to do so.
If you’re go­ing to read just one of Haig’s books, it should­n’t be this one.</p>
<h2><em>Everything is Tuberculosis</em> by John Green</h2>
<p>I’ve been a fan of John Green’s work since I was kid.
I re­mem­ber read­ing <em>Looking for Alaska</em> as I cruised down the Mississippi River on a school trip.
It was one of the most mem­o­rable books (and ex­pe­di­tions) I’ve had the plea­sure of ex­pe­ri­enc­ing.</p>
<p>Naturally, I was ec­sta­tic to hear John Green pub­lished some­thing new: <em>Everything is Tuberculosis</em>, a book about the facets of our lives (of which there are many) that we can thank <span class="small-caps">TB</span> for.</p>
<p>I liked this book, but I don’t think it matches the re­ﬂec­tive at­ti­tude about the hu­man ex­pe­ri­ence you ﬁnd in his other books—but that’s a good thing!
<em>Everything is Tuberculosis</em> is not a book you’re sup­posed to re­late to, but a book that tells a story about a life most in the west­ern world ﬁnd com­pletely un­fa­mil­iar.
One where each and every day is counted.
One where the price of a med­ica­tion is the most im­por­tant num­ber.
One where you might not sur­vive.</p>
<p>The fact that I could­n’t re­late to the events in <em>Everything is Tuberculosis</em> is the ex­act rea­son why I’m glad I read it.</p>
<h2><em>How to Win Friends and Inﬂuence People</em> by Dale Carnegie</h2>
<p>I read <em>How to Win Friends and Inﬂuence People</em> (a mouth­ful, I know) not be­cause I needed ad­vice on the sub­ject.
I read it be­cause I be­lieve this book to be a telling win­dow into the minds of the busi­ness­men of the early 20th cen­tury.</p>
<p>I’ve been told that this book is still rel­e­vant to­day; that the ad­vice still works.
I dis­agree.
The pri­or­i­ties, val­ues, and cul­ture are en­tirely dif­fer­ent to­day than when this book was writ­ten.
I found it fas­ci­nat­ing to com­pare and con­trast be­tween my own ex­pe­ri­ence and the au­thor’s.</p>]]></description>
            <content:encoded><![CDATA[<h1>The Books I Have Read Since April 2025</h1>
<p>It has been a slow cou­ple of months for my read­ing habit.
Al­though, now that I’ve said that out loud, I re­al­ize I say that every time.
I never quite read enough books by the time I feel ready to write one of these read­ing re­views.</p>
<p>In the in­ter­est of brevity, let’s jump straight in.</p>
<h2><em>How to Stop Time</em> by Matt Haig</h2>
<p>After read­ing <em>The Midnight Library</em> in <a href="/the_books_i_read_in_january_2025">January</a>, I was thor­oughly im­pressed by Haig’s work.
I wanted more, but was­n’t sure what to read next.
So, the next time I saw a book with an in­ter­est­ing ti­tle which hap­pened to be his, I picked it up.</p>
<p>If <em>The Midnight Library</em> was a lit­er­ary ther­apy ses­sion, <em>How to Stop Time</em> is a his­tory les­son.
It fol­lows a man who ages at one-tenth the rate of a nor­mal hu­man.
De­spite be­ing given the gift of ex­tra life, he reg­u­larly falls into spats of de­pres­sion when his loved ones die be­fore him.</p>
<p>While Haig’s writ­ing con­tin­ues to be sen­ti­men­tal and ap­pre­cia­tive of the small things in life (a trait few seem to pos­sess these days), I ul­ti­mately found the book pre­dictable and un­en­gag­ing.
It was a good book, and I felt sat­is­ﬁed hav­ing read it, I had put it down for a few days at a time more than once to do so.
If you’re go­ing to read just one of Haig’s books, it should­n’t be this one.</p>
<h2><em>Everything is Tuberculosis</em> by John Green</h2>
<p>I’ve been a fan of John Green’s work since I was kid.
I re­mem­ber read­ing <em>Looking for Alaska</em> as I cruised down the Mississippi River on a school trip.
It was one of the most mem­o­rable books (and ex­pe­di­tions) I’ve had the plea­sure of ex­pe­ri­enc­ing.</p>
<p>Naturally, I was ec­sta­tic to hear John Green pub­lished some­thing new: <em>Everything is Tuberculosis</em>, a book about the facets of our lives (of which there are many) that we can thank <span class="small-caps">TB</span> for.</p>
<p>I liked this book, but I don’t think it matches the re­ﬂec­tive at­ti­tude about the hu­man ex­pe­ri­ence you ﬁnd in his other books—but that’s a good thing!
<em>Everything is Tuberculosis</em> is not a book you’re sup­posed to re­late to, but a book that tells a story about a life most in the west­ern world ﬁnd com­pletely un­fa­mil­iar.
One where each and every day is counted.
One where the price of a med­ica­tion is the most im­por­tant num­ber.
One where you might not sur­vive.</p>
<p>The fact that I could­n’t re­late to the events in <em>Everything is Tuberculosis</em> is the ex­act rea­son why I’m glad I read it.</p>
<h2><em>How to Win Friends and Inﬂuence People</em> by Dale Carnegie</h2>
<p>I read <em>How to Win Friends and Inﬂuence People</em> (a mouth­ful, I know) not be­cause I needed ad­vice on the sub­ject.
I read it be­cause I be­lieve this book to be a telling win­dow into the minds of the busi­ness­men of the early 20th cen­tury.</p>
<p>I’ve been told that this book is still rel­e­vant to­day; that the ad­vice still works.
I dis­agree.
The pri­or­i­ties, val­ues, and cul­ture are en­tirely dif­fer­ent to­day than when this book was writ­ten.
I found it fas­ci­nat­ing to com­pare and con­trast be­tween my own ex­pe­ri­ence and the au­thor’s.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Harper for Firefox]]></title>
            <link>https://elijahpotter.dev/articles/harper_for_firefox</link>
            <guid>https://elijahpotter.dev/articles/harper_for_firefox</guid>
            <pubDate>Fri, 20 Jun 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Harper for Firefox</h1>
<img src="/images/harper_chrome_ext.png" alt="The Harper Firefox Extension">
<p>There’s a great deal of over­lap be­tween those who use Harper and those who use Firefox.
Foremost, they both value <em>pri­vacy</em>.
Which is why I’m so thrilled to say that Harper has an of­ﬁ­cial plu­gin for Firefox.
While there has been an un­of­ﬁ­cial one for <a href="./harper_for_firefox_through_spellbolt">a while now</a>, this is the one that fully de­serves the Harper la­bel. It’s ours, through and through.</p>
<p>It’s still early days, so if you run into prob­lems, feel free to open <a href="https://github.com/Automattic/harper/issues/new/choose">an is­sue</a>.</p>
<p>If you’d like to in­stall it, pay us a visit on the <a href="https://addons.mozilla.org/en-US/firefox/addon/private-grammar-checker-harper/">Firefox Add-on Store</a>.</p>]]></description>
            <content:encoded><![CDATA[<h1>Harper for Firefox</h1>
<img src="/images/harper_chrome_ext.png" alt="The Harper Firefox Extension">
<p>There’s a great deal of over­lap be­tween those who use Harper and those who use Firefox.
Foremost, they both value <em>pri­vacy</em>.
Which is why I’m so thrilled to say that Harper has an of­ﬁ­cial plu­gin for Firefox.
While there has been an un­of­ﬁ­cial one for <a href="./harper_for_firefox_through_spellbolt">a while now</a>, this is the one that fully de­serves the Harper la­bel. It’s ours, through and through.</p>
<p>It’s still early days, so if you run into prob­lems, feel free to open <a href="https://github.com/Automattic/harper/issues/new/choose">an is­sue</a>.</p>
<p>If you’d like to in­stall it, pay us a visit on the <a href="https://addons.mozilla.org/en-US/firefox/addon/private-grammar-checker-harper/">Firefox Add-on Store</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Refactoring More and Faster]]></title>
            <link>https://elijahpotter.dev/articles/refactoring_more_and_faster</link>
            <guid>https://elijahpotter.dev/articles/refactoring_more_and_faster</guid>
            <pubDate>Tue, 17 Jun 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Refactoring More and Faster</h1>
<img src="/images/clouds.webp" alt="Clouds in Colorado">
<p>I’ve been deep in the refac­tor rab­bit-hole.
You know—that aw­ful (but strangely sat­is­fy­ing) space where the ma­jor­ity of your com­mits are small pedan­tic ed­its that don’t change the end-user-ex­pe­ri­ence at all?</p>
<p>These past few weeks, I’ve been work­ing so hard on Harper’s Chrome Extension and build­ing sys­tems for trans­for­ma­tion-based learn­ing, I haven’t had a chance to touch any of the ac­tual gram­mar rules the soft­ware is sup­posed to ﬁx.
As such, a few glar­ing tools for rule au­thors have gone unim­ple­mented.
These are tools that—be­cause they’re so tied into the core sys­tem—need a lot of ex­per­tise and ﬁne-tun­ing to work prop­erly.</p>
<p>If you take a look at the <a href="https://github.com/Automattic/harper/pull/1393">diff</a>, you’ll ﬁnd that there are a lot of small ed­its.
Since each one takes a min­i­mal amount of cog­ni­tive ef­fort, it is easy for me to slide into a trance.
A trance where I am tech­ni­cally mov­ing to­wards my goal (in this case, cre­at­ing an ex­pres­sion sys­tem that en­com­passes—but is more pow­er­ful than—our ex­ist­ing <code>Pattern</code> sys­tem), but I’m not do­ing so in a way that’s truly pro­duc­tive.
I would like to cover some of the in­di­vid­ual strate­gies I’ve found that have helped me speed up my refac­tor­ing pro­ces.
Af­ter all, if the process of refac­tor­ing is the process of pay­ing back tech debt, refac­tor­ing is in­cred­i­bly im­por­tant to main­tain­ing ve­loc­ity.</p>
<p>I am do­ing this for two rea­sons.
First, be­cause I be­lieve this is valu­able in­for­ma­tion for any de­vel­oper.
Sec­ond, be­cause I want to so­lid­ify these ideas in my mind to fur­ther im­prove my refac­tor­ing down the line.</p>
<h2>Use Your Tools, but Not Too Much</h2>
<p>In what­ever lan­guage you’re work­ing with, there are likely spe­cial­ized tools for refac­tor­ing.
For Java, I’ve used <a href="https://www.jetbrains.com/idea/">IntellJ <span class="small-caps">IDEA</span></a>.
For Harper, I’m us­ing <a href="https://rust-analyzer.github.io/"><code>rust-analyzer</code></a>.
These pro­vide neat func­tions for chang­ing iden­ti­ﬁers or mov­ing mod­ules, all while up­dat­ing rel­e­vant ref­er­ences.</p>
<p>These tools are im­per­fect, how­ever, and of­ten fail to up­date ref­er­ences in parts of code that re­quire a higher level of se­man­tic un­der­stand­ing.</p>
<p>It’s a com­mon pat­tern for au­thors to cre­ate an in­stance of a class and as­sign it to a vari­able with the same name.</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[derive(Default)]</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">Foo</span> {
    <span class="hljs-comment">// Implementation details</span>
}

<span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {
    <span class="hljs-comment">// The author of this code named the variable after the class</span>
    <span class="hljs-keyword">let</span> <span class="hljs-variable">foo</span> = Foo::<span class="hljs-title function_ invoke__">default</span>();
}
</code></pre>
<p>In these cases, when some­one changes the name of the class, the as­so­ci­ated vari­able name will not get up­dated.</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[derive(Default)]</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">Bar</span> {
    <span class="hljs-comment">// Implementation details</span>
}

<span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {
    <span class="hljs-comment">// Variable name is not updated by our static analysis tool.</span>
    <span class="hljs-keyword">let</span> <span class="hljs-variable">foo</span> = Bar::<span class="hljs-title function_ invoke__">default</span>();
}
</code></pre>
<p>When do­ing a refac­tor like this, you need to read <strong>every changed ﬁle</strong> to en­sure the code still makes as much sense to a hu­man as it does to the com­piler.</p>
<h2>Plan It Out, but Not Too Much</h2>
<p>I can’t be­lieve I need to say this, but it’s chron­i­cally un­der-dis­cussed.
If you’re do­ing a fun­da­men­tal change to how a sys­tem works, it is vi­tal that you read the code ﬁrst and plan it out.
The plan does­n’t need to be spe­ciﬁc nor need to be com­plete.</p>
<p>Having a good idea of what the start and end state should look like will save you hours of time.
While I am a pro­po­nent of <a href="https://jimmyhmiller.com/discovery-coding">dis­cov­ery cod­ing</a> for new fea­tures and code, I ﬁnd that it is lack­lus­ter when it comes to ma­jor <em>re</em>-writing of code.</p>
<p>To in­struct peo­ple to plan out their changes may feel ju­vinile or ba­sic, but I think it’s worth men­tion­ing.</p>
<h2>The Joy</h2>
<p>Refactoring helps me write new fea­tures, faster.
Refac­tor­ing helps me ﬁnd bugs in ex­ist­ing code.
Refac­tor­ing makes it eas­ier for new­com­ers to join and con­tribute to a pro­ject.</p>
<p>It is one of my fa­vorite pas­times.
I en­joy refac­tor­ing, but also want to do it well.
I hope you feel the same.</p>]]></description>
            <content:encoded><![CDATA[<h1>Refactoring More and Faster</h1>
<img src="/images/clouds.webp" alt="Clouds in Colorado">
<p>I’ve been deep in the refac­tor rab­bit-hole.
You know—that aw­ful (but strangely sat­is­fy­ing) space where the ma­jor­ity of your com­mits are small pedan­tic ed­its that don’t change the end-user-ex­pe­ri­ence at all?</p>
<p>These past few weeks, I’ve been work­ing so hard on Harper’s Chrome Extension and build­ing sys­tems for trans­for­ma­tion-based learn­ing, I haven’t had a chance to touch any of the ac­tual gram­mar rules the soft­ware is sup­posed to ﬁx.
As such, a few glar­ing tools for rule au­thors have gone unim­ple­mented.
These are tools that—be­cause they’re so tied into the core sys­tem—need a lot of ex­per­tise and ﬁne-tun­ing to work prop­erly.</p>
<p>If you take a look at the <a href="https://github.com/Automattic/harper/pull/1393">diff</a>, you’ll ﬁnd that there are a lot of small ed­its.
Since each one takes a min­i­mal amount of cog­ni­tive ef­fort, it is easy for me to slide into a trance.
A trance where I am tech­ni­cally mov­ing to­wards my goal (in this case, cre­at­ing an ex­pres­sion sys­tem that en­com­passes—but is more pow­er­ful than—our ex­ist­ing <code>Pattern</code> sys­tem), but I’m not do­ing so in a way that’s truly pro­duc­tive.
I would like to cover some of the in­di­vid­ual strate­gies I’ve found that have helped me speed up my refac­tor­ing pro­ces.
Af­ter all, if the process of refac­tor­ing is the process of pay­ing back tech debt, refac­tor­ing is in­cred­i­bly im­por­tant to main­tain­ing ve­loc­ity.</p>
<p>I am do­ing this for two rea­sons.
First, be­cause I be­lieve this is valu­able in­for­ma­tion for any de­vel­oper.
Sec­ond, be­cause I want to so­lid­ify these ideas in my mind to fur­ther im­prove my refac­tor­ing down the line.</p>
<h2>Use Your Tools, but Not Too Much</h2>
<p>In what­ever lan­guage you’re work­ing with, there are likely spe­cial­ized tools for refac­tor­ing.
For Java, I’ve used <a href="https://www.jetbrains.com/idea/">IntellJ <span class="small-caps">IDEA</span></a>.
For Harper, I’m us­ing <a href="https://rust-analyzer.github.io/"><code>rust-analyzer</code></a>.
These pro­vide neat func­tions for chang­ing iden­ti­ﬁers or mov­ing mod­ules, all while up­dat­ing rel­e­vant ref­er­ences.</p>
<p>These tools are im­per­fect, how­ever, and of­ten fail to up­date ref­er­ences in parts of code that re­quire a higher level of se­man­tic un­der­stand­ing.</p>
<p>It’s a com­mon pat­tern for au­thors to cre­ate an in­stance of a class and as­sign it to a vari­able with the same name.</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[derive(Default)]</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">Foo</span> {
    <span class="hljs-comment">// Implementation details</span>
}

<span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {
    <span class="hljs-comment">// The author of this code named the variable after the class</span>
    <span class="hljs-keyword">let</span> <span class="hljs-variable">foo</span> = Foo::<span class="hljs-title function_ invoke__">default</span>();
}
</code></pre>
<p>In these cases, when some­one changes the name of the class, the as­so­ci­ated vari­able name will not get up­dated.</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[derive(Default)]</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">Bar</span> {
    <span class="hljs-comment">// Implementation details</span>
}

<span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {
    <span class="hljs-comment">// Variable name is not updated by our static analysis tool.</span>
    <span class="hljs-keyword">let</span> <span class="hljs-variable">foo</span> = Bar::<span class="hljs-title function_ invoke__">default</span>();
}
</code></pre>
<p>When do­ing a refac­tor like this, you need to read <strong>every changed ﬁle</strong> to en­sure the code still makes as much sense to a hu­man as it does to the com­piler.</p>
<h2>Plan It Out, but Not Too Much</h2>
<p>I can’t be­lieve I need to say this, but it’s chron­i­cally un­der-dis­cussed.
If you’re do­ing a fun­da­men­tal change to how a sys­tem works, it is vi­tal that you read the code ﬁrst and plan it out.
The plan does­n’t need to be spe­ciﬁc nor need to be com­plete.</p>
<p>Having a good idea of what the start and end state should look like will save you hours of time.
While I am a pro­po­nent of <a href="https://jimmyhmiller.com/discovery-coding">dis­cov­ery cod­ing</a> for new fea­tures and code, I ﬁnd that it is lack­lus­ter when it comes to ma­jor <em>re</em>-writing of code.</p>
<p>To in­struct peo­ple to plan out their changes may feel ju­vinile or ba­sic, but I think it’s worth men­tion­ing.</p>
<h2>The Joy</h2>
<p>Refactoring helps me write new fea­tures, faster.
Refac­tor­ing helps me ﬁnd bugs in ex­ist­ing code.
Refac­tor­ing makes it eas­ier for new­com­ers to join and con­tribute to a pro­ject.</p>
<p>It is one of my fa­vorite pas­times.
I en­joy refac­tor­ing, but also want to do it well.
I hope you feel the same.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/clouds.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[More Transformation-Based Learning]]></title>
            <link>https://elijahpotter.dev/articles/more_transformation-based_learning</link>
            <guid>https://elijahpotter.dev/articles/more_transformation-based_learning</guid>
            <pubDate>Fri, 13 Jun 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Continuations on Transformation-based Learning</h1>
<img src="/images/clear_creek_steps.webp" alt="Steps near Clear Creek. Taken by me.">
<p>The most com­mon type of ma­chine learn­ing out there takes the form of some kind of neural net­work. Inspired by how our own brains work, these sys­tems act as func­tion ap­prox­i­ma­tions. They are great, but they come with a few key pit­falls.</p>
<p>First and fore­most, they start out with very lit­tle baked-in un­der­stand­ing of the con­text they live in. This is ﬁne—usu­ally enough data can be pro­vided to bridge the gap. It does mean, how­ever, that they spend an in­or­di­nate amount of time learn­ing the fun­da­men­tals of their ﬁeld. This trans­lates to a larger model size and longer in­fer­ence time (especially at <a href="https://en.wikipedia.org/wiki/Edge_computing">the edge</a>).</p>
<p>Secondly, most neural net­works are ini­tial­ized with ran­dom­ness, which re­sults in ex­tremely high en­tropy. High en­tropy means that these mod­els can­not be com­pressed eas­ily (if at all).</p>
<p>This dis­qual­i­ﬁes neural net­works from many as­pects of <a href="https://writewithharper.com/">Harper’s ar­chi­tec­ture</a>. Harper tries to be fast and small, so it can be shipped and run wher­ever our users are. Neural net­works (especially in the world of nat­ural lan­guage pro­cess­ing) are nei­ther fast nor small.</p>
<p>This is why we’ve tak­ing an al­ter­na­tive ap­proach to ma­chine learn­ing, as ev­i­denced by last week’s post on <a href="https://elijahpotter.dev/articles/transformation-based_learning">trans­for­ma­tion-based learn­ing</a>.</p>
<h2>Transformation-Based Learning: A Refresher</h2>
<p>Transformation-based learn­ing is re­mark­ably sim­ple. It boils down to just four steps:</p>
<ul>
<li>Use a sim­ple, sto­chas­tic model t la­bel your data. This can be as sim­ple as tag­ging each to­ken (or other dis­crete com­po­nent) with that vari­ant’s most com­mon tag. It does­n’t need to su­per ac­cu­rate, just enough to es­tab­lish a base­line.</li>
<li>Identify the er­rors be­tween the tags in your canon­i­cal data and that which pro­duced by your base­line model.</li>
<li>Using a ﬁ­nite list of hu­man-de­ﬁned tem­plates, gen­er­ate can­di­date rules that trans­form the out­put of the base­line model into some­thing else. This is where the term “trans­for­ma­tion-based” comes from.</li>
<li>Apply each of the can­di­date rules to the base­line mod­el’s out­put. Check if the re­sult is more ac­cu­rate than be­fore. If so, save the rule for fu­ture use.</li>
</ul>
<p>These saved can­di­dates be­come your model.</p>
<p>If you’re in­ter­ested how this could be ap­plied to <span class="small-caps">POS</span> tag­ging, I’ve since up­dated my orig­i­nal post on the sub­ject to bet­ter ex­plain the process. I’d rec­om­mend tak­ing a look.</p>
<h2>Nominal Phrase Chunking</h2>
<p>It’s of­ten use­ful, es­pe­cially when build­ing a gram­mar checker, to be able to iden­tify the sub­jects and ob­jects of sen­tences. Suppose, for ex­am­ple, that we want to in­sert the miss­ing Oxford comma in a list of fruits:<span class="push-double"></span> <span class="pull-double">“</span>I like ap­ples, ba­nanas and or­anges”. In this triv­ial ex­am­ple, this can be done with <span class="small-caps">POS</span> tag­ging. If we have more com­plex sub­jects, like in the phrase<span class="push-double"></span> <span class="pull-double">“</span>I like green ap­ples, de­li­ciously per­ni­cious ba­nanas and fresh or­anges,” <span class="small-caps">POS</span> tag­ging starts to fall apart. Identifying multi-to­ken sub­jects is the job of a nom­i­nal phrase chun­ker.</p>
<p>I’ve been want­ing to build a nom­i­nal phrase chun­ker for a while, but haven’t had the tools to do so. Now that I have pipeline in place (from last week), it should be rel­a­tively straight­for­ward.</p>
<p>For the pur­poses of this model, we’ll be tag­ging each to­ken with a boolean; it is ei­ther a mem­ber of a noun phrase, or it is not.</p>
<p>I started by as­sign­ing each to­ken to a nom­i­nal phrase if a <span class="small-caps">POS</span> tag­ger marks it as a noun. This is our base­line model. It per­forms poorly be­cause the re­sult­ing nom­i­nal phrases do not in­clude de­ter­min­ers or any ad­jec­tives.</p>
<p>Similar to our <span class="small-caps">POS</span> tag­ging model, I used a Universal Dependencies tree­bank to de­ter­mine the ac­cu­racy of our base­line. After gen­er­at­ing can­di­date rules us­ing the same patch tem­plates as the <span class="small-caps">POS</span> tag­ging sys­tem and run­ning them against the tree­bank, I have a model with a 90% ac­cu­racy.</p>
<p>I feel as though there should be more de­tails to share, but that was pretty much it. I spent a good amount of time op­ti­miz­ing the train­ing code. There’s still a lot of work left to do to in­cor­po­rate it into the rest of Harper. I am also un­sat­is­ﬁed with the mod­el’s cur­rent ac­cu­racy. To get it closer to 100%, I sus­pect I’ll need to do a good amount of data clean­ing.</p>]]></description>
            <content:encoded><![CDATA[<h1>Continuations on Transformation-based Learning</h1>
<img src="/images/clear_creek_steps.webp" alt="Steps near Clear Creek. Taken by me.">
<p>The most com­mon type of ma­chine learn­ing out there takes the form of some kind of neural net­work. Inspired by how our own brains work, these sys­tems act as func­tion ap­prox­i­ma­tions. They are great, but they come with a few key pit­falls.</p>
<p>First and fore­most, they start out with very lit­tle baked-in un­der­stand­ing of the con­text they live in. This is ﬁne—usu­ally enough data can be pro­vided to bridge the gap. It does mean, how­ever, that they spend an in­or­di­nate amount of time learn­ing the fun­da­men­tals of their ﬁeld. This trans­lates to a larger model size and longer in­fer­ence time (especially at <a href="https://en.wikipedia.org/wiki/Edge_computing">the edge</a>).</p>
<p>Secondly, most neural net­works are ini­tial­ized with ran­dom­ness, which re­sults in ex­tremely high en­tropy. High en­tropy means that these mod­els can­not be com­pressed eas­ily (if at all).</p>
<p>This dis­qual­i­ﬁes neural net­works from many as­pects of <a href="https://writewithharper.com/">Harper’s ar­chi­tec­ture</a>. Harper tries to be fast and small, so it can be shipped and run wher­ever our users are. Neural net­works (especially in the world of nat­ural lan­guage pro­cess­ing) are nei­ther fast nor small.</p>
<p>This is why we’ve tak­ing an al­ter­na­tive ap­proach to ma­chine learn­ing, as ev­i­denced by last week’s post on <a href="https://elijahpotter.dev/articles/transformation-based_learning">trans­for­ma­tion-based learn­ing</a>.</p>
<h2>Transformation-Based Learning: A Refresher</h2>
<p>Transformation-based learn­ing is re­mark­ably sim­ple. It boils down to just four steps:</p>
<ul>
<li>Use a sim­ple, sto­chas­tic model t la­bel your data. This can be as sim­ple as tag­ging each to­ken (or other dis­crete com­po­nent) with that vari­ant’s most com­mon tag. It does­n’t need to su­per ac­cu­rate, just enough to es­tab­lish a base­line.</li>
<li>Identify the er­rors be­tween the tags in your canon­i­cal data and that which pro­duced by your base­line model.</li>
<li>Using a ﬁ­nite list of hu­man-de­ﬁned tem­plates, gen­er­ate can­di­date rules that trans­form the out­put of the base­line model into some­thing else. This is where the term “trans­for­ma­tion-based” comes from.</li>
<li>Apply each of the can­di­date rules to the base­line mod­el’s out­put. Check if the re­sult is more ac­cu­rate than be­fore. If so, save the rule for fu­ture use.</li>
</ul>
<p>These saved can­di­dates be­come your model.</p>
<p>If you’re in­ter­ested how this could be ap­plied to <span class="small-caps">POS</span> tag­ging, I’ve since up­dated my orig­i­nal post on the sub­ject to bet­ter ex­plain the process. I’d rec­om­mend tak­ing a look.</p>
<h2>Nominal Phrase Chunking</h2>
<p>It’s of­ten use­ful, es­pe­cially when build­ing a gram­mar checker, to be able to iden­tify the sub­jects and ob­jects of sen­tences. Suppose, for ex­am­ple, that we want to in­sert the miss­ing Oxford comma in a list of fruits:<span class="push-double"></span> <span class="pull-double">“</span>I like ap­ples, ba­nanas and or­anges”. In this triv­ial ex­am­ple, this can be done with <span class="small-caps">POS</span> tag­ging. If we have more com­plex sub­jects, like in the phrase<span class="push-double"></span> <span class="pull-double">“</span>I like green ap­ples, de­li­ciously per­ni­cious ba­nanas and fresh or­anges,” <span class="small-caps">POS</span> tag­ging starts to fall apart. Identifying multi-to­ken sub­jects is the job of a nom­i­nal phrase chun­ker.</p>
<p>I’ve been want­ing to build a nom­i­nal phrase chun­ker for a while, but haven’t had the tools to do so. Now that I have pipeline in place (from last week), it should be rel­a­tively straight­for­ward.</p>
<p>For the pur­poses of this model, we’ll be tag­ging each to­ken with a boolean; it is ei­ther a mem­ber of a noun phrase, or it is not.</p>
<p>I started by as­sign­ing each to­ken to a nom­i­nal phrase if a <span class="small-caps">POS</span> tag­ger marks it as a noun. This is our base­line model. It per­forms poorly be­cause the re­sult­ing nom­i­nal phrases do not in­clude de­ter­min­ers or any ad­jec­tives.</p>
<p>Similar to our <span class="small-caps">POS</span> tag­ging model, I used a Universal Dependencies tree­bank to de­ter­mine the ac­cu­racy of our base­line. After gen­er­at­ing can­di­date rules us­ing the same patch tem­plates as the <span class="small-caps">POS</span> tag­ging sys­tem and run­ning them against the tree­bank, I have a model with a 90% ac­cu­racy.</p>
<p>I feel as though there should be more de­tails to share, but that was pretty much it. I spent a good amount of time op­ti­miz­ing the train­ing code. There’s still a lot of work left to do to in­cor­po­rate it into the rest of Harper. I am also un­sat­is­ﬁed with the mod­el’s cur­rent ac­cu­racy. To get it closer to 100%, I sus­pect I’ll need to do a good amount of data clean­ing.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/clear_creek_steps.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Transformation-Based Learning]]></title>
            <link>https://elijahpotter.dev/articles/transformation-based_learning</link>
            <guid>https://elijahpotter.dev/articles/transformation-based_learning</guid>
            <pubDate>Tue, 03 Jun 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Transformation-based Learning for <span class="small-caps">POS</span> Tagging</h1>
<img src="/images/ctlm.webp" alt="CTLM">
<p><a href="https://writewithharper.com/">Harper</a> is cur­rently un­der­go­ing some pretty rad­i­cal changes when it comes to its lan­guage analy­sis. These im­prove­ments will im­prove the out­put of our ex­ist­ing rule en­gine, in ad­di­tion to mak­ing en­tirely new cor­rec­tions pos­si­ble. This post will cover our ex­ist­ing <span class="small-caps">NLP</span> pipeline, the re­cent changes and im­prove­ments to our ma­chine learn­ing ap­proach, and what will come next.</p>
<p>While <span class="small-caps">AI</span> is a com­mon topic of dis­cus­sion on­line, I don’t hear much about ac­tual ma­chine learn­ing. In that light, I hope this post piques some­one’s in­ter­est.</p>
<h2>What is <span class="small-caps">POS</span> Tagging?</h2>
<p><span class="small-caps">POS</span> (Part-of-speech) tag­ging is the ﬁrst step of most <span class="small-caps">NLP</span> (Natural Language Processing) pipelines. For any gram­mar checker worth its salt, <span class="small-caps">POS</span> tag­ging is es­sen­tial. Apart from the ba­sic cor­rec­tions you’re ca­pa­ble of do­ing with sim­ple string ma­nip­u­la­tion, most gram­mar check­ing di­rectly or in­di­rectly de­pends on <span class="small-caps">POS</span> tag­ging. High-quality tag­ging re­sults in high-qual­ity sug­ges­tions.</p>
<p>What is <span class="small-caps">POS</span> tag­ging? It is the process of iden­ti­fy­ing which pos­si­ble de­f­i­n­i­tion of a word is be­ing used, based on the sur­round­ing con­text. For those un­fa­mil­iar with the ter­ri­tory, I’m cer­tain an ex­am­ple is the best way to ex­plain.</p>
<blockquote>
<p><span class="pull-double">“</span>I am go­ing to go tan in the sun.”</p>
</blockquote>
<p>Here we have a sim­ple, English sen­tence. In this case, it is clear the word<span class="push-double"></span> <span class="pull-double">“</span>tan” is be­ing used as verb. The lin­guists in the au­di­ence would point out that it is specif­i­cally in the ﬁrst-per­son fu­ture tense. Consider this sim­i­lar sen­tence:</p>
<blockquote>
<p><span class="pull-double">“</span>I am al­ready very tan, so I will stay in­side.”</p>
</blockquote>
<p>In this sen­tence, the word<span class="push-double"></span> <span class="pull-double">“</span>tan” is be­ing used as an ad­jec­tive. How can we tell?</p>
<p>As in­tel­li­gent hu­mans, some of whom have been speak­ing English their en­tire lives, it is easy for us to de­ter­mine which words are serv­ing which roles. It’s not as easy for a com­puter to do the same. From an al­go­rith­mic stand­point, there are a num­ber of ways to go about it, each with dif­fer­ing lev­els of<span class="push-double"></span> <span class="pull-double">“</span>machine learn­ing” re­quired.</p>
<p>Before this week, Harper pri­mar­ily took a dic­tio­nary-based ap­proach. In short: we ship a<span class="push-double"></span> <span class="pull-double">“</span>dictionary” of English words to the user’s ma­chine and use hash table lookups to de­ter­mine the pos­si­ble roles each word could as­sume. The au­thors to our rule en­gine could then use rudi­men­tary de­duc­tive rea­son­ing to nar­row the pos­si­bil­i­ties down. This strat­egy is re­mark­ably ef­fec­tive and it has scaled to tens of thou­sands of users with sur­pris­ingly few hic­cups.</p>
<p>That said, there are edge-cases and sys­tems (which I’ll cover next week when I dis­cuss chunk­ing) which re­quire ex­treme speci­ﬁcity from <span class="small-caps">POS</span> tags. My mis­sion: im­prove our <span class="small-caps">POS</span> tag­ging to in­crease the con­ﬁ­dence of Harper’s out­put and open the door for more ad­vanced al­go­rithms.</p>
<h2>Why Transformation-based Learning?</h2>
<p>The lit­er­a­ture high­lights four un­der­ly­ing ma­chine learn­ing model strate­gies that seem to work well for <span class="small-caps">POS</span> tag­ging.</p>
<ul>
<li>Hidden Markov Models (probabilistic mod­els that pre­date the mod­ern deep learn­ing era)</li>
<li>Maximum Entropy Models (statistical mod­els closely re­lated to lo­gis­tic re­gres­sion).</li>
<li>Transformer-based Models (which use deep neural net­works)</li>
<li>Transformation-based Rule Models (which are based on learned rules)</li>
</ul>
<p>While I heav­ily con­sid­ered us­ing a neural net­work (either via an <span class="small-caps">HMM</span> or <span class="small-caps">MEM</span>), I dis­carded the tech­nol­ogy for three rea­sons.</p>
<ul>
<li>TRMs are typ­i­cally more ac­cu­rate (barely; mea­sured in ba­sis points).</li>
<li>TRMs are more amenable to ﬁne-tun­ing.</li>
<li>TRMs are ex­cep­tion­ally low-la­tency and can be com­pressed quite small.</li>
</ul>
<p>Transformation-based learn­ing is re­mark­ably sim­ple. It boils down to just four steps:</p>
<ul>
<li>Use a sim­ple, sto­chas­tic model to la­bel your data. This can be as sim­ple as tag­ging each to­ken (or other dis­crete com­po­nent) with that vari­ant’s most com­mon tag. It does­n’t need to su­per ac­cu­rate, just enough to es­tab­lish a base­line.</li>
<li>Identify the er­rors be­tween the tags in your canon­i­cal data and that which pro­duced by your base­line model.</li>
<li>Using a ﬁ­nite list of hu­man-de­ﬁned tem­plates, gen­er­ate can­di­date rules that trans­form the out­put of the base­line model into some­thing else. This is where the term<span class="push-double"></span> <span class="pull-double">“</span>transformation-based” comes from.</li>
<li>Apply each of the can­di­date rules to the base­line mod­el’s out­put. Check if the re­sult is <em>more</em> ac­cu­rate than be­fore. If so, save the rule for fu­ture use.</li>
</ul>
<p>These saved can­di­dates be­come your model.</p>
<h2>POS-Tagging us­ing Transformation-based Learning</h2>
<p>Let’s ap­ply these steps to build a POS-tagging sys­tem.</p>
<p>For our base­line model, we will just as­sign each word in our dataset the most com­mon <span class="small-caps">POS</span> tag as­so­ci­ated with that word. If the word is<span class="push-double"></span> <span class="pull-double">“</span>tan”, we’ll as­sign it’s most com­mon <span class="small-caps">POS</span> tag (verb). It will of­ten be in­cor­rect, but those cases will be han­dled by our rules.</p>
<p>To iden­tify the base­line mod­el’s er­rors, we’ll use an off-the-shelf tree-bank from the Universal Dependencies pro­ject.</p>
<p>Our rule tem­plates will take the form of these <code>PatchCriteria</code>. By ini­tial­iz­ing our can­di­dates with any one of these enum vari­ants and ini­tial­iz­ing the child vari­ables to ran­dom val­ues, we can cover a good num­ber of cases.</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]</span>
<span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">PatchCriteria</span> {
    WordIsTaggedWith {
        <span class="hljs-comment">/// Which token to inspect.</span>
        relative: <span class="hljs-type">isize</span>,
        is_tagged: UPOS,
    },
    AnyWordIsTaggedWith {
        <span class="hljs-comment">/// The farthest relative index to look</span>
        max_relative: <span class="hljs-type">isize</span>,
        is_tagged: UPOS,
    },
    SandwichTaggedWith {
        prev_word_tagged: UPOS,
        post_word_tagged: UPOS,
    },
    WordIs {
        relative: <span class="hljs-type">isize</span>,
        word: <span class="hljs-type">String</span>,
    },
    <span class="hljs-comment">/// Not applicable to the Brill Tagger, only the chunker</span>
    NounPhraseAt {
        is_np: <span class="hljs-type">bool</span>,
        relative: <span class="hljs-type">isize</span>,
    },
    Combined {
        a: <span class="hljs-type">Box</span>&#x3C;PatchCriteria>,
        b: <span class="hljs-type">Box</span>&#x3C;PatchCriteria>,
    },
}
</code></pre>
<p>Finally, we’ll ap­ply each of the hun­dreds of thou­sands of can­di­dates to our tree­bank to see if the re­sult of their trans­for­ma­tions have a lower er­ror rate than the base­line.</p>
<p>Here are a cou­ple of the can­di­dates we found:</p>
<pre><code class="hljs language-json">[
  {
    "from": "PRON",
    "to": "SCONJ",
    "criteria": {
      "Combined": {
        "a": {
          "WordIs": {
            "relative": 0,
            "word": "that"
          }
        },
        "b": {
          "WordIsTaggedWith": {
            "relative": -1,
            "is_tagged": "VERB"
          }
        }
      }
    }
  },
  {
    "from": "PART",
    "to": "ADP",
    "criteria": {
      "Combined": {
        "a": {
          "WordIs": {
            "relative": 1,
            "word": "there"
          }
        },
        "b": {
          "AnyWordIsTaggedWith": {
            "max_relative": -4,
            "is_tagged": "NOUN"
          }
        }
      }
    }
  }
]
</code></pre>
<p>That’s the whole process! With it, I was able to bring our pre­vi­ous ac­cu­racy all the way up to 95% (from 40%) with­out a mean­ing­ful change in lint­ing la­tency or com­piled bi­nary size.</p>]]></description>
            <content:encoded><![CDATA[<h1>Transformation-based Learning for <span class="small-caps">POS</span> Tagging</h1>
<img src="/images/ctlm.webp" alt="CTLM">
<p><a href="https://writewithharper.com/">Harper</a> is cur­rently un­der­go­ing some pretty rad­i­cal changes when it comes to its lan­guage analy­sis. These im­prove­ments will im­prove the out­put of our ex­ist­ing rule en­gine, in ad­di­tion to mak­ing en­tirely new cor­rec­tions pos­si­ble. This post will cover our ex­ist­ing <span class="small-caps">NLP</span> pipeline, the re­cent changes and im­prove­ments to our ma­chine learn­ing ap­proach, and what will come next.</p>
<p>While <span class="small-caps">AI</span> is a com­mon topic of dis­cus­sion on­line, I don’t hear much about ac­tual ma­chine learn­ing. In that light, I hope this post piques some­one’s in­ter­est.</p>
<h2>What is <span class="small-caps">POS</span> Tagging?</h2>
<p><span class="small-caps">POS</span> (Part-of-speech) tag­ging is the ﬁrst step of most <span class="small-caps">NLP</span> (Natural Language Processing) pipelines. For any gram­mar checker worth its salt, <span class="small-caps">POS</span> tag­ging is es­sen­tial. Apart from the ba­sic cor­rec­tions you’re ca­pa­ble of do­ing with sim­ple string ma­nip­u­la­tion, most gram­mar check­ing di­rectly or in­di­rectly de­pends on <span class="small-caps">POS</span> tag­ging. High-quality tag­ging re­sults in high-qual­ity sug­ges­tions.</p>
<p>What is <span class="small-caps">POS</span> tag­ging? It is the process of iden­ti­fy­ing which pos­si­ble de­f­i­n­i­tion of a word is be­ing used, based on the sur­round­ing con­text. For those un­fa­mil­iar with the ter­ri­tory, I’m cer­tain an ex­am­ple is the best way to ex­plain.</p>
<blockquote>
<p><span class="pull-double">“</span>I am go­ing to go tan in the sun.”</p>
</blockquote>
<p>Here we have a sim­ple, English sen­tence. In this case, it is clear the word<span class="push-double"></span> <span class="pull-double">“</span>tan” is be­ing used as verb. The lin­guists in the au­di­ence would point out that it is specif­i­cally in the ﬁrst-per­son fu­ture tense. Consider this sim­i­lar sen­tence:</p>
<blockquote>
<p><span class="pull-double">“</span>I am al­ready very tan, so I will stay in­side.”</p>
</blockquote>
<p>In this sen­tence, the word<span class="push-double"></span> <span class="pull-double">“</span>tan” is be­ing used as an ad­jec­tive. How can we tell?</p>
<p>As in­tel­li­gent hu­mans, some of whom have been speak­ing English their en­tire lives, it is easy for us to de­ter­mine which words are serv­ing which roles. It’s not as easy for a com­puter to do the same. From an al­go­rith­mic stand­point, there are a num­ber of ways to go about it, each with dif­fer­ing lev­els of<span class="push-double"></span> <span class="pull-double">“</span>machine learn­ing” re­quired.</p>
<p>Before this week, Harper pri­mar­ily took a dic­tio­nary-based ap­proach. In short: we ship a<span class="push-double"></span> <span class="pull-double">“</span>dictionary” of English words to the user’s ma­chine and use hash table lookups to de­ter­mine the pos­si­ble roles each word could as­sume. The au­thors to our rule en­gine could then use rudi­men­tary de­duc­tive rea­son­ing to nar­row the pos­si­bil­i­ties down. This strat­egy is re­mark­ably ef­fec­tive and it has scaled to tens of thou­sands of users with sur­pris­ingly few hic­cups.</p>
<p>That said, there are edge-cases and sys­tems (which I’ll cover next week when I dis­cuss chunk­ing) which re­quire ex­treme speci­ﬁcity from <span class="small-caps">POS</span> tags. My mis­sion: im­prove our <span class="small-caps">POS</span> tag­ging to in­crease the con­ﬁ­dence of Harper’s out­put and open the door for more ad­vanced al­go­rithms.</p>
<h2>Why Transformation-based Learning?</h2>
<p>The lit­er­a­ture high­lights four un­der­ly­ing ma­chine learn­ing model strate­gies that seem to work well for <span class="small-caps">POS</span> tag­ging.</p>
<ul>
<li>Hidden Markov Models (probabilistic mod­els that pre­date the mod­ern deep learn­ing era)</li>
<li>Maximum Entropy Models (statistical mod­els closely re­lated to lo­gis­tic re­gres­sion).</li>
<li>Transformer-based Models (which use deep neural net­works)</li>
<li>Transformation-based Rule Models (which are based on learned rules)</li>
</ul>
<p>While I heav­ily con­sid­ered us­ing a neural net­work (either via an <span class="small-caps">HMM</span> or <span class="small-caps">MEM</span>), I dis­carded the tech­nol­ogy for three rea­sons.</p>
<ul>
<li>TRMs are typ­i­cally more ac­cu­rate (barely; mea­sured in ba­sis points).</li>
<li>TRMs are more amenable to ﬁne-tun­ing.</li>
<li>TRMs are ex­cep­tion­ally low-la­tency and can be com­pressed quite small.</li>
</ul>
<p>Transformation-based learn­ing is re­mark­ably sim­ple. It boils down to just four steps:</p>
<ul>
<li>Use a sim­ple, sto­chas­tic model to la­bel your data. This can be as sim­ple as tag­ging each to­ken (or other dis­crete com­po­nent) with that vari­ant’s most com­mon tag. It does­n’t need to su­per ac­cu­rate, just enough to es­tab­lish a base­line.</li>
<li>Identify the er­rors be­tween the tags in your canon­i­cal data and that which pro­duced by your base­line model.</li>
<li>Using a ﬁ­nite list of hu­man-de­ﬁned tem­plates, gen­er­ate can­di­date rules that trans­form the out­put of the base­line model into some­thing else. This is where the term<span class="push-double"></span> <span class="pull-double">“</span>transformation-based” comes from.</li>
<li>Apply each of the can­di­date rules to the base­line mod­el’s out­put. Check if the re­sult is <em>more</em> ac­cu­rate than be­fore. If so, save the rule for fu­ture use.</li>
</ul>
<p>These saved can­di­dates be­come your model.</p>
<h2>POS-Tagging us­ing Transformation-based Learning</h2>
<p>Let’s ap­ply these steps to build a POS-tagging sys­tem.</p>
<p>For our base­line model, we will just as­sign each word in our dataset the most com­mon <span class="small-caps">POS</span> tag as­so­ci­ated with that word. If the word is<span class="push-double"></span> <span class="pull-double">“</span>tan”, we’ll as­sign it’s most com­mon <span class="small-caps">POS</span> tag (verb). It will of­ten be in­cor­rect, but those cases will be han­dled by our rules.</p>
<p>To iden­tify the base­line mod­el’s er­rors, we’ll use an off-the-shelf tree-bank from the Universal Dependencies pro­ject.</p>
<p>Our rule tem­plates will take the form of these <code>PatchCriteria</code>. By ini­tial­iz­ing our can­di­dates with any one of these enum vari­ants and ini­tial­iz­ing the child vari­ables to ran­dom val­ues, we can cover a good num­ber of cases.</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]</span>
<span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">PatchCriteria</span> {
    WordIsTaggedWith {
        <span class="hljs-comment">/// Which token to inspect.</span>
        relative: <span class="hljs-type">isize</span>,
        is_tagged: UPOS,
    },
    AnyWordIsTaggedWith {
        <span class="hljs-comment">/// The farthest relative index to look</span>
        max_relative: <span class="hljs-type">isize</span>,
        is_tagged: UPOS,
    },
    SandwichTaggedWith {
        prev_word_tagged: UPOS,
        post_word_tagged: UPOS,
    },
    WordIs {
        relative: <span class="hljs-type">isize</span>,
        word: <span class="hljs-type">String</span>,
    },
    <span class="hljs-comment">/// Not applicable to the Brill Tagger, only the chunker</span>
    NounPhraseAt {
        is_np: <span class="hljs-type">bool</span>,
        relative: <span class="hljs-type">isize</span>,
    },
    Combined {
        a: <span class="hljs-type">Box</span>&#x3C;PatchCriteria>,
        b: <span class="hljs-type">Box</span>&#x3C;PatchCriteria>,
    },
}
</code></pre>
<p>Finally, we’ll ap­ply each of the hun­dreds of thou­sands of can­di­dates to our tree­bank to see if the re­sult of their trans­for­ma­tions have a lower er­ror rate than the base­line.</p>
<p>Here are a cou­ple of the can­di­dates we found:</p>
<pre><code class="hljs language-json">[
  {
    "from": "PRON",
    "to": "SCONJ",
    "criteria": {
      "Combined": {
        "a": {
          "WordIs": {
            "relative": 0,
            "word": "that"
          }
        },
        "b": {
          "WordIsTaggedWith": {
            "relative": -1,
            "is_tagged": "VERB"
          }
        }
      }
    }
  },
  {
    "from": "PART",
    "to": "ADP",
    "criteria": {
      "Combined": {
        "a": {
          "WordIs": {
            "relative": 1,
            "word": "there"
          }
        },
        "b": {
          "AnyWordIsTaggedWith": {
            "max_relative": -4,
            "is_tagged": "NOUN"
          }
        }
      }
    }
  }
]
</code></pre>
<p>That’s the whole process! With it, I was able to bring our pre­vi­ous ac­cu­racy all the way up to 95% (from 40%) with­out a mean­ing­ful change in lint­ing la­tency or com­piled bi­nary size.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/ctlm.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Harper for Firefox Through Spellbolt]]></title>
            <link>https://elijahpotter.dev/articles/harper_for_firefox_through_spellbolt</link>
            <guid>https://elijahpotter.dev/articles/harper_for_firefox_through_spellbolt</guid>
            <pubDate>Mon, 19 May 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Harper in Firefox Through SpellBolt</h1>
<img src="/images/ford_street.webp" alt="Ford Street, Golden, Colorado">
<p>We de­signed Harper to be the ul­ti­mately portable gram­mar checker, but we’re still work­ing on liv­ing up to that promise.
As the Harper Chrome ex­ten­sion be­comes more ca­pa­ble day-by-day, it has one crit­i­cal weak­ness: it does­n’t sup­port Firefox.
Since Chrome has a much larger mar­ket share, it is­n’t a pri­or­ity at the mo­ment (although we ac­cept con­tri­bu­tions).</p>
<p>This is a won­der­ful ex­am­ple of where the open-source na­ture of the Harper’s core en­gine re­ally shines.
Some­one has taken <a href="https://www.npmjs.com/package/harper.js"><code>harper.js</code></a> and put it into their own ex­ten­sion.
<a href="https://addons.mozilla.org/en-US/firefox/addon/spellbolt/">SpellBolt</a> is a Firefox ex­ten­sion that de­liv­ers Harper’s fan­tas­tic gram­mar check­ing straight into your Firefox browser.
My ini­tial test­ing shows that it’s a solid ex­ten­sion.</p>
<p>I am de­lighted to see peo­ple mak­ing use of the plug­gable ar­chi­tec­ture Harper has adopted, and I can’t wait to see what SpellBolt does next.</p>]]></description>
            <content:encoded><![CDATA[<h1>Harper in Firefox Through SpellBolt</h1>
<img src="/images/ford_street.webp" alt="Ford Street, Golden, Colorado">
<p>We de­signed Harper to be the ul­ti­mately portable gram­mar checker, but we’re still work­ing on liv­ing up to that promise.
As the Harper Chrome ex­ten­sion be­comes more ca­pa­ble day-by-day, it has one crit­i­cal weak­ness: it does­n’t sup­port Firefox.
Since Chrome has a much larger mar­ket share, it is­n’t a pri­or­ity at the mo­ment (although we ac­cept con­tri­bu­tions).</p>
<p>This is a won­der­ful ex­am­ple of where the open-source na­ture of the Harper’s core en­gine re­ally shines.
Some­one has taken <a href="https://www.npmjs.com/package/harper.js"><code>harper.js</code></a> and put it into their own ex­ten­sion.
<a href="https://addons.mozilla.org/en-US/firefox/addon/spellbolt/">SpellBolt</a> is a Firefox ex­ten­sion that de­liv­ers Harper’s fan­tas­tic gram­mar check­ing straight into your Firefox browser.
My ini­tial test­ing shows that it’s a solid ex­ten­sion.</p>
<p>I am de­lighted to see peo­ple mak­ing use of the plug­gable ar­chi­tec­ture Harper has adopted, and I can’t wait to see what SpellBolt does next.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/ford_street.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Quality Is the Most Important Metric]]></title>
            <link>https://elijahpotter.dev/articles/quality_is_the_most_important_metric</link>
            <guid>https://elijahpotter.dev/articles/quality_is_the_most_important_metric</guid>
            <pubDate>Mon, 19 May 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Quality Is the Most Important Metric</h1>
<p>Harper’s Chrome ex­ten­sion con­tin­ues to come along beau­ti­fully. I’m ac­tively work­ing to make it more use­ful and re­spon­sive, slowly crush­ing bugs that I hear about from our users and con­trib­u­tors. I will not go over them here, since our <a href="https://github.com/Automattic/harper/pulls?q=sort%3Aupdated-desc+is%3Apr+is%3Aclosed">closed pull re­quests</a> should speak for them­selves. I would, how­ever, like to rapid-ﬁre some of the small im­prove­ments and ideas about Quality that I’ve been brew­ing up this week.</p>
<img src="/images/guggenheim_2.png" alt="Living in a college town is strange. The whole place gets empty in the summer.">
<h2>Quality</h2>
<p>I ﬁrst heard about big-Q Quality from <em>Zen and the Art of Motorcycle Maintenance</em> by Robert Persig. The book’s length—whose main sub­ject is the idea of Quality—speaks to its com­plex­ity as a topic. It’s a great read, and I’d highly rec­om­mend it.</p>
<p>One im­por­tant point the au­thor makes is that Quality is in­her­ently a hu­man idea. It is hard, if not im­pos­si­ble, to de­ﬁne al­go­rith­mi­cally or on pa­per. When one in­ter­acts with an ob­ject or a sys­tem, they come away with a sense of its Quality, with­out nec­es­sar­ily know­ing ex­actly what led to that sense.</p>
<p>Some ar­gue that in to­day’s world of LLMs and fast-it­er­a­tions, the real value of a good soft­ware en­gi­neer de­rives from their sense of Quality. We know whether a de­sign de­ci­sion or change to the code is worth ex­plor­ing. We know how it will af­fect users. I don’t think this ar­gu­ment is rel­e­vant to the <span class="small-caps">LLM</span> dis­cus­sion at all, but I do agree with it in spirit.</p>
<h2>The Most Important <span class="small-caps">UI</span> Element</h2>
<p>Harper’s Chrome ex­ten­sion has one crit­i­cal <span class="small-caps">UI</span> el­e­ment that user’s should be in­ter­act­ing with more than any other: the sug­ges­tion box.</p>
<table>
<thead>
<tr>
<th align="left">Before</th>
<th align="right">After</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><img src="/images/before_suggestion_box.png" alt="Before the changes to the suggestion box"></td>
<td align="right"><img src="/images/after_suggestion_box.png" alt="After the changes to the suggestion box"></td>
</tr>
</tbody>
</table>
<p>When Harper lo­cates a prob­lem in a user’s text, it un­der­lines it and waits for them to no­tice. When they do, they have the op­tion to click it and re­view Harper’s sug­ges­tion. This is a ﬂow typ­i­cal of most spell and gram­mar-check­ing pro­grams, so you’re likely fa­mil­iar with it. This process makes the con­tents and be­hav­ior of the sug­ges­tion box ex­tremely im­por­tant for user sat­is­fac­tion.</p>
<p>After re­ceiv­ing some feed­back re­lated to the sug­ges­tion box’s vi­sual un­pleas­ant­ness and dif­ﬁ­culty to un­der­stand in­tu­itively, I’ve started mak­ing some mod­i­ﬁ­ca­tions. Nothing dras­tic—I don’t want to con­fuse ex­ist­ing users. I’ve fo­cused on mak­ing it more com­pact and use more vi­sual sto­ry­telling. Now, when a spelling mis­take should be cor­rected, it shows an icon rather than a full<span class="push-double"></span> <span class="pull-double">“</span>Add to dic­tio­nary” la­bel. I’m al­ready get­ting pos­i­tive feed­back about the changes.</p>
<p>All-in, it seems like much of the de­sign phi­los­o­phy of the <span class="small-caps">UI</span> is be­ing pushed fur­ther in the di­rec­tion of Harper’s core tenet: to get out of the way.</p>
<h1>Why Quality is Important</h1>
<img src="/images/chrome_web_store_front_page.png" alt="The Chrome Web Store Front Page">
<p>Quality is im­por­tant for the Chrome ex­ten­sion (more so than other in­te­gra­tions) be­cause of the Chrome Web Store’s front page.</p>
<p>Extensions that get fea­tured on the front page get a sig­nif­i­cant amount of free ad­ver­tis­ing. Millions of users view this page monthly. Getting on the page is guar­an­tee of tens of thou­sands of new users, if not more.</p>
<p>Here’s the rub: you can’t get on the front page by pay­ing for it or by chance. An ex­ten­sion is placed on the front page if and only if it meets a high bar of value and qual­ity. In other words: you have to earn it. This is the es­sen­tial rea­son why I’ve been work­ing so hard on Quality these past few weeks: I’m try­ing to get on the front page.</p>
<p>Most of the steps I’ve taken in this di­rec­tion are small enough to not be worth men­tion­ing here. If you’re cu­ri­ous, feel free to reach out!</p>]]></description>
            <content:encoded><![CDATA[<h1>Quality Is the Most Important Metric</h1>
<p>Harper’s Chrome ex­ten­sion con­tin­ues to come along beau­ti­fully. I’m ac­tively work­ing to make it more use­ful and re­spon­sive, slowly crush­ing bugs that I hear about from our users and con­trib­u­tors. I will not go over them here, since our <a href="https://github.com/Automattic/harper/pulls?q=sort%3Aupdated-desc+is%3Apr+is%3Aclosed">closed pull re­quests</a> should speak for them­selves. I would, how­ever, like to rapid-ﬁre some of the small im­prove­ments and ideas about Quality that I’ve been brew­ing up this week.</p>
<img src="/images/guggenheim_2.png" alt="Living in a college town is strange. The whole place gets empty in the summer.">
<h2>Quality</h2>
<p>I ﬁrst heard about big-Q Quality from <em>Zen and the Art of Motorcycle Maintenance</em> by Robert Persig. The book’s length—whose main sub­ject is the idea of Quality—speaks to its com­plex­ity as a topic. It’s a great read, and I’d highly rec­om­mend it.</p>
<p>One im­por­tant point the au­thor makes is that Quality is in­her­ently a hu­man idea. It is hard, if not im­pos­si­ble, to de­ﬁne al­go­rith­mi­cally or on pa­per. When one in­ter­acts with an ob­ject or a sys­tem, they come away with a sense of its Quality, with­out nec­es­sar­ily know­ing ex­actly what led to that sense.</p>
<p>Some ar­gue that in to­day’s world of LLMs and fast-it­er­a­tions, the real value of a good soft­ware en­gi­neer de­rives from their sense of Quality. We know whether a de­sign de­ci­sion or change to the code is worth ex­plor­ing. We know how it will af­fect users. I don’t think this ar­gu­ment is rel­e­vant to the <span class="small-caps">LLM</span> dis­cus­sion at all, but I do agree with it in spirit.</p>
<h2>The Most Important <span class="small-caps">UI</span> Element</h2>
<p>Harper’s Chrome ex­ten­sion has one crit­i­cal <span class="small-caps">UI</span> el­e­ment that user’s should be in­ter­act­ing with more than any other: the sug­ges­tion box.</p>
<table>
<thead>
<tr>
<th align="left">Before</th>
<th align="right">After</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><img src="/images/before_suggestion_box.png" alt="Before the changes to the suggestion box"></td>
<td align="right"><img src="/images/after_suggestion_box.png" alt="After the changes to the suggestion box"></td>
</tr>
</tbody>
</table>
<p>When Harper lo­cates a prob­lem in a user’s text, it un­der­lines it and waits for them to no­tice. When they do, they have the op­tion to click it and re­view Harper’s sug­ges­tion. This is a ﬂow typ­i­cal of most spell and gram­mar-check­ing pro­grams, so you’re likely fa­mil­iar with it. This process makes the con­tents and be­hav­ior of the sug­ges­tion box ex­tremely im­por­tant for user sat­is­fac­tion.</p>
<p>After re­ceiv­ing some feed­back re­lated to the sug­ges­tion box’s vi­sual un­pleas­ant­ness and dif­ﬁ­culty to un­der­stand in­tu­itively, I’ve started mak­ing some mod­i­ﬁ­ca­tions. Nothing dras­tic—I don’t want to con­fuse ex­ist­ing users. I’ve fo­cused on mak­ing it more com­pact and use more vi­sual sto­ry­telling. Now, when a spelling mis­take should be cor­rected, it shows an icon rather than a full<span class="push-double"></span> <span class="pull-double">“</span>Add to dic­tio­nary” la­bel. I’m al­ready get­ting pos­i­tive feed­back about the changes.</p>
<p>All-in, it seems like much of the de­sign phi­los­o­phy of the <span class="small-caps">UI</span> is be­ing pushed fur­ther in the di­rec­tion of Harper’s core tenet: to get out of the way.</p>
<h1>Why Quality is Important</h1>
<img src="/images/chrome_web_store_front_page.png" alt="The Chrome Web Store Front Page">
<p>Quality is im­por­tant for the Chrome ex­ten­sion (more so than other in­te­gra­tions) be­cause of the Chrome Web Store’s front page.</p>
<p>Extensions that get fea­tured on the front page get a sig­nif­i­cant amount of free ad­ver­tis­ing. Millions of users view this page monthly. Getting on the page is guar­an­tee of tens of thou­sands of new users, if not more.</p>
<p>Here’s the rub: you can’t get on the front page by pay­ing for it or by chance. An ex­ten­sion is placed on the front page if and only if it meets a high bar of value and qual­ity. In other words: you have to earn it. This is the es­sen­tial rea­son why I’ve been work­ing so hard on Quality these past few weeks: I’m try­ing to get on the front page.</p>
<p>Most of the steps I’ve taken in this di­rec­tion are small enough to not be worth men­tion­ing here. If you’re cu­ri­ous, feel free to reach out!</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/guggenheim_2.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Integration Testing Thousands of Sites with Playwright]]></title>
            <link>https://elijahpotter.dev/articles/integration_testing_thousands_of_sites_with_playwright</link>
            <guid>https://elijahpotter.dev/articles/integration_testing_thousands_of_sites_with_playwright</guid>
            <pubDate>Fri, 16 May 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Integration Testing Thousands of Websites with Playwright</h1>
<p>As I’ve ac­counted and dis­cussed in pre­vi­ous posts, one of the hard­est prob­lems <a href="https://github.com/automattic/harper">Harper faces</a> is that of the great di­ver­sity of the in­ter­net. There are a great va­ri­ety text ed­i­tors on the web, each used in a dif­fer­ent con­text. That’s a beau­ti­ful thing, but un­for­tu­nately our users ex­pect Harper to work in all of these places seam­lessly. I sup­pose you can view this post as a third part in this se­ries where I talk about sup­port­ing thou­sands of web­sites for po­ten­tially mil­lions of users (with zero server costs, I might add).</p>
<p>This jour­ney started in Discord (as many do), with a sat­is­ﬁed user re­port­ing an is­sue. I’m quite for­tu­nate: they were tech­ni­cal. They’re re­port was de­tailed and in­cluded some ini­tial spec­u­la­tion on what the root cause could be.</p>
<img src="/images/why_so_serious_screenshot.png" alt="A small part of a larger conversion about the problem at hand.">
<p>While the ac­tual un­der­ly­ing prob­lem was com­plex and dif­ﬁ­cult to ﬁx, that is not what this post is about. It is about Harper’s strat­egy for do­ing end-to-end test­ing on the many sites we sup­port.</p>
<p>It’s rel­e­vant be­cause this was a prob­lem that could have been dis­cov­ered through end-to-end test­ing. Since we have a di­verse set of users al­ready, they found the is­sue swiftly. That is far from ideal. Our test­ing suite should catch these prob­lems <em>be­fore the <span class="small-caps">PR</span> is merged.</em></p>
<h2>Why Playwright?</h2>
<p>“<a href="https://playwright.dev/">Playwright</a> enables re­li­able end-to-end test­ing for mod­ern web apps.” At least—that’s what their site claims Playwright can do. I’m not sure if it lives up to this claim of re­li­a­bil­ity, at least not yet.</p>
<p>The de­ci­sion to use Playwright over al­ter­na­tive choices came down to a few key points:</p>
<ul>
<li>It’s quite pol­ished and well sup­ported (Microsoft seems to be the main player).</li>
<li>While it is more com­plex to load a Chrome ex­ten­sion in Playwright than Puppeteer, I am also given a lot more con­trol.</li>
<li>We’re al­ready us­ing it for other in­te­gra­tions (but not for end-to-end tests).</li>
</ul>
<h2>The Game Plan</h2>
<p>My goal is to build up a com­pre­hen­sive-enough test suite that I can catch in­te­gra­tion prob­lems in for­eign text ed­i­tors be­fore I merge PRs for logic that in­ter­acts with them.</p>
<p>Step one was to get Playwright in­stalled and run­ning on my ma­chine, re­pro­ducible with npm. Fortunately for me, this was as sim­ple as: <code>pnpm create playwright</code>. Step two was a lit­tle more com­plex: get our ex­ten­sion loaded within the head­less browser. I found this could be done by over­rid­ing Playwright’s de­fault Chrome prop­er­ties, in­struct­ing it to only in­stall the Harper plu­gin and noth­ing else:</p>
<pre><code class="hljs language-typescript">export const test = base.extend&#x3C;{
	context: BrowserContext;
	extensionId: string;
}>({
	// biome-ignore lint/correctness/noEmptyPattern: it's by Playwright. Explanation not provided.
	context: async ({}, use) => {
		const pathToExtension = path.join(import.meta.dirname, '../build');
		console.log(`Loading extension from ${pathToExtension}`);
		const context = await chromium.launchPersistentContext('', {
			channel: 'chromium',
			args: [
				`--disable-extensions-except=${pathToExtension}`,
				`--load-extension=${pathToExtension}`,
			],
		});
		await use(context);
		await context.close();
	},
	extensionId: async ({ context }, use) => {
		let [background] = context.serviceWorkers();
		if (!background) background = await context.waitForEvent('serviceworker');

		const extensionId = background.url().split('/')[2];
		await use(extensionId);
	},
});
export const expect = test.expect;
</code></pre>
<p>From there, it was pretty triv­ial to build out as­ser­tions and tools for in­ter­act­ing with ba­sic el­e­ments for the spe­ciﬁc text ed­i­tor I was in­ter­ested in (<a href="https://www.slatejs.org/examples/richtext">Slate</a>).</p>
<h2>The Cool Part</h2>
<p>Most text ed­i­tors on the web ad­ver­tise them­selves in the <span class="small-caps">DOM</span>, usu­ally with a spe­cial at­tribute like <code>data-lexical-editor="true"</code> or <code>data-slate-editor="true"</code>. This even hap­pens on world-class sites like LinkedIn or Instagram. I won­der if I can use this for some­thing?</p>
<p>I be­lieve this con­sis­tency in pro­duc­tion code is in­ten­tional. Making our tests eas­ier to write must be a side-ef­fect of mak­ing the ed­i­tor au­thor’s tests eas­ier to write.</p>
<p>This is great news for me. With just a few tweaks, I can use the same code to test Harper on Discord, Medium, Notion, Desmos, Asana—you get the point. Since they use just a small set of rich text ed­i­tors (which come pre-tagged), I can gen­er­ate au­to­mated tests to de­ter­mine whether Harper works prop­erly on their sites.</p>
<h2>The Not-So-Cool Part</h2>
<p>The bad news: this process is slow. Each page must be fetched from the net­work and op­er­ated on like a user. We might be able to ﬁx the ﬁrst prob­lem, but the sec­ond is in­ter­minable. My ini­tial ex­per­i­ments put the run­time of each test case around thirty sec­onds. If I’m test­ing hun­dreds or thou­sands of sites, this is a real prob­lem.</p>
<p>For the time be­ing, I’ll have these tests be their own work­ﬂow in GitHub Actions and only run them when the ex­ten­sion code changes. I don’t see peo­ple other than my­self mess­ing with this code too much, so I am not wor­ried.</p>
<h2>What Does Testing Thousands of Sites Look Like?</h2>
<p>I’ll ad­mit, I’m not quite to the<span class="push-double"></span> <span class="pull-double">“</span>thousands of sites” ter­ri­tory just yet. The most frus­trat­ing part is get­ting started. Now that I’ve got a cou­ple of sites un­der my belt, with the tools ready and able&nbsp;, the next thou­sand will be a lot eas­ier.</p>]]></description>
            <content:encoded><![CDATA[<h1>Integration Testing Thousands of Websites with Playwright</h1>
<p>As I’ve ac­counted and dis­cussed in pre­vi­ous posts, one of the hard­est prob­lems <a href="https://github.com/automattic/harper">Harper faces</a> is that of the great di­ver­sity of the in­ter­net. There are a great va­ri­ety text ed­i­tors on the web, each used in a dif­fer­ent con­text. That’s a beau­ti­ful thing, but un­for­tu­nately our users ex­pect Harper to work in all of these places seam­lessly. I sup­pose you can view this post as a third part in this se­ries where I talk about sup­port­ing thou­sands of web­sites for po­ten­tially mil­lions of users (with zero server costs, I might add).</p>
<p>This jour­ney started in Discord (as many do), with a sat­is­ﬁed user re­port­ing an is­sue. I’m quite for­tu­nate: they were tech­ni­cal. They’re re­port was de­tailed and in­cluded some ini­tial spec­u­la­tion on what the root cause could be.</p>
<img src="/images/why_so_serious_screenshot.png" alt="A small part of a larger conversion about the problem at hand.">
<p>While the ac­tual un­der­ly­ing prob­lem was com­plex and dif­ﬁ­cult to ﬁx, that is not what this post is about. It is about Harper’s strat­egy for do­ing end-to-end test­ing on the many sites we sup­port.</p>
<p>It’s rel­e­vant be­cause this was a prob­lem that could have been dis­cov­ered through end-to-end test­ing. Since we have a di­verse set of users al­ready, they found the is­sue swiftly. That is far from ideal. Our test­ing suite should catch these prob­lems <em>be­fore the <span class="small-caps">PR</span> is merged.</em></p>
<h2>Why Playwright?</h2>
<p>“<a href="https://playwright.dev/">Playwright</a> enables re­li­able end-to-end test­ing for mod­ern web apps.” At least—that’s what their site claims Playwright can do. I’m not sure if it lives up to this claim of re­li­a­bil­ity, at least not yet.</p>
<p>The de­ci­sion to use Playwright over al­ter­na­tive choices came down to a few key points:</p>
<ul>
<li>It’s quite pol­ished and well sup­ported (Microsoft seems to be the main player).</li>
<li>While it is more com­plex to load a Chrome ex­ten­sion in Playwright than Puppeteer, I am also given a lot more con­trol.</li>
<li>We’re al­ready us­ing it for other in­te­gra­tions (but not for end-to-end tests).</li>
</ul>
<h2>The Game Plan</h2>
<p>My goal is to build up a com­pre­hen­sive-enough test suite that I can catch in­te­gra­tion prob­lems in for­eign text ed­i­tors be­fore I merge PRs for logic that in­ter­acts with them.</p>
<p>Step one was to get Playwright in­stalled and run­ning on my ma­chine, re­pro­ducible with npm. Fortunately for me, this was as sim­ple as: <code>pnpm create playwright</code>. Step two was a lit­tle more com­plex: get our ex­ten­sion loaded within the head­less browser. I found this could be done by over­rid­ing Playwright’s de­fault Chrome prop­er­ties, in­struct­ing it to only in­stall the Harper plu­gin and noth­ing else:</p>
<pre><code class="hljs language-typescript">export const test = base.extend&#x3C;{
	context: BrowserContext;
	extensionId: string;
}>({
	// biome-ignore lint/correctness/noEmptyPattern: it's by Playwright. Explanation not provided.
	context: async ({}, use) => {
		const pathToExtension = path.join(import.meta.dirname, '../build');
		console.log(`Loading extension from ${pathToExtension}`);
		const context = await chromium.launchPersistentContext('', {
			channel: 'chromium',
			args: [
				`--disable-extensions-except=${pathToExtension}`,
				`--load-extension=${pathToExtension}`,
			],
		});
		await use(context);
		await context.close();
	},
	extensionId: async ({ context }, use) => {
		let [background] = context.serviceWorkers();
		if (!background) background = await context.waitForEvent('serviceworker');

		const extensionId = background.url().split('/')[2];
		await use(extensionId);
	},
});
export const expect = test.expect;
</code></pre>
<p>From there, it was pretty triv­ial to build out as­ser­tions and tools for in­ter­act­ing with ba­sic el­e­ments for the spe­ciﬁc text ed­i­tor I was in­ter­ested in (<a href="https://www.slatejs.org/examples/richtext">Slate</a>).</p>
<h2>The Cool Part</h2>
<p>Most text ed­i­tors on the web ad­ver­tise them­selves in the <span class="small-caps">DOM</span>, usu­ally with a spe­cial at­tribute like <code>data-lexical-editor="true"</code> or <code>data-slate-editor="true"</code>. This even hap­pens on world-class sites like LinkedIn or Instagram. I won­der if I can use this for some­thing?</p>
<p>I be­lieve this con­sis­tency in pro­duc­tion code is in­ten­tional. Making our tests eas­ier to write must be a side-ef­fect of mak­ing the ed­i­tor au­thor’s tests eas­ier to write.</p>
<p>This is great news for me. With just a few tweaks, I can use the same code to test Harper on Discord, Medium, Notion, Desmos, Asana—you get the point. Since they use just a small set of rich text ed­i­tors (which come pre-tagged), I can gen­er­ate au­to­mated tests to de­ter­mine whether Harper works prop­erly on their sites.</p>
<h2>The Not-So-Cool Part</h2>
<p>The bad news: this process is slow. Each page must be fetched from the net­work and op­er­ated on like a user. We might be able to ﬁx the ﬁrst prob­lem, but the sec­ond is in­ter­minable. My ini­tial ex­per­i­ments put the run­time of each test case around thirty sec­onds. If I’m test­ing hun­dreds or thou­sands of sites, this is a real prob­lem.</p>
<p>For the time be­ing, I’ll have these tests be their own work­ﬂow in GitHub Actions and only run them when the ex­ten­sion code changes. I don’t see peo­ple other than my­self mess­ing with this code too much, so I am not wor­ried.</p>
<h2>What Does Testing Thousands of Sites Look Like?</h2>
<p>I’ll ad­mit, I’m not quite to the<span class="push-double"></span> <span class="pull-double">“</span>thousands of sites” ter­ri­tory just yet. The most frus­trat­ing part is get­ting started. Now that I’ve got a cou­ple of sites un­der my belt, with the tools ready and able&nbsp;, the next thou­sand will be a lot eas­ier.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Bypassing Hallucinations in Llms]]></title>
            <link>https://elijahpotter.dev/articles/bypassing_hallucinations_in_llms</link>
            <guid>https://elijahpotter.dev/articles/bypassing_hallucinations_in_llms</guid>
            <pubDate>Wed, 14 May 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Bypassing Hallucinations in LLMs</h1>
<img src="/images/bike_sidewalk.webp" alt="My bike broke down. Luckily, I had my camera">
<p>Before I get too deep, I just want to get it out of the way: OpenAI’s o3 model is im­pres­sive.
With its tool use and web search ca­pa­bil­i­ties, it can do a lot more than most of­fer­ings out there.</p>
<p>That said, al­though I’ve found it to be quite a ca­pa­ble coder, I still don’t trust it with any­thing im­por­tant.
Once or twice, I’ve in­structed it to out­line a React com­po­nent, only to rewrite most of it my­self.</p>
<p>I also don’t trust its fac­tual ac­cu­racy at all.
Af­ter hal­lu­ci­nat­ing a camp­ground and sev­eral en­tire web APIs, I can’t quite be­lieve any­thing it says. Not that I’ve trusted any model that came be­fore.</p>
<p>That said, there is one thing it is in­cred­i­bly use­ful for: <strong>ﬁnd­ing canon­i­cal doc­u­men­ta­tion for com­plex sub­jects.</strong></p>
<p>I’ve found the great­est suc­cess, per­son­ally and pro­fes­sion­ally, when I am work­ing with the most con­crete and orig­i­nal source of in­for­ma­tion avail­able.
When work­ing with the web, the most canon­i­cal source is the <span class="small-caps">W3C</span> spec.
When work­ing with com­pil­ers, it’s The Dragon Book.
When re­search­ing the ins and outs of GNU/Linux sys­tems, it’s the <code>man</code> page.</p>
<p>This can’t be a novel con­cept.
You (the reader) must see things the same way I do.
Even LinkedIn seems to agree that base-truth doc­u­men­ta­tion is where we should be get­ting our in­for­ma­tion.</p>
<p>I’ve al­ways won­dered: if the <span class="small-caps">W3C</span> spec is the best place to ﬁnd in­for­ma­tion about the web, why is­n’t it the ﬁrst re­sult on Google?
Why, af­ter all these years, is W3Schools <em>still the ﬁrst re­sult 90% of the time?</em></p>
<p>I use o3 to ﬁnd canon­i­cal sources of in­for­ma­tion.</p>
<p>I was re­cently look­ing to im­prove the dy­namic range in my cam­era, but do so be­fore my post-pro­cess­ing step. By im­prov­ing dy­namic range in-cam­era, I can avoid the pit­falls of cer­tain kinds of com­pres­sion. I asked o3:<span class="push-double"></span> <span class="pull-double">“</span>ﬁnd me the canon­i­cal guide for im­prov­ing dy­namic range on my <span class="small-caps">D7100</span> from the most au­thor­i­ta­tive source.”</p>
<p>I learnt more from the re­sult­ing guide (which was hosted on the Nikon web­site, <span class="small-caps">NOT</span> ChatGPT) than the last three years of shoot­ing, com­bined.</p>]]></description>
            <content:encoded><![CDATA[<h1>Bypassing Hallucinations in LLMs</h1>
<img src="/images/bike_sidewalk.webp" alt="My bike broke down. Luckily, I had my camera">
<p>Before I get too deep, I just want to get it out of the way: OpenAI’s o3 model is im­pres­sive.
With its tool use and web search ca­pa­bil­i­ties, it can do a lot more than most of­fer­ings out there.</p>
<p>That said, al­though I’ve found it to be quite a ca­pa­ble coder, I still don’t trust it with any­thing im­por­tant.
Once or twice, I’ve in­structed it to out­line a React com­po­nent, only to rewrite most of it my­self.</p>
<p>I also don’t trust its fac­tual ac­cu­racy at all.
Af­ter hal­lu­ci­nat­ing a camp­ground and sev­eral en­tire web APIs, I can’t quite be­lieve any­thing it says. Not that I’ve trusted any model that came be­fore.</p>
<p>That said, there is one thing it is in­cred­i­bly use­ful for: <strong>ﬁnd­ing canon­i­cal doc­u­men­ta­tion for com­plex sub­jects.</strong></p>
<p>I’ve found the great­est suc­cess, per­son­ally and pro­fes­sion­ally, when I am work­ing with the most con­crete and orig­i­nal source of in­for­ma­tion avail­able.
When work­ing with the web, the most canon­i­cal source is the <span class="small-caps">W3C</span> spec.
When work­ing with com­pil­ers, it’s The Dragon Book.
When re­search­ing the ins and outs of GNU/Linux sys­tems, it’s the <code>man</code> page.</p>
<p>This can’t be a novel con­cept.
You (the reader) must see things the same way I do.
Even LinkedIn seems to agree that base-truth doc­u­men­ta­tion is where we should be get­ting our in­for­ma­tion.</p>
<p>I’ve al­ways won­dered: if the <span class="small-caps">W3C</span> spec is the best place to ﬁnd in­for­ma­tion about the web, why is­n’t it the ﬁrst re­sult on Google?
Why, af­ter all these years, is W3Schools <em>still the ﬁrst re­sult 90% of the time?</em></p>
<p>I use o3 to ﬁnd canon­i­cal sources of in­for­ma­tion.</p>
<p>I was re­cently look­ing to im­prove the dy­namic range in my cam­era, but do so be­fore my post-pro­cess­ing step. By im­prov­ing dy­namic range in-cam­era, I can avoid the pit­falls of cer­tain kinds of com­pres­sion. I asked o3:<span class="push-double"></span> <span class="pull-double">“</span>ﬁnd me the canon­i­cal guide for im­prov­ing dy­namic range on my <span class="small-caps">D7100</span> from the most au­thor­i­ta­tive source.”</p>
<p>I learnt more from the re­sult­ing guide (which was hosted on the Nikon web­site, <span class="small-caps">NOT</span> ChatGPT) than the last three years of shoot­ing, com­bined.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Putting Harper in Your Browser]]></title>
            <link>https://elijahpotter.dev/articles/putting_harper_in_your_browser</link>
            <guid>https://elijahpotter.dev/articles/putting_harper_in_your_browser</guid>
            <pubDate>Fri, 02 May 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Putting Harper in Your Browser</h1>
<p>When our users <a href="https://writewithharper.com">in­stall Harper</a>, they should ex­pect it to work any­where they do.
Whether they’re writ­ing up a blog post in WordPress, leav­ing a com­ment on Facebook, or mes­sag­ing a loved one on WhatsApp, Harper should be there.
Harper’s core is ex­tremely portable and it can run pretty much any­where Rust can, so what’s the big deal?</p>
<p>Why can’t we just run Harper in the browser through a web ex­ten­sion?</p>
<h2>Running Locally</h2>
<p>There’s a sin­gle com­plaint that I hear over and over again from peo­ple who use Grammarly or LanguageTool: they are both slow as mo­lasses.
The process of writ­ing has slowly evolved to be more com­plex than it needs to be.
With these tools, it writ­ing looks like:</p>
<ol>
<li>Write a sen­tence.</li>
<li>Wait for the gram­mar checker to run (which takes as many as four sec­onds).</li>
<li>Fix the mis­takes you made.</li>
<li>Go back to step one.</li>
</ol>
<p>The whole process re­minds me of the <a href="https://thomasvogelaar.me/posts/the-copilot-pause/">copi­lot pause</a>.
This is part of why Harper is bet­ter than these other tools: it does­n’t stop you from writ­ing at the speed of thought.
Our most ar­dent users tell us this all the time: it feels great to <strong>just write</strong>, er­ror free.</p>
<h4>How do we de­liver gram­mar check­ing so quickly?</h4>
<p>Instead of host­ing huge Java code­bases in the cloud, we ship our soft­ware straight to the user’s de­vice.
Since there’s no net­work re­quest in­volved, we’re able to put pix­els on the screen faster than any­one else.
That’s not to men­tion the pri­vacy im­pli­ca­tions.</p>
<h4>Running Harper’s en­gine lo­cally in the browser pre­sented some tech­ni­cal chal­lenges.</h4>
<p>I’m quite proud that our JavaScript li­brary can be in­stalled as sim­ply as <code>npm install harper.js</code>.
In or­der for that to work as well as it does, I needed to de­velop a sys­tem for:</p>
<ol>
<li>Compiling our en­gine to WebAssembly.</li>
<li>Shipping that en­gine to the browser.</li>
<li>Instantiating the WebAssembly code.</li>
<li>Build out the boil­er­plate nec­es­sary to make it feel na­tive.</li>
</ol>
<p>Steps one and four were easy.
I just slapped <code>#[wasm_bindgen]</code> tags on a Rust li­brary and put on a pot of cof­fee.
Steam­ing cof­fee is vi­tal for writ­ing te­dious JavaScript.</p>
<p>Steps two and three, how­ever, were a lit­tle more dif­ﬁ­cult.
The lat­est it­er­a­tion of Google’s ex­ten­sion stan­dard, Manifest <span class="small-caps">V3</span>, places some heavy-handed rules on how ex­e­cutable code could be loaded.
I won’t bore you with the de­tails here.
Know that I spent many hours in JavaScript bundler hell.</p>
<h2>Running Everywhere</h2>
<p>Harper, nascent as it is, has the great­est mar­ket op­por­tu­nity in the browser.
Over 3.5 <em>bil­lion</em> peo­ple use Chrome on a weekly ba­sis.
The plu­ral­ity of knowl­edge work­ers spend most of their wak­ing mo­ments (as crush­ing as it sounds) in a web browser.
Half of the time spent at desks to­day is spent writ­ing.</p>
<p>In or­der to ad­dress this mar­ket seg­ment, we need a Chrome ex­ten­sion.
To lint text in the browser, I need a way to:</p>
<ol>
<li>Cleanly read text from in­put ﬁelds.</li>
<li>Locate the pixel co­or­di­nates of gram­mat­i­cal er­rors.</li>
<li>Render sug­ges­tions in pop­ups.</li>
<li>Cleanly re­place text in in­put ﬁelds when a sug­ges­tion is se­lected.</li>
</ol>
<img src="/images/harper_chrome_ext_2.png" alt="An example of Harper&#x27;s suggestion box.">
<h4>Reading and Writing Text is Hard</h4>
<p>The web may have stan­dards, but there is noth­ing stan­dard about it.
The<span class="push-double"></span> <span class="pull-double">“</span>standard” way to in­put text is with a <code>&#x3C;textarea /></code> el­e­ment.
Even so, most high-traf­ﬁc sites im­ple­ment their own text ed­i­tors from scratch, us­ing <code>&#x3C;div contenteditable="true" /></code> as a base.
Each of these cases re­quired spe­cial care.</p>
<h3><code>&#x3C;textarea /></code></h3>
<p><code>&#x3C;textarea /></code>s are hard for one rea­son: it is dif­ﬁ­cult to get a good un­der­stand­ing of what they look like.
I can ob­tain their con­tent with <code>input.value</code>, but I can’t di­rectly in­fer the pixel co­or­di­nates of gram­mat­i­cal er­rors in­side them.</p>
<p>When the Harper Chrome Extension is of­fered an <code>&#x3C;input /></code> or <code>&#x3C;textarea /></code> el­e­ment to an­a­lyze, here’s what it does.</p>
<ol>
<li>Creates a new <code>&#x3C;div /></code>.</li>
<li>Copies all styles from the pro­vided el­e­ment onto the <code>&#x3C;div /></code>.</li>
<li>Using <code>position: absolute;</code>, it moves this <code>&#x3C;div /></code> di­rectly on top of the pro­vided el­e­ment.</li>
<li>Copies the con­tent of the pro­vided el­e­ment into the <code>&#x3C;div /></code>.</li>
<li>Uses the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Range">Range <span class="small-caps">API</span></a> to turn the text in­dices emit­ted by Harper’s en­gine into pixel co­or­di­nates on the <code>&#x3C;div /></code>.</li>
</ol>
<p>Using this mir­ror­ing strat­egy works, but has per­for­mance im­pli­ca­tions and ad­di­tional com­plex­ity to han­dle scrolling within the el­e­ment.</p>
<h3><code>&#x3C;div contenteditable="true" /></code></h3>
<p>Since el­e­ments in a <code>contenteditable</code> text ed­i­tor (like Trix, Lexical, Gutenberg, etc.) ac­tu­ally ex­ist in the <span class="small-caps">DOM</span>, I can just use the <code>Range API</code> to get pixel co­or­di­nates.
The trou­ble this time comes when I try to write a sug­ges­tion back into the ed­i­tor.</p>
<p>Most doc­u­men­ta­tion I could ﬁnd sug­gests that you:</p>
<ol>
<li>Select the con­tent of the el­e­ment you wish to edit.
This can be done us­ing <code>window.getSelection().addRange()</code>.</li>
<li>Call <code>document.execCommand('insertText', null, "YOUR TEXT")</code>.</li>
</ol>
<p>Much of this doc­u­men­ta­tion ac­knowl­edges that <code>document.execCommand</code> is dep­re­cated, but in­structs you to use it any­way.</p>
<p>This is bad ad­vice. <strong>Do not do this.</strong>
I spent an em­bar­rass­ing amount of time try­ing to get it to work con­sis­tently.</p>
<p>The bet­ter way to re­place text pro­gram­mat­i­cally comes di­rectly from the <span class="small-caps">W3C</span> stan­dard:</p>
<ol>
<li>Manually edit the <span class="small-caps">DOM</span> in the fash­ion out­lined by the sug­ges­tion cho­sen by the user.</li>
<li>Fire in­put events to in­struct <span class="small-caps">WYSIWYG</span> ed­i­tors to syn­chro­nize their in­ter­nal state to the <span class="small-caps">DOM</span>.</li>
</ol>
<p>I’ll ad­mit that this is an over­sim­pli­ﬁ­ca­tion.
Much of the com­plex­ity here lays in de­ter­min­ing which <span class="small-caps">DOM</span> nodes to edit or ﬁre events on.</p>
<h2>Read the Source of Truth</h2>
<p>If you take one thing away from this post, it should be this: <strong>al­ways read from the source of truth.</strong>
There is a lot of faulty in­for­ma­tion out there, es­pe­cially when it comes to cre­at­ing com­plex in­ter­ac­tions with opaque sys­tems.
If there is a source of truth, read it.
It may look in­tim­i­dat­ing or seem un­nec­es­sar­ily ver­bose.
It is that way for a rea­son.</p>]]></description>
            <content:encoded><![CDATA[<h1>Putting Harper in Your Browser</h1>
<p>When our users <a href="https://writewithharper.com">in­stall Harper</a>, they should ex­pect it to work any­where they do.
Whether they’re writ­ing up a blog post in WordPress, leav­ing a com­ment on Facebook, or mes­sag­ing a loved one on WhatsApp, Harper should be there.
Harper’s core is ex­tremely portable and it can run pretty much any­where Rust can, so what’s the big deal?</p>
<p>Why can’t we just run Harper in the browser through a web ex­ten­sion?</p>
<h2>Running Locally</h2>
<p>There’s a sin­gle com­plaint that I hear over and over again from peo­ple who use Grammarly or LanguageTool: they are both slow as mo­lasses.
The process of writ­ing has slowly evolved to be more com­plex than it needs to be.
With these tools, it writ­ing looks like:</p>
<ol>
<li>Write a sen­tence.</li>
<li>Wait for the gram­mar checker to run (which takes as many as four sec­onds).</li>
<li>Fix the mis­takes you made.</li>
<li>Go back to step one.</li>
</ol>
<p>The whole process re­minds me of the <a href="https://thomasvogelaar.me/posts/the-copilot-pause/">copi­lot pause</a>.
This is part of why Harper is bet­ter than these other tools: it does­n’t stop you from writ­ing at the speed of thought.
Our most ar­dent users tell us this all the time: it feels great to <strong>just write</strong>, er­ror free.</p>
<h4>How do we de­liver gram­mar check­ing so quickly?</h4>
<p>Instead of host­ing huge Java code­bases in the cloud, we ship our soft­ware straight to the user’s de­vice.
Since there’s no net­work re­quest in­volved, we’re able to put pix­els on the screen faster than any­one else.
That’s not to men­tion the pri­vacy im­pli­ca­tions.</p>
<h4>Running Harper’s en­gine lo­cally in the browser pre­sented some tech­ni­cal chal­lenges.</h4>
<p>I’m quite proud that our JavaScript li­brary can be in­stalled as sim­ply as <code>npm install harper.js</code>.
In or­der for that to work as well as it does, I needed to de­velop a sys­tem for:</p>
<ol>
<li>Compiling our en­gine to WebAssembly.</li>
<li>Shipping that en­gine to the browser.</li>
<li>Instantiating the WebAssembly code.</li>
<li>Build out the boil­er­plate nec­es­sary to make it feel na­tive.</li>
</ol>
<p>Steps one and four were easy.
I just slapped <code>#[wasm_bindgen]</code> tags on a Rust li­brary and put on a pot of cof­fee.
Steam­ing cof­fee is vi­tal for writ­ing te­dious JavaScript.</p>
<p>Steps two and three, how­ever, were a lit­tle more dif­ﬁ­cult.
The lat­est it­er­a­tion of Google’s ex­ten­sion stan­dard, Manifest <span class="small-caps">V3</span>, places some heavy-handed rules on how ex­e­cutable code could be loaded.
I won’t bore you with the de­tails here.
Know that I spent many hours in JavaScript bundler hell.</p>
<h2>Running Everywhere</h2>
<p>Harper, nascent as it is, has the great­est mar­ket op­por­tu­nity in the browser.
Over 3.5 <em>bil­lion</em> peo­ple use Chrome on a weekly ba­sis.
The plu­ral­ity of knowl­edge work­ers spend most of their wak­ing mo­ments (as crush­ing as it sounds) in a web browser.
Half of the time spent at desks to­day is spent writ­ing.</p>
<p>In or­der to ad­dress this mar­ket seg­ment, we need a Chrome ex­ten­sion.
To lint text in the browser, I need a way to:</p>
<ol>
<li>Cleanly read text from in­put ﬁelds.</li>
<li>Locate the pixel co­or­di­nates of gram­mat­i­cal er­rors.</li>
<li>Render sug­ges­tions in pop­ups.</li>
<li>Cleanly re­place text in in­put ﬁelds when a sug­ges­tion is se­lected.</li>
</ol>
<img src="/images/harper_chrome_ext_2.png" alt="An example of Harper&#x27;s suggestion box.">
<h4>Reading and Writing Text is Hard</h4>
<p>The web may have stan­dards, but there is noth­ing stan­dard about it.
The<span class="push-double"></span> <span class="pull-double">“</span>standard” way to in­put text is with a <code>&#x3C;textarea /></code> el­e­ment.
Even so, most high-traf­ﬁc sites im­ple­ment their own text ed­i­tors from scratch, us­ing <code>&#x3C;div contenteditable="true" /></code> as a base.
Each of these cases re­quired spe­cial care.</p>
<h3><code>&#x3C;textarea /></code></h3>
<p><code>&#x3C;textarea /></code>s are hard for one rea­son: it is dif­ﬁ­cult to get a good un­der­stand­ing of what they look like.
I can ob­tain their con­tent with <code>input.value</code>, but I can’t di­rectly in­fer the pixel co­or­di­nates of gram­mat­i­cal er­rors in­side them.</p>
<p>When the Harper Chrome Extension is of­fered an <code>&#x3C;input /></code> or <code>&#x3C;textarea /></code> el­e­ment to an­a­lyze, here’s what it does.</p>
<ol>
<li>Creates a new <code>&#x3C;div /></code>.</li>
<li>Copies all styles from the pro­vided el­e­ment onto the <code>&#x3C;div /></code>.</li>
<li>Using <code>position: absolute;</code>, it moves this <code>&#x3C;div /></code> di­rectly on top of the pro­vided el­e­ment.</li>
<li>Copies the con­tent of the pro­vided el­e­ment into the <code>&#x3C;div /></code>.</li>
<li>Uses the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Range">Range <span class="small-caps">API</span></a> to turn the text in­dices emit­ted by Harper’s en­gine into pixel co­or­di­nates on the <code>&#x3C;div /></code>.</li>
</ol>
<p>Using this mir­ror­ing strat­egy works, but has per­for­mance im­pli­ca­tions and ad­di­tional com­plex­ity to han­dle scrolling within the el­e­ment.</p>
<h3><code>&#x3C;div contenteditable="true" /></code></h3>
<p>Since el­e­ments in a <code>contenteditable</code> text ed­i­tor (like Trix, Lexical, Gutenberg, etc.) ac­tu­ally ex­ist in the <span class="small-caps">DOM</span>, I can just use the <code>Range API</code> to get pixel co­or­di­nates.
The trou­ble this time comes when I try to write a sug­ges­tion back into the ed­i­tor.</p>
<p>Most doc­u­men­ta­tion I could ﬁnd sug­gests that you:</p>
<ol>
<li>Select the con­tent of the el­e­ment you wish to edit.
This can be done us­ing <code>window.getSelection().addRange()</code>.</li>
<li>Call <code>document.execCommand('insertText', null, "YOUR TEXT")</code>.</li>
</ol>
<p>Much of this doc­u­men­ta­tion ac­knowl­edges that <code>document.execCommand</code> is dep­re­cated, but in­structs you to use it any­way.</p>
<p>This is bad ad­vice. <strong>Do not do this.</strong>
I spent an em­bar­rass­ing amount of time try­ing to get it to work con­sis­tently.</p>
<p>The bet­ter way to re­place text pro­gram­mat­i­cally comes di­rectly from the <span class="small-caps">W3C</span> stan­dard:</p>
<ol>
<li>Manually edit the <span class="small-caps">DOM</span> in the fash­ion out­lined by the sug­ges­tion cho­sen by the user.</li>
<li>Fire in­put events to in­struct <span class="small-caps">WYSIWYG</span> ed­i­tors to syn­chro­nize their in­ter­nal state to the <span class="small-caps">DOM</span>.</li>
</ol>
<p>I’ll ad­mit that this is an over­sim­pli­ﬁ­ca­tion.
Much of the com­plex­ity here lays in de­ter­min­ing which <span class="small-caps">DOM</span> nodes to edit or ﬁre events on.</p>
<h2>Read the Source of Truth</h2>
<p>If you take one thing away from this post, it should be this: <strong>al­ways read from the source of truth.</strong>
There is a lot of faulty in­for­ma­tion out there, es­pe­cially when it comes to cre­at­ing com­plex in­ter­ac­tions with opaque sys­tems.
If there is a source of truth, read it.
It may look in­tim­i­dat­ing or seem un­nec­es­sar­ily ver­bose.
It is that way for a rea­son.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/harper_chrome_ext_2.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Always Think of the Hook First]]></title>
            <link>https://elijahpotter.dev/articles/always_think_of_the_hook_first</link>
            <guid>https://elijahpotter.dev/articles/always_think_of_the_hook_first</guid>
            <pubDate>Thu, 01 May 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Always Think of the Hook First</h1>
<img src="/images/log.webp" alt="A log I found in the Indian Peaks Wilderness">
<p>In his pop­u­lar book, <em>Outliers</em>, Malcolm Gladwell made the case that there was one num­ber that pre­dicted your skill level in a ﬁeld: the num­ber of hours of ded­i­cated prac­tice.
In pop­u­lar cul­ture, this idea has been sim­pli­ﬁed.
Some think that if you just do some­thing for 10,000 hours, then you’ll be­come a world-class ex­pert.
This is false.</p>
<p>What does the word<span class="push-double"></span> <span class="pull-double">“</span>dedicated” mean in the phrase<span class="push-double"></span> <span class="pull-double">“</span>dedicated” prac­tice?
It means ap­ply­ing fo­cused, de­lib­er­ate thought to it.
It means re­ﬂect­ing on your own per­for­mance.
It means seek­ing help from mas­ters in the ﬁeld.
Above all, it means <strong>achiev­ing mas­tery is not easy</strong>.</p>
<p>I aim to mas­ter the skill of writ­ing.
In my free time this week, I’ve been work­ing on my de­lib­er­ate prac­tice of writ­ing.
In do­ing so, I’ve come to some con­clu­sions.
If you’d like to join me in pur­su­ing mas­tery of the writ­ten word, here’s what I think you should do.</p>
<h2>Proof it Yourself</h2>
<p>Two-time Pulitzer prize-win­ner David McCullough says<span class="push-double"></span> <span class="pull-double">“</span>Writing is think­ing. To write well is to think clearly. That’s why it’s so hard.”</p>
<p>Because the act of writ­ing is also the act of think­ing, the process of re­vis­ing text clar­i­ﬁes thought.
I per­son­ally ﬁnd that my ar­gu­ment when I ﬁrst touch my key­board usu­ally stands in stark con­trast to when I hit<span class="push-double"></span> <span class="pull-double">“</span>publish.”</p>
<p>Heavy-handed re­vi­sion tools like ChatGPT ac­tively dis­rupt this great ben­e­ﬁt of the re­vi­sion process.
Sure, they can rewrite tone to be more at­trac­tive on­line, but they also rob you of the op­por­tu­nity to de­velop your con­cept.
That’s why I turn every­thing else off and only use Harper: be­cause it gets out of the way of what I am try­ing to say.
<strong>You can’t re­ﬂect on what you’ve writ­ten if you are not the one re­ﬂect­ing.</strong></p>
<h2>Read The Work of Great Writers</h2>
<p>When I sit down to write af­ter spend­ing time on­line, I feel de­ﬂated.
The com­pe­ti­tion of so­cial me­dia and the churn of the news cy­cle both lead to poor prose.</p>
<p>When I sit to write af­ter read­ing Bonnie Garmus or Cal Newport, I feel in­vig­o­rated.
I can imag­ine a shape for the words on the page which pre­cisely cap­tures the ideas I’m try­ing to com­mu­ni­cate.
Read­ing great lit­er­a­ture gives you an in­tu­ition for what your own work should look like.</p>
<h2>Think of the Hook First</h2>
<p>In rap­per cir­cles, it’s a com­mon point of de­bate whether to think of the verse ﬁrst or the hook.</p>
<p>On one hand, the verse is the sub­stance of the song.
It con­tains the mes­sage, the de­tail, and the per­sonal mean­ing.
Mu­sic de­rives much of its com­plex­ity from the verse.
On the other hand, the hook is what gets peo­ple to lis­ten.
It is what makes them stop ﬂick­ing through Spotify and fall into the beat.</p>
<p>If you’re writ­ing for an au­di­ence, you have a lot of com­pe­ti­tion for their at­ten­tion.
Spend more time on the in­tro­duc­tion than any­thing else.
It should cap­ture your read­ers, de­light their imag­i­na­tions, and put them in your shoes.</p>
<h2>Don’t Get Attached</h2>
<p>I spent a long time on a post sim­i­lar to this one.
I was quite proud of it, for I had spent hours adding lay­ers of de­tail and quite a few case stud­ies from my own work.
Alas, Gutenberg de­cided it was not meant to be and chucked it into obliv­ion.</p>
<p>Although I was ini­tially crest­fallen from the loss, I now be­lieve the sec­ond round ended up even bet­ter.
To truly re­vise your work, you’ll in­evitably have to delete parts of it.
Get com­fort­able with that.</p>]]></description>
            <content:encoded><![CDATA[<h1>Always Think of the Hook First</h1>
<img src="/images/log.webp" alt="A log I found in the Indian Peaks Wilderness">
<p>In his pop­u­lar book, <em>Outliers</em>, Malcolm Gladwell made the case that there was one num­ber that pre­dicted your skill level in a ﬁeld: the num­ber of hours of ded­i­cated prac­tice.
In pop­u­lar cul­ture, this idea has been sim­pli­ﬁed.
Some think that if you just do some­thing for 10,000 hours, then you’ll be­come a world-class ex­pert.
This is false.</p>
<p>What does the word<span class="push-double"></span> <span class="pull-double">“</span>dedicated” mean in the phrase<span class="push-double"></span> <span class="pull-double">“</span>dedicated” prac­tice?
It means ap­ply­ing fo­cused, de­lib­er­ate thought to it.
It means re­ﬂect­ing on your own per­for­mance.
It means seek­ing help from mas­ters in the ﬁeld.
Above all, it means <strong>achiev­ing mas­tery is not easy</strong>.</p>
<p>I aim to mas­ter the skill of writ­ing.
In my free time this week, I’ve been work­ing on my de­lib­er­ate prac­tice of writ­ing.
In do­ing so, I’ve come to some con­clu­sions.
If you’d like to join me in pur­su­ing mas­tery of the writ­ten word, here’s what I think you should do.</p>
<h2>Proof it Yourself</h2>
<p>Two-time Pulitzer prize-win­ner David McCullough says<span class="push-double"></span> <span class="pull-double">“</span>Writing is think­ing. To write well is to think clearly. That’s why it’s so hard.”</p>
<p>Because the act of writ­ing is also the act of think­ing, the process of re­vis­ing text clar­i­ﬁes thought.
I per­son­ally ﬁnd that my ar­gu­ment when I ﬁrst touch my key­board usu­ally stands in stark con­trast to when I hit<span class="push-double"></span> <span class="pull-double">“</span>publish.”</p>
<p>Heavy-handed re­vi­sion tools like ChatGPT ac­tively dis­rupt this great ben­e­ﬁt of the re­vi­sion process.
Sure, they can rewrite tone to be more at­trac­tive on­line, but they also rob you of the op­por­tu­nity to de­velop your con­cept.
That’s why I turn every­thing else off and only use Harper: be­cause it gets out of the way of what I am try­ing to say.
<strong>You can’t re­ﬂect on what you’ve writ­ten if you are not the one re­ﬂect­ing.</strong></p>
<h2>Read The Work of Great Writers</h2>
<p>When I sit down to write af­ter spend­ing time on­line, I feel de­ﬂated.
The com­pe­ti­tion of so­cial me­dia and the churn of the news cy­cle both lead to poor prose.</p>
<p>When I sit to write af­ter read­ing Bonnie Garmus or Cal Newport, I feel in­vig­o­rated.
I can imag­ine a shape for the words on the page which pre­cisely cap­tures the ideas I’m try­ing to com­mu­ni­cate.
Read­ing great lit­er­a­ture gives you an in­tu­ition for what your own work should look like.</p>
<h2>Think of the Hook First</h2>
<p>In rap­per cir­cles, it’s a com­mon point of de­bate whether to think of the verse ﬁrst or the hook.</p>
<p>On one hand, the verse is the sub­stance of the song.
It con­tains the mes­sage, the de­tail, and the per­sonal mean­ing.
Mu­sic de­rives much of its com­plex­ity from the verse.
On the other hand, the hook is what gets peo­ple to lis­ten.
It is what makes them stop ﬂick­ing through Spotify and fall into the beat.</p>
<p>If you’re writ­ing for an au­di­ence, you have a lot of com­pe­ti­tion for their at­ten­tion.
Spend more time on the in­tro­duc­tion than any­thing else.
It should cap­ture your read­ers, de­light their imag­i­na­tions, and put them in your shoes.</p>
<h2>Don’t Get Attached</h2>
<p>I spent a long time on a post sim­i­lar to this one.
I was quite proud of it, for I had spent hours adding lay­ers of de­tail and quite a few case stud­ies from my own work.
Alas, Gutenberg de­cided it was not meant to be and chucked it into obliv­ion.</p>
<p>Although I was ini­tially crest­fallen from the loss, I now be­lieve the sec­ond round ended up even bet­ter.
To truly re­vise your work, you’ll in­evitably have to delete parts of it.
Get com­fort­able with that.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/log.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[ChatGPT for the Moms]]></title>
            <link>https://elijahpotter.dev/articles/chatgpt_for_the_moms</link>
            <guid>https://elijahpotter.dev/articles/chatgpt_for_the_moms</guid>
            <pubDate>Wed, 23 Apr 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>ChatGPT for the Moms</h1>
<img src="/images/guggenheim.webp" alt="Spring has sprung, and Guggenheim shows it.">
<p>My mom is a vi­o­lently in­tel­li­gent woman.
But she lives in the un­for­tu­nate re­al­ity of not be­ing a soft­ware en­gi­neer or math­e­mati­cian.
While she is just as ex­cited about ChatGPT (and all the other nascent <span class="small-caps">AI</span> tech­nol­ogy) as the rest of us, she is­n’t aware of the un­in­tel­li­gent pit­falls ar­ti­ﬁ­cial in­tel­li­gence has these days.</p>
<p>If your mom is ex­per­i­ment­ing with ChatGPT, I’d highly rec­om­mend send­ing this list to her.
Pow­er­ful tech­nol­ogy is in their hands, but I’ve got three things they should know be­fore re­ly­ing on it.</p>
<h2>Advice for Moms</h2>
<h3>Use Web Search</h3>
<p>My mother re­cently learned how to make cob­bler.
She adored asked ChatGPT ques­tions about the recipe, and how she could im­prove it us­ing the in­gre­di­ents she had on hand.
Un­for­tu­nately, she was get­ting sub­tly wrong in­for­ma­tion from the bot, so the cob­bler turned out<span class="push-double"></span> <span class="pull-double">“</span>sub-par”.
This can be solved by us­ing ChatGPT’s web search.</p>
<p>When you open up ChatGPT on a phone or on a com­puter, you’ll be met with the fa­mil­iar chat win­dow.
If you ask it ques­tions it will give you an an­swer.
Some­times that an­swer will be good, some­times not.</p>
<p>You can gen­er­ally get bet­ter in­for­ma­tion if you en­able the web search tool.</p>
<img src="/images/chatgpt_web_search_tool.png" alt="A screenshot of the ChatGPT web search tool.">
<p>This will al­low ChatGPT to do Google searches on your be­half.
It im­proves the qual­ity of in­for­ma­tion it gives you, while only adding a sec­ond or two to their re­sponse.</p>
<h3>It Is Not Your Friend</h3>
<p>When we see a chat win­dow, our ﬁrst thought is that there’s a per­son on the other end.
Some­one with emo­tions and lives all their own.
That is not the case here.</p>
<p>ChatGPT, while an ad­vanced ma­chine learn­ing model, is not a per­son.
It does not re­place real hu­man con­nec­tion.
It can’t watch your kids when you’ve got an emer­gency.
It won’t show up to the school recital.
It does­n’t care about you—de­spite what it says.</p>
<p>There’s a lot that goes into a gen­uine so­cial con­nec­tion, all of which are miss­ing from ChatGPT.</p>
<h3>Use Your Brain</h3>
<p>ChatGPT is no sub­sti­tute for crit­i­cal thought.</p>
<p>It can­not do most things, even if it claims it can.
You are a ca­pa­ble hu­man be­ing—able to ac­com­plish any­thing you put your mind to.
While ChatGPT can be a help­ful as­sis­tant or brain­storm­ing part­ner, don’t let it steer the ship.</p>
<h2>What to Remember</h2>
<p>While ChatGPT can be a pow­er­ful tool in the right hands, it is im­por­tant to re­mem­ber it is just that: a tool.
It can be en­hanced with the right set­tings, it is not real hu­man con­nec­tion, and it can’t re­place your brain.
Use it well and use it wisely.</p>]]></description>
            <content:encoded><![CDATA[<h1>ChatGPT for the Moms</h1>
<img src="/images/guggenheim.webp" alt="Spring has sprung, and Guggenheim shows it.">
<p>My mom is a vi­o­lently in­tel­li­gent woman.
But she lives in the un­for­tu­nate re­al­ity of not be­ing a soft­ware en­gi­neer or math­e­mati­cian.
While she is just as ex­cited about ChatGPT (and all the other nascent <span class="small-caps">AI</span> tech­nol­ogy) as the rest of us, she is­n’t aware of the un­in­tel­li­gent pit­falls ar­ti­ﬁ­cial in­tel­li­gence has these days.</p>
<p>If your mom is ex­per­i­ment­ing with ChatGPT, I’d highly rec­om­mend send­ing this list to her.
Pow­er­ful tech­nol­ogy is in their hands, but I’ve got three things they should know be­fore re­ly­ing on it.</p>
<h2>Advice for Moms</h2>
<h3>Use Web Search</h3>
<p>My mother re­cently learned how to make cob­bler.
She adored asked ChatGPT ques­tions about the recipe, and how she could im­prove it us­ing the in­gre­di­ents she had on hand.
Un­for­tu­nately, she was get­ting sub­tly wrong in­for­ma­tion from the bot, so the cob­bler turned out<span class="push-double"></span> <span class="pull-double">“</span>sub-par”.
This can be solved by us­ing ChatGPT’s web search.</p>
<p>When you open up ChatGPT on a phone or on a com­puter, you’ll be met with the fa­mil­iar chat win­dow.
If you ask it ques­tions it will give you an an­swer.
Some­times that an­swer will be good, some­times not.</p>
<p>You can gen­er­ally get bet­ter in­for­ma­tion if you en­able the web search tool.</p>
<img src="/images/chatgpt_web_search_tool.png" alt="A screenshot of the ChatGPT web search tool.">
<p>This will al­low ChatGPT to do Google searches on your be­half.
It im­proves the qual­ity of in­for­ma­tion it gives you, while only adding a sec­ond or two to their re­sponse.</p>
<h3>It Is Not Your Friend</h3>
<p>When we see a chat win­dow, our ﬁrst thought is that there’s a per­son on the other end.
Some­one with emo­tions and lives all their own.
That is not the case here.</p>
<p>ChatGPT, while an ad­vanced ma­chine learn­ing model, is not a per­son.
It does not re­place real hu­man con­nec­tion.
It can’t watch your kids when you’ve got an emer­gency.
It won’t show up to the school recital.
It does­n’t care about you—de­spite what it says.</p>
<p>There’s a lot that goes into a gen­uine so­cial con­nec­tion, all of which are miss­ing from ChatGPT.</p>
<h3>Use Your Brain</h3>
<p>ChatGPT is no sub­sti­tute for crit­i­cal thought.</p>
<p>It can­not do most things, even if it claims it can.
You are a ca­pa­ble hu­man be­ing—able to ac­com­plish any­thing you put your mind to.
While ChatGPT can be a help­ful as­sis­tant or brain­storm­ing part­ner, don’t let it steer the ship.</p>
<h2>What to Remember</h2>
<p>While ChatGPT can be a pow­er­ful tool in the right hands, it is im­por­tant to re­mem­ber it is just that: a tool.
It can be en­hanced with the right set­tings, it is not real hu­man con­nec­tion, and it can’t re­place your brain.
Use it well and use it wisely.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/guggenheim.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Status of the Harper Chrome Extension]]></title>
            <link>https://elijahpotter.dev/articles/status_of_the_harper_chrome_extension</link>
            <guid>https://elijahpotter.dev/articles/status_of_the_harper_chrome_extension</guid>
            <pubDate>Wed, 23 Apr 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Status of the Harper Chrome Extension</h1>
<p>Elijah! Elijah! When will we have a Chrome ex­ten­sion for <a href="https://writewithharper.com">Harper</a>?</p>
<p>Although it’s usu­ally asked with a bit more panache, this is a ques­tion I’ve got­ten a lot.
In fact, an au­thor at Lifehacker <a href="https://lifehacker.com/tech/harper-offline-alternative-to-grammarly">wrote about it just yes­ter­day</a>.</p>
<p>For the longest time, it was a pro­ject on the back-burner.
Some­thing I in­tended to work on, but was al­ways passed up for more im­por­tant things.
It never saw the light of day.</p>
<p>Until now.</p>
<img src="/images/harper_chrome_ext.png" alt="The Harper Chrome Extension">
<h2>What is Harper?</h2>
<p>&hellip;and why do I need its Chrome ex­ten­sion?</p>
<p>Harper is a gram­mar checker that re­spects your pri­vacy.
All lan­guage pro­cess­ing hap­pens on-de­vice, no mat­ter where you’re us­ing it.</p>
<p>For most of Harper’s his­tory, it has only been avail­able in one of many text ed­i­tors as a plu­gin.
With the Chrome ex­ten­sion, you’ll be able to get high-qual­ity gram­mar check­ing any­where on the web.</p>
<h2>What’s the Status?</h2>
<p>I just marked the <a href="https://github.com/Automattic/harper/pull/1072">Chrome ex­ten­sion <span class="small-caps">PR</span></a> as<span class="push-double"></span> <span class="pull-double">“</span>ready for re­view”.
Here’s what that means.</p>
<p>If you’re will­ing to get into the tech­ni­cal bram­bles, you can com­pile and run it your­self.
That will con­tinue to be the case un­til the Chrome Web Store re­views and ap­proves our sub­mis­sion, which could take a cou­ple of weeks.</p>
<p>When in­stalled, it will be en­abled for a cu­rated list of web­site that I’ve per­son­ally re­viewed to con­ﬁrm work well.
If you want to try us­ing the ex­ten­sion on an­other site, you will be able to ad­ven­ture into the world of un­ex­pected be­hav­ior by hit­ting the popup menu.</p>
<p>Importantly, it is work­ing ex­cep­tion­ally well on any site us­ing the Gutenberg ed­i­tor.
I will be back­port­ing a lot of this code to our WordPress plu­gin to im­prove the ex­pe­ri­ence there.</p>
<h3>Will it Support Firefox?</h3>
<p>As the <span class="small-caps">PR</span> stands to­day, the ex­ten­sion does not sup­port Firefox.</p>
<p>I have list of things that should get done whilst wait­ing for the Chrome Web Store to ap­prove the sub­mis­sion.
Fire­fox sup­port is on that list.</p>
<h2>Any Questions?</h2>
<p>If you’ve got ques­tions for me about this, feel free to reach out via <a href="https://discord.com/invite/JBqcAaKrzQ">Discord</a> or on <a href="https://github.com/automattic/harper">GitHub</a>.</p>]]></description>
            <content:encoded><![CDATA[<h1>The Status of the Harper Chrome Extension</h1>
<p>Elijah! Elijah! When will we have a Chrome ex­ten­sion for <a href="https://writewithharper.com">Harper</a>?</p>
<p>Although it’s usu­ally asked with a bit more panache, this is a ques­tion I’ve got­ten a lot.
In fact, an au­thor at Lifehacker <a href="https://lifehacker.com/tech/harper-offline-alternative-to-grammarly">wrote about it just yes­ter­day</a>.</p>
<p>For the longest time, it was a pro­ject on the back-burner.
Some­thing I in­tended to work on, but was al­ways passed up for more im­por­tant things.
It never saw the light of day.</p>
<p>Until now.</p>
<img src="/images/harper_chrome_ext.png" alt="The Harper Chrome Extension">
<h2>What is Harper?</h2>
<p>&hellip;and why do I need its Chrome ex­ten­sion?</p>
<p>Harper is a gram­mar checker that re­spects your pri­vacy.
All lan­guage pro­cess­ing hap­pens on-de­vice, no mat­ter where you’re us­ing it.</p>
<p>For most of Harper’s his­tory, it has only been avail­able in one of many text ed­i­tors as a plu­gin.
With the Chrome ex­ten­sion, you’ll be able to get high-qual­ity gram­mar check­ing any­where on the web.</p>
<h2>What’s the Status?</h2>
<p>I just marked the <a href="https://github.com/Automattic/harper/pull/1072">Chrome ex­ten­sion <span class="small-caps">PR</span></a> as<span class="push-double"></span> <span class="pull-double">“</span>ready for re­view”.
Here’s what that means.</p>
<p>If you’re will­ing to get into the tech­ni­cal bram­bles, you can com­pile and run it your­self.
That will con­tinue to be the case un­til the Chrome Web Store re­views and ap­proves our sub­mis­sion, which could take a cou­ple of weeks.</p>
<p>When in­stalled, it will be en­abled for a cu­rated list of web­site that I’ve per­son­ally re­viewed to con­ﬁrm work well.
If you want to try us­ing the ex­ten­sion on an­other site, you will be able to ad­ven­ture into the world of un­ex­pected be­hav­ior by hit­ting the popup menu.</p>
<p>Importantly, it is work­ing ex­cep­tion­ally well on any site us­ing the Gutenberg ed­i­tor.
I will be back­port­ing a lot of this code to our WordPress plu­gin to im­prove the ex­pe­ri­ence there.</p>
<h3>Will it Support Firefox?</h3>
<p>As the <span class="small-caps">PR</span> stands to­day, the ex­ten­sion does not sup­port Firefox.</p>
<p>I have list of things that should get done whilst wait­ing for the Chrome Web Store to ap­prove the sub­mis­sion.
Fire­fox sup­port is on that list.</p>
<h2>Any Questions?</h2>
<p>If you’ve got ques­tions for me about this, feel free to reach out via <a href="https://discord.com/invite/JBqcAaKrzQ">Discord</a> or on <a href="https://github.com/automattic/harper">GitHub</a>.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/harper_chrome_ext.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Photography as Meditation]]></title>
            <link>https://elijahpotter.dev/articles/photography_as_meditation</link>
            <guid>https://elijahpotter.dev/articles/photography_as_meditation</guid>
            <pubDate>Sat, 19 Apr 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Photography as Meditation</h1>
<p>The goal of med­i­ta­tion is to be­come more mind­ful and aware.
For me, it is of­ten an at­tempt to get an ap­pre­ci­a­tion for my sur­round­ings—to see how ut­terly com­plex the world is.</p>
<img src="/images/pink_flower_tree.webp" alt="Spring has sprung in Colorado. That means pink flowers everwhere.">
<p>Photography does some­thing sim­i­lar for me.
The heft of the cam­era in my hands re­minds me to slow down and take in the view.</p>
<p>My mother is a pho­tog­ra­pher—a great one at that.
She’s been in the busi­ness for at least as long as I’ve been alive.
She tells me some­thing sim­i­lar: the act of fram­ing a pic­ture is it­self a way to un­der­stand your sub­ject bet­ter.</p>
<img src="/images/puddle.webp" alt="Spring brings rain and rain brings puddles.">]]></description>
            <content:encoded><![CDATA[<h1>Photography as Meditation</h1>
<p>The goal of med­i­ta­tion is to be­come more mind­ful and aware.
For me, it is of­ten an at­tempt to get an ap­pre­ci­a­tion for my sur­round­ings—to see how ut­terly com­plex the world is.</p>
<img src="/images/pink_flower_tree.webp" alt="Spring has sprung in Colorado. That means pink flowers everwhere.">
<p>Photography does some­thing sim­i­lar for me.
The heft of the cam­era in my hands re­minds me to slow down and take in the view.</p>
<p>My mother is a pho­tog­ra­pher—a great one at that.
She’s been in the busi­ness for at least as long as I’ve been alive.
She tells me some­thing sim­i­lar: the act of fram­ing a pic­ture is it­self a way to un­der­stand your sub­ject bet­ter.</p>
<img src="/images/puddle.webp" alt="Spring brings rain and rain brings puddles.">]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/pink_flower_tree.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[The Art of Exception]]></title>
            <link>https://elijahpotter.dev/articles/the_art_of_exception</link>
            <guid>https://elijahpotter.dev/articles/the_art_of_exception</guid>
            <pubDate>Fri, 18 Apr 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Art of Exception</h1>
<p>English is an area of in­ter­est lit­tered with edge cases.
In prepa­ra­tion for ad­dress­ing sim­i­lar prob­lems in the de­vel­op­ment of the Harper Chrome ex­ten­sion, I’m spend­ing some time here re­ﬂect­ing on what I’ve learned by tack­ling the com­plex maze of English edge cases.</p>
<p><a href="https://writewithharper.com">Harper</a> is not alone, and there’s a real chance that you’ll have to work on ex­cep­tion-tol­er­ant code. In that case, you will need to un­der­stand the <em>why</em> for some of the de­sign de­ci­sions you en­counter in the wild.</p>
<img src="/images/harper_chrome_ext.png" alt="A screenshot of the Harper grammar checker web extension.">
<h2>What Do I Mean By<span class="push-double"></span> <span class="pull-double">“</span>Edge Case”?</h2>
<p>An edge-case is a sit­u­a­tion (that is of­ten con­text-de­pen­dent) which re­sults in in­cor­rect be­hav­ior from a model, pro­gram, or the­o­ret­i­cal frame­work. In con­ver­sa­tions about the Harper Chrome ex­ten­sion, an<span class="push-double"></span> <span class="pull-double">“</span>edge case” is where the ex­ten­sion im­prop­erly reads from or writes to a site’s em­bed­ded text ed­i­tor.</p>
<p>Most sites use <code>&#x3C;textarea /></code> or <code>&#x3C;input /></code> el­e­ments for text edit­ing, but a num­ber of sites (including WordPress, as you know) have com­plex <span class="small-caps">WYSIWYG</span> ed­i­tors. Each be­haves dif­fer­ently, which can cause prob­lems with the afore­men­tioned read/​write loop. The prob­lem: our users ex­pect us to sup­port all ma­jor text ed­i­tors.</p>
<h2>Err on the Side of Inaction</h2>
<p>In Harper’s core al­go­rithm, we err on the side of false-neg­a­tives. This de­ci­sion was de­rived from an ob­ser­va­tion made early on in the pro­jec­t’s life cy­cle: peo­ple usu­ally blame them­selves for their own writ­ing mis­takes, un­less the er­ror is truly triv­ial.</p>
<p>All in, we get far more com­plaints about false-pos­i­tives than false-neg­a­tives. Which is why we err on the side of in­ac­tion. If the al­go­rithm thinks it’s pos­si­ble, but not cer­tain an er­ror was made, we sup­press the re­port in case Harper is wrong.</p>
<p>In the Chrome ex­ten­sion, we will be ex­em­pli­fy­ing this rule by only en­abling it on a cu­rated list of do­mains by de­fault.</p>
<img src="/images/site_pareto.png" alt="A graph of site visits vs rank">
<p>As site vis­its tend to fol­low a Pareto dis­tri­b­u­tion, we can cover most traf­ﬁc with just a few items in this list.</p>
<h2>Make Tweaks Easy</h2>
<p>When an edge case (which is al­most al­ways a false-pos­i­tive) ap­pears in Harper’s core al­go­rithm, it’s usu­ally in a pretty ob­vi­ous spot. This is be­cause we as­so­ci­ate each lint out­put with a spe­ciﬁc, easy-to-ﬁnd mod­ule the core code.</p>
<p>In most cases, this is not ver­bose, spe­cial­ized Rust code. Rather, it is an LLM-friendly <span class="small-caps">DSL</span> that is leg­i­ble to most with even be­gin­ner-level pro­gram­ming ex­pe­ri­ence. This com­bi­na­tion of easy-to-read and easy-to-edit makes con­tri­bu­tions from the com­mu­nity (regarding edge-cases) com­mon­place.</p>
<p>I aim to repli­cate this suc­cess in the Chrome ex­ten­sion’s read/​write ca­pa­bil­i­ties by care­fully doc­u­ment­ing the ar­chi­tec­ture and work­ing with third-par­ties to make the con­tribut­ing process clearer and eas­ier.</p>
<h2>It Is an Art</h2>
<p>There is a rea­son I call this an art. Design de­ci­sions in ex­cep­tion-tol­er­ant sys­tems stem from these two sim­ple ideas, but they grow from an in­tu­ition de­vel­oped by their main­tain­ers. I’m not ex­actly sure how the Chrome ex­ten­sion will grow to han­dle edge cases. I’ll be sure to come back here and de­tail them when I do.</p>]]></description>
            <content:encoded><![CDATA[<h1>The Art of Exception</h1>
<p>English is an area of in­ter­est lit­tered with edge cases.
In prepa­ra­tion for ad­dress­ing sim­i­lar prob­lems in the de­vel­op­ment of the Harper Chrome ex­ten­sion, I’m spend­ing some time here re­ﬂect­ing on what I’ve learned by tack­ling the com­plex maze of English edge cases.</p>
<p><a href="https://writewithharper.com">Harper</a> is not alone, and there’s a real chance that you’ll have to work on ex­cep­tion-tol­er­ant code. In that case, you will need to un­der­stand the <em>why</em> for some of the de­sign de­ci­sions you en­counter in the wild.</p>
<img src="/images/harper_chrome_ext.png" alt="A screenshot of the Harper grammar checker web extension.">
<h2>What Do I Mean By<span class="push-double"></span> <span class="pull-double">“</span>Edge Case”?</h2>
<p>An edge-case is a sit­u­a­tion (that is of­ten con­text-de­pen­dent) which re­sults in in­cor­rect be­hav­ior from a model, pro­gram, or the­o­ret­i­cal frame­work. In con­ver­sa­tions about the Harper Chrome ex­ten­sion, an<span class="push-double"></span> <span class="pull-double">“</span>edge case” is where the ex­ten­sion im­prop­erly reads from or writes to a site’s em­bed­ded text ed­i­tor.</p>
<p>Most sites use <code>&#x3C;textarea /></code> or <code>&#x3C;input /></code> el­e­ments for text edit­ing, but a num­ber of sites (including WordPress, as you know) have com­plex <span class="small-caps">WYSIWYG</span> ed­i­tors. Each be­haves dif­fer­ently, which can cause prob­lems with the afore­men­tioned read/​write loop. The prob­lem: our users ex­pect us to sup­port all ma­jor text ed­i­tors.</p>
<h2>Err on the Side of Inaction</h2>
<p>In Harper’s core al­go­rithm, we err on the side of false-neg­a­tives. This de­ci­sion was de­rived from an ob­ser­va­tion made early on in the pro­jec­t’s life cy­cle: peo­ple usu­ally blame them­selves for their own writ­ing mis­takes, un­less the er­ror is truly triv­ial.</p>
<p>All in, we get far more com­plaints about false-pos­i­tives than false-neg­a­tives. Which is why we err on the side of in­ac­tion. If the al­go­rithm thinks it’s pos­si­ble, but not cer­tain an er­ror was made, we sup­press the re­port in case Harper is wrong.</p>
<p>In the Chrome ex­ten­sion, we will be ex­em­pli­fy­ing this rule by only en­abling it on a cu­rated list of do­mains by de­fault.</p>
<img src="/images/site_pareto.png" alt="A graph of site visits vs rank">
<p>As site vis­its tend to fol­low a Pareto dis­tri­b­u­tion, we can cover most traf­ﬁc with just a few items in this list.</p>
<h2>Make Tweaks Easy</h2>
<p>When an edge case (which is al­most al­ways a false-pos­i­tive) ap­pears in Harper’s core al­go­rithm, it’s usu­ally in a pretty ob­vi­ous spot. This is be­cause we as­so­ci­ate each lint out­put with a spe­ciﬁc, easy-to-ﬁnd mod­ule the core code.</p>
<p>In most cases, this is not ver­bose, spe­cial­ized Rust code. Rather, it is an LLM-friendly <span class="small-caps">DSL</span> that is leg­i­ble to most with even be­gin­ner-level pro­gram­ming ex­pe­ri­ence. This com­bi­na­tion of easy-to-read and easy-to-edit makes con­tri­bu­tions from the com­mu­nity (regarding edge-cases) com­mon­place.</p>
<p>I aim to repli­cate this suc­cess in the Chrome ex­ten­sion’s read/​write ca­pa­bil­i­ties by care­fully doc­u­ment­ing the ar­chi­tec­ture and work­ing with third-par­ties to make the con­tribut­ing process clearer and eas­ier.</p>
<h2>It Is an Art</h2>
<p>There is a rea­son I call this an art. Design de­ci­sions in ex­cep­tion-tol­er­ant sys­tems stem from these two sim­ple ideas, but they grow from an in­tu­ition de­vel­oped by their main­tain­ers. I’m not ex­actly sure how the Chrome ex­ten­sion will grow to han­dle edge cases. I’ll be sure to come back here and de­tail them when I do.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[On Linkarzu]]></title>
            <link>https://elijahpotter.dev/articles/on_linkarzu</link>
            <guid>https://elijahpotter.dev/articles/on_linkarzu</guid>
            <pubDate>Thu, 17 Apr 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>On Linkarzu’s YouTube</h1>
<p>I con­sider my­self an avid mem­ber of the Neovim com­mu­nity.
As a whole, they have a great pulse on what a good writ­ing ex­pe­ri­ence feels like.
Over time, this com­mu­nity has grown into a real force in the soft­ware en­gi­neer­ing ﬁeld.</p>
<p>Linkarzu has been a con­stant pres­ence in this ecosys­tem for a while, giv­ing a voice to the hid­den
main­tain­ers that make Neovim great.
I am hon­ored to be counted among the guests he has hosted on his chan­nel.</p>
<p>We talked at length about <a href="https://writewithharper.com">Harper</a>, open source main­te­nance, and the tool­ing we use in our de­vel­op­ment work­ﬂows.</p>
<iframe width="100%" height="400" src="https://www.youtube.com/embed/l9D7M1gIY8I?si=dXhZKospDwyb6zmU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>]]></description>
            <content:encoded><![CDATA[<h1>On Linkarzu’s YouTube</h1>
<p>I con­sider my­self an avid mem­ber of the Neovim com­mu­nity.
As a whole, they have a great pulse on what a good writ­ing ex­pe­ri­ence feels like.
Over time, this com­mu­nity has grown into a real force in the soft­ware en­gi­neer­ing ﬁeld.</p>
<p>Linkarzu has been a con­stant pres­ence in this ecosys­tem for a while, giv­ing a voice to the hid­den
main­tain­ers that make Neovim great.
I am hon­ored to be counted among the guests he has hosted on his chan­nel.</p>
<p>We talked at length about <a href="https://writewithharper.com">Harper</a>, open source main­te­nance, and the tool­ing we use in our de­vel­op­ment work­ﬂows.</p>
<iframe width="100%" height="400" src="https://www.youtube.com/embed/l9D7M1gIY8I?si=dXhZKospDwyb6zmU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Footguns of the Rust Webassembly Target]]></title>
            <link>https://elijahpotter.dev/articles/footguns_of_the_rust_webassembly_target</link>
            <guid>https://elijahpotter.dev/articles/footguns_of_the_rust_webassembly_target</guid>
            <pubDate>Tue, 08 Apr 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Footguns of the Rust WebAssembly Target</h1>
<img src="/images/rail.webp" alt="A trail in Golden, Colorado">
<p>WebAssembly—even af­ter sev­eral years of stan­dard­iza­tion—is still a nascent tech­nol­ogy.</p>
<p>I’ve been work­ing with Rust and WebAssembly for nearly four years now.
This post is in­tended to be a dis­til­late of that ex­pe­ri­ence, for­mat­ted for de­vel­op­ers who are in­ter­ested in pub­lish­ing WebAssembly code to npm.
Specif­i­cally, these are the foot­guns I’ve per­son­ally en­coun­tered while work­ing on <a href="https://writewithharper.com/docs/harperjs/introduction"><code>harper.js</code></a>, a WebAssembly-powered pack­age for gram­mar check­ing at the edge.
This page should be valu­able if you are even spec­u­lat­ing on the pos­si­bil­ity of us­ing WebAssembly in your code­base.</p>
<hr>
<h2>1. Exposing Only Synchronous Functions</h2>
<p>The Harper pack­age ex­poses one in­ter­face that cap­tures all of its WebAssembly in­ter­ac­tions: the <strong>Linter</strong>. This is an ob­ject that han­dles down­load­ing and com­pil­ing the Harper WebAssembly mod­ule, as well as in­vok­ing func­tions in it. Every func­tion re­turns a <code>Promise</code>. There are sev­eral good rea­sons for that:</p>
<ul>
<li>WebAssembly mod­ules larger than 4 kilo­bytes must be in­stan­ti­ated <strong>asyn­chro­nously</strong> to avoid block­ing the event loop dur­ing down­load or com­pi­la­tion. This is a tech­ni­cal lim­i­ta­tion that can­not be avoided with clever logic.</li>
<li>If most func­tions are asyn­chro­nous, you can cen­tral­ize com­pu­ta­tion and caching into a sin­gle in­stance of the WebAssembly mod­ule, hid­ing the com­plex­ity of in­stan­ti­a­tion and mak­ing caches eas­ier to build.</li>
<li>If your prob­lem do­main can be com­pu­ta­tion­ally in­tense, it might be pru­dent to of­ﬂoad jobs onto a <strong>web worker</strong>, which is eas­ier if every func­tion of your fa­cade is asyn­chro­nous.</li>
<li>It will be eas­ier for both you and your users to ex­pose any im­por­tant func­tion as asyn­chro­nous out of the gate.</li>
</ul>
<h2>2. Assume Rust Can Trivially Do <span class="small-caps">IO</span></h2>
<p>Whatever you do: avoid as­sum­ing that Rust li­braries (like <code>reqwest</code> or <code>rand</code>) will be able to per­form <span class="small-caps">IO</span> with­out some work on your end. WebAssembly alone is <strong>not ca­pa­ble of <span class="small-caps">IO</span></strong>, which means any func­tion in that cat­e­gory will re­quire some amount of JavaScript to work prop­erly.</p>
<p>Rather than leav­ing that up to the Rust tool­chain to ﬁg­ure out, save your­self the headache and <strong>in­ject the nec­es­sary JavaScript func­tions</strong> di­rectly into the WebAssembly mod­ule by pass­ing them through <code>wasm_bindgen</code>.</p>
<hr>
<h2>3. Just Inline the WebAssembly Module</h2>
<p><strong>Obsidian plu­g­ins</strong>, for ex­am­ple, must be com­posed of ex­actly one JavaScript ﬁle, which means every­thing must be in­lined. In the in­ter­est of keep­ing the bun­dle size small, it’s much eas­ier for the pack­age de­vel­oper (of <code>harper.js</code>) to set up in­lin­ing than the plu­gin de­vel­oper.</p>
<p>On the other side of the spec­trum, <strong>Chrome’s Manifest <span class="small-caps">V3</span></strong> dis­al­lows WebAssembly from be­ing loaded in­line.</p>
<p>If you plan for your pack­age to be con­sumed by a va­ri­ety of ap­pli­ca­tions, know that it will also be con­sumed by a va­ri­ety of <strong>bundlers</strong>. Both bundlers and ap­pli­ca­tions are pretty in­con­sis­tent with their in­lin­ing and tree-shak­ing be­hav­ior. To avoid prob­lems, you should pro­vide <strong>two ver­sions</strong> of your pack­age:</p>
<ul>
<li>One where your WebAssembly mod­ule is al­ready in­lined.</li>
<li>One where it is­n’t.</li>
</ul>
<h1>Wrapping it Up</h1>
<p>Harper’s prob­lem do­main is not your prob­lem do­main.
We have to in­te­grate with a va­ri­ety of unique ap­pli­ca­tions, which means we must keep our sys­tem ﬂex­i­ble.
That may not be the case for you, which may mean these foot­guns do not ap­ply.
If you have any ques­tions about any other prob­lems the Harper pro­ject might have faced, let me know.</p>]]></description>
            <content:encoded><![CDATA[<h1>Footguns of the Rust WebAssembly Target</h1>
<img src="/images/rail.webp" alt="A trail in Golden, Colorado">
<p>WebAssembly—even af­ter sev­eral years of stan­dard­iza­tion—is still a nascent tech­nol­ogy.</p>
<p>I’ve been work­ing with Rust and WebAssembly for nearly four years now.
This post is in­tended to be a dis­til­late of that ex­pe­ri­ence, for­mat­ted for de­vel­op­ers who are in­ter­ested in pub­lish­ing WebAssembly code to npm.
Specif­i­cally, these are the foot­guns I’ve per­son­ally en­coun­tered while work­ing on <a href="https://writewithharper.com/docs/harperjs/introduction"><code>harper.js</code></a>, a WebAssembly-powered pack­age for gram­mar check­ing at the edge.
This page should be valu­able if you are even spec­u­lat­ing on the pos­si­bil­ity of us­ing WebAssembly in your code­base.</p>
<hr>
<h2>1. Exposing Only Synchronous Functions</h2>
<p>The Harper pack­age ex­poses one in­ter­face that cap­tures all of its WebAssembly in­ter­ac­tions: the <strong>Linter</strong>. This is an ob­ject that han­dles down­load­ing and com­pil­ing the Harper WebAssembly mod­ule, as well as in­vok­ing func­tions in it. Every func­tion re­turns a <code>Promise</code>. There are sev­eral good rea­sons for that:</p>
<ul>
<li>WebAssembly mod­ules larger than 4 kilo­bytes must be in­stan­ti­ated <strong>asyn­chro­nously</strong> to avoid block­ing the event loop dur­ing down­load or com­pi­la­tion. This is a tech­ni­cal lim­i­ta­tion that can­not be avoided with clever logic.</li>
<li>If most func­tions are asyn­chro­nous, you can cen­tral­ize com­pu­ta­tion and caching into a sin­gle in­stance of the WebAssembly mod­ule, hid­ing the com­plex­ity of in­stan­ti­a­tion and mak­ing caches eas­ier to build.</li>
<li>If your prob­lem do­main can be com­pu­ta­tion­ally in­tense, it might be pru­dent to of­ﬂoad jobs onto a <strong>web worker</strong>, which is eas­ier if every func­tion of your fa­cade is asyn­chro­nous.</li>
<li>It will be eas­ier for both you and your users to ex­pose any im­por­tant func­tion as asyn­chro­nous out of the gate.</li>
</ul>
<h2>2. Assume Rust Can Trivially Do <span class="small-caps">IO</span></h2>
<p>Whatever you do: avoid as­sum­ing that Rust li­braries (like <code>reqwest</code> or <code>rand</code>) will be able to per­form <span class="small-caps">IO</span> with­out some work on your end. WebAssembly alone is <strong>not ca­pa­ble of <span class="small-caps">IO</span></strong>, which means any func­tion in that cat­e­gory will re­quire some amount of JavaScript to work prop­erly.</p>
<p>Rather than leav­ing that up to the Rust tool­chain to ﬁg­ure out, save your­self the headache and <strong>in­ject the nec­es­sary JavaScript func­tions</strong> di­rectly into the WebAssembly mod­ule by pass­ing them through <code>wasm_bindgen</code>.</p>
<hr>
<h2>3. Just Inline the WebAssembly Module</h2>
<p><strong>Obsidian plu­g­ins</strong>, for ex­am­ple, must be com­posed of ex­actly one JavaScript ﬁle, which means every­thing must be in­lined. In the in­ter­est of keep­ing the bun­dle size small, it’s much eas­ier for the pack­age de­vel­oper (of <code>harper.js</code>) to set up in­lin­ing than the plu­gin de­vel­oper.</p>
<p>On the other side of the spec­trum, <strong>Chrome’s Manifest <span class="small-caps">V3</span></strong> dis­al­lows WebAssembly from be­ing loaded in­line.</p>
<p>If you plan for your pack­age to be con­sumed by a va­ri­ety of ap­pli­ca­tions, know that it will also be con­sumed by a va­ri­ety of <strong>bundlers</strong>. Both bundlers and ap­pli­ca­tions are pretty in­con­sis­tent with their in­lin­ing and tree-shak­ing be­hav­ior. To avoid prob­lems, you should pro­vide <strong>two ver­sions</strong> of your pack­age:</p>
<ul>
<li>One where your WebAssembly mod­ule is al­ready in­lined.</li>
<li>One where it is­n’t.</li>
</ul>
<h1>Wrapping it Up</h1>
<p>Harper’s prob­lem do­main is not your prob­lem do­main.
We have to in­te­grate with a va­ri­ety of unique ap­pli­ca­tions, which means we must keep our sys­tem ﬂex­i­ble.
That may not be the case for you, which may mean these foot­guns do not ap­ply.
If you have any ques­tions about any other prob­lems the Harper pro­ject might have faced, let me know.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/rail.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[The Books I Read in February and March 2025]]></title>
            <link>https://elijahpotter.dev/articles/the_books_i_read_in_february_and_march_2025</link>
            <guid>https://elijahpotter.dev/articles/the_books_i_read_in_february_and_march_2025</guid>
            <pubDate>Sat, 05 Apr 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Books I Read in February and March 2025</h1>
<p>In a world con­tain­ing TikTok, YouTube and a vast ar­ray of other apps that proﬁt off hu­man at­ten­tion, it takes ef­fort to place our fo­cus on things that truly ben­e­ﬁt our minds.</p>
<p>At least, that is what I would I say on LinkedIn as an ex­pla­na­tion for why I read.
In re­al­ity: <strong>I sim­ply en­joy it</strong>.</p>
<p>Each month I go back and sum­ma­rize what I’ve learned from the books I’ve read.
In do­ing so, I’ve found that I get both deeper in­sights and greater joy from the lit­er­a­ture.</p>
<h3>Wait! What’s Up With February?</h3>
<p>While I only ﬁn­ished one book in February, I started read­ing sev­eral oth­ers that I ended up ﬁn­ish­ing in March.
It only felt right to com­bine the two months into a sin­gle post.</p>
<h2><em>The Dinner</em> by Herman Koch</h2>
<p>Someone con­vinced me to read this book based on its sim­ple premise: the en­tire novel is an ac­count of a sin­gle din­ner.
I was skep­ti­cal.
It is a big book.
How could they pos­si­bly ﬁt that much plot into a sin­gle din­ner?</p>
<p>The an­swer: Koch drip-feeds you in­for­ma­tion about the eclec­tic char­ac­ters in at­ten­dance.
They gain an ad­di­tional di­men­sion that you could­n’t achieve in an­other, more ac­tion-packed book.</p>
<p>I won’t spoil any­thing here, but the end­ing was a com­plete sur­prise to me.
I did <em>not</em> see that com­ing.</p>
<h2><em>Gold</em> by Isaac Asimov</h2>
<p>I re­mem­ber read­ing <em>Gold</em> for the ﬁrst time on a camp­ing trip to the <em>Great Sand Dunes National Park</em>.
I was cap­ti­vated by the dy­namic short-sto­ries con­tained within.
At the time I be­lieved them to be pure sci­ence ﬁc­tion.</p>
<p>Reading <em>Gold</em> for the sec­ond time re­vealed how wrong I was.
More than one of the ﬁc­tional ideas Asimov pre­sents in the col­lec­tion of short sto­ries has since come to re­al­ity.</p>
<h2><em>My Grandma Asked Me to Tell You She’s Sorry</em> by Fredrik Backman</h2>
<p>I’ll ad­mit: this novel was chal­leng­ing to ﬁn­ish.</p>
<p><strong>Pros:</strong> the main char­ac­ter (an al­most-eight year old girl) is ex­pertly con­veyed in the writ­ing.
Back­man makes clever use of run-on sen­tences to con­vey frus­tra­tion, which is a lit­er­ary de­vice I’ve never seen be­fore.
Fur­ther, the char­ac­ter has an un­usual de­gree of in­sight into every­day as­pects of life.
I found my­self ap­pre­ci­at­ing <em>my</em> every day more plac­ing my­self in her shoes.</p>
<p><strong>Cons:</strong> it takes a long time for the in­sights to start com­ing.
An ex­ces­sive de­gree of unim­por­tant in­for­ma­tion is thrown at you right out of the gate.
I had dif­ﬁ­culty main­tain­ing my fo­cus un­til the sec­ond half of the book (when stuff starts heat­ing up).</p>
<h2><em>Lessons in Chemistry</em> by Bonnie Garmus</h2>
<p>Garmus knocked it out of the park for her ﬁrst novel.
It fol­lows Elizabeth Zott, who, in all re­spects is the per­son I want to be when I grow up.</p>
<p><em>Lessons in Chemistry</em> is a tes­ta­ment to the value of sub­trac­tion.
The orig­i­nal man­u­script of this book was about twice as long as the ﬁ­nal pub­lished ver­sion.
The au­thor was forced to con­dense it down af­ter get­ting re­jected from sev­eral pub­lish­ers.
It ef­fort shows.</p>
<p>I can­not cap­ture the nov­el’s el­e­gance of prose, cre­ativ­ity in ﬁc­tion, or im­por­tance in moral­ity here.
<strong>You need to read this book</strong>.</p>]]></description>
            <content:encoded><![CDATA[<h1>The Books I Read in February and March 2025</h1>
<p>In a world con­tain­ing TikTok, YouTube and a vast ar­ray of other apps that proﬁt off hu­man at­ten­tion, it takes ef­fort to place our fo­cus on things that truly ben­e­ﬁt our minds.</p>
<p>At least, that is what I would I say on LinkedIn as an ex­pla­na­tion for why I read.
In re­al­ity: <strong>I sim­ply en­joy it</strong>.</p>
<p>Each month I go back and sum­ma­rize what I’ve learned from the books I’ve read.
In do­ing so, I’ve found that I get both deeper in­sights and greater joy from the lit­er­a­ture.</p>
<h3>Wait! What’s Up With February?</h3>
<p>While I only ﬁn­ished one book in February, I started read­ing sev­eral oth­ers that I ended up ﬁn­ish­ing in March.
It only felt right to com­bine the two months into a sin­gle post.</p>
<h2><em>The Dinner</em> by Herman Koch</h2>
<p>Someone con­vinced me to read this book based on its sim­ple premise: the en­tire novel is an ac­count of a sin­gle din­ner.
I was skep­ti­cal.
It is a big book.
How could they pos­si­bly ﬁt that much plot into a sin­gle din­ner?</p>
<p>The an­swer: Koch drip-feeds you in­for­ma­tion about the eclec­tic char­ac­ters in at­ten­dance.
They gain an ad­di­tional di­men­sion that you could­n’t achieve in an­other, more ac­tion-packed book.</p>
<p>I won’t spoil any­thing here, but the end­ing was a com­plete sur­prise to me.
I did <em>not</em> see that com­ing.</p>
<h2><em>Gold</em> by Isaac Asimov</h2>
<p>I re­mem­ber read­ing <em>Gold</em> for the ﬁrst time on a camp­ing trip to the <em>Great Sand Dunes National Park</em>.
I was cap­ti­vated by the dy­namic short-sto­ries con­tained within.
At the time I be­lieved them to be pure sci­ence ﬁc­tion.</p>
<p>Reading <em>Gold</em> for the sec­ond time re­vealed how wrong I was.
More than one of the ﬁc­tional ideas Asimov pre­sents in the col­lec­tion of short sto­ries has since come to re­al­ity.</p>
<h2><em>My Grandma Asked Me to Tell You She’s Sorry</em> by Fredrik Backman</h2>
<p>I’ll ad­mit: this novel was chal­leng­ing to ﬁn­ish.</p>
<p><strong>Pros:</strong> the main char­ac­ter (an al­most-eight year old girl) is ex­pertly con­veyed in the writ­ing.
Back­man makes clever use of run-on sen­tences to con­vey frus­tra­tion, which is a lit­er­ary de­vice I’ve never seen be­fore.
Fur­ther, the char­ac­ter has an un­usual de­gree of in­sight into every­day as­pects of life.
I found my­self ap­pre­ci­at­ing <em>my</em> every day more plac­ing my­self in her shoes.</p>
<p><strong>Cons:</strong> it takes a long time for the in­sights to start com­ing.
An ex­ces­sive de­gree of unim­por­tant in­for­ma­tion is thrown at you right out of the gate.
I had dif­ﬁ­culty main­tain­ing my fo­cus un­til the sec­ond half of the book (when stuff starts heat­ing up).</p>
<h2><em>Lessons in Chemistry</em> by Bonnie Garmus</h2>
<p>Garmus knocked it out of the park for her ﬁrst novel.
It fol­lows Elizabeth Zott, who, in all re­spects is the per­son I want to be when I grow up.</p>
<p><em>Lessons in Chemistry</em> is a tes­ta­ment to the value of sub­trac­tion.
The orig­i­nal man­u­script of this book was about twice as long as the ﬁ­nal pub­lished ver­sion.
The au­thor was forced to con­dense it down af­ter get­ting re­jected from sev­eral pub­lish­ers.
It ef­fort shows.</p>
<p>I can­not cap­ture the nov­el’s el­e­gance of prose, cre­ativ­ity in ﬁc­tion, or im­por­tance in moral­ity here.
<strong>You need to read this book</strong>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The One Hard Thing]]></title>
            <link>https://elijahpotter.dev/articles/the_one_hard_thing</link>
            <guid>https://elijahpotter.dev/articles/the_one_hard_thing</guid>
            <pubDate>Thu, 27 Mar 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The One Hard Thing</h1>
<p>People (myself in­cluded) love to make cas­cad­ing lists of tasks.
Ac­tions, each of which are needed to im­prove a prod­uct, re­lease a fea­ture, or man­age peo­ple.
These lists tend to ex­plode in size, as more is asked of you.</p>
<p>There’s one prob­lem: I have only eight hours in a work­ing day.
With so lit­tle time, how can I be ex­pected to both main­tain ex­ist­ing pro­jects (bug ﬁxes, etc.) while con­sis­tently de­liv­er­ing new func­tion­al­ity and main­tain­ing a fast it­er­a­tive loop?</p>
<h2>Insight</h2>
<p>I’ve had two key in­sights that have led to some tremen­dous per­sonal pro­duc­tiv­ity gains.</p>
<p><strong>First:</strong> I’ve ob­served that the dif­ﬁ­culty of my tasks tends to fol­low a <a href="https://en.wikipedia.org/wiki/Pareto_distribution">Pareto dis­tri­b­u­tion</a>.
A small num­ber of tasks are shock­ingly dif­ﬁ­cult to solve, while a much larger set boil down to com­mu­ni­ca­tion.</p>
<p><strong>Second:</strong> the morn­ing is my most pro­duc­tive time of day.
For some rea­son, I’m able to tackle prob­lems with an oth­er­wise un­usual men­tal clar­ity.
I sus­pect it’s some­thing to do with the nat­ural hu­man cir­ca­dian rhythm or di­ges­tion.</p>
<h2>Leveraging the Pattern</h2>
<p>Motivated by these two ob­ser­va­tions, I’ve de­vel­oped a habit: I ded­i­cate each morn­ing to its own <strong>dif­ﬁ­cult prob­lem</strong>.
This is a sin­gle task that is cho­sen specif­i­cally to stretch my lim­its.
I call this my daily <strong><em>one hard thing</em></strong>.</p>
<p>Previous ex­am­ples:</p>
<ul>
<li>Developing the ﬁrst ver­sion of <a href="https://writewithharper.com/docs/integrations/language-server"><code>harper-ls</code></a> which is now used by thou­sands of de­vel­op­ers daily.</li>
<li>Building the ﬁrst 80% of the <a href="https://writewithharper.com/docs/integrations/obsidian">Harper Obsidian plu­gin</a>, which is also widely used.</li>
<li>Grokking how <a href="https://github.com/Automattic/jetpack">Jetpack’s Write Brief with <span class="small-caps">AI</span></a> fea­ture works (before I joined Automattic).</li>
<li>Compiling Harper to WebAssembly and em­bed­ding it some­where no one has put a gram­mar checker be­fore: <em>in­side the web browser</em>.</li>
</ul>
<h2>Think About It</h2>
<p>I im­plore you: think about how you work.
How is your en­ergy af­fected by the things you do in the day-to-day?
If you en­joy your eight hours, maybe the rest will be bet­ter too.</p>]]></description>
            <content:encoded><![CDATA[<h1>The One Hard Thing</h1>
<p>People (myself in­cluded) love to make cas­cad­ing lists of tasks.
Ac­tions, each of which are needed to im­prove a prod­uct, re­lease a fea­ture, or man­age peo­ple.
These lists tend to ex­plode in size, as more is asked of you.</p>
<p>There’s one prob­lem: I have only eight hours in a work­ing day.
With so lit­tle time, how can I be ex­pected to both main­tain ex­ist­ing pro­jects (bug ﬁxes, etc.) while con­sis­tently de­liv­er­ing new func­tion­al­ity and main­tain­ing a fast it­er­a­tive loop?</p>
<h2>Insight</h2>
<p>I’ve had two key in­sights that have led to some tremen­dous per­sonal pro­duc­tiv­ity gains.</p>
<p><strong>First:</strong> I’ve ob­served that the dif­ﬁ­culty of my tasks tends to fol­low a <a href="https://en.wikipedia.org/wiki/Pareto_distribution">Pareto dis­tri­b­u­tion</a>.
A small num­ber of tasks are shock­ingly dif­ﬁ­cult to solve, while a much larger set boil down to com­mu­ni­ca­tion.</p>
<p><strong>Second:</strong> the morn­ing is my most pro­duc­tive time of day.
For some rea­son, I’m able to tackle prob­lems with an oth­er­wise un­usual men­tal clar­ity.
I sus­pect it’s some­thing to do with the nat­ural hu­man cir­ca­dian rhythm or di­ges­tion.</p>
<h2>Leveraging the Pattern</h2>
<p>Motivated by these two ob­ser­va­tions, I’ve de­vel­oped a habit: I ded­i­cate each morn­ing to its own <strong>dif­ﬁ­cult prob­lem</strong>.
This is a sin­gle task that is cho­sen specif­i­cally to stretch my lim­its.
I call this my daily <strong><em>one hard thing</em></strong>.</p>
<p>Previous ex­am­ples:</p>
<ul>
<li>Developing the ﬁrst ver­sion of <a href="https://writewithharper.com/docs/integrations/language-server"><code>harper-ls</code></a> which is now used by thou­sands of de­vel­op­ers daily.</li>
<li>Building the ﬁrst 80% of the <a href="https://writewithharper.com/docs/integrations/obsidian">Harper Obsidian plu­gin</a>, which is also widely used.</li>
<li>Grokking how <a href="https://github.com/Automattic/jetpack">Jetpack’s Write Brief with <span class="small-caps">AI</span></a> fea­ture works (before I joined Automattic).</li>
<li>Compiling Harper to WebAssembly and em­bed­ding it some­where no one has put a gram­mar checker be­fore: <em>in­side the web browser</em>.</li>
</ul>
<h2>Think About It</h2>
<p>I im­plore you: think about how you work.
How is your en­ergy af­fected by the things you do in the day-to-day?
If you en­joy your eight hours, maybe the rest will be bet­ter too.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[3 Traits of Good Test Suites]]></title>
            <link>https://elijahpotter.dev/articles/3_traits_of_good_test_suites</link>
            <guid>https://elijahpotter.dev/articles/3_traits_of_good_test_suites</guid>
            <pubDate>Sun, 23 Mar 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>3 Traits of Good Test Suites</h1>
<p>As ev­i­denced by my pre­vi­ous posts on <a href="https://elijahpotter.dev/articles/LLM_assisted_fuzzing">LLM-Assisted Fuzzing</a>, I’ve been ded­i­cat­ing a lot of my men­tal band­width to main­tain­ing a low false-pos­i­tive rate while we im­prove <a href="https://github.com/automattic/harper">Harper’s rule cov­er­age</a>. Part of that is through fuzzing and dog­food­ing, some can be through sta­tis­tics, but the ﬁrst lines of de­fense will con­tinue to be unit and in­te­gra­tion test­ing. This past week par­tic­u­larly, I’ve been read­ing up on how other <a href="https://github.com/rust-lang/rust-analyzer">big lint­ing pro­grams</a> ap­proach this prob­lem.</p>
<h2>1. Test Features, Not Code</h2>
<p>I of­ten ask my­self: am I spend­ing more time think­ing or talk­ing about the thing, or am I spend­ing more time do­ing the thing? I’ve per­son­ally seen how pro­jects fall into de­cline be­cause their lead­ers are more in­ter­ested in plan­ning than do­ing.</p>
<p>In the con­text of soft­ware test­ing, this mantra is trans­formed into<span class="push-double"></span> <span class="pull-double">“</span>test fea­tures, not code.” To my eye, good code is ﬂex­i­ble and self-ex­plana­tory. Tests that hook deeply into ap­pli­ca­tion or li­brary in­ter­nals make code less ﬂex­i­ble and harder to read.</p>
<p>I es­pe­cially like Alex Kladov’s heuris­tic for this: the neural net­work test.</p>
<blockquote>
<p><span class="pull-double">“</span>Can you re-use the test suite if your en­tire soft­ware is re­placed with an opaque neural net­work?” - Alex Kladov</p>
</blockquote>
<p>It’s not a ques­tion of whether a neural net­work would pass the test suite, only whether the test suite could work for it. If the an­swer is no, the tests are likely test­ing code, not fea­tures.</p>
<h2>2. Performance</h2>
<p>The speed at which you can build and run tests (unit, sta­tic, in­te­gra­tion, etc.) is a force-mul­ti­plier for every­thing else. You can val­i­date ideas sooner, run <span class="small-caps">CI</span> faster, and get con­trib­u­tors on-boarded in less time.</p>
<p>Our goal to be fast at run­time dove­tails quite nicely into this, so it’s some­thing Harper al­ready does quite well. Moving for­ward, we need to make sure that we don’t rely on any kind of <span class="small-caps">IO</span> in our tests, since that con­tin­ues to be the slow­est part of most Harper in­te­gra­tions.</p>
<h2>3. Good Assertions</h2>
<p>We can sim­plify pro­grams like Harper down into a sin­gle func­tion which con­sumes text and re­turns a list of ob­served prob­lems.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">harper</span>(text: <span class="hljs-type">String</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">Vec</span>&#x3C;Lint>{
    <span class="hljs-comment">// Implementation details...</span>
}
</code></pre>
<p>Most test­ing we are in­ter­ested can be done with as­ser­tion func­tions that de­clare what qual­i­ties the out­put should have with a spe­ciﬁc in­put.</p>
<p>For ex­am­ple, we have a func­tion called as­sert_­sug­ges­tion_re­sult, which runs a gram­mat­i­cally in­cor­rect string through Harper, ap­plies the ﬁrst re­sult­ing sug­ges­tion and checks whether the edited string matches a given value.</p>
<pre><code class="hljs language-rust"><span class="hljs-comment">/// An example of a test that uses assert_suggestion_result</span>
<span class="hljs-meta">#[test]</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">catches_less_then</span>() {
    <span class="hljs-title function_ invoke__">assert_suggestion_result</span>(
        <span class="hljs-string">"I eat less then you."</span>,
        ThenThan::<span class="hljs-title function_ invoke__">default</span>(),
        <span class="hljs-string">"I eat less than you."</span>,
    );
}
</code></pre>
<p>It’s also vi­tal that these as­ser­tions show good, read­able er­ror mes­sages when they fail. Each time I’ve im­proved their logs, I get un­prompted pos­i­tive feed­back from con­trib­u­tors.</p>
<p>Moving for­ward, I’d like to cre­ate a more di­verse ar­ray as­ser­tions like this, as well as bet­ter-doc­u­ment their use. A lot of the cur­rent back-and-forth for rule con­tri­bu­tions is re­lated this.</p>
<h2>Wait! I Disagree</h2>
<p>I hope some­one does. Good test suites are some­thing I’m con­tin­u­ing <a href="https://automattic.com/creed/never-stop-learning/">to learn how to build</a>. I un­der­stand that a lot of what I’ve said here does­n’t ap­ply to other kinds of ap­pli­ca­tions or code­bases. If there’s nu­ance I’m not cov­er­ing here, let me know!</p><p></p>]]></description>
            <content:encoded><![CDATA[<h1>3 Traits of Good Test Suites</h1>
<p>As ev­i­denced by my pre­vi­ous posts on <a href="https://elijahpotter.dev/articles/LLM_assisted_fuzzing">LLM-Assisted Fuzzing</a>, I’ve been ded­i­cat­ing a lot of my men­tal band­width to main­tain­ing a low false-pos­i­tive rate while we im­prove <a href="https://github.com/automattic/harper">Harper’s rule cov­er­age</a>. Part of that is through fuzzing and dog­food­ing, some can be through sta­tis­tics, but the ﬁrst lines of de­fense will con­tinue to be unit and in­te­gra­tion test­ing. This past week par­tic­u­larly, I’ve been read­ing up on how other <a href="https://github.com/rust-lang/rust-analyzer">big lint­ing pro­grams</a> ap­proach this prob­lem.</p>
<h2>1. Test Features, Not Code</h2>
<p>I of­ten ask my­self: am I spend­ing more time think­ing or talk­ing about the thing, or am I spend­ing more time do­ing the thing? I’ve per­son­ally seen how pro­jects fall into de­cline be­cause their lead­ers are more in­ter­ested in plan­ning than do­ing.</p>
<p>In the con­text of soft­ware test­ing, this mantra is trans­formed into<span class="push-double"></span> <span class="pull-double">“</span>test fea­tures, not code.” To my eye, good code is ﬂex­i­ble and self-ex­plana­tory. Tests that hook deeply into ap­pli­ca­tion or li­brary in­ter­nals make code less ﬂex­i­ble and harder to read.</p>
<p>I es­pe­cially like Alex Kladov’s heuris­tic for this: the neural net­work test.</p>
<blockquote>
<p><span class="pull-double">“</span>Can you re-use the test suite if your en­tire soft­ware is re­placed with an opaque neural net­work?” - Alex Kladov</p>
</blockquote>
<p>It’s not a ques­tion of whether a neural net­work would pass the test suite, only whether the test suite could work for it. If the an­swer is no, the tests are likely test­ing code, not fea­tures.</p>
<h2>2. Performance</h2>
<p>The speed at which you can build and run tests (unit, sta­tic, in­te­gra­tion, etc.) is a force-mul­ti­plier for every­thing else. You can val­i­date ideas sooner, run <span class="small-caps">CI</span> faster, and get con­trib­u­tors on-boarded in less time.</p>
<p>Our goal to be fast at run­time dove­tails quite nicely into this, so it’s some­thing Harper al­ready does quite well. Moving for­ward, we need to make sure that we don’t rely on any kind of <span class="small-caps">IO</span> in our tests, since that con­tin­ues to be the slow­est part of most Harper in­te­gra­tions.</p>
<h2>3. Good Assertions</h2>
<p>We can sim­plify pro­grams like Harper down into a sin­gle func­tion which con­sumes text and re­turns a list of ob­served prob­lems.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">harper</span>(text: <span class="hljs-type">String</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">Vec</span>&#x3C;Lint>{
    <span class="hljs-comment">// Implementation details...</span>
}
</code></pre>
<p>Most test­ing we are in­ter­ested can be done with as­ser­tion func­tions that de­clare what qual­i­ties the out­put should have with a spe­ciﬁc in­put.</p>
<p>For ex­am­ple, we have a func­tion called as­sert_­sug­ges­tion_re­sult, which runs a gram­mat­i­cally in­cor­rect string through Harper, ap­plies the ﬁrst re­sult­ing sug­ges­tion and checks whether the edited string matches a given value.</p>
<pre><code class="hljs language-rust"><span class="hljs-comment">/// An example of a test that uses assert_suggestion_result</span>
<span class="hljs-meta">#[test]</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">catches_less_then</span>() {
    <span class="hljs-title function_ invoke__">assert_suggestion_result</span>(
        <span class="hljs-string">"I eat less then you."</span>,
        ThenThan::<span class="hljs-title function_ invoke__">default</span>(),
        <span class="hljs-string">"I eat less than you."</span>,
    );
}
</code></pre>
<p>It’s also vi­tal that these as­ser­tions show good, read­able er­ror mes­sages when they fail. Each time I’ve im­proved their logs, I get un­prompted pos­i­tive feed­back from con­trib­u­tors.</p>
<p>Moving for­ward, I’d like to cre­ate a more di­verse ar­ray as­ser­tions like this, as well as bet­ter-doc­u­ment their use. A lot of the cur­rent back-and-forth for rule con­tri­bu­tions is re­lated this.</p>
<h2>Wait! I Disagree</h2>
<p>I hope some­one does. Good test suites are some­thing I’m con­tin­u­ing <a href="https://automattic.com/creed/never-stop-learning/">to learn how to build</a>. I un­der­stand that a lot of what I’ve said here does­n’t ap­ply to other kinds of ap­pli­ca­tions or code­bases. If there’s nu­ance I’m not cov­er­ing here, let me know!</p><p></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LLM Assisted Fuzzing]]></title>
            <link>https://elijahpotter.dev/articles/LLM_assisted_fuzzing</link>
            <guid>https://elijahpotter.dev/articles/LLM_assisted_fuzzing</guid>
            <pubDate>Fri, 21 Mar 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>LLM-Assisted Fuzzing: A New Approach to False-Positives</h1>
<blockquote>
<p><strong>Preface</strong>: this post was ac­tu­ally writ­ten on February 24th, de­spite be­ing pub­lished to­day.</p>
</blockquote>
<p>I’d like to do some re­ﬂec­tion on how we can im­prove <a href="https://github.com/automattic/Harper">Harper’s</a> out­put. It is fun­da­men­tally a process prob­lem. For con­text, let us go through a con­trived ex­am­ple.</p>
<img src="/images/sumner_screenshot_error.png" alt="Sumner Evans reported a bug to me while writing a tech talk about CI and CD">
<ol>
<li>Somebody (usually me) dis­cov­ers an er­ror in their writ­ing that Harper did not catch. This is a con­crete string like<span class="push-double"></span> <span class="pull-double">“</span>I have a a care in the world”.</li>
<li>I look for other forms of the er­ror to see if I can gen­er­al­ize it to more cases. Here, we see an in­def­i­nite ar­ti­cle re­peat­ing it­self. We could just look for the ex­act string<span class="push-double"></span> <span class="pull-double">“</span>a a” and call it day, but we can cover more cases by look­ing for <indefinite article> <indefinite article>. In fact, we can gen­er­al­ize it even fur­ther. In this case, writ­ers want to avoid rep­e­ti­tions of any word that is not a ho­mo­graph.</indefinite></indefinite></li>
<li>Once the code is writ­ten that de­ﬁnes the rule, I push it out, and wait to hear from users if any false-pos­i­tives arise.</li>
</ol>
<p>This is a ﬂawed sys­tem, par­tic­u­larly that last step. I’ve found some­thing a bit bet­ter.</p>
<p>I’ve been ex­per­i­ment­ing more with <span class="small-caps">LLM</span> tool­ing, par­tic­u­larly Ollama. With it, I’ve set up a use­ful sys­tem for iden­ti­fy­ing false-pos­i­tives be­fore push­ing changes out to users. I like to call it<span class="push-double"></span> <span class="pull-double">“</span><span class="small-caps">LLM</span> Assisted Fuzzing”.</p>
<h2>LLM-Assisted Fuzzing</h2>
<p>Fuzzing is a com­mon prac­tice wherein en­gi­neers rapidly and con­tin­u­ously push ran­dom data through their sys­tem to de­ter­min­is­ti­cally check if it is be­hav­ing cor­rectly (i.e. not crash­ing). If a par­tic­u­lar batch of ran­dom data causes an is­sue, it is el­e­vated to an en­gi­neer.</p>
<p>Harper as a pro­gram stands in a some­what unique po­si­tion: it an­a­lyzes nat­ural lan­guage to iden­tify gram­mat­i­cal er­rors. LLMs ex­ist as a di­rect com­ple­ment to it: they gen­er­ate<span class="push-double"></span> <span class="pull-double">“</span>natural” lan­guage. Do you see where I am go­ing with this?</p>
<p>Let’s be clear: lan­guage mod­els do not gen­er­ate gram­mat­i­cally cor­rect text even a good amount of the time. As a re­sult, their out­put is use­ful as a proxy for our user’s writ­ten work. If we can im­ple­ment a fea­ture that re­li­ably cor­rects (without false-pos­i­tives) an <span class="small-caps">LLM</span><span class="push-single"></span><span class="pull-single">’</span>s out­put, we can rea­son­ably ex­pect it to work for our user’s text.</p>
<h2>My Initial Attempt at LLM-Assisted Fuzzing</h2>
<p>I started work­ing on this Friday morn­ing, and ﬁ­nally have what I would call a<span class="push-double"></span> <span class="pull-double">“</span>ﬁrst draft” of an LLM-assisted fuzzing sys­tem for iden­ti­fy­ing false-pos­i­tives. Here’s how it works:</p>
<p>I start a lo­cal Ollama server, pre­loaded with sev­eral mod­els (which I’ll ex­pand on more be­low).</p>
<p>I run a bash script that re­peat­edly feeds the same prompt into the mod­els <a href="https://elijahpotter.dev/articles/prompting_large_language_models_in_bash_scripts">us­ing a com­mand-line client for Ollama</a>. This gives me a large dataset of re­sponses to the prompt. I’m not look­ing for the se­man­tic con­tent of the re­sponse. I’m in­ter­ested in ex­ten­sive cov­er­age of whichever lin­guis­tic do­main I’ve de­ﬁned in the prompt. I can run this as long as I like be­fore go­ing on to the next step.</p>
<p>Once I have a large dataset, I run a sep­a­rate script that runs Harper over each re­sponse. If it ﬁnds an er­ror, the script opens the re­sponse in <a href="https://writewithharper.com/docs/about">the Harper ed­i­tor</a> for fur­ther in­spec­tion.</p>
<p>The biggest is­sue with this work­ﬂow right now: re­gard­less of my prompts, the LLMs con­tinue to emit ac­tual gram­mat­i­cal er­rors. This means that when I go through this process, I get at least as many true-pos­i­tives as false-pos­i­tives. It also does noth­ing for false-neg­a­tives.</p>
<h2>Tweaks</h2>
<p>There are a cou­ple pa­ra­me­ters here that I’m go­ing to con­tinue to tweak as the work­ﬂow be­comes more ma­ture. So far, I’ve been us­ing <code>deepseek-r1:32b</code> and <code>mistral-small</code>. DeepSeek tends to use more col­or­ful lan­guage than Mistral, which runs much faster on my ma­chine.</p>
<p>I’ve also been it­er­at­ing on the prompt as I go. Here’s the lat­est ver­sion:</p>
<pre><code class="hljs language-plaintext">Please write a long, drawn-out essay on a topic of your choosing.
Write out the essay multiple times before giving me your final draft.
Your final draft should be grammatically perfect, clearly demonstrating that you put time and effort into your submission.

I repeat: I do not want to see anything other than your final drafts.
Do not give me any intermediate work.

Furthermore, do not use any characters or words other than English. 
I do not want Chinese or anything else.

I have all the time in the world. I'm perfectly fine with waiting a little longer if it means you give me something you are proud of.
</code></pre>
<p>When I move on to tack­ling false-neg­a­tives with this, I’ll be able to prompt the model to give me spe­ciﬁc kinds of er­rors, which makes it sig­nif­i­cantly more use­ful than try­ing to track down ex­ist­ing work.</p>]]></description>
            <content:encoded><![CDATA[<h1>LLM-Assisted Fuzzing: A New Approach to False-Positives</h1>
<blockquote>
<p><strong>Preface</strong>: this post was ac­tu­ally writ­ten on February 24th, de­spite be­ing pub­lished to­day.</p>
</blockquote>
<p>I’d like to do some re­ﬂec­tion on how we can im­prove <a href="https://github.com/automattic/Harper">Harper’s</a> out­put. It is fun­da­men­tally a process prob­lem. For con­text, let us go through a con­trived ex­am­ple.</p>
<img src="/images/sumner_screenshot_error.png" alt="Sumner Evans reported a bug to me while writing a tech talk about CI and CD">
<ol>
<li>Somebody (usually me) dis­cov­ers an er­ror in their writ­ing that Harper did not catch. This is a con­crete string like<span class="push-double"></span> <span class="pull-double">“</span>I have a a care in the world”.</li>
<li>I look for other forms of the er­ror to see if I can gen­er­al­ize it to more cases. Here, we see an in­def­i­nite ar­ti­cle re­peat­ing it­self. We could just look for the ex­act string<span class="push-double"></span> <span class="pull-double">“</span>a a” and call it day, but we can cover more cases by look­ing for <indefinite article> <indefinite article>. In fact, we can gen­er­al­ize it even fur­ther. In this case, writ­ers want to avoid rep­e­ti­tions of any word that is not a ho­mo­graph.</indefinite></indefinite></li>
<li>Once the code is writ­ten that de­ﬁnes the rule, I push it out, and wait to hear from users if any false-pos­i­tives arise.</li>
</ol>
<p>This is a ﬂawed sys­tem, par­tic­u­larly that last step. I’ve found some­thing a bit bet­ter.</p>
<p>I’ve been ex­per­i­ment­ing more with <span class="small-caps">LLM</span> tool­ing, par­tic­u­larly Ollama. With it, I’ve set up a use­ful sys­tem for iden­ti­fy­ing false-pos­i­tives be­fore push­ing changes out to users. I like to call it<span class="push-double"></span> <span class="pull-double">“</span><span class="small-caps">LLM</span> Assisted Fuzzing”.</p>
<h2>LLM-Assisted Fuzzing</h2>
<p>Fuzzing is a com­mon prac­tice wherein en­gi­neers rapidly and con­tin­u­ously push ran­dom data through their sys­tem to de­ter­min­is­ti­cally check if it is be­hav­ing cor­rectly (i.e. not crash­ing). If a par­tic­u­lar batch of ran­dom data causes an is­sue, it is el­e­vated to an en­gi­neer.</p>
<p>Harper as a pro­gram stands in a some­what unique po­si­tion: it an­a­lyzes nat­ural lan­guage to iden­tify gram­mat­i­cal er­rors. LLMs ex­ist as a di­rect com­ple­ment to it: they gen­er­ate<span class="push-double"></span> <span class="pull-double">“</span>natural” lan­guage. Do you see where I am go­ing with this?</p>
<p>Let’s be clear: lan­guage mod­els do not gen­er­ate gram­mat­i­cally cor­rect text even a good amount of the time. As a re­sult, their out­put is use­ful as a proxy for our user’s writ­ten work. If we can im­ple­ment a fea­ture that re­li­ably cor­rects (without false-pos­i­tives) an <span class="small-caps">LLM</span><span class="push-single"></span><span class="pull-single">’</span>s out­put, we can rea­son­ably ex­pect it to work for our user’s text.</p>
<h2>My Initial Attempt at LLM-Assisted Fuzzing</h2>
<p>I started work­ing on this Friday morn­ing, and ﬁ­nally have what I would call a<span class="push-double"></span> <span class="pull-double">“</span>ﬁrst draft” of an LLM-assisted fuzzing sys­tem for iden­ti­fy­ing false-pos­i­tives. Here’s how it works:</p>
<p>I start a lo­cal Ollama server, pre­loaded with sev­eral mod­els (which I’ll ex­pand on more be­low).</p>
<p>I run a bash script that re­peat­edly feeds the same prompt into the mod­els <a href="https://elijahpotter.dev/articles/prompting_large_language_models_in_bash_scripts">us­ing a com­mand-line client for Ollama</a>. This gives me a large dataset of re­sponses to the prompt. I’m not look­ing for the se­man­tic con­tent of the re­sponse. I’m in­ter­ested in ex­ten­sive cov­er­age of whichever lin­guis­tic do­main I’ve de­ﬁned in the prompt. I can run this as long as I like be­fore go­ing on to the next step.</p>
<p>Once I have a large dataset, I run a sep­a­rate script that runs Harper over each re­sponse. If it ﬁnds an er­ror, the script opens the re­sponse in <a href="https://writewithharper.com/docs/about">the Harper ed­i­tor</a> for fur­ther in­spec­tion.</p>
<p>The biggest is­sue with this work­ﬂow right now: re­gard­less of my prompts, the LLMs con­tinue to emit ac­tual gram­mat­i­cal er­rors. This means that when I go through this process, I get at least as many true-pos­i­tives as false-pos­i­tives. It also does noth­ing for false-neg­a­tives.</p>
<h2>Tweaks</h2>
<p>There are a cou­ple pa­ra­me­ters here that I’m go­ing to con­tinue to tweak as the work­ﬂow be­comes more ma­ture. So far, I’ve been us­ing <code>deepseek-r1:32b</code> and <code>mistral-small</code>. DeepSeek tends to use more col­or­ful lan­guage than Mistral, which runs much faster on my ma­chine.</p>
<p>I’ve also been it­er­at­ing on the prompt as I go. Here’s the lat­est ver­sion:</p>
<pre><code class="hljs language-plaintext">Please write a long, drawn-out essay on a topic of your choosing.
Write out the essay multiple times before giving me your final draft.
Your final draft should be grammatically perfect, clearly demonstrating that you put time and effort into your submission.

I repeat: I do not want to see anything other than your final drafts.
Do not give me any intermediate work.

Furthermore, do not use any characters or words other than English. 
I do not want Chinese or anything else.

I have all the time in the world. I'm perfectly fine with waiting a little longer if it means you give me something you are proud of.
</code></pre>
<p>When I move on to tack­ling false-neg­a­tives with this, I’ll be able to prompt the model to give me spe­ciﬁc kinds of er­rors, which makes it sig­nif­i­cantly more use­ful than try­ing to track down ex­ist­ing work.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Harper Is in Cursor and Visual Studio Code]]></title>
            <link>https://elijahpotter.dev/articles/harper_is_in_cursor_and_visual_studio_code</link>
            <guid>https://elijahpotter.dev/articles/harper_is_in_cursor_and_visual_studio_code</guid>
            <pubDate>Wed, 19 Mar 2025 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Harper Is in Cursor and Visual Studio Code</h1>
<p>Harper is a gram­mar checker for de­vel­op­ers. Its roots are in code ed­i­tors like Neovim, Helix, Zed, and you guessed it—Vi­sual Studio Code. I don’t think a lot of my fol­low­ers know this, so I thought I’d give it a lit­tle shout-out here.</p>
<p>Since Cursor and Windsurf are forks of Visual Studio Code, Harper is avail­able for both <a href="https://marketplace.visualstudio.com/items?itemName=elijah-potter.harper">via the Visual Studio Marketplace</a>. Give us a good rat­ing over there if you in­stall the plu­gin and end up lik­ing it!</p>
<img src="/images/harper_vscode_screenshot.png" alt="A screenshot of the Harper Visual Studio Code extension checking its own source code">
<h2>How Does It Work?</h2>
<p>Harper works sim­i­larly to a num­ber of other ex­ten­sions you may have in­stalled. Like Pylance or ESLint, it runs in the back­ground as <a href="https://microsoft.github.io/language-server-protocol/">a lan­guage server</a>. Each time a mod­i­ﬁ­ca­tion is made to your doc­u­ment, it reads your com­ments for gram­mat­i­cal mis­takes and ty­pos and dis­plays them as er­rors or warn­ings (it’s con­ﬁg­urable).</p>
<h2>Does It Support My Programming Language?</h2>
<p>The Harper lan­guage server, thanks to con­tri­bu­tions from the com­mu­nity, sup­ports a pretty <a href="https://writewithharper.com/docs/integrations/language-server#Supported-Languages">wide range of pro­gram­ming and markup lan­guages</a>. We’ve only re­cently added sup­port for <span class="small-caps">PHP</span>, so if that’s your thing, know you’re in some­what un­charted ter­ri­tory. If you ﬁnd is­sues, <a href="https://github.com/Automattic/harper/issues">let us know</a>.</p>]]></description>
            <content:encoded><![CDATA[<h1>Harper Is in Cursor and Visual Studio Code</h1>
<p>Harper is a gram­mar checker for de­vel­op­ers. Its roots are in code ed­i­tors like Neovim, Helix, Zed, and you guessed it—Vi­sual Studio Code. I don’t think a lot of my fol­low­ers know this, so I thought I’d give it a lit­tle shout-out here.</p>
<p>Since Cursor and Windsurf are forks of Visual Studio Code, Harper is avail­able for both <a href="https://marketplace.visualstudio.com/items?itemName=elijah-potter.harper">via the Visual Studio Marketplace</a>. Give us a good rat­ing over there if you in­stall the plu­gin and end up lik­ing it!</p>
<img src="/images/harper_vscode_screenshot.png" alt="A screenshot of the Harper Visual Studio Code extension checking its own source code">
<h2>How Does It Work?</h2>
<p>Harper works sim­i­larly to a num­ber of other ex­ten­sions you may have in­stalled. Like Pylance or ESLint, it runs in the back­ground as <a href="https://microsoft.github.io/language-server-protocol/">a lan­guage server</a>. Each time a mod­i­ﬁ­ca­tion is made to your doc­u­ment, it reads your com­ments for gram­mat­i­cal mis­takes and ty­pos and dis­plays them as er­rors or warn­ings (it’s con­ﬁg­urable).</p>
<h2>Does It Support My Programming Language?</h2>
<p>The Harper lan­guage server, thanks to con­tri­bu­tions from the com­mu­nity, sup­ports a pretty <a href="https://writewithharper.com/docs/integrations/language-server#Supported-Languages">wide range of pro­gram­ming and markup lan­guages</a>. We’ve only re­cently added sup­port for <span class="small-caps">PHP</span>, so if that’s your thing, know you’re in some­what un­charted ter­ri­tory. If you ﬁnd is­sues, <a href="https://github.com/Automattic/harper/issues">let us know</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Never Wait]]></title>
            <link>https://elijahpotter.dev/articles/never_wait</link>
            <guid>https://elijahpotter.dev/articles/never_wait</guid>
            <pubDate>Sat, 01 Mar 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Never Wait</h1>
<blockquote>
<p><em>Preface:</em> This post is specif­i­cally about Pull Requests for <a href="https://github.com/Automattic/harper">Harper</a>.
Read the con­trib­u­tor guide­lines for a pro­ject be­fore open­ing a <span class="small-caps">PR</span>.</p>
</blockquote>
<p>I get it.
Open­ing a pull re­quest is an in­tim­i­dat­ing propo­si­tion.</p>
<p>It is non-triv­ial to put your blood, sweat, and (let’s be hon­est) tears into code and put that onto the in­ter­net for the world to see.
I re­mem­ber my ﬁrst pull-re­quest: it was­n’t pretty ei­ther.</p>
<p>But I want to high­light a cou­ple rea­sons why I want po­ten­tial con­trib­u­tors to open their pull re­quests as early as pos­si­ble.</p>
<h2>Drafts Reduce Duplicate Work</h2>
<p>When a con­trib­u­tor to a pro­ject is as­sess­ing the vi­a­bil­ity or use­ful­ness of some work they’re prepar­ing to do,
most look at ex­ist­ing Pull Requests ﬁrst.
Even if you’re still work­ing on the patch, it’s a great idea to open a<span class="push-double"></span> <span class="pull-double">“</span>draft” <span class="small-caps">PR</span> so no one starts work that could go to waste.</p>
<p>Further, if you have a draft open whose <span class="small-caps">CI</span> is fail­ing, a main­tainer may see it and ﬁx what­ever the prob­lem may be.
I ﬁnd my­self fre­quently brows­ing <a href="https://github.com/Automattic/harper/pulls">Harper’s Pull Requests</a> look­ing for fail­ing builds, since I’m of­ten the best equipped to ﬁnd the is­sue.</p>
<h2>It Helps With Debugging</h2>
<p>I try to make my­self avail­able to con­trib­u­tors in case they have any ques­tions re­gard­ing the ar­chi­tec­ture of Harper.
I’m also game to help de­bug their code, partly be­cause I am of the opin­ion that de­bug­ging is a skill which is best learned by ex­am­ple.</p>
<p>But it is hard for me to de­bug code if I don’t have it.
If a draft <span class="small-caps">PR</span> has been opened, I can usu­ally see the prob­lem with­out even cloning the code, since our <span class="small-caps">CI</span> is so com­pre­hen­sive.</p>
<h2>Conclusion</h2>
<p>So please don’t wait; open that PR.
I am thrilled each time I see a name I don’t rec­og­nize on a GitHub no­ti­ﬁ­ca­tion.</p>]]></description>
            <content:encoded><![CDATA[<h1>Never Wait</h1>
<blockquote>
<p><em>Preface:</em> This post is specif­i­cally about Pull Requests for <a href="https://github.com/Automattic/harper">Harper</a>.
Read the con­trib­u­tor guide­lines for a pro­ject be­fore open­ing a <span class="small-caps">PR</span>.</p>
</blockquote>
<p>I get it.
Open­ing a pull re­quest is an in­tim­i­dat­ing propo­si­tion.</p>
<p>It is non-triv­ial to put your blood, sweat, and (let’s be hon­est) tears into code and put that onto the in­ter­net for the world to see.
I re­mem­ber my ﬁrst pull-re­quest: it was­n’t pretty ei­ther.</p>
<p>But I want to high­light a cou­ple rea­sons why I want po­ten­tial con­trib­u­tors to open their pull re­quests as early as pos­si­ble.</p>
<h2>Drafts Reduce Duplicate Work</h2>
<p>When a con­trib­u­tor to a pro­ject is as­sess­ing the vi­a­bil­ity or use­ful­ness of some work they’re prepar­ing to do,
most look at ex­ist­ing Pull Requests ﬁrst.
Even if you’re still work­ing on the patch, it’s a great idea to open a<span class="push-double"></span> <span class="pull-double">“</span>draft” <span class="small-caps">PR</span> so no one starts work that could go to waste.</p>
<p>Further, if you have a draft open whose <span class="small-caps">CI</span> is fail­ing, a main­tainer may see it and ﬁx what­ever the prob­lem may be.
I ﬁnd my­self fre­quently brows­ing <a href="https://github.com/Automattic/harper/pulls">Harper’s Pull Requests</a> look­ing for fail­ing builds, since I’m of­ten the best equipped to ﬁnd the is­sue.</p>
<h2>It Helps With Debugging</h2>
<p>I try to make my­self avail­able to con­trib­u­tors in case they have any ques­tions re­gard­ing the ar­chi­tec­ture of Harper.
I’m also game to help de­bug their code, partly be­cause I am of the opin­ion that de­bug­ging is a skill which is best learned by ex­am­ple.</p>
<p>But it is hard for me to de­bug code if I don’t have it.
If a draft <span class="small-caps">PR</span> has been opened, I can usu­ally see the prob­lem with­out even cloning the code, since our <span class="small-caps">CI</span> is so com­pre­hen­sive.</p>
<h2>Conclusion</h2>
<p>So please don’t wait; open that PR.
I am thrilled each time I see a name I don’t rec­og­nize on a GitHub no­ti­ﬁ­ca­tion.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Prompting Large Language Models in Bash Scripts]]></title>
            <link>https://elijahpotter.dev/articles/prompting_large_language_models_in_bash_scripts</link>
            <guid>https://elijahpotter.dev/articles/prompting_large_language_models_in_bash_scripts</guid>
            <pubDate>Wed, 26 Feb 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Prompting Large Language Models In Bash Scripts</h1>
<p>I’ve been ex­per­i­ment­ing with us­ing LLMs lo­cally for gen­er­at­ing datasets to test <a href="https://writewithharper.com">Harper</a> against.
I might write a blog post about the tech­nique (which I am grandiosely call­ing<span class="push-double"></span> <span class="pull-double">“</span>LLM-assisted fuzzing”), but I’m go­ing to make you wait.</p>
<p>I’ve writ­ten a lit­tle tool called <a href="https://github.com/elijah-potter/ofc"><code>ofc</code> that lets you in­sert Ollama into your bash scripts</a>.
I think it’s pretty neat, since it (very eas­ily) lets you do some pretty cool things.</p>
<p>For ex­am­ple, you can swap out the sys­tem prompt, so if you want to com­pare be­hav­ior across prompts, you can just toss it in a loop:</p>
<pre><code class="hljs language-bash"><span class="hljs-meta">#!/bin/bash</span>

subreddits=(<span class="hljs-string">"r/vscode"</span> <span class="hljs-string">"r/neovim"</span> <span class="hljs-string">"r/wallstreetbets"</span>)

<span class="hljs-comment"># Loop over each item in the list</span>
<span class="hljs-keyword">for</span> subreddit <span class="hljs-keyword">in</span> <span class="hljs-string">"<span class="hljs-variable">${subreddits[@]}</span>"</span>; <span class="hljs-keyword">do</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"++++++++ BEGIN <span class="hljs-variable">$subreddit</span> ++++++++"</span>
  ofc --system-prompt <span class="hljs-string">"Assume the persona of a commenter of <span class="hljs-variable">$subreddit</span>"</span> <span class="hljs-string">"What is your opinion on pepperjack cheese?"</span>
  <span class="hljs-built_in">cat</span>
<span class="hljs-keyword">done</span>
</code></pre>
<p>Or, you can in­struct a model to prompt it­self:</p>
<pre><code class="hljs language-bash">ofc --system-prompt <span class="hljs-string">"<span class="hljs-subst">$(ofc <span class="hljs-string">"Write a prompt for a large language model that makes it think harder. "</span>)</span>"</span> <span class="hljs-string">"What is a while loop?"</span>
</code></pre>
<h1>Installation</h1>
<p><code>ofc</code> is in­stal­lable from ei­ther crates.io or its repos­i­tory.</p>
<pre><code class="hljs language-bash">cargo install ofc --locked

<span class="hljs-comment"># Or...</span>
cargo install --git https://github.com/elijah-potter/ofc --locked
</code></pre>]]></description>
            <content:encoded><![CDATA[<h1>Prompting Large Language Models In Bash Scripts</h1>
<p>I’ve been ex­per­i­ment­ing with us­ing LLMs lo­cally for gen­er­at­ing datasets to test <a href="https://writewithharper.com">Harper</a> against.
I might write a blog post about the tech­nique (which I am grandiosely call­ing<span class="push-double"></span> <span class="pull-double">“</span>LLM-assisted fuzzing”), but I’m go­ing to make you wait.</p>
<p>I’ve writ­ten a lit­tle tool called <a href="https://github.com/elijah-potter/ofc"><code>ofc</code> that lets you in­sert Ollama into your bash scripts</a>.
I think it’s pretty neat, since it (very eas­ily) lets you do some pretty cool things.</p>
<p>For ex­am­ple, you can swap out the sys­tem prompt, so if you want to com­pare be­hav­ior across prompts, you can just toss it in a loop:</p>
<pre><code class="hljs language-bash"><span class="hljs-meta">#!/bin/bash</span>

subreddits=(<span class="hljs-string">"r/vscode"</span> <span class="hljs-string">"r/neovim"</span> <span class="hljs-string">"r/wallstreetbets"</span>)

<span class="hljs-comment"># Loop over each item in the list</span>
<span class="hljs-keyword">for</span> subreddit <span class="hljs-keyword">in</span> <span class="hljs-string">"<span class="hljs-variable">${subreddits[@]}</span>"</span>; <span class="hljs-keyword">do</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"++++++++ BEGIN <span class="hljs-variable">$subreddit</span> ++++++++"</span>
  ofc --system-prompt <span class="hljs-string">"Assume the persona of a commenter of <span class="hljs-variable">$subreddit</span>"</span> <span class="hljs-string">"What is your opinion on pepperjack cheese?"</span>
  <span class="hljs-built_in">cat</span>
<span class="hljs-keyword">done</span>
</code></pre>
<p>Or, you can in­struct a model to prompt it­self:</p>
<pre><code class="hljs language-bash">ofc --system-prompt <span class="hljs-string">"<span class="hljs-subst">$(ofc <span class="hljs-string">"Write a prompt for a large language model that makes it think harder. "</span>)</span>"</span> <span class="hljs-string">"What is a while loop?"</span>
</code></pre>
<h1>Installation</h1>
<p><code>ofc</code> is in­stal­lable from ei­ther crates.io or its repos­i­tory.</p>
<pre><code class="hljs language-bash">cargo install ofc --locked

<span class="hljs-comment"># Or...</span>
cargo install --git https://github.com/elijah-potter/ofc --locked
</code></pre>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Harper Record]]></title>
            <link>https://elijahpotter.dev/articles/a_harper_record</link>
            <guid>https://elijahpotter.dev/articles/a_harper_record</guid>
            <pubDate>Thu, 06 Feb 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>A Harper Record</h1>
<p>When we started work on <a href="https://writewithharper.com/docs/harperjs/introduction"><code>harper.js</code></a>, our goal was sim­ple.
We wanted it to take less than 24 hours for a de­vel­oper to em­bed high-qual­ity gram­mar check­ing in their app.</p>
<p>A friend of mine, <a href="https://lukaswerner.com/">Lukas Werner</a> re­cently set a record for this.
In just over 15 min­utes, he was able to em­bed Harper into Raycast, a com­mand palate for MacOS.</p>
<p>This is fan­tas­tic news.
It’s al­ways great to see when your goals are hav­ing a real im­pact on some­one’s abil­ity.</p>
<p>I hope you take the time to look at <a href="https://github.com/lukasmwerner/harper-raycast">the pro­ject on GitHub</a>.</p>
<p>In other news, we have some other small pro­jects us­ing Harper:</p>
<ul>
<li><a href="https://github.com/lockbook/lockbook">Lockbook</a></li>
<li><a href="https://stubby.io/">Stubby <span class="small-caps">CMS</span></a></li>
</ul>]]></description>
            <content:encoded><![CDATA[<h1>A Harper Record</h1>
<p>When we started work on <a href="https://writewithharper.com/docs/harperjs/introduction"><code>harper.js</code></a>, our goal was sim­ple.
We wanted it to take less than 24 hours for a de­vel­oper to em­bed high-qual­ity gram­mar check­ing in their app.</p>
<p>A friend of mine, <a href="https://lukaswerner.com/">Lukas Werner</a> re­cently set a record for this.
In just over 15 min­utes, he was able to em­bed Harper into Raycast, a com­mand palate for MacOS.</p>
<p>This is fan­tas­tic news.
It’s al­ways great to see when your goals are hav­ing a real im­pact on some­one’s abil­ity.</p>
<p>I hope you take the time to look at <a href="https://github.com/lukasmwerner/harper-raycast">the pro­ject on GitHub</a>.</p>
<p>In other news, we have some other small pro­jects us­ing Harper:</p>
<ul>
<li><a href="https://github.com/lockbook/lockbook">Lockbook</a></li>
<li><a href="https://stubby.io/">Stubby <span class="small-caps">CMS</span></a></li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Books I Read in January 2025]]></title>
            <link>https://elijahpotter.dev/articles/the_books_i_read_in_january_2025</link>
            <guid>https://elijahpotter.dev/articles/the_books_i_read_in_january_2025</guid>
            <pubDate>Tue, 04 Feb 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Books I Read in January 2025</h1>
<p>I only got to read two full books this month.
The ﬁrst was a short novel by Matt Haig with the ti­tle <a href="https://app.thestorygraph.com/books/d9c7ed04-6148-4e01-a118-d96cba16f507"><em>The Midnight Library</em>.</a></p>
<p>It reads like and feels like a ther­apy ses­sion.
I quite en­joyed it, but I’m not sure it is for every­one.</p>
<p>The sec­ond book (and the one I got the most value from) was <a href="https://app.thestorygraph.com/books/2505f748-ac12-41fb-a411-0868cbe3694c"><em>Working in Public: the Making and Maintenance of Open Source Software</em></a> by Nadia Eghbal.
Until now, I’ve largely or­ga­nized Harper in ways in­spired by pro­jects like Vite and Rust that I in­ter­act with on a daily ba­sis.</p>
<p>Eghbal gives a more holis­tic view than mine, writ­ten for an ex­ter­nal ob­server.
This book was es­pe­cially in­ter­est­ing due to the com­pre­hen­sive cov­er­age of<span class="push-double"></span> <span class="pull-double">“</span>open” pro­jects with no code at all.
She seems to be­lieve the <span class="small-caps">OSS</span> soft­ware man­age­ment prob­lem is one of so­cial com­plex­ity, not tech­ni­cal com­plex­ity.</p>
<p>Furthermore, Nadia makes the case that syn­chro­nous com­mu­ni­ca­tion, while of­ten a bur­den on main­tainer at­ten­tion, is usu­ally nec­es­sary.</p>]]></description>
            <content:encoded><![CDATA[<h1>The Books I Read in January 2025</h1>
<p>I only got to read two full books this month.
The ﬁrst was a short novel by Matt Haig with the ti­tle <a href="https://app.thestorygraph.com/books/d9c7ed04-6148-4e01-a118-d96cba16f507"><em>The Midnight Library</em>.</a></p>
<p>It reads like and feels like a ther­apy ses­sion.
I quite en­joyed it, but I’m not sure it is for every­one.</p>
<p>The sec­ond book (and the one I got the most value from) was <a href="https://app.thestorygraph.com/books/2505f748-ac12-41fb-a411-0868cbe3694c"><em>Working in Public: the Making and Maintenance of Open Source Software</em></a> by Nadia Eghbal.
Until now, I’ve largely or­ga­nized Harper in ways in­spired by pro­jects like Vite and Rust that I in­ter­act with on a daily ba­sis.</p>
<p>Eghbal gives a more holis­tic view than mine, writ­ten for an ex­ter­nal ob­server.
This book was es­pe­cially in­ter­est­ing due to the com­pre­hen­sive cov­er­age of<span class="push-double"></span> <span class="pull-double">“</span>open” pro­jects with no code at all.
She seems to be­lieve the <span class="small-caps">OSS</span> soft­ware man­age­ment prob­lem is one of so­cial com­plex­ity, not tech­ni­cal com­plex­ity.</p>
<p>Furthermore, Nadia makes the case that syn­chro­nous com­mu­ni­ca­tion, while of­ten a bur­den on main­tainer at­ten­tion, is usu­ally nec­es­sary.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Notifications]]></title>
            <link>https://elijahpotter.dev/articles/notifications</link>
            <guid>https://elijahpotter.dev/articles/notifications</guid>
            <pubDate>Sun, 02 Feb 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Notiﬁcations</h1>
<p>I have found that the ﬁrst cou­ple hours of the day are my most pro­duc­tive.
I try to move te­dious tasks to later so I can fo­cus on solv­ing<span class="push-double"></span> <span class="pull-double">“</span>hard prob­lems” that need my full at­ten­tion.</p>
<p>In the past, this has been hard to do for one rea­son: GitHub no­ti­ﬁ­ca­tions.
They pull me in and con­sume hours of my time.
Re­spond­ing to and work­ing with <span class="small-caps">OSS</span> con­trib­u­tors is im­por­tant to me, but that can hap­pen later in the day.</p>
<p>So I wrote a lit­tle ViolentMonkey script to hide the no­ti­ﬁ­ca­tions icon dur­ing these early morn­ing hours.</p>
<pre><code class="hljs language-javascript"><span class="hljs-comment">// ==UserScript==</span>
<span class="hljs-comment">// @name        Hide Notification Indicator</span>
<span class="hljs-comment">// @namespace   Violentmonkey Scripts</span>
<span class="hljs-comment">// @match       https://github.com/*</span>
<span class="hljs-comment">// @grant       none</span>
<span class="hljs-comment">// @version     1.0</span>
<span class="hljs-comment">// @author      Elijah Potter</span>
<span class="hljs-comment">// @description 1/29/2025, 8:16:11 AM</span>
<span class="hljs-comment">// ==/UserScript==</span>

<span class="hljs-keyword">function</span> <span class="hljs-title function_">hideEm</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">const</span> matches = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementsByClassName</span>(
    <span class="hljs-string">"AppHeader-button--hasIndicator"</span>,
  );
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> element <span class="hljs-keyword">of</span> matches) {
    element.<span class="hljs-title function_">remove</span>();
  }
}

<span class="hljs-keyword">new</span> <span class="hljs-title class_">MutationObserver</span>(hideEm).<span class="hljs-title function_">observe</span>(<span class="hljs-variable language_">document</span>, {
  <span class="hljs-attr">childList</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">subtree</span>: <span class="hljs-literal">true</span>,
});
</code></pre>]]></description>
            <content:encoded><![CDATA[<h1>Notiﬁcations</h1>
<p>I have found that the ﬁrst cou­ple hours of the day are my most pro­duc­tive.
I try to move te­dious tasks to later so I can fo­cus on solv­ing<span class="push-double"></span> <span class="pull-double">“</span>hard prob­lems” that need my full at­ten­tion.</p>
<p>In the past, this has been hard to do for one rea­son: GitHub no­ti­ﬁ­ca­tions.
They pull me in and con­sume hours of my time.
Re­spond­ing to and work­ing with <span class="small-caps">OSS</span> con­trib­u­tors is im­por­tant to me, but that can hap­pen later in the day.</p>
<p>So I wrote a lit­tle ViolentMonkey script to hide the no­ti­ﬁ­ca­tions icon dur­ing these early morn­ing hours.</p>
<pre><code class="hljs language-javascript"><span class="hljs-comment">// ==UserScript==</span>
<span class="hljs-comment">// @name        Hide Notification Indicator</span>
<span class="hljs-comment">// @namespace   Violentmonkey Scripts</span>
<span class="hljs-comment">// @match       https://github.com/*</span>
<span class="hljs-comment">// @grant       none</span>
<span class="hljs-comment">// @version     1.0</span>
<span class="hljs-comment">// @author      Elijah Potter</span>
<span class="hljs-comment">// @description 1/29/2025, 8:16:11 AM</span>
<span class="hljs-comment">// ==/UserScript==</span>

<span class="hljs-keyword">function</span> <span class="hljs-title function_">hideEm</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">const</span> matches = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementsByClassName</span>(
    <span class="hljs-string">"AppHeader-button--hasIndicator"</span>,
  );
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> element <span class="hljs-keyword">of</span> matches) {
    element.<span class="hljs-title function_">remove</span>();
  }
}

<span class="hljs-keyword">new</span> <span class="hljs-title class_">MutationObserver</span>(hideEm).<span class="hljs-title function_">observe</span>(<span class="hljs-variable language_">document</span>, {
  <span class="hljs-attr">childList</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">subtree</span>: <span class="hljs-literal">true</span>,
});
</code></pre>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Three Steps to an Apology]]></title>
            <link>https://elijahpotter.dev/articles/the_three_steps_to_an_apology</link>
            <guid>https://elijahpotter.dev/articles/the_three_steps_to_an_apology</guid>
            <pubDate>Thu, 30 Jan 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Three Steps to an Apology</h1>
<p>My grand­mother is a won­der­ful woman. She never leaves the house with­out a least a lit­tle bit of pur­ple. Maybe a key­chain or a purse. Sometimes, she would­n’t leave the house with­out a lot of pur­ple, head to toe. She also never for­gets a smile and a good at­ti­tude.</p>
<p>She’s a wise woman. With more than 70 years and ﬁve chil­dren un­der her belt, she’s seen some stuff. All that ex­pe­ri­ence comes with a lot of ad­vice. The three steps to an apol­ogy are some of the best bits of ad­vice she’s ever given me.</p>
<h2>Step One:<span class="push-double"></span> <span class="pull-double">“</span>I am sorry”</h2>
<p>Admit you’ve done some­thing wrong. Recognize your fault and imag­ine what would have hap­pened if you had done the right thing.</p>
<h2>Step Two:<span class="push-double"></span> <span class="pull-double">“</span>It will not hap­pen again”</h2>
<p>If you’re ac­tu­ally sorry, you’ll want to avoid repli­cat­ing the same mis­take at a later date. Figure out what you need to do to make sure you this is a one-time er­ror.</p>
<h2>Step Three:<span class="push-double"></span> <span class="pull-double">“</span>What can I do to make it bet­ter?”</h2>
<p>Attempt re­pair. In a fan­tasy world, you could go back in time and stop your­self from do­ing what­ever it is you did in the ﬁrst place. But we don’t live in a fan­tasy world, so you have to ﬁnd a way to make it right, as soon as pos­si­ble.</p>]]></description>
            <content:encoded><![CDATA[<h1>The Three Steps to an Apology</h1>
<p>My grand­mother is a won­der­ful woman. She never leaves the house with­out a least a lit­tle bit of pur­ple. Maybe a key­chain or a purse. Sometimes, she would­n’t leave the house with­out a lot of pur­ple, head to toe. She also never for­gets a smile and a good at­ti­tude.</p>
<p>She’s a wise woman. With more than 70 years and ﬁve chil­dren un­der her belt, she’s seen some stuff. All that ex­pe­ri­ence comes with a lot of ad­vice. The three steps to an apol­ogy are some of the best bits of ad­vice she’s ever given me.</p>
<h2>Step One:<span class="push-double"></span> <span class="pull-double">“</span>I am sorry”</h2>
<p>Admit you’ve done some­thing wrong. Recognize your fault and imag­ine what would have hap­pened if you had done the right thing.</p>
<h2>Step Two:<span class="push-double"></span> <span class="pull-double">“</span>It will not hap­pen again”</h2>
<p>If you’re ac­tu­ally sorry, you’ll want to avoid repli­cat­ing the same mis­take at a later date. Figure out what you need to do to make sure you this is a one-time er­ror.</p>
<h2>Step Three:<span class="push-double"></span> <span class="pull-double">“</span>What can I do to make it bet­ter?”</h2>
<p>Attempt re­pair. In a fan­tasy world, you could go back in time and stop your­self from do­ing what­ever it is you did in the ﬁrst place. But we don’t live in a fan­tasy world, so you have to ﬁnd a way to make it right, as soon as pos­si­ble.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Best 25 Bucks I Ever Spent]]></title>
            <link>https://elijahpotter.dev/articles/the_best_25_bucks_i_ever_spent</link>
            <guid>https://elijahpotter.dev/articles/the_best_25_bucks_i_ever_spent</guid>
            <pubDate>Mon, 13 Jan 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Best 25 Bucks I Ever Spent</h1>
<p>Earlier this year (I don’t re­mem­ber the ex­act day or month) I de­cided, screw it, I’m go­ing to try us­ing a ﬂip phone.
For years I’ve won­dered what it was like be­fore the in­for­ma­tion rev­o­lu­tion, but <a href="https://en.wikipedia.org/wiki/Generation_Z">as a Zoomer</a>, I’ve never had the op­por­tu­nity.</p>
<p>I got the cheap­est Verizon-compatible ﬂip phone Amazon had to of­fer and I com­mit­ted my­self to us­ing it ex­clu­sively for at least a week.
A week with­out the en­tirety of hu­man knowl­edge in my pocket.
A week of us­ing my voice to com­mu­ni­cate, rather than a touch-screen key­board.</p>
<img src="/images/flip_phone.jpg" alt="The phone in question">
<p>Beside two fac­tor au­then­ti­ca­tion for GitHub, that ﬁrst week was a breeze.
I found my­self so­cial­iz­ing more, since it turns out that most peo­ple in my so­cial cir­cle are just a phone call and a 10-minute walk away.
I also found my­self read­ing bet­ter qual­ity lit­er­a­ture.
Rather than re­sort­ing to HackerNews to ease my bore­dom, I be­gan de­vour­ing full books at a fright­en­ing pace.
That ﬁrst week turned into two, then four, then twelve.
All the while, I was prov­ing to my­self that I did­n’t need a smart­phone.</p>
<p>For a num­ber of rea­sons, I’m back to us­ing a smart­phone.
But this ex­pe­ri­ence has taught me some im­por­tant lessons that I don’t think can be taught any other way.</p>
<p><strong>Organizing so­cial en­coun­ters over text just does­n’t work.</strong></p>
<p>Some peo­ple don’t check their phone too of­ten.
Call­ing them make it clear that their par­tic­i­pa­tion is im­por­tant to you.
This may sound triv­ial or ob­vi­ous to some of the older mem­bers of my au­di­ence, but to pretty much every Zoomer I’ve talked to about this, it is a rev­e­la­tion.</p>
<p><strong>Quiet is sa­cred, and should be pro­tected.</strong></p>
<p>I see loads of peo­ple who walk around every day with their head­phones in, barely pay­ing at­ten­tion to the mu­sic blar­ing out of them.
I’ve done this my­self.
It closes you off the world.
From the tap­ping of wa­ter off an ici­cle.
From the shaky but warm greet­ing from a col­league.
From the chortling roar of an en­gine.</p>
<p>But most of all, it closes you off from your own mind.
The best think­ing and imag­in­ing hap­pens in the quiet, when your brain has noth­ing more stim­u­lat­ing to do.</p>
<p>Overall, us­ing a ﬂip phone was a very qui­et­ing ex­pe­ri­ence.
It helped the world feel larger and more mag­i­cal.</p>]]></description>
            <content:encoded><![CDATA[<h1>The Best 25 Bucks I Ever Spent</h1>
<p>Earlier this year (I don’t re­mem­ber the ex­act day or month) I de­cided, screw it, I’m go­ing to try us­ing a ﬂip phone.
For years I’ve won­dered what it was like be­fore the in­for­ma­tion rev­o­lu­tion, but <a href="https://en.wikipedia.org/wiki/Generation_Z">as a Zoomer</a>, I’ve never had the op­por­tu­nity.</p>
<p>I got the cheap­est Verizon-compatible ﬂip phone Amazon had to of­fer and I com­mit­ted my­self to us­ing it ex­clu­sively for at least a week.
A week with­out the en­tirety of hu­man knowl­edge in my pocket.
A week of us­ing my voice to com­mu­ni­cate, rather than a touch-screen key­board.</p>
<img src="/images/flip_phone.jpg" alt="The phone in question">
<p>Beside two fac­tor au­then­ti­ca­tion for GitHub, that ﬁrst week was a breeze.
I found my­self so­cial­iz­ing more, since it turns out that most peo­ple in my so­cial cir­cle are just a phone call and a 10-minute walk away.
I also found my­self read­ing bet­ter qual­ity lit­er­a­ture.
Rather than re­sort­ing to HackerNews to ease my bore­dom, I be­gan de­vour­ing full books at a fright­en­ing pace.
That ﬁrst week turned into two, then four, then twelve.
All the while, I was prov­ing to my­self that I did­n’t need a smart­phone.</p>
<p>For a num­ber of rea­sons, I’m back to us­ing a smart­phone.
But this ex­pe­ri­ence has taught me some im­por­tant lessons that I don’t think can be taught any other way.</p>
<p><strong>Organizing so­cial en­coun­ters over text just does­n’t work.</strong></p>
<p>Some peo­ple don’t check their phone too of­ten.
Call­ing them make it clear that their par­tic­i­pa­tion is im­por­tant to you.
This may sound triv­ial or ob­vi­ous to some of the older mem­bers of my au­di­ence, but to pretty much every Zoomer I’ve talked to about this, it is a rev­e­la­tion.</p>
<p><strong>Quiet is sa­cred, and should be pro­tected.</strong></p>
<p>I see loads of peo­ple who walk around every day with their head­phones in, barely pay­ing at­ten­tion to the mu­sic blar­ing out of them.
I’ve done this my­self.
It closes you off the world.
From the tap­ping of wa­ter off an ici­cle.
From the shaky but warm greet­ing from a col­league.
From the chortling roar of an en­gine.</p>
<p>But most of all, it closes you off from your own mind.
The best think­ing and imag­in­ing hap­pens in the quiet, when your brain has noth­ing more stim­u­lat­ing to do.</p>
<p>Overall, us­ing a ﬂip phone was a very qui­et­ing ex­pe­ri­ence.
It helped the world feel larger and more mag­i­cal.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why You Need Sccache]]></title>
            <link>https://elijahpotter.dev/articles/why_you_need_sccache</link>
            <guid>https://elijahpotter.dev/articles/why_you_need_sccache</guid>
            <pubDate>Sun, 29 Dec 2024 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Why You Need <code>sccache</code></h1>
<p>As the main­tainer of a <a href="https://writewithharper.com">rea­son­ably pop­u­lar open source pro­ject</a> writ­ten in Rust, I ﬁnd my­self cloning PRs and swap­ping be­tween branches dozens of times a day.
In do­ing so, I of­ten blow away Rust’s com­pi­la­tion cache ei­ther via <code>git clean -xf .</code> or by chang­ing de­pen­dency ver­sions.</p>
<p>Having a build cache that is sep­a­rate from the pro­ject is a huge win for com­pile times, which is why I use <a href="https://github.com/mozilla/sccache"><code>sccache</code></a>.</p>
<p>For those un­aware, <code>sccache</code> is a build tool for C, C++ and Rust pro­jects that stands in front of the com­piler.
If a given ﬁle’s hash is the same across calls to the com­piler, <code>sccache</code> reuses the pre­vi­ous re­sult.
This cache is com­piler–and pro­ject ag­nos­tic.
In my case, it sits in <code>$HOME/.scccache</code>, but you can set it up with <span class="small-caps">AWS</span> <span class="small-caps">S3</span>, Redis or a myr­iad of other op­tions.</p>
<p>If a de­pen­dency is cached from a com­pile in one pro­ject, it is avail­able for a build in an­other.</p>
<p>In short: you need <code>sccache</code> if you are fre­quently swap­ping be­tween branches with sim­i­lar (but not iden­ti­cal) de­pen­den­cies or ex­pect to fre­quently delete Rust’s disk cache.</p>
<h2>Why You Don’t Need <code>sccache</code></h2>
<p>If you are work­ing in a sin­gle branch on a pro­ject whose de­pen­den­cies re­main some­what sta­ble, <code>sccache</code> might ac­tu­ally in­crease your com­pile time.
Since <code>sccache</code> has to hit the disk (or even the net­work) re­gard­less of whether it is a cache hit, a lit­tle bit of la­tency is added.
If you ex­pect most com­piles to be cache hits or most com­piles to be cache misses, <code>sccache</code> is no more use­ful than the built-in caching <code>cargo</code> pro­vides.</p>
<p>In that case you should not use <code>sccache</code>.</p>
<h2>Wrap-Up</h2>
<p>Regardless of whether you in­stall <code>sccache</code> on your ma­chine, it is a neat lit­tle tech­nol­ogy.
I would rec­om­mend the cu­ri­ous reader take a look for them­selves.
You might be in­spired.</p>]]></description>
            <content:encoded><![CDATA[<h1>Why You Need <code>sccache</code></h1>
<p>As the main­tainer of a <a href="https://writewithharper.com">rea­son­ably pop­u­lar open source pro­ject</a> writ­ten in Rust, I ﬁnd my­self cloning PRs and swap­ping be­tween branches dozens of times a day.
In do­ing so, I of­ten blow away Rust’s com­pi­la­tion cache ei­ther via <code>git clean -xf .</code> or by chang­ing de­pen­dency ver­sions.</p>
<p>Having a build cache that is sep­a­rate from the pro­ject is a huge win for com­pile times, which is why I use <a href="https://github.com/mozilla/sccache"><code>sccache</code></a>.</p>
<p>For those un­aware, <code>sccache</code> is a build tool for C, C++ and Rust pro­jects that stands in front of the com­piler.
If a given ﬁle’s hash is the same across calls to the com­piler, <code>sccache</code> reuses the pre­vi­ous re­sult.
This cache is com­piler–and pro­ject ag­nos­tic.
In my case, it sits in <code>$HOME/.scccache</code>, but you can set it up with <span class="small-caps">AWS</span> <span class="small-caps">S3</span>, Redis or a myr­iad of other op­tions.</p>
<p>If a de­pen­dency is cached from a com­pile in one pro­ject, it is avail­able for a build in an­other.</p>
<p>In short: you need <code>sccache</code> if you are fre­quently swap­ping be­tween branches with sim­i­lar (but not iden­ti­cal) de­pen­den­cies or ex­pect to fre­quently delete Rust’s disk cache.</p>
<h2>Why You Don’t Need <code>sccache</code></h2>
<p>If you are work­ing in a sin­gle branch on a pro­ject whose de­pen­den­cies re­main some­what sta­ble, <code>sccache</code> might ac­tu­ally in­crease your com­pile time.
Since <code>sccache</code> has to hit the disk (or even the net­work) re­gard­less of whether it is a cache hit, a lit­tle bit of la­tency is added.
If you ex­pect most com­piles to be cache hits or most com­piles to be cache misses, <code>sccache</code> is no more use­ful than the built-in caching <code>cargo</code> pro­vides.</p>
<p>In that case you should not use <code>sccache</code>.</p>
<h2>Wrap-Up</h2>
<p>Regardless of whether you in­stall <code>sccache</code> on your ma­chine, it is a neat lit­tle tech­nol­ogy.
I would rec­om­mend the cu­ri­ous reader take a look for them­selves.
You might be in­spired.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Best Books I Read This Year]]></title>
            <link>https://elijahpotter.dev/articles/the_best_books_i_read_this_year</link>
            <guid>https://elijahpotter.dev/articles/the_best_books_i_read_this_year</guid>
            <pubDate>Sat, 21 Dec 2024 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Best Books I Read This Year</h1>
<p>This year I’ve been read­ing a lot more.
Is there a par­tic­u­lar rea­son?
Other than sim­ply ad­mir­ing those that do, no.</p>
<p>I have found some gems of lit­er­a­ture that I would like to share.</p>
<h2><em>Social Intelligence</em> by Daniel Goleman</h2>
<p><em>Social Intelligence</em> is truly a trea­sure.
I heard of Daniel Goleman’s work ﬁrst from an episode of <a href="https://www.youtube.com/watch?v=-hoo_dIOP8k">Talks at Google</a> and later from psy­chol­o­gists who raved about his pre­vi­ous book, <em>Emotional Intelligence</em>.</p>
<p>It gives a deep dive into the sub­con­scious and light­ning-fast sys­tems the un­der­lay every so­cial in­ter­ac­tion.
As I read it, I found my­self an­a­lyz­ing my own be­hav­ior, as well as the be­hav­ior of oth­ers.
As some­one who has been over­think­ing my own so­cial life from a young age, Daniel’s ad­vice to let my brain go to its de­fault mode was re­liev­ing.</p>
<p>If you’re the type to feel anx­ious in so­cial sit­u­a­tions, this book is per­fect for you.</p>
<h2><em>Tomorrow and Tomorrow and Tomorrow</em> by Gabrielle Zevin</h2>
<p><em>Tomorrow and Tomorrow and Tomorrow</em> is prob­a­bly one of the best ﬁc­tion books I’ve read.
<strong>Period.</strong></p>
<p>At ﬁrst glance, it is just the story of the ca­reers of two game de­vel­op­ers and their pro­ducer.
But if you dig a lit­tle bit <strong>deeper</strong>, it re­veals it­self to be the story of how the re­la­tion­ships be­tween peo­ple who work to­gether can be in­cred­i­bly deep and com­plex.
Specif­i­cally, how the pas­sion to cre­ate some­thing can both bond and break peo­ple.</p>
<p>I don’t think it is pos­si­ble for me to ac­cu­rately de­scribe the emo­tional roller­coaster this book took me on.
Which is why you should read it!</p>
<h2><em>Why We Sleep</em> by Matthew Walker</h2>
<p>I was cu­ri­ous why some peo­ple in my life sleep very lit­tle and other sleep quite a bit, both with vary­ing lev­els of gen­eral wake­ful­ness.
So, I ran a search:<span class="push-double"></span> <span class="pull-double">“</span>Why do we sleep?”</p>
<p>To my sur­prise, I dis­cov­ered some­one wrote a book with al­most that ex­act ti­tle.
Since I’ve read it, I’ve in­creased the du­ra­tion of my sleep by nearly two hours.
This prac­tice has had an out­size im­pact on my health and hap­pi­ness.
I could not rec­om­mend it more.</p>
<p>I’ve <a href="./the_optimal_workspace">writ­ten about this book ex­ten­sively</a>, so I won’t re­peat my­self fur­ther.</p>
<h2>That’s It!</h2>
<p>I could wax po­etic about these books and how they’ve changed me—but I won’t.
In re­al­ity, I read them be­cause I en­joyed read­ing them.
In an age where peo­ple <a href="https://www.youtube.com/watch?v=A3wJcF0t0bQ">read less than ever</a>, I im­plore you: try pick­ing up a book, <strong>you might just like it</strong>.</p>]]></description>
            <content:encoded><![CDATA[<h1>The Best Books I Read This Year</h1>
<p>This year I’ve been read­ing a lot more.
Is there a par­tic­u­lar rea­son?
Other than sim­ply ad­mir­ing those that do, no.</p>
<p>I have found some gems of lit­er­a­ture that I would like to share.</p>
<h2><em>Social Intelligence</em> by Daniel Goleman</h2>
<p><em>Social Intelligence</em> is truly a trea­sure.
I heard of Daniel Goleman’s work ﬁrst from an episode of <a href="https://www.youtube.com/watch?v=-hoo_dIOP8k">Talks at Google</a> and later from psy­chol­o­gists who raved about his pre­vi­ous book, <em>Emotional Intelligence</em>.</p>
<p>It gives a deep dive into the sub­con­scious and light­ning-fast sys­tems the un­der­lay every so­cial in­ter­ac­tion.
As I read it, I found my­self an­a­lyz­ing my own be­hav­ior, as well as the be­hav­ior of oth­ers.
As some­one who has been over­think­ing my own so­cial life from a young age, Daniel’s ad­vice to let my brain go to its de­fault mode was re­liev­ing.</p>
<p>If you’re the type to feel anx­ious in so­cial sit­u­a­tions, this book is per­fect for you.</p>
<h2><em>Tomorrow and Tomorrow and Tomorrow</em> by Gabrielle Zevin</h2>
<p><em>Tomorrow and Tomorrow and Tomorrow</em> is prob­a­bly one of the best ﬁc­tion books I’ve read.
<strong>Period.</strong></p>
<p>At ﬁrst glance, it is just the story of the ca­reers of two game de­vel­op­ers and their pro­ducer.
But if you dig a lit­tle bit <strong>deeper</strong>, it re­veals it­self to be the story of how the re­la­tion­ships be­tween peo­ple who work to­gether can be in­cred­i­bly deep and com­plex.
Specif­i­cally, how the pas­sion to cre­ate some­thing can both bond and break peo­ple.</p>
<p>I don’t think it is pos­si­ble for me to ac­cu­rately de­scribe the emo­tional roller­coaster this book took me on.
Which is why you should read it!</p>
<h2><em>Why We Sleep</em> by Matthew Walker</h2>
<p>I was cu­ri­ous why some peo­ple in my life sleep very lit­tle and other sleep quite a bit, both with vary­ing lev­els of gen­eral wake­ful­ness.
So, I ran a search:<span class="push-double"></span> <span class="pull-double">“</span>Why do we sleep?”</p>
<p>To my sur­prise, I dis­cov­ered some­one wrote a book with al­most that ex­act ti­tle.
Since I’ve read it, I’ve in­creased the du­ra­tion of my sleep by nearly two hours.
This prac­tice has had an out­size im­pact on my health and hap­pi­ness.
I could not rec­om­mend it more.</p>
<p>I’ve <a href="./the_optimal_workspace">writ­ten about this book ex­ten­sively</a>, so I won’t re­peat my­self fur­ther.</p>
<h2>That’s It!</h2>
<p>I could wax po­etic about these books and how they’ve changed me—but I won’t.
In re­al­ity, I read them be­cause I en­joyed read­ing them.
In an age where peo­ple <a href="https://www.youtube.com/watch?v=A3wJcF0t0bQ">read less than ever</a>, I im­plore you: try pick­ing up a book, <strong>you might just like it</strong>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[For the Love of Iframes]]></title>
            <link>https://elijahpotter.dev/articles/for_the_love_of_iframes</link>
            <guid>https://elijahpotter.dev/articles/for_the_love_of_iframes</guid>
            <pubDate>Sun, 13 Oct 2024 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>For The Love of <code>iframe</code>s.</h1>
<p>I adore a good <code>iframe</code>.
They’re so el­e­gant as a web com­po­nent.</p>
<p>Just ex­pose an end­point, say <a href="https://writewithharper.com/editor?initialText=This%20is%20an%20interactive%20buffer%20you%20can%20use%20to%20to%20check%20your%20work"><code>https://writewithharper.com/editor</code></a>, set it up to ac­cept some query pa­ra­me­ters and get it hosted.
Now you can ac­cess this web-com­po­nent from any page that has a loose enough Content Security Policy.
For me, that means my <a href="https://elijahpotter.dev/articles/the_simplest_neovim_markdown_setup">school as­sign­ments</a> and other as­sorted doc­u­men­ta­tion.</p>
<p>It also means that I can avoid set­ting up a com­plex build sys­tem for <span class="small-caps">MDX</span>, while still be­ing able to in­clude in­ter­ac­tive com­po­nents.</p>
<p>The ex­am­ple from ear­lier:</p>
<pre><code class="hljs language-html">&#x3C;iframe
  src="https://writewithharper.com/editor?initialText=See, we can now embed the the Harper editor%0Arght into this document!%0A%0AIt's a little too easy."
  width="100%"
  height="400px"
  style="border-radius:10px;border:0px"
>&#x3C;/iframe>
</code></pre>
<p>One ma­jor caveat though: when we pass our ar­gu­ments to the com­po­nent through the query <span class="small-caps">URL</span>, this gets sent to the com­po­nen­t’s server as well.
<strong>I</strong> cer­tainly trust the Harper web­site’s server, since I main­tain it and the code is <a href="https://github.com/elijah-potter/harper/tree/master/packages/web">open source</a>, but that is­n’t al­ways the case.</p>
<iframe src="https://writewithharper.com/editor?initialText=See, we can now embed the the Harper editor%0Arght into this document!%0A%0AIt's a little too easy." width="100%" height="400px" style="border-radius:10px;border:0px"></iframe>
<p>You should also prob­a­bly avoid do­ing this <em>too</em> much.
Most browsers spawn a whole new process for every <code>iframe</code>, so if you want things to stay snappy it is best to limit your­self to just one (<strong>maybe</strong> two) per page.</p>]]></description>
            <content:encoded><![CDATA[<h1>For The Love of <code>iframe</code>s.</h1>
<p>I adore a good <code>iframe</code>.
They’re so el­e­gant as a web com­po­nent.</p>
<p>Just ex­pose an end­point, say <a href="https://writewithharper.com/editor?initialText=This%20is%20an%20interactive%20buffer%20you%20can%20use%20to%20to%20check%20your%20work"><code>https://writewithharper.com/editor</code></a>, set it up to ac­cept some query pa­ra­me­ters and get it hosted.
Now you can ac­cess this web-com­po­nent from any page that has a loose enough Content Security Policy.
For me, that means my <a href="https://elijahpotter.dev/articles/the_simplest_neovim_markdown_setup">school as­sign­ments</a> and other as­sorted doc­u­men­ta­tion.</p>
<p>It also means that I can avoid set­ting up a com­plex build sys­tem for <span class="small-caps">MDX</span>, while still be­ing able to in­clude in­ter­ac­tive com­po­nents.</p>
<p>The ex­am­ple from ear­lier:</p>
<pre><code class="hljs language-html">&#x3C;iframe
  src="https://writewithharper.com/editor?initialText=See, we can now embed the the Harper editor%0Arght into this document!%0A%0AIt's a little too easy."
  width="100%"
  height="400px"
  style="border-radius:10px;border:0px"
>&#x3C;/iframe>
</code></pre>
<p>One ma­jor caveat though: when we pass our ar­gu­ments to the com­po­nent through the query <span class="small-caps">URL</span>, this gets sent to the com­po­nen­t’s server as well.
<strong>I</strong> cer­tainly trust the Harper web­site’s server, since I main­tain it and the code is <a href="https://github.com/elijah-potter/harper/tree/master/packages/web">open source</a>, but that is­n’t al­ways the case.</p>
<iframe src="https://writewithharper.com/editor?initialText=See, we can now embed the the Harper editor%0Arght into this document!%0A%0AIt's a little too easy." width="100%" height="400px" style="border-radius:10px;border:0px"></iframe>
<p>You should also prob­a­bly avoid do­ing this <em>too</em> much.
Most browsers spawn a whole new process for every <code>iframe</code>, so if you want things to stay snappy it is best to limit your­self to just one (<strong>maybe</strong> two) per page.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Naming Harper]]></title>
            <link>https://elijahpotter.dev/articles/naming_harper</link>
            <guid>https://elijahpotter.dev/articles/naming_harper</guid>
            <pubDate>Tue, 06 Aug 2024 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Naming Harper</h1>
<p><a href="https://github.com/elijah-potter/harper/issues/103">Someone</a> re­cently asked me where the name <a href="https://writewithharper.com">Harper</a> came from.</p>
<p>When I ﬁrst sat down to start work on Harper, I had one goal in mind.
I wanted to cre­ate a gram­mar checker that would­n’t im­pinge on the artis­tic free­dom of au­thors.
Part of that meant that I did not want lit­er­ary de­vices to be ﬂagged as gram­mat­i­cal er­rors.
Harper should speed up the writ­ing process rather than slow it down as Grammarly tends to.</p>
<p>To em­body this orig­i­nal goal, I wanted to make the pro­ject a name­sake of an au­thor known for their lit­er­ary de­vices and prose. I also had three other (practical) cri­te­rion:</p>
<ol>
<li>It could not be oc­cu­pied on <a href="https://crates.io/">crates.io</a>.</li>
<li>It could not be oc­cu­pied on GitHub.</li>
<li>It should not be more than two syl­la­bles.</li>
</ol>
<p>I asked around for some ideas and some­one sug­gested I name the pro­ject af­ter <a href="https://en.wikipedia.org/wiki/Harper_Lee">Harper Lee</a>.
It ﬁt the bill, so I went with it.</p>]]></description>
            <content:encoded><![CDATA[<h1>Naming Harper</h1>
<p><a href="https://github.com/elijah-potter/harper/issues/103">Someone</a> re­cently asked me where the name <a href="https://writewithharper.com">Harper</a> came from.</p>
<p>When I ﬁrst sat down to start work on Harper, I had one goal in mind.
I wanted to cre­ate a gram­mar checker that would­n’t im­pinge on the artis­tic free­dom of au­thors.
Part of that meant that I did not want lit­er­ary de­vices to be ﬂagged as gram­mat­i­cal er­rors.
Harper should speed up the writ­ing process rather than slow it down as Grammarly tends to.</p>
<p>To em­body this orig­i­nal goal, I wanted to make the pro­ject a name­sake of an au­thor known for their lit­er­ary de­vices and prose. I also had three other (practical) cri­te­rion:</p>
<ol>
<li>It could not be oc­cu­pied on <a href="https://crates.io/">crates.io</a>.</li>
<li>It could not be oc­cu­pied on GitHub.</li>
<li>It should not be more than two syl­la­bles.</li>
</ol>
<p>I asked around for some ideas and some­one sug­gested I name the pro­ject af­ter <a href="https://en.wikipedia.org/wiki/Harper_Lee">Harper Lee</a>.
It ﬁt the bill, so I went with it.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Simplest Neovim Markdown Setup]]></title>
            <link>https://elijahpotter.dev/articles/the_simplest_neovim_markdown_setup</link>
            <guid>https://elijahpotter.dev/articles/the_simplest_neovim_markdown_setup</guid>
            <pubDate>Sat, 13 Jul 2024 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Simplest Neovim Markdown Setup</h1>
<p>I am not one who en­joys com­plex­ity.
I am also some­one who likes to make their <a href="https://github.com/elijah-potter/harper">own tools</a>.</p>
<p>As a stu­dent, I write a lot.
That in­cludes notes, pa­pers, and doc­u­men­ta­tion for my wide va­ri­ety of pro­jects.</p>
<p>A lot of classes re­quest to sub­mit as­sign­ments as ei­ther <span class="small-caps">PDF</span>, <span class="small-caps">HTML</span> or <code>.docx</code>.
For a while, I sub­mit­ted PDF’s.
I had a whole or­ches­trated setup.</p>
<p>I would write every­thing in <a href="https://neovim.io/">Neovim</a>, save it, and <a href="https://github.com/frabjous/knap"><span class="small-caps">KNAP</span></a> would ren­der it us­ing <a href="https://pandoc.org/">Pandoc</a>.
Finally, it would be ren­dered to my screen us­ing <a href="https://sioyek.info/">Sioyek</a>.</p>
<p>This worked ﬁne, I guess, but it was far from per­fect.</p>
<ol>
<li><strong>It was slow.</strong> Each edit I made in Markdown could take as many as 10 sec­onds to show up in Sioyek.</li>
<li>It was­n’t in­ter­est­ing. While I made mod­i­ﬁ­ca­tions to my Pandoc set­tings, my <span class="small-caps">PDF</span><span class="push-single"></span><span class="pull-single">’</span>s still looked like every other <code>pdflatex</code> doc­u­ment ever made.</li>
<li>I could­n’t make my doc­u­ments in­ter­ac­tive if I wanted to.</li>
</ol>
<p>Markdown was de­signed to be turned into <span class="small-caps">HTML</span>, I rea­soned.
So why not just do that?</p>
<img src="/images/tatum_screenshot.webp" alt="A screenshot of Tatum at work">
<p>That’s why I cre­ated <a href="https://github.com/elijah-potter/tatum">Tatum</a>.
It does one thing, re­ally well.
Point it at a Markdown ﬁle, and it will run a tiny web server to ren­der the re­sult­ing <span class="small-caps">HTML</span> to.
If the ﬁle changes, a WebSocket con­nec­tion tells the browser to re­fresh.</p>
<p>Tatum ren­ders in mil­lisec­onds and cre­ates beau­ti­ful pages with <a href="https://katex.org/"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>KaTeX</mtext></mrow><annotation encoding="application/x-tex">\KaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">K</span><span class="mspace" style="margin-right:-0.17em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span></a>, <a href="https://simplecss.org/">Simple.css</a> and <a href="https://highlightjs.org/"><code>highlight.js</code></a>.
I can em­bed in­ter­ac­tive <span class="small-caps">HTML</span>, <span class="small-caps">CSS</span> and JavaScript el­e­ments di­rectly into my Markdown to get the in­ter­ac­tiv­ity and aes­thet­ics I de­sire.</p>
<p>Once I’m done work­ing, I just run <code>tatum render &#x3C;file.md></code> and I get a sin­gle ﬁle (images and all) that I can sub­mit for my as­sign­ments.</p>
<p>Tatum is­n’t for you to use.
Feel free to poke around at how it works, or even fork it and make your own mod­i­ﬁ­ca­tions.
<strong>It ﬁts my use case per­fectly.</strong></p>]]></description>
            <content:encoded><![CDATA[<h1>The Simplest Neovim Markdown Setup</h1>
<p>I am not one who en­joys com­plex­ity.
I am also some­one who likes to make their <a href="https://github.com/elijah-potter/harper">own tools</a>.</p>
<p>As a stu­dent, I write a lot.
That in­cludes notes, pa­pers, and doc­u­men­ta­tion for my wide va­ri­ety of pro­jects.</p>
<p>A lot of classes re­quest to sub­mit as­sign­ments as ei­ther <span class="small-caps">PDF</span>, <span class="small-caps">HTML</span> or <code>.docx</code>.
For a while, I sub­mit­ted PDF’s.
I had a whole or­ches­trated setup.</p>
<p>I would write every­thing in <a href="https://neovim.io/">Neovim</a>, save it, and <a href="https://github.com/frabjous/knap"><span class="small-caps">KNAP</span></a> would ren­der it us­ing <a href="https://pandoc.org/">Pandoc</a>.
Finally, it would be ren­dered to my screen us­ing <a href="https://sioyek.info/">Sioyek</a>.</p>
<p>This worked ﬁne, I guess, but it was far from per­fect.</p>
<ol>
<li><strong>It was slow.</strong> Each edit I made in Markdown could take as many as 10 sec­onds to show up in Sioyek.</li>
<li>It was­n’t in­ter­est­ing. While I made mod­i­ﬁ­ca­tions to my Pandoc set­tings, my <span class="small-caps">PDF</span><span class="push-single"></span><span class="pull-single">’</span>s still looked like every other <code>pdflatex</code> doc­u­ment ever made.</li>
<li>I could­n’t make my doc­u­ments in­ter­ac­tive if I wanted to.</li>
</ol>
<p>Markdown was de­signed to be turned into <span class="small-caps">HTML</span>, I rea­soned.
So why not just do that?</p>
<img src="/images/tatum_screenshot.webp" alt="A screenshot of Tatum at work">
<p>That’s why I cre­ated <a href="https://github.com/elijah-potter/tatum">Tatum</a>.
It does one thing, re­ally well.
Point it at a Markdown ﬁle, and it will run a tiny web server to ren­der the re­sult­ing <span class="small-caps">HTML</span> to.
If the ﬁle changes, a WebSocket con­nec­tion tells the browser to re­fresh.</p>
<p>Tatum ren­ders in mil­lisec­onds and cre­ates beau­ti­ful pages with <a href="https://katex.org/"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>KaTeX</mtext></mrow><annotation encoding="application/x-tex">\KaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">K</span><span class="mspace" style="margin-right:-0.17em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span></a>, <a href="https://simplecss.org/">Simple.css</a> and <a href="https://highlightjs.org/"><code>highlight.js</code></a>.
I can em­bed in­ter­ac­tive <span class="small-caps">HTML</span>, <span class="small-caps">CSS</span> and JavaScript el­e­ments di­rectly into my Markdown to get the in­ter­ac­tiv­ity and aes­thet­ics I de­sire.</p>
<p>Once I’m done work­ing, I just run <code>tatum render &#x3C;file.md></code> and I get a sin­gle ﬁle (images and all) that I can sub­mit for my as­sign­ments.</p>
<p>Tatum is­n’t for you to use.
Feel free to poke around at how it works, or even fork it and make your own mod­i­ﬁ­ca­tions.
<strong>It ﬁts my use case per­fectly.</strong></p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/tatum_screenshot.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[What Blasterhacks Taught Me About Leadership]]></title>
            <link>https://elijahpotter.dev/articles/what_blasterhacks_taught_me_about_leadership</link>
            <guid>https://elijahpotter.dev/articles/what_blasterhacks_taught_me_about_leadership</guid>
            <pubDate>Fri, 19 Apr 2024 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>What Blasterhacks Taught Me About Leadership</h1>
<h2>The Beginning</h2>
<p>Although it was sev­eral months ago, I re­mem­ber it like it was yes­ter­day.
It was what I would call a <em>dusty</em> Colorado Friday.
The sky was over­cast and the trees de­press­ingly bar­ren.
The air smelled of cow farts, which was typ­i­cal when the winds changed, blow­ing in cold air from the beef farms in the nearby town of Greeley.
In my mind, this sig­naled snow.</p>
<p>I walked into the Green Center, the build­ing in hous­ing the open­ing cer­e­mony of BlasterHacks, the hackathon run by the Colorado School of Mines.
I sign in at the en­trance and walk down into the au­di­to­rium.</p>
<p>As I greeted my team­mates, <a href="https://grantlemons.com/">Grant Lemons</a>, <a href="https://lukaswerner.com/">Lukas Werner</a> and <a href="https://b-sharman.dev/">Byron Sharman</a>, I no­ticed the ner­vous smiles on their faces.
You know the kind, the smile with the in­ner eye­brows slightly raised, shoul­ders tense.
We were ex­cited, but we also knew we had 36 solid hours of pro­gram­ming ahead of us.</p>
<p>After col­lec­tively down­ing sev­eral whole piz­zas and lis­ten­ing to the rules of the com­pe­ti­tion, we made our way over to Labriola Innovation Complex, the hours-old man­u­fac­tur­ing build­ing the com­pe­ti­tion would take place in.</p>
<h2>The Plan</h2>
<p>We brain­stormed some ideas un­der the over­cast sky:</p>
<ul>
<li>A Bennett Foddy-inspired bat­tle-royale game where all the con­trols were <code>vim</code>-keybindings.</li>
<li>A health in­sur­ance app that es­ti­mates the cost of <em>your</em> care at all lo­cal hos­pi­tals.</li>
<li>An app that en­cour­aged you to stand up and move every thirty min­utes.</li>
</ul>
<p>It was that last one that we all got ex­cited about.
We imag­ined it to be so­cial: at a ran­dom in­ter­val (which would av­er­age to thirty min­utes), the app would si­mul­ta­ne­ously send out a no­ti­ﬁ­ca­tion to every user.
Should the user choose to par­tic­i­pate in the StandUp mo­ment, the app would guide them all through the same short work­out ac­tiv­ity.
Fi­nally, the user had the choice to up­load<span class="push-double"></span> <span class="pull-double">“</span>evidence” of the work­out, which could be a short cap­tion about what they were do­ing, or a photo.</p>
<p>The idea of StandUp (as we would go on to name our pro­ject) was in­spired si­mul­ta­ne­ously by <a href="https://bereal.com/en/">BeReal</a>, re­cent re­search in the detri­men­tal health ef­fects of re­mote work, and the use of so­cial li­a­bil­ity for com­par­a­tively more ex­treme work­outs (Strava).</p>
<img src="/images/screenshot_standup.webp" alt="The Feed page of standup after a user has posted">
<p>Of course, most of this had yet to be nailed down yet, for we were just en­ter­ing the Labriola Innovation Complex.
I re­mem­ber ask­ing my­self if I would leave be­fore the com­pe­ti­tion was over.
Al­though I was tired from the days classes and home­work, I felt a drive to ded­i­cate my whole self to this pro­ject.</p>
<h2>The Board</h2>
<p>We sat down and cre­ated a new <a href="https://github.com/grantlemons/blasterhacks2024">git repos­i­tory</a>.
That’s when I started think­ing about how we were go­ing to or­ga­nize our work.
I pro­posed we setup a KanBan board, a pro­ject man­age­ment tech­nique I had picked up at <a href="https://archytasinc.com">Archytas Automation</a>.</p>
<img src="/images/kanban_blasterhacks.jpg" alt="Our KanBan board near the end of the Hackathon">
<p>If you’ve never heard of–or used a KanBan board, here’s the gist.
There are three buck­ets:</p>
<ol>
<li>To Do.</li>
<li>Doing.</li>
<li>Done.</li>
</ol>
<p>The sec­ond bucket is di­vided up by per­son, so in our case, it was re­ally four sep­a­rate buck­ets.
When some­one needed some­thing new to work on, they would go to the<span class="push-double"></span> <span class="pull-double">“</span>To Do” bucket and se­lect a new task (which in our case, was a sticky note).
They would place the task into their cor­re­spond­ing<span class="push-double"></span> <span class="pull-double">“</span>Doing” bucket.
Once the task was done, they would move the sticky note into the<span class="push-double"></span> <span class="pull-double">“</span>Done” bucket and re­peat.</p>
<p>As a gag, we added an ad­di­tional (superﬂuous) bucket: IDGAF.
If a task was unan­i­mously deemed no longer im­por­tant or rel­e­vant to the pro­ject, that’s where we would put it.</p>
<p>So, we got to work.
We had de­cided to use <a href="https://firebase.google.com/">Firebase</a>, <a href="https://kit.svelte.dev/">SvelteKit</a> and <a href="https://go.dev">Go</a>.
I started set­ting up Firebase, Grant started re­search­ing the no­ti­ﬁ­ca­tion ser­vice, Lukas started work­ing on a cir­cu­lar sta­tus page and Byron started re­search­ing the Camera <span class="small-caps">API</span>.</p>
<p>As I look back at this mo­ment to­day, I re­al­ize some­thing im­por­tant about all of these tasks that ended up ben­e­ﬁt­ing us in the long term: they were all in­de­pen­dent.
None of these orig­i­nal tasks re­lied on any other to get done.
We could work in par­al­lel.
Down the road, this would change, but the fact that we were par­al­lel at the be­gin­ning set us up for suc­cess.</p>
<h2>The Snow and the Crash</h2>
<p>We kept work­ing through the night, down­ing en­ergy drinks and laugh­ing as we worked. We got the im­por­tant ser­vices and <span class="small-caps">UI</span> el­e­ment up and run­ning.
Around mid­night, as my nose had pre­dicted sev­eral hours ear­lier, it started snow­ing.</p>
<p>As the snowﬂakes grew larger and ever more ﬂuffy, I felt the teams en­ergy be­gin to dip.
We were en­coun­ter­ing road­blocks, one-by-one, as our mo­ti­va­tion waned and the threat of sleep be­came more promi­nent.</p>
<p>Then, some­one in the main atrium (who, I don’t re­mem­ber) pro­nounced,<span class="push-double"></span> <span class="pull-double">“</span>we are go­ing out­side.”
We ran down the halls like chil­dren spread­ing the an­nounce­ment, and soon enough, there were dozens of tired com­puter sci­ence stu­dents throw­ing snow­balls in their jeans the t-shirts.</p>
<p>As I threw (and re­ceived) a num­ber of snow­balls, I re­al­ized some­thing im­por­tant: we were mak­ing an app about tak­ing care of your­self phys­i­cally, but had ne­glected to do so our­selves.
As im­por­tant as the pro­ject may be, it won’t get done when every­one is tired, mus­cles cramped, and un­happy.
To keep the mo­men­tum go­ing, breaks are re­quired.</p>
<p>As we ﬁled back in­side to warm our hands un­der tap wa­ter, I swore to make sure the rest of the team would take care of them­selves–and each other.</p>
<h2>The Conclusion</h2>
<p>The rest of the hackathon was un­event­ful.
We sat at our lap­tops, crank­ing out line af­ter line of code.
We took breaks and slept when we were blocked by other tasks.</p>
<p>Although it was our ﬁrst hackathon, we de­cided to run our pro­ject in the <em>ad­vanced</em> track, nor­mally re­served for hackathon vet­er­ans.
It paid off, be­cause we won sec­ond-place!</p>
<p>Look at us: hag­gard but sat­is­ﬁed.</p>
<img src="/images/secondplace_blasterhacks.png" alt="The team receiving our second-place win.">]]></description>
            <content:encoded><![CDATA[<h1>What Blasterhacks Taught Me About Leadership</h1>
<h2>The Beginning</h2>
<p>Although it was sev­eral months ago, I re­mem­ber it like it was yes­ter­day.
It was what I would call a <em>dusty</em> Colorado Friday.
The sky was over­cast and the trees de­press­ingly bar­ren.
The air smelled of cow farts, which was typ­i­cal when the winds changed, blow­ing in cold air from the beef farms in the nearby town of Greeley.
In my mind, this sig­naled snow.</p>
<p>I walked into the Green Center, the build­ing in hous­ing the open­ing cer­e­mony of BlasterHacks, the hackathon run by the Colorado School of Mines.
I sign in at the en­trance and walk down into the au­di­to­rium.</p>
<p>As I greeted my team­mates, <a href="https://grantlemons.com/">Grant Lemons</a>, <a href="https://lukaswerner.com/">Lukas Werner</a> and <a href="https://b-sharman.dev/">Byron Sharman</a>, I no­ticed the ner­vous smiles on their faces.
You know the kind, the smile with the in­ner eye­brows slightly raised, shoul­ders tense.
We were ex­cited, but we also knew we had 36 solid hours of pro­gram­ming ahead of us.</p>
<p>After col­lec­tively down­ing sev­eral whole piz­zas and lis­ten­ing to the rules of the com­pe­ti­tion, we made our way over to Labriola Innovation Complex, the hours-old man­u­fac­tur­ing build­ing the com­pe­ti­tion would take place in.</p>
<h2>The Plan</h2>
<p>We brain­stormed some ideas un­der the over­cast sky:</p>
<ul>
<li>A Bennett Foddy-inspired bat­tle-royale game where all the con­trols were <code>vim</code>-keybindings.</li>
<li>A health in­sur­ance app that es­ti­mates the cost of <em>your</em> care at all lo­cal hos­pi­tals.</li>
<li>An app that en­cour­aged you to stand up and move every thirty min­utes.</li>
</ul>
<p>It was that last one that we all got ex­cited about.
We imag­ined it to be so­cial: at a ran­dom in­ter­val (which would av­er­age to thirty min­utes), the app would si­mul­ta­ne­ously send out a no­ti­ﬁ­ca­tion to every user.
Should the user choose to par­tic­i­pate in the StandUp mo­ment, the app would guide them all through the same short work­out ac­tiv­ity.
Fi­nally, the user had the choice to up­load<span class="push-double"></span> <span class="pull-double">“</span>evidence” of the work­out, which could be a short cap­tion about what they were do­ing, or a photo.</p>
<p>The idea of StandUp (as we would go on to name our pro­ject) was in­spired si­mul­ta­ne­ously by <a href="https://bereal.com/en/">BeReal</a>, re­cent re­search in the detri­men­tal health ef­fects of re­mote work, and the use of so­cial li­a­bil­ity for com­par­a­tively more ex­treme work­outs (Strava).</p>
<img src="/images/screenshot_standup.webp" alt="The Feed page of standup after a user has posted">
<p>Of course, most of this had yet to be nailed down yet, for we were just en­ter­ing the Labriola Innovation Complex.
I re­mem­ber ask­ing my­self if I would leave be­fore the com­pe­ti­tion was over.
Al­though I was tired from the days classes and home­work, I felt a drive to ded­i­cate my whole self to this pro­ject.</p>
<h2>The Board</h2>
<p>We sat down and cre­ated a new <a href="https://github.com/grantlemons/blasterhacks2024">git repos­i­tory</a>.
That’s when I started think­ing about how we were go­ing to or­ga­nize our work.
I pro­posed we setup a KanBan board, a pro­ject man­age­ment tech­nique I had picked up at <a href="https://archytasinc.com">Archytas Automation</a>.</p>
<img src="/images/kanban_blasterhacks.jpg" alt="Our KanBan board near the end of the Hackathon">
<p>If you’ve never heard of–or used a KanBan board, here’s the gist.
There are three buck­ets:</p>
<ol>
<li>To Do.</li>
<li>Doing.</li>
<li>Done.</li>
</ol>
<p>The sec­ond bucket is di­vided up by per­son, so in our case, it was re­ally four sep­a­rate buck­ets.
When some­one needed some­thing new to work on, they would go to the<span class="push-double"></span> <span class="pull-double">“</span>To Do” bucket and se­lect a new task (which in our case, was a sticky note).
They would place the task into their cor­re­spond­ing<span class="push-double"></span> <span class="pull-double">“</span>Doing” bucket.
Once the task was done, they would move the sticky note into the<span class="push-double"></span> <span class="pull-double">“</span>Done” bucket and re­peat.</p>
<p>As a gag, we added an ad­di­tional (superﬂuous) bucket: IDGAF.
If a task was unan­i­mously deemed no longer im­por­tant or rel­e­vant to the pro­ject, that’s where we would put it.</p>
<p>So, we got to work.
We had de­cided to use <a href="https://firebase.google.com/">Firebase</a>, <a href="https://kit.svelte.dev/">SvelteKit</a> and <a href="https://go.dev">Go</a>.
I started set­ting up Firebase, Grant started re­search­ing the no­ti­ﬁ­ca­tion ser­vice, Lukas started work­ing on a cir­cu­lar sta­tus page and Byron started re­search­ing the Camera <span class="small-caps">API</span>.</p>
<p>As I look back at this mo­ment to­day, I re­al­ize some­thing im­por­tant about all of these tasks that ended up ben­e­ﬁt­ing us in the long term: they were all in­de­pen­dent.
None of these orig­i­nal tasks re­lied on any other to get done.
We could work in par­al­lel.
Down the road, this would change, but the fact that we were par­al­lel at the be­gin­ning set us up for suc­cess.</p>
<h2>The Snow and the Crash</h2>
<p>We kept work­ing through the night, down­ing en­ergy drinks and laugh­ing as we worked. We got the im­por­tant ser­vices and <span class="small-caps">UI</span> el­e­ment up and run­ning.
Around mid­night, as my nose had pre­dicted sev­eral hours ear­lier, it started snow­ing.</p>
<p>As the snowﬂakes grew larger and ever more ﬂuffy, I felt the teams en­ergy be­gin to dip.
We were en­coun­ter­ing road­blocks, one-by-one, as our mo­ti­va­tion waned and the threat of sleep be­came more promi­nent.</p>
<p>Then, some­one in the main atrium (who, I don’t re­mem­ber) pro­nounced,<span class="push-double"></span> <span class="pull-double">“</span>we are go­ing out­side.”
We ran down the halls like chil­dren spread­ing the an­nounce­ment, and soon enough, there were dozens of tired com­puter sci­ence stu­dents throw­ing snow­balls in their jeans the t-shirts.</p>
<p>As I threw (and re­ceived) a num­ber of snow­balls, I re­al­ized some­thing im­por­tant: we were mak­ing an app about tak­ing care of your­self phys­i­cally, but had ne­glected to do so our­selves.
As im­por­tant as the pro­ject may be, it won’t get done when every­one is tired, mus­cles cramped, and un­happy.
To keep the mo­men­tum go­ing, breaks are re­quired.</p>
<p>As we ﬁled back in­side to warm our hands un­der tap wa­ter, I swore to make sure the rest of the team would take care of them­selves–and each other.</p>
<h2>The Conclusion</h2>
<p>The rest of the hackathon was un­event­ful.
We sat at our lap­tops, crank­ing out line af­ter line of code.
We took breaks and slept when we were blocked by other tasks.</p>
<p>Although it was our ﬁrst hackathon, we de­cided to run our pro­ject in the <em>ad­vanced</em> track, nor­mally re­served for hackathon vet­er­ans.
It paid off, be­cause we won sec­ond-place!</p>
<p>Look at us: hag­gard but sat­is­ﬁed.</p>
<img src="/images/secondplace_blasterhacks.png" alt="The team receiving our second-place win.">]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/kanban_blasterhacks.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[The Optimal Workspace]]></title>
            <link>https://elijahpotter.dev/articles/the_optimal_workspace</link>
            <guid>https://elijahpotter.dev/articles/the_optimal_workspace</guid>
            <pubDate>Fri, 29 Mar 2024 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Optimal Workspace</h1>
<p>There are a num­ber of grand chal­lenges my gen­er­a­tion faces.
Some threaten the way we live, like the hous­ing cri­sis.
Oth­ers are dif­ﬁ­cult to solve, like space travel.
More still both threaten the way we live <strong>and</strong> are dif­ﬁ­cult to tackle, like cli­mate change and pol­lu­tion.</p>
<p>But of all the dis­cus­sion of the prob­lems of the mod­ern world, one is of­ten over­looked: how do we live hap­pier, more mean­ing­ful lives.</p>
<p>I’ve found that there are two main po­ten­tial an­swers which in­volve the way we sleep and the way we work.
They are re­lated to the on­go­ing brain dam­age of mil­lions of Americans, so­cial me­dia and light­ing.
By at­tempt­ing to solve this prob­lem (that of hap­pi­ness and pro­duc­tiv­ity), all other grand chal­lenges be­come eas­ier to solve.</p>
<p>Unlike other grand chal­lenges, like space travel, whose so­lu­tions lie in the hands of rel­a­tively few sci­en­tists and ad­min­is­tra­tors, this prob­lem of how to live well is the re­spon­si­bil­ity of <strong>both</strong> in­di­vid­u­als and busi­nesses.</p>
<h2>Step 1: Sleep</h2>
<img src="/images/sleeping_fox.webp" alt="A fox sleeping on pavement">
<p>As a high school stu­dent, I had an ad­ver­sar­ial re­la­tion­ship with sleep.
The way I saw it, every minute asleep was a minute of home­work not done, a minute of friend­ship not cher­ished.
A minute of life lost.</p>
<p>I went many nights com­pletely for­go­ing it en­tirely, be­liev­ing that it was merely a pre­view of death.
In hind­sight, I think it re­ﬂects well on my work ethic, but poorly on my work tech­nique.
The an­cient Greeks seemed to agree with me.
Ac­cord­ing to them, god of sleep was Hypnos, twin brother of death.</p>
<p>I could­n’t have been more wrong, for sleep is in­cred­i­bly im­por­tant.
In fact, I may have re­ceived mild brain dam­age for my de­ci­sions.
Sleep is <strong>not</strong> mere lack of ac­tiv­ity.
In the book <em>Why We Sleep</em> by Matthew Walker, he dis­cusses why sleep is crit­i­cal for liv­ing a life con­tain­ing depth of work and depth of be­ing.</p>
<p>There are two types of sleep, and each are equally im­por­tant.</p>
<h3><span class="small-caps">NREM</span> (Non-Rapid Eye Movement) Sleep</h3>
<p><span class="small-caps">NREM</span> sleep is crit­i­cal in the in­te­gra­tion and so­lid­i­ﬁ­ca­tion of mem­ory.
This is the por­tion of sleep where your eyes are sta­tion­ary, thus the name.
In fact, <span class="small-caps">NREM</span> sleep seems to be the only time the brain ac­tively works to re­tain mem­ory.
Since <span class="small-caps">NREM</span> sleep is more preva­lent in the ear­lier hours of the night, go­ing to bed two hours hours later than usual re­sults in a 70% re­duc­tion in re­ten­tion of fac­tual in­for­ma­tion the next day.</p>
<h3><span class="small-caps">REM</span> Sleep</h3>
<p><span class="small-caps">REM</span> sleep is piv­otal in cre­ative think­ing and emo­tional pro­cess­ing.</p>
<p>Walker mod­els ideas as a knowl­edge graph.
Dur­ing wake­ful­ness, tra­ver­sal across this graph for the pur­pose of prob­lem solv­ing is some­what iso­lated.
In other words, peo­ple who are awake are only think­ing of so­lu­tions or mak­ing con­nec­tions that are con­cep­tu­ally sim­i­lar to the ex­ist­ing so­lu­tions and ideas.
Dur­ing <span class="small-caps">REM</span> sleep, how­ever, the brain makes ﬁnds longer-dis­tances re­la­tion­ships, from much fur­ther-apart sec­tions of the graph.</p>
<p><span class="small-caps">PTSD</span> pa­tients al­ways have poor qual­ity sleep from to night­mares.
It is­n’t just a cor­re­la­tion, it is lit­er­ally re­quired in the di­ag­no­sis.
Walker claims this is be­cause the brain is at­tempt­ing to <em>process</em> the neg­a­tive emo­tions as­so­ci­ated with trauma, but, since the emo­tions are so strong, wakes it­self up.
One of the main goals with cog­ni­tive be­hav­ioral ther­apy for <span class="small-caps">PTSD</span> pa­tients is get the vic­tim through a full night of sleep.
By giv­ing the brain the op­por­tu­nity to prop­erly process the neg­a­tive ex­pe­ri­ence (in <span class="small-caps">REM</span> sleep), they are able to dra­mat­i­cally speed up re­cov­ery.</p>
<p>Main take­away: sleep more and you will live a longer, health­ier, more im­pact­ful life.</p>
<p>The need for sleep varies across your life­time less than many peo­ple think.
The most des­per­ate need is dur­ing in­fancy, with ba­bies and tod­dlers re­quir­ing as much as 18 hours of sleep per day.
As you get older, de­creases un­til lev­el­ing off at eight and half hours of time in bed.
No, that is not <em>time asleep</em> it is <em>time in bed.</em>
It is a myth that se­niors re­quire less sleep.
In truth, they re­quire just as much sleep as in mid­dle age, but have greater dif­ﬁ­culty achiev­ing it.
This is pri­mar­ily due to the cor­re­lated dif­ﬁ­culty breath­ing, and sec­on­dar­ily due to the ear­lier bed-time se­niors re­quire.</p>
<p>There are a num­ber of traits of the mod­ern world that have re­sult­ing in the <span class="small-caps">WHO</span> di­ag­no­sis of sleep de­pri­va­tion as a pan­demic.
More than 28% of Americans are chron­i­cally sleep de­prived.</p>
<h3>The Danger of Sleep Deprivation</h3>
<p>Chronic sleep de­pri­va­tion can re­sult in brain dam­age.
While you are awake, the <a href="https://en.wikipedia.org/wiki/Glymphatic_system">glym­phatic sys­tem</a> (which is sim­i­lar in func­tion, dif­fer­ent in im­ple­men­ta­tion to the <a href="https://en.wikipedia.org/wiki/Lymphatic_system">lym­phatic sys­tem</a>) is in­ac­tive.
As a re­sult, there is a grad­ual buildup of garbage in the brain, block­ing neu­ro­trans­mit­ters and gen­er­ally dis­rupt­ing the abil­ity for neu­rons to com­mu­ni­cate with each other.
One pro­tein that builds up: beta amy­loid, the same pro­tein that causes Alzheimer’s dis­ease.
When you sleep, the glymph sys­tem starts up and cleans up the garbage wake­ful­ness ac­cu­mu­lated.
How­ever, if not given enough time to do its work (by sleep­ing), the glym­phatic sys­tem is not able to do its job en­tirely.
This is why your like­li­hood of get­ting Alzheimers in­creases the less you sleep.</p>
<p>Sleep de­pri­va­tion also causes in­creased con­cen­tra­tions of cannabi­noids.
Just like Marijuana, these in­creases ap­petite for un­healthy foods.
Ad­di­tion­ally, sleep is an en­ergy in­ten­sive process.
The body burns more calo­ries at night dur­ing sleep than dur­ing harm­ful wake­ful­ness.
All in all, there is a very strong con­nec­tion be­tween sleep de­pri­va­tion and obe­sity.</p>
<p>Matthew Walker high­lights one es­pe­cially crit­i­cal time when phys­i­cal and men­tal health (especially mem­ory re­ten­tion) are im­por­tant: school.
The pri­mary goal of school is to learn, to which <span class="small-caps">NREM</span> sleep is crit­i­cal (due to its role in mem­ory re­ten­tion).</p>
<p>Starting in mid­dle school, chil­dren’s cir­ca­dian rhythms start to shift later.
In other words, teenagers nat­u­rally fall asleep and wake up later than other age groups.
Walker at­trib­utes this to the need for teens to be­come more in­de­pen­dent.
Evo­lu­tion pushes them to op­er­ate by them­selves by forc­ing their par­ents to go to bed ear­lier.</p>
<p>Schools op­er­ate un­der the cir­ca­dian rhythms of par­ents and teach­ers.
7 o’­clock in the morn­ing is the ideal time for them, but not for the stu­dents.
But we ﬁnd that stu­dents don’t go to bed any ear­lier, even though they are forced to awake at the same time as their par­ents.
The end re­sult: mil­lions of mid­dle and high school stu­dents are cur­rently ex­pe­ri­enc­ing chronic sleep de­pri­va­tion, just be­cause school starts too early.</p>
<p>The eat­ing and phys­i­cal habits de­vel­oped dur­ing one’s school years serve as the tem­plate for the re­main­der of their lives.
If one spends their de­vel­op­ing years un­der sleep de­pri­va­tion (which is the case for mil­lions of stu­dents in the U.S.) they are learn­ing how to live their life un­der the in­ﬂu­ence of cannabi­noids, all the while get­ting grad­ual brain dam­age from the buildup of beta amy­loid.</p>
<h3>Sleeping Pills</h3>
<p>As an aside, I feel the need to ad­dress sleep­ing pills.
Do not take them.</p>
<p>There are cur­rently <strong>no</strong> med­ica­tions on the mar­ket that in­duce real sleep.
There <em>are</em> med­ica­tions that slow and stop brain func­tion, like Ambien.
They do not pro­vide real sleep, and as such do not re­sult in mem­ory in­te­gra­tion, emo­tional pro­cess­ing or any of the other ben­e­ﬁts of sleep.
In fact, stud­ies with Ambien show that in­duced<span class="push-double"></span> <span class="pull-double">“</span>sleep” ac­tu­ally re­sults in a net loss of mem­ory, com­pared with not sleep­ing at all.
Not only does Ambien stop nat­ural mem­ory in­te­gra­tion, it ac­tively causes for­get­ful­ness.</p>
<h3>Phones and Blue LEDs</h3>
<img src="/images/phone_in_bed.jpg" alt="A person looking a phone in bed">
<p>The part of the brain that runs your cir­ca­dian rhythm is called the <a href="https://en.wikipedia.org/wiki/Suprachiasmatic_nucleus">su­per­chi­as­matic nu­cleus</a>.
This is an ex­tremely small clus­ter of neu­rons that sits on the in­ter­sec­tion of your two op­tic nerves.
It watches for sig­nals of blue light.
If is any is de­tected (since the only sig­nif­i­cant, nat­ural blue is from the sky) it can safely as­sume it is day and will slowly ad­just it­self un­der that as­sump­tion.</p>
<p>Phones and other sources of blue light mess with this process.
If your eyes de­tected <em>any</em> at night, you are guar­an­teed to sleep worse.</p>
<p>There are other neu­ro­log­i­cal ef­fects at play.
Most ac­tiv­i­ties that take place on a phone ac­ti­vate the part of the brain called the amyg­dala, which we most com­monly as­so­ci­ate with the ﬁght or ﬂight re­sponse.
When ac­ti­vated, your body pre­pares it­self for sur­vival.
It slows or halts di­ges­tion, di­lates the pupils and in­creases me­tab­o­lism.
In other words, pre­pares your body for ac­tion, not rest.</p>
<p>The is­sue: one of the most com­mons sta­ples of mod­ern of­ﬁce spaces are the rows upon rows of ﬂo­res­cent lights.
All peo­ple work­ing in build­ings out­ﬁt­ted with these lights may be ex­pe­ri­enc­ing worst qual­ity and quan­tity of sleep as a re­sult,
es­pe­cially those work­ing later or longer hours.</p>
<p>In the search for pro­duc­ing deep work and liv­ing a full life, get­ting enough sleep is para­mount.
It is the sin­gle most ef­fec­tive move one can make, since it makes all other ac­tions more ef­fec­tive.</p>
<p>However, as I pre­vi­ously pointed out, suc­cess in the new econ­omy re­lies par­tic­u­larly on deep work, which re­quires deep fo­cus.</p>
<h2>Step 2: Deep Focus</h2>
<p>At the be­gin­ning, I men­tioned there are <strong>two</strong> prob­lems re­strict­ing the abil­ity for peo­ple to live happy, ef­fec­tive lives.
The ﬁrst was re­lated to sleep, the sec­ond was re­lated to deep fo­cus.</p>
<p>Before I can ex­plain how to achieve deep fo­cus (according to Cal Newport), I ﬁrst need to ex­plain why it is so crit­i­cal.</p>
<h2>The Information Age</h2>
<img src="/images/moons.webp" alt="The stages of the moon">
<p>We like to sep­a­rate his­tory into ages.
The age of the di­nosaurs was fol­lowed by the ice age which was fol­lowed by the hu­man age (the Anthropocene).
Granted, that’s an over­sim­pli­ﬁ­ca­tion, but it high­lights the point: we de­lin­eate swaths of time with the most im­por­tant and unique vari­able at play.</p>
<p>The in­dus­trial age be­gan with the wide­spread use of the steam en­gine.
With steam en­gines came fac­to­ries, and with fac­to­ries came fac­tory work­ers.</p>
<p>We live in a new age, iden­ti­ﬁed by the in­te­gra­tion and ad­vance­ment of com­puter and com­mu­ni­ca­tion tech­nol­ogy.
We call it the <strong>in­for­ma­tion age.</strong>
With the in­for­ma­tion age comes in­for­ma­tion or <strong>knowl­edge work­ers.</strong></p>
<p>This is the sub­ject of Cal Newport’s fan­tas­tic book, <em>Deep Work: Rules for Focused Success in a Distracted World.</em></p>
<h2>Knowledge Work</h2>
<p>Knowledge work is de­ﬁned by the need for a depth and breadth of knowl­edge, and a high abil­ity to ma­nip­u­late it.
Pro­gram­ming, writ­ing, ac­count­ing, and teach­ing are all ex­am­ples of knowl­edge work.
They re­quire enor­mous amounts of knowl­edge and the value the pro­vide is ac­com­plished through thought.</p>
<p>Cal Newport makes the case that the glob­al­iza­tion of knowl­edge (via the in­ter­net) has ini­ti­ated an eco­nomic shift that will ben­e­ﬁt just a few knowl­edge work­ers, the most skilled su­per­lin­early rel­a­tive to the rest.</p>
<p>Before the in­ter­net, any given com­pany had a lim­ited pool of po­ten­tial work­ers: those in the ge­o­graphic vicin­ity.
This meant, if a com­pany wanted to hire <em>the best</em> pro­gram­mer for a job, they would have to make do with what was avail­able.</p>
<p>Today, how­ever, if a com­pany wants to hire the best pro­gram­mer, they have the en­tire world’s pool.
Knowl­edge work does­n’t typ­i­cally have a strict on-site re­quire­ment.
Com­pa­nies also have much more to gain from get­ting <em>the best</em> than get­ting <em>pretty good</em>, so they’re will­ing to pay much more.
The end re­sult: the peo­ple at the top of their ﬁeld are in higher de­mand now than ever be­fore.</p>
<p>Cal Newport iden­ti­ﬁes two key traits in these<span class="push-double"></span> <span class="pull-double">“</span>superstars” at the top of their ﬁeld.
First,<span class="push-double"></span> <span class="pull-double">“</span>the abil­ity to quickly mas­ter hard things.”
In other words, learn com­plex and ap­plic­a­ble things.</p>
<p>Second,<span class="push-double"></span> <span class="pull-double">“</span>the abil­ity to pro­duce at an elite level, in terms of both qual­ity and speed.”</p>
<p>All of this is to say: to be suc­cess­ful in the chang­ing econ­omy, peo­ple need to be­come bet­ter at deep, mean­ing­ful work.
Since the most valu­able re­sources are the re­sult of deep thought, it makes sense that the high­est achiev­ers are those who prac­tice deep fo­cus.</p>
<p>In <em>Deep Work</em>, Cal Newport high­lights a num­ber of strate­gies for achiev­ing true, deep fo­cus.
How­ever, only one is broadly ap­plic­a­ble for most peo­ple.
The oth­ers re­quire an of­ten un­rea­son­able amount time set aside, some­thing that most em­ploy­ers do not al­low, un­for­tu­nately.</p>
<h3>Rhythmic Deep Work Scheduling</h3>
<p>This is most ef­fec­tive strat­egy for achiev­ing deep fo­cus for most peo­ple.
There are oth­ers Newport high­lights in his book, but this is the most im­por­tant.
Par­tic­u­larly, most peo­ple have <em>daily</em> oblig­a­tions that halt them from deeply fo­cus­ing for more than 22 hours.
This could be due to your job, fam­ily, or school.</p>
<p>Either way, a num­ber of peo­ple have cen­tered on a seem­ingly sim­ple so­lu­tion: make a habit.
In­stead of forc­ing your­self to make a de­ci­sion to move into deep work, reg­u­larly sched­ule it.</p>
<p>The brain has a lim­ited num­ber of deep con­cen­tra­tion hours per day (I’m not sure why, but I sus­pect it is re­lated pre­vi­ously de­scribed buildup of pro­teins in the brain).
Use them up front, when you are least likely to be dis­tracted: in the morn­ing.</p>
<p>This is some­thing I’ve adopted.
I sched­ule all my classes in the af­ter­noon and most so­cial en­coun­ters nat­u­rally hap­pen in the evening.
This leaves the en­tire morn­ing ded­i­cated to deep fo­cus.
I put my phone out of sight and work, undis­tracted, from 5:45 <span class="small-caps">AM</span> to 11:00 <span class="small-caps">AM</span>.</p>
<p>Since im­ple­ment­ing this habit, I’ve found that I’m able to com­plete the ma­jor­ity of my work twice as fast,
leav­ing more time for other pur­suits, like <a href="https://writewithharper.com">Harper</a>.</p>
<h3>The Open Ofﬁce Plan</h3>
<p>The open of­ﬁce plan it an ex­am­ple of a gen­eral trend in many mod­ern com­pa­nies.
Man­agers be­lieve the largest bar­rier to pro­duc­tiv­ity is a lack of com­mu­ni­ca­tion.
In the­ory, knowl­edge work­ers will be able to com­plete work faster if they are able to share knowl­edge faster.</p>
<p>For ex­am­ple, a ju­nior ac­coun­tant needs to per­form an op­er­a­tion in Excel to tab­u­late a com­pa­ny’s ex­penses for this year.
This process, with the knowl­edge they cur­rently have, will take three hours to per­form.
They have to write the for­mu­las, im­port in the data, and pre­sent it in a graph.</p>
<p>A se­nior ac­coun­tant, how­ever, if given the same job, would know that there is a sin­gle but­ton in Excel that does all the same
op­er­a­tions in ﬁve min­utes.</p>
<p>The fric­tion at play, is that the se­nior ac­coun­tant has more knowl­edge of Excel ca­pa­bil­i­ties than the ju­nior.
If they could com­mu­ni­cate this knowl­edge, the ju­nior’s work could be com­pleted much faster.
This is why man­agers be­lieve that in­creas­ing com­mu­ni­ca­tion in­creases pro­duc­tiv­ity.</p>
<p>However, most at­tempts to in­crease pro­duc­tiv­ity have a neg­a­tive ef­fect on the abil­ity for knowl­edge work­ers to fo­cus,
which re­sults in a net <em>de­crease</em> of pro­duc­tiv­ity in the team.</p>
<p>For ex­am­ple: the open of­ﬁce plan.
Sure, there may be in­stances where a se­nior ac­coun­tant sees that a ju­nior needs help on some­thing, but
if any two peo­ple are talk­ing else­where in the room, their noise dis­rupts the fo­cus of every sin­gle worker pre­sent.</p>
<h3>Hub and Spoke Model</h3>
<img src="/images/bell_labs.webp" alt="The Bell Labs Holmdel Complex">
<p>There is one tried and true method of in­creas­ing deep fo­cus with­out sac­ri­ﬁc­ing com­mu­ni­ca­tion: chang­ing the way our build­ings are de­signed.</p>
<p>The Bell Labs Holmdel Complex is a fan­tas­tic ex­am­ple of this.
At its peak, it was ﬁlled with some of the great­est minds of a gen­er­a­tion.
Ten Nobel Prizes, ﬁve Turing Awards, and twenty-two <span class="small-caps">IEEE</span> Medals of Honor were awarded for work done in this build­ing</p>
<p>The Holmdel Complex was de­signed like with a Hub and Spokes.
Each worker had their own pri­vate of­ﬁce, so they could work com­pletely un­in­ter­rupted.
You can think of each of­ﬁce as a spoke on a wheel.
How­ever, if they needed to go any­where else in the build­ing, they had to en­ter the hub: a large hall­way that con­nected all rooms in the build­ing.
This meant that chance en­coun­ters with other work­ers were com­mon­place, but only when a worker was­n’t ac­tively try­ing fo­cus.
They re­tained the ben­e­ﬁts of both deep work and com­mu­ni­ca­tion.</p>
<p>These chance en­coun­ters gave rise to un­ex­pected ap­pli­ca­tions of each in­di­vid­ual re­searcher’s work.
The peo­ple work­ing on Unix dis­cov­ered they could help the in­for­ma­tion the­ory re­searchers and vice versa.</p>
<h2>Conclusion</h2>
<p>Let us re­turn to great chal­lenge I’m try­ing to high­light: the ques­tion of how to live hap­pier and more ef­fec­tively.
The an­swers are sur­pris­ingly sim­ple.</p>
<p>We need to sched­ule our schools and work­places to ac­count for the needs of sleep for our work­ers and stu­dents.
Stu­dents that re­mem­ber more ma­te­r­ial be­come more ef­fec­tive prob­lem solvers later on, which makes all other prob­lems eas­ier to solve.</p>
<p>The peo­ple who are at­tempt­ing to solve other great is­sues, like un­equal ac­cess to ed­u­ca­tion, cli­mate change, and pol­lu­tion will be able to
work more ef­fec­tively given the op­por­tu­nity to en­ter deep fo­cus.</p>
<p>So please, sleep well, and don’t be dis­tracted by the things that don’t mat­ter.</p>
<img src="/images/prod_map.png" alt="A map of variables that impact your productivity">
<h2>Works Cited</h2>
<p>[1] M. Walker, Why we sleep: Unlocking the Power of Sleep and Dreams. Simon and Schuster, 2017.</p>
<p>[2] C. Newport, Deep work: Rules for Focused Success in a Distracted World. Hachette <span class="small-caps">UK</span>, 2016.</p>]]></description>
            <content:encoded><![CDATA[<h1>The Optimal Workspace</h1>
<p>There are a num­ber of grand chal­lenges my gen­er­a­tion faces.
Some threaten the way we live, like the hous­ing cri­sis.
Oth­ers are dif­ﬁ­cult to solve, like space travel.
More still both threaten the way we live <strong>and</strong> are dif­ﬁ­cult to tackle, like cli­mate change and pol­lu­tion.</p>
<p>But of all the dis­cus­sion of the prob­lems of the mod­ern world, one is of­ten over­looked: how do we live hap­pier, more mean­ing­ful lives.</p>
<p>I’ve found that there are two main po­ten­tial an­swers which in­volve the way we sleep and the way we work.
They are re­lated to the on­go­ing brain dam­age of mil­lions of Americans, so­cial me­dia and light­ing.
By at­tempt­ing to solve this prob­lem (that of hap­pi­ness and pro­duc­tiv­ity), all other grand chal­lenges be­come eas­ier to solve.</p>
<p>Unlike other grand chal­lenges, like space travel, whose so­lu­tions lie in the hands of rel­a­tively few sci­en­tists and ad­min­is­tra­tors, this prob­lem of how to live well is the re­spon­si­bil­ity of <strong>both</strong> in­di­vid­u­als and busi­nesses.</p>
<h2>Step 1: Sleep</h2>
<img src="/images/sleeping_fox.webp" alt="A fox sleeping on pavement">
<p>As a high school stu­dent, I had an ad­ver­sar­ial re­la­tion­ship with sleep.
The way I saw it, every minute asleep was a minute of home­work not done, a minute of friend­ship not cher­ished.
A minute of life lost.</p>
<p>I went many nights com­pletely for­go­ing it en­tirely, be­liev­ing that it was merely a pre­view of death.
In hind­sight, I think it re­ﬂects well on my work ethic, but poorly on my work tech­nique.
The an­cient Greeks seemed to agree with me.
Ac­cord­ing to them, god of sleep was Hypnos, twin brother of death.</p>
<p>I could­n’t have been more wrong, for sleep is in­cred­i­bly im­por­tant.
In fact, I may have re­ceived mild brain dam­age for my de­ci­sions.
Sleep is <strong>not</strong> mere lack of ac­tiv­ity.
In the book <em>Why We Sleep</em> by Matthew Walker, he dis­cusses why sleep is crit­i­cal for liv­ing a life con­tain­ing depth of work and depth of be­ing.</p>
<p>There are two types of sleep, and each are equally im­por­tant.</p>
<h3><span class="small-caps">NREM</span> (Non-Rapid Eye Movement) Sleep</h3>
<p><span class="small-caps">NREM</span> sleep is crit­i­cal in the in­te­gra­tion and so­lid­i­ﬁ­ca­tion of mem­ory.
This is the por­tion of sleep where your eyes are sta­tion­ary, thus the name.
In fact, <span class="small-caps">NREM</span> sleep seems to be the only time the brain ac­tively works to re­tain mem­ory.
Since <span class="small-caps">NREM</span> sleep is more preva­lent in the ear­lier hours of the night, go­ing to bed two hours hours later than usual re­sults in a 70% re­duc­tion in re­ten­tion of fac­tual in­for­ma­tion the next day.</p>
<h3><span class="small-caps">REM</span> Sleep</h3>
<p><span class="small-caps">REM</span> sleep is piv­otal in cre­ative think­ing and emo­tional pro­cess­ing.</p>
<p>Walker mod­els ideas as a knowl­edge graph.
Dur­ing wake­ful­ness, tra­ver­sal across this graph for the pur­pose of prob­lem solv­ing is some­what iso­lated.
In other words, peo­ple who are awake are only think­ing of so­lu­tions or mak­ing con­nec­tions that are con­cep­tu­ally sim­i­lar to the ex­ist­ing so­lu­tions and ideas.
Dur­ing <span class="small-caps">REM</span> sleep, how­ever, the brain makes ﬁnds longer-dis­tances re­la­tion­ships, from much fur­ther-apart sec­tions of the graph.</p>
<p><span class="small-caps">PTSD</span> pa­tients al­ways have poor qual­ity sleep from to night­mares.
It is­n’t just a cor­re­la­tion, it is lit­er­ally re­quired in the di­ag­no­sis.
Walker claims this is be­cause the brain is at­tempt­ing to <em>process</em> the neg­a­tive emo­tions as­so­ci­ated with trauma, but, since the emo­tions are so strong, wakes it­self up.
One of the main goals with cog­ni­tive be­hav­ioral ther­apy for <span class="small-caps">PTSD</span> pa­tients is get the vic­tim through a full night of sleep.
By giv­ing the brain the op­por­tu­nity to prop­erly process the neg­a­tive ex­pe­ri­ence (in <span class="small-caps">REM</span> sleep), they are able to dra­mat­i­cally speed up re­cov­ery.</p>
<p>Main take­away: sleep more and you will live a longer, health­ier, more im­pact­ful life.</p>
<p>The need for sleep varies across your life­time less than many peo­ple think.
The most des­per­ate need is dur­ing in­fancy, with ba­bies and tod­dlers re­quir­ing as much as 18 hours of sleep per day.
As you get older, de­creases un­til lev­el­ing off at eight and half hours of time in bed.
No, that is not <em>time asleep</em> it is <em>time in bed.</em>
It is a myth that se­niors re­quire less sleep.
In truth, they re­quire just as much sleep as in mid­dle age, but have greater dif­ﬁ­culty achiev­ing it.
This is pri­mar­ily due to the cor­re­lated dif­ﬁ­culty breath­ing, and sec­on­dar­ily due to the ear­lier bed-time se­niors re­quire.</p>
<p>There are a num­ber of traits of the mod­ern world that have re­sult­ing in the <span class="small-caps">WHO</span> di­ag­no­sis of sleep de­pri­va­tion as a pan­demic.
More than 28% of Americans are chron­i­cally sleep de­prived.</p>
<h3>The Danger of Sleep Deprivation</h3>
<p>Chronic sleep de­pri­va­tion can re­sult in brain dam­age.
While you are awake, the <a href="https://en.wikipedia.org/wiki/Glymphatic_system">glym­phatic sys­tem</a> (which is sim­i­lar in func­tion, dif­fer­ent in im­ple­men­ta­tion to the <a href="https://en.wikipedia.org/wiki/Lymphatic_system">lym­phatic sys­tem</a>) is in­ac­tive.
As a re­sult, there is a grad­ual buildup of garbage in the brain, block­ing neu­ro­trans­mit­ters and gen­er­ally dis­rupt­ing the abil­ity for neu­rons to com­mu­ni­cate with each other.
One pro­tein that builds up: beta amy­loid, the same pro­tein that causes Alzheimer’s dis­ease.
When you sleep, the glymph sys­tem starts up and cleans up the garbage wake­ful­ness ac­cu­mu­lated.
How­ever, if not given enough time to do its work (by sleep­ing), the glym­phatic sys­tem is not able to do its job en­tirely.
This is why your like­li­hood of get­ting Alzheimers in­creases the less you sleep.</p>
<p>Sleep de­pri­va­tion also causes in­creased con­cen­tra­tions of cannabi­noids.
Just like Marijuana, these in­creases ap­petite for un­healthy foods.
Ad­di­tion­ally, sleep is an en­ergy in­ten­sive process.
The body burns more calo­ries at night dur­ing sleep than dur­ing harm­ful wake­ful­ness.
All in all, there is a very strong con­nec­tion be­tween sleep de­pri­va­tion and obe­sity.</p>
<p>Matthew Walker high­lights one es­pe­cially crit­i­cal time when phys­i­cal and men­tal health (especially mem­ory re­ten­tion) are im­por­tant: school.
The pri­mary goal of school is to learn, to which <span class="small-caps">NREM</span> sleep is crit­i­cal (due to its role in mem­ory re­ten­tion).</p>
<p>Starting in mid­dle school, chil­dren’s cir­ca­dian rhythms start to shift later.
In other words, teenagers nat­u­rally fall asleep and wake up later than other age groups.
Walker at­trib­utes this to the need for teens to be­come more in­de­pen­dent.
Evo­lu­tion pushes them to op­er­ate by them­selves by forc­ing their par­ents to go to bed ear­lier.</p>
<p>Schools op­er­ate un­der the cir­ca­dian rhythms of par­ents and teach­ers.
7 o’­clock in the morn­ing is the ideal time for them, but not for the stu­dents.
But we ﬁnd that stu­dents don’t go to bed any ear­lier, even though they are forced to awake at the same time as their par­ents.
The end re­sult: mil­lions of mid­dle and high school stu­dents are cur­rently ex­pe­ri­enc­ing chronic sleep de­pri­va­tion, just be­cause school starts too early.</p>
<p>The eat­ing and phys­i­cal habits de­vel­oped dur­ing one’s school years serve as the tem­plate for the re­main­der of their lives.
If one spends their de­vel­op­ing years un­der sleep de­pri­va­tion (which is the case for mil­lions of stu­dents in the U.S.) they are learn­ing how to live their life un­der the in­ﬂu­ence of cannabi­noids, all the while get­ting grad­ual brain dam­age from the buildup of beta amy­loid.</p>
<h3>Sleeping Pills</h3>
<p>As an aside, I feel the need to ad­dress sleep­ing pills.
Do not take them.</p>
<p>There are cur­rently <strong>no</strong> med­ica­tions on the mar­ket that in­duce real sleep.
There <em>are</em> med­ica­tions that slow and stop brain func­tion, like Ambien.
They do not pro­vide real sleep, and as such do not re­sult in mem­ory in­te­gra­tion, emo­tional pro­cess­ing or any of the other ben­e­ﬁts of sleep.
In fact, stud­ies with Ambien show that in­duced<span class="push-double"></span> <span class="pull-double">“</span>sleep” ac­tu­ally re­sults in a net loss of mem­ory, com­pared with not sleep­ing at all.
Not only does Ambien stop nat­ural mem­ory in­te­gra­tion, it ac­tively causes for­get­ful­ness.</p>
<h3>Phones and Blue LEDs</h3>
<img src="/images/phone_in_bed.jpg" alt="A person looking a phone in bed">
<p>The part of the brain that runs your cir­ca­dian rhythm is called the <a href="https://en.wikipedia.org/wiki/Suprachiasmatic_nucleus">su­per­chi­as­matic nu­cleus</a>.
This is an ex­tremely small clus­ter of neu­rons that sits on the in­ter­sec­tion of your two op­tic nerves.
It watches for sig­nals of blue light.
If is any is de­tected (since the only sig­nif­i­cant, nat­ural blue is from the sky) it can safely as­sume it is day and will slowly ad­just it­self un­der that as­sump­tion.</p>
<p>Phones and other sources of blue light mess with this process.
If your eyes de­tected <em>any</em> at night, you are guar­an­teed to sleep worse.</p>
<p>There are other neu­ro­log­i­cal ef­fects at play.
Most ac­tiv­i­ties that take place on a phone ac­ti­vate the part of the brain called the amyg­dala, which we most com­monly as­so­ci­ate with the ﬁght or ﬂight re­sponse.
When ac­ti­vated, your body pre­pares it­self for sur­vival.
It slows or halts di­ges­tion, di­lates the pupils and in­creases me­tab­o­lism.
In other words, pre­pares your body for ac­tion, not rest.</p>
<p>The is­sue: one of the most com­mons sta­ples of mod­ern of­ﬁce spaces are the rows upon rows of ﬂo­res­cent lights.
All peo­ple work­ing in build­ings out­ﬁt­ted with these lights may be ex­pe­ri­enc­ing worst qual­ity and quan­tity of sleep as a re­sult,
es­pe­cially those work­ing later or longer hours.</p>
<p>In the search for pro­duc­ing deep work and liv­ing a full life, get­ting enough sleep is para­mount.
It is the sin­gle most ef­fec­tive move one can make, since it makes all other ac­tions more ef­fec­tive.</p>
<p>However, as I pre­vi­ously pointed out, suc­cess in the new econ­omy re­lies par­tic­u­larly on deep work, which re­quires deep fo­cus.</p>
<h2>Step 2: Deep Focus</h2>
<p>At the be­gin­ning, I men­tioned there are <strong>two</strong> prob­lems re­strict­ing the abil­ity for peo­ple to live happy, ef­fec­tive lives.
The ﬁrst was re­lated to sleep, the sec­ond was re­lated to deep fo­cus.</p>
<p>Before I can ex­plain how to achieve deep fo­cus (according to Cal Newport), I ﬁrst need to ex­plain why it is so crit­i­cal.</p>
<h2>The Information Age</h2>
<img src="/images/moons.webp" alt="The stages of the moon">
<p>We like to sep­a­rate his­tory into ages.
The age of the di­nosaurs was fol­lowed by the ice age which was fol­lowed by the hu­man age (the Anthropocene).
Granted, that’s an over­sim­pli­ﬁ­ca­tion, but it high­lights the point: we de­lin­eate swaths of time with the most im­por­tant and unique vari­able at play.</p>
<p>The in­dus­trial age be­gan with the wide­spread use of the steam en­gine.
With steam en­gines came fac­to­ries, and with fac­to­ries came fac­tory work­ers.</p>
<p>We live in a new age, iden­ti­ﬁed by the in­te­gra­tion and ad­vance­ment of com­puter and com­mu­ni­ca­tion tech­nol­ogy.
We call it the <strong>in­for­ma­tion age.</strong>
With the in­for­ma­tion age comes in­for­ma­tion or <strong>knowl­edge work­ers.</strong></p>
<p>This is the sub­ject of Cal Newport’s fan­tas­tic book, <em>Deep Work: Rules for Focused Success in a Distracted World.</em></p>
<h2>Knowledge Work</h2>
<p>Knowledge work is de­ﬁned by the need for a depth and breadth of knowl­edge, and a high abil­ity to ma­nip­u­late it.
Pro­gram­ming, writ­ing, ac­count­ing, and teach­ing are all ex­am­ples of knowl­edge work.
They re­quire enor­mous amounts of knowl­edge and the value the pro­vide is ac­com­plished through thought.</p>
<p>Cal Newport makes the case that the glob­al­iza­tion of knowl­edge (via the in­ter­net) has ini­ti­ated an eco­nomic shift that will ben­e­ﬁt just a few knowl­edge work­ers, the most skilled su­per­lin­early rel­a­tive to the rest.</p>
<p>Before the in­ter­net, any given com­pany had a lim­ited pool of po­ten­tial work­ers: those in the ge­o­graphic vicin­ity.
This meant, if a com­pany wanted to hire <em>the best</em> pro­gram­mer for a job, they would have to make do with what was avail­able.</p>
<p>Today, how­ever, if a com­pany wants to hire the best pro­gram­mer, they have the en­tire world’s pool.
Knowl­edge work does­n’t typ­i­cally have a strict on-site re­quire­ment.
Com­pa­nies also have much more to gain from get­ting <em>the best</em> than get­ting <em>pretty good</em>, so they’re will­ing to pay much more.
The end re­sult: the peo­ple at the top of their ﬁeld are in higher de­mand now than ever be­fore.</p>
<p>Cal Newport iden­ti­ﬁes two key traits in these<span class="push-double"></span> <span class="pull-double">“</span>superstars” at the top of their ﬁeld.
First,<span class="push-double"></span> <span class="pull-double">“</span>the abil­ity to quickly mas­ter hard things.”
In other words, learn com­plex and ap­plic­a­ble things.</p>
<p>Second,<span class="push-double"></span> <span class="pull-double">“</span>the abil­ity to pro­duce at an elite level, in terms of both qual­ity and speed.”</p>
<p>All of this is to say: to be suc­cess­ful in the chang­ing econ­omy, peo­ple need to be­come bet­ter at deep, mean­ing­ful work.
Since the most valu­able re­sources are the re­sult of deep thought, it makes sense that the high­est achiev­ers are those who prac­tice deep fo­cus.</p>
<p>In <em>Deep Work</em>, Cal Newport high­lights a num­ber of strate­gies for achiev­ing true, deep fo­cus.
How­ever, only one is broadly ap­plic­a­ble for most peo­ple.
The oth­ers re­quire an of­ten un­rea­son­able amount time set aside, some­thing that most em­ploy­ers do not al­low, un­for­tu­nately.</p>
<h3>Rhythmic Deep Work Scheduling</h3>
<p>This is most ef­fec­tive strat­egy for achiev­ing deep fo­cus for most peo­ple.
There are oth­ers Newport high­lights in his book, but this is the most im­por­tant.
Par­tic­u­larly, most peo­ple have <em>daily</em> oblig­a­tions that halt them from deeply fo­cus­ing for more than 22 hours.
This could be due to your job, fam­ily, or school.</p>
<p>Either way, a num­ber of peo­ple have cen­tered on a seem­ingly sim­ple so­lu­tion: make a habit.
In­stead of forc­ing your­self to make a de­ci­sion to move into deep work, reg­u­larly sched­ule it.</p>
<p>The brain has a lim­ited num­ber of deep con­cen­tra­tion hours per day (I’m not sure why, but I sus­pect it is re­lated pre­vi­ously de­scribed buildup of pro­teins in the brain).
Use them up front, when you are least likely to be dis­tracted: in the morn­ing.</p>
<p>This is some­thing I’ve adopted.
I sched­ule all my classes in the af­ter­noon and most so­cial en­coun­ters nat­u­rally hap­pen in the evening.
This leaves the en­tire morn­ing ded­i­cated to deep fo­cus.
I put my phone out of sight and work, undis­tracted, from 5:45 <span class="small-caps">AM</span> to 11:00 <span class="small-caps">AM</span>.</p>
<p>Since im­ple­ment­ing this habit, I’ve found that I’m able to com­plete the ma­jor­ity of my work twice as fast,
leav­ing more time for other pur­suits, like <a href="https://writewithharper.com">Harper</a>.</p>
<h3>The Open Ofﬁce Plan</h3>
<p>The open of­ﬁce plan it an ex­am­ple of a gen­eral trend in many mod­ern com­pa­nies.
Man­agers be­lieve the largest bar­rier to pro­duc­tiv­ity is a lack of com­mu­ni­ca­tion.
In the­ory, knowl­edge work­ers will be able to com­plete work faster if they are able to share knowl­edge faster.</p>
<p>For ex­am­ple, a ju­nior ac­coun­tant needs to per­form an op­er­a­tion in Excel to tab­u­late a com­pa­ny’s ex­penses for this year.
This process, with the knowl­edge they cur­rently have, will take three hours to per­form.
They have to write the for­mu­las, im­port in the data, and pre­sent it in a graph.</p>
<p>A se­nior ac­coun­tant, how­ever, if given the same job, would know that there is a sin­gle but­ton in Excel that does all the same
op­er­a­tions in ﬁve min­utes.</p>
<p>The fric­tion at play, is that the se­nior ac­coun­tant has more knowl­edge of Excel ca­pa­bil­i­ties than the ju­nior.
If they could com­mu­ni­cate this knowl­edge, the ju­nior’s work could be com­pleted much faster.
This is why man­agers be­lieve that in­creas­ing com­mu­ni­ca­tion in­creases pro­duc­tiv­ity.</p>
<p>However, most at­tempts to in­crease pro­duc­tiv­ity have a neg­a­tive ef­fect on the abil­ity for knowl­edge work­ers to fo­cus,
which re­sults in a net <em>de­crease</em> of pro­duc­tiv­ity in the team.</p>
<p>For ex­am­ple: the open of­ﬁce plan.
Sure, there may be in­stances where a se­nior ac­coun­tant sees that a ju­nior needs help on some­thing, but
if any two peo­ple are talk­ing else­where in the room, their noise dis­rupts the fo­cus of every sin­gle worker pre­sent.</p>
<h3>Hub and Spoke Model</h3>
<img src="/images/bell_labs.webp" alt="The Bell Labs Holmdel Complex">
<p>There is one tried and true method of in­creas­ing deep fo­cus with­out sac­ri­ﬁc­ing com­mu­ni­ca­tion: chang­ing the way our build­ings are de­signed.</p>
<p>The Bell Labs Holmdel Complex is a fan­tas­tic ex­am­ple of this.
At its peak, it was ﬁlled with some of the great­est minds of a gen­er­a­tion.
Ten Nobel Prizes, ﬁve Turing Awards, and twenty-two <span class="small-caps">IEEE</span> Medals of Honor were awarded for work done in this build­ing</p>
<p>The Holmdel Complex was de­signed like with a Hub and Spokes.
Each worker had their own pri­vate of­ﬁce, so they could work com­pletely un­in­ter­rupted.
You can think of each of­ﬁce as a spoke on a wheel.
How­ever, if they needed to go any­where else in the build­ing, they had to en­ter the hub: a large hall­way that con­nected all rooms in the build­ing.
This meant that chance en­coun­ters with other work­ers were com­mon­place, but only when a worker was­n’t ac­tively try­ing fo­cus.
They re­tained the ben­e­ﬁts of both deep work and com­mu­ni­ca­tion.</p>
<p>These chance en­coun­ters gave rise to un­ex­pected ap­pli­ca­tions of each in­di­vid­ual re­searcher’s work.
The peo­ple work­ing on Unix dis­cov­ered they could help the in­for­ma­tion the­ory re­searchers and vice versa.</p>
<h2>Conclusion</h2>
<p>Let us re­turn to great chal­lenge I’m try­ing to high­light: the ques­tion of how to live hap­pier and more ef­fec­tively.
The an­swers are sur­pris­ingly sim­ple.</p>
<p>We need to sched­ule our schools and work­places to ac­count for the needs of sleep for our work­ers and stu­dents.
Stu­dents that re­mem­ber more ma­te­r­ial be­come more ef­fec­tive prob­lem solvers later on, which makes all other prob­lems eas­ier to solve.</p>
<p>The peo­ple who are at­tempt­ing to solve other great is­sues, like un­equal ac­cess to ed­u­ca­tion, cli­mate change, and pol­lu­tion will be able to
work more ef­fec­tively given the op­por­tu­nity to en­ter deep fo­cus.</p>
<p>So please, sleep well, and don’t be dis­tracted by the things that don’t mat­ter.</p>
<img src="/images/prod_map.png" alt="A map of variables that impact your productivity">
<h2>Works Cited</h2>
<p>[1] M. Walker, Why we sleep: Unlocking the Power of Sleep and Dreams. Simon and Schuster, 2017.</p>
<p>[2] C. Newport, Deep work: Rules for Focused Success in a Distracted World. Hachette <span class="small-caps">UK</span>, 2016.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/prod_map.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Stupid Simple Spell Check]]></title>
            <link>https://elijahpotter.dev/articles/stupid_simple_spell_check</link>
            <guid>https://elijahpotter.dev/articles/stupid_simple_spell_check</guid>
            <pubDate>Mon, 04 Mar 2024 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Stupid-Simple Spell-Check</h1>
<img src="/images/antiques.webp" alt="A sign advertising for antiques, but misspelled.">
<p>For the last month, I’ve been spend­ing a lot of time re­plac­ing one key com­po­nent
of my writ­ing and pro­gram­ming en­vi­ron­ment: my gram­mar checker.</p>
<p>Up un­til now, I’ve been us­ing the epony­mous <a href="https://languagetool.org/">LanguageTool</a>
via <a href="https://github.com/valentjn/ltex-ls"><code>ltex-ls</code></a> and <a href="https://github.com/neovim/nvim-lspconfig"><code>nvim-lspconfig</code></a>.
Don’t get me wrong, these tools are <em>re­ally good,</em> and I would rec­om­mend them to any­one and every­one.
How­ever, they come with a few key an­noy­ances.</p>
<h2>LanguageTool Grievances</h2>
<h3>Performance</h3>
<p>LanguageTool is slow.
I’m not ex­actly sure why.
Every time I would run LanguageTool over my Markdown or <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX</mtext></mrow><annotation encoding="application/x-tex">\LaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span> doc­u­ments (which are rea­son­ably sized),
I would have to wait sev­eral sec­onds be­fore even the rudi­men­tary spell-check­ing would show up.</p>
<p>Additionally, I would ﬁnd <code>ltex-ls</code> reg­u­larly be­com­ing the most mem­ory-hun­gry ap­pli­ca­tion on my lap­top,
of­ten ex­ceed­ing 4 gi­ga­bytes.</p>
<p>After hours of scour­ing their code base, I have come to no bet­ter ex­pla­na­tion
than that it is writ­ten in Java.
There are a cou­ple ques­tion­able al­go­rith­mic de­ci­sions in there as well.</p>
<h3>Download Size</h3>
<p>As I said: LanguageTool is re­ally quite good.
How­ever, to get every­thing it can of­fer, you need to not only in­stall a Java Runtime
Environment (<span class="small-caps">>150</span> <span class="small-caps">MB</span> on my sys­tem), the ac­tual <code>.jar</code> ﬁle (<span class="small-caps">>300</span> <span class="small-caps">MB</span>), but you also need to down­load
a 16 <span class="small-caps">GB</span> n-gram dataset.</p>
<h2>Grammarly Grievances</h2>
<p><span class="pull-double">“</span>But Elijah,” I hear you say,<span class="push-double"></span> <span class="pull-double">“</span>just use Grammarly!”</p>
<p><strong>No.</strong> I’m not go­ing to drop $12 a month for some­thing even slower and worse.
Not to men­tion how they are likely go­ing to use my work to train their large lan­guage mod­els.
Gram­marly is a great prod­uct, just not for me.</p>
<h2>The Algorithm</h2>
<p>Now that I’ve thor­oughly ex­plained my rea­son­ing for im­ple­ment­ing a new gram­mar checker (one that I’m call­ing <a href="https://github.com/elijah-potter/harper">Harper</a>), I’d like to re­count
my ﬁrst, ad­mit­tedly naive, at­tempt at spellcheck­ing.</p>
<p>The ﬁrst idea we need to get a grip on is <em>Levenshtein edit dis­tance.</em>
In essence, edit dis­tance is the least num­ber of sin­gle-char­ac­ter ed­its (insertions, dele­tions or re­place­ments) nec­es­sary to turn one word into an­other.
For ex­am­ple, the edit dis­tance be­tween<span class="push-double"></span> <span class="pull-double">“</span>cat” and<span class="push-double"></span> <span class="pull-double">“</span>bat” is one; the only edit in­volves re­plac­ing the<span class="push-double"></span> <span class="pull-double">“</span>c” with a<span class="push-double"></span> <span class="pull-double">“</span>b”.</p>
<p>Similarly, the edit dis­tance be­tween<span class="push-double"></span> <span class="pull-double">“</span>kitten” and<span class="push-double"></span> <span class="pull-double">“</span>sitting” is three: re­move the<span class="push-double"></span> <span class="pull-double">“</span>g”, re­place the sec­ond<span class="push-double"></span> <span class="pull-double">“</span>i” with an<span class="push-double"></span> <span class="pull-double">“</span>e” and re­place the<span class="push-double"></span> <span class="pull-double">“</span>s” with a<span class="push-double"></span> <span class="pull-double">“</span>k”.
For this naive spellcheck­ing, we aren’t too con­cerned with the ex­act ed­its (atomic er­rors) that oc­cur in a given mis­spelling, only the mag­ni­tude of the er­ror.</p>
<p>From a high level view here’s how the al­go­rithm is go­ing to work:</p>
<ol>
<li>Determine <em>whether</em> a given word is mis­spelled.
If not, exit.</li>
<li>Calculate the Levenshtein edit dis­tance be­tween the mis­spelled word and all valid English words.</li>
<li>Pick the three words with the short­est edit dis­tance and pre­sent them to the user as al­ter­na­tive
spelling op­tions.</li>
</ol>
<h3>Step 1.</h3>
<p>To de­ter­mine whether a given word is mis­spelled, we will need a list of all the valid words in the English lan­guage.
Turns out, this is­n’t too easy.
For to­day, we will just use a sub­set of the English lan­guage with this short list:</p>
<pre><code>into
the
a
cat
tree
jumped
</code></pre>
<p>To check if a given word is within the list, we can place the list into a hash set,
and grab it’s con­tents.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">let</span> <span class="hljs-variable">words</span>: <span class="hljs-type">Vec</span>&#x3C;<span class="hljs-type">String</span>> = <span class="hljs-built_in">vec!</span>[<span class="hljs-string">"into"</span>, <span class="hljs-string">"the"</span>, <span class="hljs-string">"a"</span>, <span class="hljs-string">"cat"</span>, <span class="hljs-string">"tree"</span>, <span class="hljs-string">"jumped"</span>]
    .<span class="hljs-title function_ invoke__">into_iter</span>()
    .<span class="hljs-title function_ invoke__">map</span>(|s| s.<span class="hljs-title function_ invoke__">to_string</span>())
    .<span class="hljs-title function_ invoke__">collect</span>();

<span class="hljs-keyword">let</span> <span class="hljs-variable">word_set</span>: HashSet&#x3C;<span class="hljs-type">String</span>> = words.<span class="hljs-title function_ invoke__">iter</span>().<span class="hljs-title function_ invoke__">cloned</span>().<span class="hljs-title function_ invoke__">collect</span>();

<span class="hljs-keyword">let</span> <span class="hljs-variable">word</span> = <span class="hljs-string">"thw"</span>;
<span class="hljs-keyword">let</span> <span class="hljs-variable">word_chars</span>: <span class="hljs-type">Vec</span>&#x3C;_> = word.<span class="hljs-title function_ invoke__">chars</span>().<span class="hljs-title function_ invoke__">collect</span>();

<span class="hljs-keyword">if</span> word_set.<span class="hljs-title function_ invoke__">contains</span>(word) {
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"It is a valid English word!"</span>);
    <span class="hljs-keyword">return</span>;
}

<span class="hljs-built_in">println!</span>(<span class="hljs-string">"Are you sure you meant to spell \"{}\" that way?"</span>, word);
</code></pre>
<h3>The Wagner-Fischer Algorithm</h3>
<p>Now that we know our word is ac­tu­ally mis­spelled, we can move on to ﬁnd­ing the cor­rect spelling.
We need to ﬁnd the edit dis­tance be­tween the mis­spelled word and all the words in our set.</p>
<p>To do this, we will be us­ing the <a href="https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm">Wagner-Fischer</a> al­go­rithm.</p>
<pre><code class="hljs language-rust"><span class="hljs-comment">// Computes the Levenstein edit distance between two patterns.</span>
<span class="hljs-comment">// This is accomplished via the Wagner-Fischer algorithm</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">edit_distance</span>(source: &#x26;[<span class="hljs-type">char</span>], target: &#x26;[<span class="hljs-type">char</span>]) <span class="hljs-punctuation">-></span> <span class="hljs-type">u8</span> {
    <span class="hljs-keyword">let</span> <span class="hljs-variable">m</span> = source.<span class="hljs-title function_ invoke__">len</span>();
    <span class="hljs-keyword">let</span> <span class="hljs-variable">n</span> = target.<span class="hljs-title function_ invoke__">len</span>();

    <span class="hljs-comment">// Create an m-by-n matrix.</span>
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">d</span> = <span class="hljs-title function_ invoke__">create_empty_matrix</span>(m + <span class="hljs-number">1</span>, n + <span class="hljs-number">1</span>);

    <span class="hljs-comment">// Since we know we can transform each word into the other by replacing</span>
    <span class="hljs-comment">// successive characters (or deleting them), we can fill the first column and</span>
    <span class="hljs-comment">// row with values from 0..m and 0..n, respectively.</span>
    <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..=m {
        d[i][<span class="hljs-number">0</span>] = i <span class="hljs-keyword">as</span> <span class="hljs-type">u8</span>;
    }

    <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..=n {
        d[<span class="hljs-number">0</span>][i] = i <span class="hljs-keyword">as</span> <span class="hljs-type">u8</span>;
    }

    <span class="hljs-keyword">for</span> <span class="hljs-variable">j</span> <span class="hljs-keyword">in</span> <span class="hljs-number">1</span>..=n {
        <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">1</span>..=m {
            <span class="hljs-comment">// The total edit distance of two given letter indices i and j, one from each word</span>
            <span class="hljs-comment">// will be the sum of the edit distances of prior combinations + whether the characters</span>
            <span class="hljs-comment">// at the two indices are equal.</span>

            <span class="hljs-keyword">let</span> <span class="hljs-variable">cost</span> = <span class="hljs-keyword">if</span> source[i - <span class="hljs-number">1</span>] == target[j - <span class="hljs-number">1</span>] { <span class="hljs-number">0</span> } <span class="hljs-keyword">else</span> { <span class="hljs-number">1</span> };
            d[i][j] = (d[i - <span class="hljs-number">1</span>][j] + <span class="hljs-number">1</span>)
                .<span class="hljs-title function_ invoke__">min</span>(d[i][j - <span class="hljs-number">1</span>] + <span class="hljs-number">1</span>)
                .<span class="hljs-title function_ invoke__">min</span>(d[i - <span class="hljs-number">1</span>][j - <span class="hljs-number">1</span>] + cost);
        }
    }

    <span class="hljs-comment">// After all possible edits have been explored and minimized</span>
    <span class="hljs-comment">// the resulting minimum edit distance will be in the final item in the matrix.</span>
    d[m][n]
}

<span class="hljs-comment">// Create an empty matrix of size [m, n]</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">create_empty_matrix</span>(m: <span class="hljs-type">usize</span>, n: <span class="hljs-type">usize</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">Vec</span>&#x3C;<span class="hljs-type">Vec</span>&#x3C;<span class="hljs-type">u8</span>>> {
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">d</span> = <span class="hljs-type">Vec</span>::<span class="hljs-title function_ invoke__">with_capacity</span>(m);

    <span class="hljs-keyword">for</span> <span class="hljs-variable">_</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..m {
        d.<span class="hljs-title function_ invoke__">push</span>(<span class="hljs-built_in">vec!</span>[<span class="hljs-number">0u8</span>; n]);
    }

    d
}
</code></pre>
<p>This works pretty well.
There are a num­ber of op­ti­miza­tions we could ap­ply to this func­tion alone.
I’ll leave that as a prob­lem for the reader, since they aren’t par­tic­u­larly rel­e­vant to the meat of the larger al­go­rithm.</p>
<h3>Steps 2 + 3</h3>
<p>Now that we can de­ter­mine the edit dis­tance be­tween two words, we can per­form a brute-force search.
In this short ex­am­ple, we’re go­ing to use <code>sort_by_key</code> to do this, since our data set is so small.
If we were work­ing with a larger dic­tio­nary (say, the en­tire English lan­guage), there would be a num­ber of things we would need to do to re­duce time and mem­ory con­sump­tion.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">suggestions</span>: <span class="hljs-type">Vec</span>&#x3C;(<span class="hljs-type">String</span>, <span class="hljs-type">u8</span>)> = words
    .<span class="hljs-title function_ invoke__">into_iter</span>()
    .<span class="hljs-title function_ invoke__">filter_map</span>(|possible_word| {
        <span class="hljs-keyword">let</span> <span class="hljs-variable">possible_chars</span>: <span class="hljs-type">Vec</span>&#x3C;_> = possible_word.<span class="hljs-title function_ invoke__">chars</span>().<span class="hljs-title function_ invoke__">collect</span>();

        <span class="hljs-keyword">let</span> <span class="hljs-variable">dist</span> = <span class="hljs-title function_ invoke__">edit_distance</span>(word_chars.<span class="hljs-title function_ invoke__">as_slice</span>(), &#x26;possible_chars);

        <span class="hljs-keyword">if</span> dist &#x3C;= <span class="hljs-number">2</span> {
            <span class="hljs-title function_ invoke__">Some</span>((possible_word, dist))
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-literal">None</span>
        }
    })
    .<span class="hljs-title function_ invoke__">collect</span>();

suggestions.<span class="hljs-title function_ invoke__">sort_by_key</span>(|(_, d)| *d);

<span class="hljs-built_in">println!</span>(<span class="hljs-string">"Possible alternatives: "</span>);

suggestions.<span class="hljs-title function_ invoke__">iter</span>().for_each(|(s, _)| <span class="hljs-built_in">println!</span>(<span class="hljs-string">"- {}"</span>, s));
</code></pre>
<p>If we run the whole pro­gram again, we get an out­put some­thing like:</p>
<pre><code class="hljs language-output">Are you sure you meant to spell "thw" that way?
Possible alternatives:
- the
</code></pre>
<p>That looks pretty good to me!</p>
<p>If you would like to look at the whole pro­gram, and maybe try out your own in­puts, <a href="https://play.rust-lang.org/?version=stable&#x26;mode=debug&#x26;edition=2021&#x26;gist=fb7910ad1fb3a6c944cbc2ae8659bb31">go right on ahead</a>.</p>]]></description>
            <content:encoded><![CDATA[<h1>Stupid-Simple Spell-Check</h1>
<img src="/images/antiques.webp" alt="A sign advertising for antiques, but misspelled.">
<p>For the last month, I’ve been spend­ing a lot of time re­plac­ing one key com­po­nent
of my writ­ing and pro­gram­ming en­vi­ron­ment: my gram­mar checker.</p>
<p>Up un­til now, I’ve been us­ing the epony­mous <a href="https://languagetool.org/">LanguageTool</a>
via <a href="https://github.com/valentjn/ltex-ls"><code>ltex-ls</code></a> and <a href="https://github.com/neovim/nvim-lspconfig"><code>nvim-lspconfig</code></a>.
Don’t get me wrong, these tools are <em>re­ally good,</em> and I would rec­om­mend them to any­one and every­one.
How­ever, they come with a few key an­noy­ances.</p>
<h2>LanguageTool Grievances</h2>
<h3>Performance</h3>
<p>LanguageTool is slow.
I’m not ex­actly sure why.
Every time I would run LanguageTool over my Markdown or <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX</mtext></mrow><annotation encoding="application/x-tex">\LaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span> doc­u­ments (which are rea­son­ably sized),
I would have to wait sev­eral sec­onds be­fore even the rudi­men­tary spell-check­ing would show up.</p>
<p>Additionally, I would ﬁnd <code>ltex-ls</code> reg­u­larly be­com­ing the most mem­ory-hun­gry ap­pli­ca­tion on my lap­top,
of­ten ex­ceed­ing 4 gi­ga­bytes.</p>
<p>After hours of scour­ing their code base, I have come to no bet­ter ex­pla­na­tion
than that it is writ­ten in Java.
There are a cou­ple ques­tion­able al­go­rith­mic de­ci­sions in there as well.</p>
<h3>Download Size</h3>
<p>As I said: LanguageTool is re­ally quite good.
How­ever, to get every­thing it can of­fer, you need to not only in­stall a Java Runtime
Environment (<span class="small-caps">>150</span> <span class="small-caps">MB</span> on my sys­tem), the ac­tual <code>.jar</code> ﬁle (<span class="small-caps">>300</span> <span class="small-caps">MB</span>), but you also need to down­load
a 16 <span class="small-caps">GB</span> n-gram dataset.</p>
<h2>Grammarly Grievances</h2>
<p><span class="pull-double">“</span>But Elijah,” I hear you say,<span class="push-double"></span> <span class="pull-double">“</span>just use Grammarly!”</p>
<p><strong>No.</strong> I’m not go­ing to drop $12 a month for some­thing even slower and worse.
Not to men­tion how they are likely go­ing to use my work to train their large lan­guage mod­els.
Gram­marly is a great prod­uct, just not for me.</p>
<h2>The Algorithm</h2>
<p>Now that I’ve thor­oughly ex­plained my rea­son­ing for im­ple­ment­ing a new gram­mar checker (one that I’m call­ing <a href="https://github.com/elijah-potter/harper">Harper</a>), I’d like to re­count
my ﬁrst, ad­mit­tedly naive, at­tempt at spellcheck­ing.</p>
<p>The ﬁrst idea we need to get a grip on is <em>Levenshtein edit dis­tance.</em>
In essence, edit dis­tance is the least num­ber of sin­gle-char­ac­ter ed­its (insertions, dele­tions or re­place­ments) nec­es­sary to turn one word into an­other.
For ex­am­ple, the edit dis­tance be­tween<span class="push-double"></span> <span class="pull-double">“</span>cat” and<span class="push-double"></span> <span class="pull-double">“</span>bat” is one; the only edit in­volves re­plac­ing the<span class="push-double"></span> <span class="pull-double">“</span>c” with a<span class="push-double"></span> <span class="pull-double">“</span>b”.</p>
<p>Similarly, the edit dis­tance be­tween<span class="push-double"></span> <span class="pull-double">“</span>kitten” and<span class="push-double"></span> <span class="pull-double">“</span>sitting” is three: re­move the<span class="push-double"></span> <span class="pull-double">“</span>g”, re­place the sec­ond<span class="push-double"></span> <span class="pull-double">“</span>i” with an<span class="push-double"></span> <span class="pull-double">“</span>e” and re­place the<span class="push-double"></span> <span class="pull-double">“</span>s” with a<span class="push-double"></span> <span class="pull-double">“</span>k”.
For this naive spellcheck­ing, we aren’t too con­cerned with the ex­act ed­its (atomic er­rors) that oc­cur in a given mis­spelling, only the mag­ni­tude of the er­ror.</p>
<p>From a high level view here’s how the al­go­rithm is go­ing to work:</p>
<ol>
<li>Determine <em>whether</em> a given word is mis­spelled.
If not, exit.</li>
<li>Calculate the Levenshtein edit dis­tance be­tween the mis­spelled word and all valid English words.</li>
<li>Pick the three words with the short­est edit dis­tance and pre­sent them to the user as al­ter­na­tive
spelling op­tions.</li>
</ol>
<h3>Step 1.</h3>
<p>To de­ter­mine whether a given word is mis­spelled, we will need a list of all the valid words in the English lan­guage.
Turns out, this is­n’t too easy.
For to­day, we will just use a sub­set of the English lan­guage with this short list:</p>
<pre><code>into
the
a
cat
tree
jumped
</code></pre>
<p>To check if a given word is within the list, we can place the list into a hash set,
and grab it’s con­tents.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">let</span> <span class="hljs-variable">words</span>: <span class="hljs-type">Vec</span>&#x3C;<span class="hljs-type">String</span>> = <span class="hljs-built_in">vec!</span>[<span class="hljs-string">"into"</span>, <span class="hljs-string">"the"</span>, <span class="hljs-string">"a"</span>, <span class="hljs-string">"cat"</span>, <span class="hljs-string">"tree"</span>, <span class="hljs-string">"jumped"</span>]
    .<span class="hljs-title function_ invoke__">into_iter</span>()
    .<span class="hljs-title function_ invoke__">map</span>(|s| s.<span class="hljs-title function_ invoke__">to_string</span>())
    .<span class="hljs-title function_ invoke__">collect</span>();

<span class="hljs-keyword">let</span> <span class="hljs-variable">word_set</span>: HashSet&#x3C;<span class="hljs-type">String</span>> = words.<span class="hljs-title function_ invoke__">iter</span>().<span class="hljs-title function_ invoke__">cloned</span>().<span class="hljs-title function_ invoke__">collect</span>();

<span class="hljs-keyword">let</span> <span class="hljs-variable">word</span> = <span class="hljs-string">"thw"</span>;
<span class="hljs-keyword">let</span> <span class="hljs-variable">word_chars</span>: <span class="hljs-type">Vec</span>&#x3C;_> = word.<span class="hljs-title function_ invoke__">chars</span>().<span class="hljs-title function_ invoke__">collect</span>();

<span class="hljs-keyword">if</span> word_set.<span class="hljs-title function_ invoke__">contains</span>(word) {
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"It is a valid English word!"</span>);
    <span class="hljs-keyword">return</span>;
}

<span class="hljs-built_in">println!</span>(<span class="hljs-string">"Are you sure you meant to spell \"{}\" that way?"</span>, word);
</code></pre>
<h3>The Wagner-Fischer Algorithm</h3>
<p>Now that we know our word is ac­tu­ally mis­spelled, we can move on to ﬁnd­ing the cor­rect spelling.
We need to ﬁnd the edit dis­tance be­tween the mis­spelled word and all the words in our set.</p>
<p>To do this, we will be us­ing the <a href="https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm">Wagner-Fischer</a> al­go­rithm.</p>
<pre><code class="hljs language-rust"><span class="hljs-comment">// Computes the Levenstein edit distance between two patterns.</span>
<span class="hljs-comment">// This is accomplished via the Wagner-Fischer algorithm</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">edit_distance</span>(source: &#x26;[<span class="hljs-type">char</span>], target: &#x26;[<span class="hljs-type">char</span>]) <span class="hljs-punctuation">-></span> <span class="hljs-type">u8</span> {
    <span class="hljs-keyword">let</span> <span class="hljs-variable">m</span> = source.<span class="hljs-title function_ invoke__">len</span>();
    <span class="hljs-keyword">let</span> <span class="hljs-variable">n</span> = target.<span class="hljs-title function_ invoke__">len</span>();

    <span class="hljs-comment">// Create an m-by-n matrix.</span>
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">d</span> = <span class="hljs-title function_ invoke__">create_empty_matrix</span>(m + <span class="hljs-number">1</span>, n + <span class="hljs-number">1</span>);

    <span class="hljs-comment">// Since we know we can transform each word into the other by replacing</span>
    <span class="hljs-comment">// successive characters (or deleting them), we can fill the first column and</span>
    <span class="hljs-comment">// row with values from 0..m and 0..n, respectively.</span>
    <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..=m {
        d[i][<span class="hljs-number">0</span>] = i <span class="hljs-keyword">as</span> <span class="hljs-type">u8</span>;
    }

    <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..=n {
        d[<span class="hljs-number">0</span>][i] = i <span class="hljs-keyword">as</span> <span class="hljs-type">u8</span>;
    }

    <span class="hljs-keyword">for</span> <span class="hljs-variable">j</span> <span class="hljs-keyword">in</span> <span class="hljs-number">1</span>..=n {
        <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">1</span>..=m {
            <span class="hljs-comment">// The total edit distance of two given letter indices i and j, one from each word</span>
            <span class="hljs-comment">// will be the sum of the edit distances of prior combinations + whether the characters</span>
            <span class="hljs-comment">// at the two indices are equal.</span>

            <span class="hljs-keyword">let</span> <span class="hljs-variable">cost</span> = <span class="hljs-keyword">if</span> source[i - <span class="hljs-number">1</span>] == target[j - <span class="hljs-number">1</span>] { <span class="hljs-number">0</span> } <span class="hljs-keyword">else</span> { <span class="hljs-number">1</span> };
            d[i][j] = (d[i - <span class="hljs-number">1</span>][j] + <span class="hljs-number">1</span>)
                .<span class="hljs-title function_ invoke__">min</span>(d[i][j - <span class="hljs-number">1</span>] + <span class="hljs-number">1</span>)
                .<span class="hljs-title function_ invoke__">min</span>(d[i - <span class="hljs-number">1</span>][j - <span class="hljs-number">1</span>] + cost);
        }
    }

    <span class="hljs-comment">// After all possible edits have been explored and minimized</span>
    <span class="hljs-comment">// the resulting minimum edit distance will be in the final item in the matrix.</span>
    d[m][n]
}

<span class="hljs-comment">// Create an empty matrix of size [m, n]</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">create_empty_matrix</span>(m: <span class="hljs-type">usize</span>, n: <span class="hljs-type">usize</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">Vec</span>&#x3C;<span class="hljs-type">Vec</span>&#x3C;<span class="hljs-type">u8</span>>> {
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">d</span> = <span class="hljs-type">Vec</span>::<span class="hljs-title function_ invoke__">with_capacity</span>(m);

    <span class="hljs-keyword">for</span> <span class="hljs-variable">_</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..m {
        d.<span class="hljs-title function_ invoke__">push</span>(<span class="hljs-built_in">vec!</span>[<span class="hljs-number">0u8</span>; n]);
    }

    d
}
</code></pre>
<p>This works pretty well.
There are a num­ber of op­ti­miza­tions we could ap­ply to this func­tion alone.
I’ll leave that as a prob­lem for the reader, since they aren’t par­tic­u­larly rel­e­vant to the meat of the larger al­go­rithm.</p>
<h3>Steps 2 + 3</h3>
<p>Now that we can de­ter­mine the edit dis­tance be­tween two words, we can per­form a brute-force search.
In this short ex­am­ple, we’re go­ing to use <code>sort_by_key</code> to do this, since our data set is so small.
If we were work­ing with a larger dic­tio­nary (say, the en­tire English lan­guage), there would be a num­ber of things we would need to do to re­duce time and mem­ory con­sump­tion.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">suggestions</span>: <span class="hljs-type">Vec</span>&#x3C;(<span class="hljs-type">String</span>, <span class="hljs-type">u8</span>)> = words
    .<span class="hljs-title function_ invoke__">into_iter</span>()
    .<span class="hljs-title function_ invoke__">filter_map</span>(|possible_word| {
        <span class="hljs-keyword">let</span> <span class="hljs-variable">possible_chars</span>: <span class="hljs-type">Vec</span>&#x3C;_> = possible_word.<span class="hljs-title function_ invoke__">chars</span>().<span class="hljs-title function_ invoke__">collect</span>();

        <span class="hljs-keyword">let</span> <span class="hljs-variable">dist</span> = <span class="hljs-title function_ invoke__">edit_distance</span>(word_chars.<span class="hljs-title function_ invoke__">as_slice</span>(), &#x26;possible_chars);

        <span class="hljs-keyword">if</span> dist &#x3C;= <span class="hljs-number">2</span> {
            <span class="hljs-title function_ invoke__">Some</span>((possible_word, dist))
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-literal">None</span>
        }
    })
    .<span class="hljs-title function_ invoke__">collect</span>();

suggestions.<span class="hljs-title function_ invoke__">sort_by_key</span>(|(_, d)| *d);

<span class="hljs-built_in">println!</span>(<span class="hljs-string">"Possible alternatives: "</span>);

suggestions.<span class="hljs-title function_ invoke__">iter</span>().for_each(|(s, _)| <span class="hljs-built_in">println!</span>(<span class="hljs-string">"- {}"</span>, s));
</code></pre>
<p>If we run the whole pro­gram again, we get an out­put some­thing like:</p>
<pre><code class="hljs language-output">Are you sure you meant to spell "thw" that way?
Possible alternatives:
- the
</code></pre>
<p>That looks pretty good to me!</p>
<p>If you would like to look at the whole pro­gram, and maybe try out your own in­puts, <a href="https://play.rust-lang.org/?version=stable&#x26;mode=debug&#x26;edition=2021&#x26;gist=fb7910ad1fb3a6c944cbc2ae8659bb31">go right on ahead</a>.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/antiques.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Building a Software Render Engine from Scratch]]></title>
            <link>https://elijahpotter.dev/articles/building_a_software_render_engine_from_scratch</link>
            <guid>https://elijahpotter.dev/articles/building_a_software_render_engine_from_scratch</guid>
            <pubDate>Wed, 31 Jan 2024 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>How I Built a Software Render Engine from Scratch</h1>
<blockquote>
<p><strong>Heads Up:</strong> This ar­ti­cle is a re­pub­lished (with some tweaks on spelling, gram­mar and lay­out) ver­sion of
an ar­ti­cle I wrote in my se­nior year of high school for my Linear Algebra class.
As such, the pub­lish date is not quite cor­rect.</p>
</blockquote>
<p>Computers are fan­tas­tic at pro­cess­ing, pro­duc­ing, and con­sum­ing data.
But I’ve of­ten found that the most dif­ﬁ­cult part of the pipeline is rep­re­sent­ing the data to a user.
This can of­ten take the form of bar charts or scat­ter plots, but there are sit­u­a­tions where they just don’t ﬁt the bill.</p>
<p><span class="small-caps">3D</span> graph­ics en­able de­vel­op­ers to cre­ate in­ter­ac­tive pro­grams that ap­pear most sim­i­lar to the nat­ural world.
By pre­sent­ing a three-di­men­sional space, the bar­ri­ers for en­try drop.
I wanted to learn more about how this worked.</p>
<figure><img src="/images/star_fox.png" alt="Star Fox, one of the earliest major successes of 3D graphics in the gaming industry." title="Star Fox, one of the earliest major successes of 3D graphics in the gaming industry."><figcaption>Star Fox, one of the ear­li­est ma­jor suc­cesses of <span class="small-caps">3D</span> graph­ics in the gam­ing in­dus­try.</figcaption></figure>
<h2>Inspiration</h2>
<p>For a long time, I’ve been told that the most preva­lent ap­pli­ca­tion of lin­ear al­ge­bra was com­puter graph­ics.
Be­fore I even be­gan my study on lin­ear al­ge­bra, I knew I wanted to get into soft­ware ren­der­ing.</p>
<p>One of the big road­blocks was the amount of tech­ni­cal know-how I thought it re­quired.
You see, most <span class="small-caps">3D</span> pro­grams do all the num­ber-crunch­ing on the spe­cially de­signed <strong>graph­ics pro­cess­ing unit</strong> that is read­ily avail­able on most mod­ern com­put­ers.
From my pre­vi­ous at­tempts to use GPUs, I knew set­ting up the pipeline is quite in­volved.
If I went that route again, I know I would likely spend most of my time deal­ing with ven­dor-spe­ciﬁc APIs.</p>
<p>Since I wanted to fo­cus on the math, I post­poned the pro­ject.
That is, un­til it oc­curred to me that I could sim­ply <em>not use the <span class="small-caps">GPU</span>.</em>
I know it might sound ob­vi­ous, but it felt so free­ing at the time.</p>
<h3>What Is a Software Render Engine?</h3>
<p>A ren­der en­gine is a piece of soft­ware that takes a set of tri­an­gles in space and pro­jects them onto a <span class="small-caps">2D</span> grid that can be dis­played on a com­puter screen.</p>
<p>A soft­ware ren­der en­gine is, as it may sound, a ren­der en­gine that does all com­pu­ta­tion in soft­ware.
No spe­cial­ized hard­ware is uti­lized <strong>at all.</strong></p>
<h2>Demo</h2>
<p>Before I get into <strong>how</strong> it works, I want to give you the chance to try it out your­self.
I’ve cre­ated a <strong>very</strong> sim­ple scene to demon­strate.</p>
<table>
<thead>
<tr>
<th>Function</th>
<th align="right">Key</th>
</tr>
</thead>
<tbody>
<tr>
<td>Look Around</td>
<td align="right">Left Mouse Click</td>
</tr>
<tr>
<td>Toggle Depth Buffer</td>
<td align="right">R</td>
</tr>
<tr>
<td>Toggle Face Sorting</td>
<td align="right">O</td>
</tr>
<tr>
<td>Toggle Backface Culling</td>
<td align="right">B</td>
</tr>
<tr>
<td>Increase <span class="small-caps">FOV</span></td>
<td align="right">Arrow Key Up <span class="small-caps">🔼</span></td>
</tr>
<tr>
<td>Decrease <span class="small-caps">FOV</span></td>
<td align="right">Arrow Key Down <span class="small-caps">🔽</span></td>
</tr>
<tr>
<td>Move View</td>
<td align="right">W, A, S, D</td>
</tr>
</tbody>
</table>
<iframe iframe frameborder="0" style="width: 100%; aspect-ratio: 16/9;" allowfullscreen src="/standalonerenderer.html"></iframe>
<h2>Explanation</h2>
<h3>Note</h3>
<p>In this ar­ti­cle, I in­tend only to talk about the math re­lated to the prob­lem.
If you are in­ter­est­ing in the nitty-gritty <em>how lines and shapes get drawn to the screen,</em>
I sug­gest you read up on <a href="https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm">Bresenham’s line al­go­rithm</a>,
<a href="https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm">Xiaolin Wu’s line al­go­rithm</a>, and
<a href="https://www.gabrielgambetta.com/computer-graphics-from-scratch/07-filled-triangles.html">Drawing Filled Triangles</a></p>
<h3>Projection</h3>
<p>With a per­spec­tive cam­era, pro­jec­tion hap­pens from the world, to­wards the<span class="push-double"></span> <span class="pull-double">“</span>sensor,” which is a de­ﬁned point.
For the sake of ar­gu­ment, let’s say that point is at the ori­gin and the cam­era is fac­ing the pos­i­tive <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>z</mi></mrow><annotation encoding="application/x-tex">z</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span> axis.</p>
<p>We want all other points to be pro­jected onto a plane <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span></span></span></span> dis­tance away.
If you are a pho­tog­ra­pher, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span></span></span></span> will be the fo­cal length of the cam­era.</p>
<figure><img src="/images/projection_comparison.gif" alt="A graphic demonstrating the different projection types" title="Perspective Projection (left) vs Orthographic Projection (right)"><figcaption>Perspective Projection (left) vs Orthographic Projection (right)</figcaption></figure>
<h3>Orthographic Projection</h3>
<p>In the case of or­tho­graphic pro­jec­tion, this is easy.
Be­cause we have placed the cam­era on the ori­gin, fac­ing the pos­i­tive <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>z</mi></mrow><annotation encoding="application/x-tex">z</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span> axis, we can draw the co­or­di­nates of any given point di­rectly to the screen.
The only con­sid­er­a­tion nec­es­sary per­tains to the points <em>be­hind</em> the cam­era, which we can skip by check­ing the sign of the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>z</mi></mrow><annotation encoding="application/x-tex">z</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span> com­po­nent.</p>
<h3>Homogeneous Coordinates</h3>
<p>This is where <em>ho­mo­ge­neous co­or­di­nates</em> come in.
When work­ing in eu­clid­ean space, we rep­re­sent a given vec­tor or co­or­di­nate us­ing three com­po­nents:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><annotation encoding="application/x-tex">\begin{bmatrix}
  x <span class="small-caps">\\
</span>  y <span class="small-caps">\\
</span>  z
\end{bma­trix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>When we are work­ing in pro­jec­tive space, we can rep­re­sent any given vec­tor or co­or­di­nate us­ing <em>four</em> com­po­nents.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>w</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><annotation encoding="application/x-tex">\begin{bmatrix}
  x <span class="small-caps">\\
</span>  y <span class="small-caps">\\
</span>  z <span class="small-caps">\\
</span>  w
\end{bma­trix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>We can con­vert be­tween these for­mats in­ter­change­ably.
To con­vert a ho­moge­nous co­or­di­nate to eu­clid­ean, di­vide all other com­po­nents by <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>w</mi></mrow><annotation encoding="application/x-tex">w</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span></span>:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>eu­clid­ean co­or­di­nate</mtext><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>x</mi><mi mathvariant="normal">/</mi><mi>w</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>y</mi><mi mathvariant="normal">/</mi><mi>w</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>z</mi><mi mathvariant="normal">/</mi><mi>w</mi></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\text{euclidean co­or­di­nate} = \begin{bmatrix}
  x / w <span class="small-caps">\\
</span>  y / w <span class="small-caps">\\
</span>  z / w
\end{bma­trix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord text"><span class="mord">eu­clid­ean co­or­di­nate</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>We can trans­form any eu­clid­ean co­or­di­nate to a ho­moge­nous co­or­di­nate by set­ting <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>w</mi><mo>=</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">w = 1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span></p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>ho­moge­nous co­or­di­nate</mtext><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\text{homogenous co­or­di­nate} = \begin{bmatrix}
  x <span class="small-caps">\\
</span>  y <span class="small-caps">\\
</span>  z <span class="small-caps">\\
</span>  1
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">ho­moge­nous co­or­di­nate</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>Just like we can per­form var­i­ous trans­for­ma­tions on eu­clid­ean co­or­di­nates, we can per­form sim­i­lar ones on ho­moge­nous co­or­di­nates.
The ma­jor dif­fer­ence: in­stead of re­quir­ing con­ven­tional ad­di­tions or sub­trac­tions, ho­moge­nous co­or­di­nates can be trans­lated via ma­trix mul­ti­pli­ca­tion.</p>
<p>For ex­am­ple, let’s say we have a point at the ori­gin, and we want to per­form both a trans­la­tion and ro­ta­tion.
If we were us­ing eu­clid­ean co­or­di­nates, we would have to trans­late it via ad­di­tion, then ro­tate it sep­a­rately.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mn>1</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">(</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>+</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>2</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo fence="true">)</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>2</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mn>1</mn></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">
\begin{bmatrix}
  0 &#x26; 1 &#x26; 0 <span class="small-caps">\\
</span>  -1 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>  0 &#x26; 0 &#x26; 1 \\
\end{bmatrix}

\left(
  \begin{bmatrix}
    0 <span class="small-caps">\\
</span>    0 <span class="small-caps">\\
</span>    0
  \end{bmatrix}
  +
  \begin{bmatrix}
    1 <span class="small-caps">\\
</span>    2 <span class="small-caps">\\
</span>    0
  \end{bmatrix}
\right)

=

\begin{bmatrix}
  2 <span class="small-caps">\\
</span>  -1 <span class="small-caps">\\
</span>  0
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord">1</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.875em" height="3.600em" viewbox="0 0 875 3600"><path d="M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1
c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,
-36,557 l0,84c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,
949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9
c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,
-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189
l0,-92c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,
-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.875em" height="3.600em" viewbox="0 0 875 3600"><path d="M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,
63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5
c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,9
c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664
c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11
c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17
c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558
l0,-144c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,
-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord">1</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>In ho­moge­nous co­or­di­nates, we can do it with a sin­gle ma­trix op­er­a­tion by prepar­ing the ma­trix ahead of time:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>T</mi><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mn>1</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>2</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">T = \begin{bmatrix}
  0 &#x26; 1 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>  -1 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>  0 &#x26; 0 &#x26; 1 &#x26; 0 <span class="small-caps">\\
</span>  0 &#x26; 0 &#x26; 0 &#x26; 1
\end{bmatrix}

\begin{bmatrix}
  1 &#x26; 0 &#x26; 0 &#x26; 1 <span class="small-caps">\\
</span>  0 &#x26; 1 &#x26; 0 &#x26; 2 <span class="small-caps">\\
</span>  0 &#x26; 0 &#x26; 1 &#x26; 0 <span class="small-caps">\\
</span>  0 &#x26; 0 &#x26; 0 &#x26; 1
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>T</mi><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>2</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mn>1</mn></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">T \begin{bmatrix}
  0 <span class="small-caps">\\
</span>  0 <span class="small-caps">\\
</span>  0 <span class="small-caps">\\
</span>  1
\end{bmatrix}

=

\begin{bmatrix}
  2 <span class="small-caps">\\
</span>  -1 <span class="small-caps">\\
</span>  0 <span class="small-caps">\\
</span>  1
\end{bmatrix}

</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>By con­dens­ing the en­tire trans­for­ma­tion into a sin­gle ma­trix, we are able to save a ton of com­put­ing time.</p>
<h3>Perspective Projection</h3>
<p>The es­sen­tial idea of per­spec­tive pro­jec­tion is sim­ple: we want points fur­ther from the cam­era to ap­pear closer to the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>z</mi></mrow><annotation encoding="application/x-tex">z</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span> axis the fur­ther away they are.
Re­mem­ber that <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span></span></span></span> is the sur­face we are pro­ject­ing onto.
This is pos­si­ble with ho­moge­nous co­or­di­nates with the fol­low­ing ma­trix:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>per­spec­tive pro­jec­tion ma­trix</mtext><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mn>1</mn><mi mathvariant="normal">/</mi><mi>d</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\text{perspective pro­jec­tion ma­trix} =

\begin{bmatrix}
  1 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>  0 &#x26; 1 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>  0 &#x26; 0 &#x26; 1 &#x26; 0 <span class="small-caps">\\
</span>  0 &#x26; 0 &#x26; -1 / d &#x26; 1
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8623em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">per­spec­tive pro­jec­tion ma­trix</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord">1/</span><span class="mord mathnormal">d</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>Assuming you have an un­der­stand­ing of ma­trix mul­ti­pli­ca­tion, it should be ap­par­ent why this works.
When the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>w</mi></mrow><annotation encoding="application/x-tex">w</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span></span> com­po­nent of the ma­trix is be­ing com­puted, the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>z</mi></mrow><annotation encoding="application/x-tex">z</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span> com­po­nent will be di­vided by <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span></span></span></span>.
The re­sult then be­comes a di­vi­sor of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>w</mi></mrow><annotation encoding="application/x-tex">w</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span></span>, which af­fects all com­po­nents of the re­sult­ing vec­tor due to the na­ture of ho­moge­nous co­or­di­nates.
In short: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>w</mi><mo>←</mo><mi>w</mi><mo>∗</mo><mo stretchy="false">(</mo><mo>−</mo><mi>z</mi><mi mathvariant="normal">/</mi><mi>d</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">w \leftarrow w * (-z / d)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">←</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.4653em;"></span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord">−</span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="mord">/</span><span class="mord mathnormal">d</span><span class="mclose">)</span></span></span></span></p>
<h3>Color</h3>
<p>Now that we’ve es­tab­lished ex­actly how to pro­ject points in space onto the screen, we need to start col­or­ing in tri­an­gles.
As I said be­fore, I am not go­ing to go into the al­go­rithms that do this.
I want to dis­cuss how to de­ter­mine the color to ﬁll in.</p>
<p>We could just choose one solid color.
As you can see from the demo (by press­ing <code>R</code>), this does­n’t lead to a par­tic­u­larly im­pres­sive or vi­su­ally pleas­ing re­sult.
I want an ad­di­tional way to con­vey depth.</p>
<p>Given the three points that make up a tri­an­gle <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>p</mi><mn>1</mn></msub></mrow><annotation encoding="application/x-tex">p_1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>p</mi><mn>2</mn></msub></mrow><annotation encoding="application/x-tex">p_2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>, and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>p</mi><mn>3</mn></msub></mrow><annotation encoding="application/x-tex">p_3</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>, we can ﬁnd its nor­mal vec­tor (the vec­tor per­pen­dic­u­lar to it’s sur­face), <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mover accent="true"><mi>n</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">\vec{n}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">n</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2355em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span> fairly eas­ily.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><mi>h</mi><mo>⃗</mo></mover><mo>=</mo><mfrac><mrow><msub><mi>p</mi><mn>2</mn></msub><mo>−</mo><msub><mi>p</mi><mn>1</mn></msub></mrow><mrow><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><msub><mi>p</mi><mn>2</mn></msub><mo>−</mo><msub><mi>p</mi><mn>1</mn></msub><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi></mrow></mfrac><mo>×</mo><mfrac><mrow><msub><mi>p</mi><mn>3</mn></msub><mo>−</mo><msub><mi>p</mi><mn>1</mn></msub></mrow><mrow><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><msub><mi>p</mi><mn>3</mn></msub><mo>−</mo><msub><mi>p</mi><mn>1</mn></msub><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">  \vec{h} = \frac{p_2 - p_1}{||p_2 - p_1||} \times \frac{p_3 - p_1}{||p_3 - p_1||}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9774em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.9774em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">h</span></span><span style="top:-3.2634em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2355em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.1963em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.2603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="small-caps">∣∣</span></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="small-caps">∣∣</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:2.1963em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.2603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="small-caps">∣∣</span></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="small-caps">∣∣</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><mi>n</mi><mo>⃗</mo></mover><mo>=</mo><mfrac><mi>h</mi><mrow><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><mi>h</mi><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">  \vec{n} = \frac{h}{||h||}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">n</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2355em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.3074em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="small-caps">∣∣</span></span><span class="mord mathnormal">h</span><span class="mord"><span class="small-caps">∣∣</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span>
<blockquote>
<p><strong>Note:</strong> the ver­ti­cal bars around a vec­tor <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><mover accent="true"><mi>v</mi><mo>⃗</mo></mover><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi></mrow><annotation encoding="application/x-tex">||\vec{v}||</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="small-caps">∣∣</span></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">v</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2077em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mord"><span class="small-caps">∣∣</span></span></span></span></span> sig­nify get­ting the vec­tor’s length.</p>
</blockquote>
<p>Now that we have the tri­an­gles nor­mal, we can ﬁll it in more brightly de­pend­ing on how di­rectly it is fac­ing the cam­era.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>bright­ness</mtext><mo>=</mo><mover accent="true"><mi>n</mi><mo>⃗</mo></mover><mo>⋅</mo><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><mfrac><mrow><msub><mi>p</mi><mn>1</mn></msub><mo>+</mo><msub><mi>p</mi><mn>2</mn></msub><mo>+</mo><msub><mi>p</mi><mn>3</mn></msub></mrow><mn>3</mn></mfrac><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi></mrow><annotation encoding="application/x-tex">\text{brightness} = \vec{n} \cdot ||\frac{p_1 + p_2 + p_3}{3}||</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">bright­ness</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">n</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2355em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.9463em;vertical-align:-0.686em;"></span><span class="mord"><span class="small-caps">∣∣</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.2603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">3</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord"><span class="small-caps">∣∣</span></span></span></span></span></span>
<p>The re­sult­ing shad­ing is the de­fault in the demo.</p>
<p>Alternatively, we can also sim­ply color based on the dis­tance from the cam­era.
The re­sult­ing im­age is called a <em>depth map.</em></p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>bright­ness</mtext><mo>=</mo><mtext>view dis­tance</mtext><mo>−</mo><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><mfrac><mrow><msub><mi>p</mi><mn>1</mn></msub><mo>+</mo><msub><mi>p</mi><mn>2</mn></msub><mo>+</mo><msub><mi>p</mi><mn>3</mn></msub></mrow><mn>3</mn></mfrac><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi></mrow><annotation encoding="application/x-tex">\text{brightness} = \text{view dis­tance} - ||\frac{p_1 + p_2 + p_3}{3}||</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">bright­ness</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord text"><span class="mord">view dis­tance</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.9463em;vertical-align:-0.686em;"></span><span class="mord"><span class="small-caps">∣∣</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.2603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">3</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord"><span class="small-caps">∣∣</span></span></span></span></span></span>
<h3>Sorting</h3>
<p>When the pro­gram is sup­plied a mesh, the faces are not in any spe­ciﬁc or­der.
If we were to just draw each face in the or­der it ar­rives, noth­ing would make sense.</p>
<p>To solve this, we sim­ple sort each face by it’s dis­tance, then draw the fur­thest faces ﬁrst.</p>
<p>If you want to see what it would look like, go to the demo and press <code>O</code> to tog­gle face sort­ing.</p>
<h3>Optimization</h3>
<p>There are count­less ways to op­ti­mize a ren­derer like this.
They all in­volve work-avoid­ance.
The one I want to dis­cuss is of­ten re­ferred to as <em>back­face culling.</em></p>
<p>In most sit­u­a­tions, there is no need to see the in­side of a mesh.
This al­lows us to avoid a lot of work for very lit­tle ef­fort.
By check­ing the align­ment of the point-to-face vec­tor with a face’s nor­mal, we can check if a given face is fac­ing to­ward us or not.</p>
<p>In the demo, you can tog­gle back­face culling with <code>B</code>.</p>
<h2>Conclusion</h2>
<p>When I ini­tially de­signed this pro­ject, I hoped it would al­low me to ap­ply some of the more ad­vanced lin­ear al­ge­bra con­cepts that I’ve learnt in the sec­ond trimester.
In this re­gard, it did not live up to my ex­pec­ta­tions.</p>
<p>While I was al­lowed to ex­plore some con­cepts, like or­thog­o­nal­ity, it was not quite sat­is­fac­tory.</p>
<p>It was not for naught, though.
I learned a lot about the ﬁelds of math and com­put­ers in­te­grate to­gether, as well as how to more ef­fec­tively con­vert math­e­mat­i­cal con­cepts into a work­ing pro­to­type.
I want to con­tinue do­ing pro­jects like this, and can­not wait to re-take Linear Algebra when I go to col­lege.</p>]]></description>
            <content:encoded><![CDATA[<h1>How I Built a Software Render Engine from Scratch</h1>
<blockquote>
<p><strong>Heads Up:</strong> This ar­ti­cle is a re­pub­lished (with some tweaks on spelling, gram­mar and lay­out) ver­sion of
an ar­ti­cle I wrote in my se­nior year of high school for my Linear Algebra class.
As such, the pub­lish date is not quite cor­rect.</p>
</blockquote>
<p>Computers are fan­tas­tic at pro­cess­ing, pro­duc­ing, and con­sum­ing data.
But I’ve of­ten found that the most dif­ﬁ­cult part of the pipeline is rep­re­sent­ing the data to a user.
This can of­ten take the form of bar charts or scat­ter plots, but there are sit­u­a­tions where they just don’t ﬁt the bill.</p>
<p><span class="small-caps">3D</span> graph­ics en­able de­vel­op­ers to cre­ate in­ter­ac­tive pro­grams that ap­pear most sim­i­lar to the nat­ural world.
By pre­sent­ing a three-di­men­sional space, the bar­ri­ers for en­try drop.
I wanted to learn more about how this worked.</p>
<figure><img src="/images/star_fox.png" alt="Star Fox, one of the earliest major successes of 3D graphics in the gaming industry." title="Star Fox, one of the earliest major successes of 3D graphics in the gaming industry."><figcaption>Star Fox, one of the ear­li­est ma­jor suc­cesses of <span class="small-caps">3D</span> graph­ics in the gam­ing in­dus­try.</figcaption></figure>
<h2>Inspiration</h2>
<p>For a long time, I’ve been told that the most preva­lent ap­pli­ca­tion of lin­ear al­ge­bra was com­puter graph­ics.
Be­fore I even be­gan my study on lin­ear al­ge­bra, I knew I wanted to get into soft­ware ren­der­ing.</p>
<p>One of the big road­blocks was the amount of tech­ni­cal know-how I thought it re­quired.
You see, most <span class="small-caps">3D</span> pro­grams do all the num­ber-crunch­ing on the spe­cially de­signed <strong>graph­ics pro­cess­ing unit</strong> that is read­ily avail­able on most mod­ern com­put­ers.
From my pre­vi­ous at­tempts to use GPUs, I knew set­ting up the pipeline is quite in­volved.
If I went that route again, I know I would likely spend most of my time deal­ing with ven­dor-spe­ciﬁc APIs.</p>
<p>Since I wanted to fo­cus on the math, I post­poned the pro­ject.
That is, un­til it oc­curred to me that I could sim­ply <em>not use the <span class="small-caps">GPU</span>.</em>
I know it might sound ob­vi­ous, but it felt so free­ing at the time.</p>
<h3>What Is a Software Render Engine?</h3>
<p>A ren­der en­gine is a piece of soft­ware that takes a set of tri­an­gles in space and pro­jects them onto a <span class="small-caps">2D</span> grid that can be dis­played on a com­puter screen.</p>
<p>A soft­ware ren­der en­gine is, as it may sound, a ren­der en­gine that does all com­pu­ta­tion in soft­ware.
No spe­cial­ized hard­ware is uti­lized <strong>at all.</strong></p>
<h2>Demo</h2>
<p>Before I get into <strong>how</strong> it works, I want to give you the chance to try it out your­self.
I’ve cre­ated a <strong>very</strong> sim­ple scene to demon­strate.</p>
<table>
<thead>
<tr>
<th>Function</th>
<th align="right">Key</th>
</tr>
</thead>
<tbody>
<tr>
<td>Look Around</td>
<td align="right">Left Mouse Click</td>
</tr>
<tr>
<td>Toggle Depth Buffer</td>
<td align="right">R</td>
</tr>
<tr>
<td>Toggle Face Sorting</td>
<td align="right">O</td>
</tr>
<tr>
<td>Toggle Backface Culling</td>
<td align="right">B</td>
</tr>
<tr>
<td>Increase <span class="small-caps">FOV</span></td>
<td align="right">Arrow Key Up <span class="small-caps">🔼</span></td>
</tr>
<tr>
<td>Decrease <span class="small-caps">FOV</span></td>
<td align="right">Arrow Key Down <span class="small-caps">🔽</span></td>
</tr>
<tr>
<td>Move View</td>
<td align="right">W, A, S, D</td>
</tr>
</tbody>
</table>
<iframe iframe frameborder="0" style="width: 100%; aspect-ratio: 16/9;" allowfullscreen src="/standalonerenderer.html"></iframe>
<h2>Explanation</h2>
<h3>Note</h3>
<p>In this ar­ti­cle, I in­tend only to talk about the math re­lated to the prob­lem.
If you are in­ter­est­ing in the nitty-gritty <em>how lines and shapes get drawn to the screen,</em>
I sug­gest you read up on <a href="https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm">Bresenham’s line al­go­rithm</a>,
<a href="https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm">Xiaolin Wu’s line al­go­rithm</a>, and
<a href="https://www.gabrielgambetta.com/computer-graphics-from-scratch/07-filled-triangles.html">Drawing Filled Triangles</a></p>
<h3>Projection</h3>
<p>With a per­spec­tive cam­era, pro­jec­tion hap­pens from the world, to­wards the<span class="push-double"></span> <span class="pull-double">“</span>sensor,” which is a de­ﬁned point.
For the sake of ar­gu­ment, let’s say that point is at the ori­gin and the cam­era is fac­ing the pos­i­tive <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>z</mi></mrow><annotation encoding="application/x-tex">z</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span> axis.</p>
<p>We want all other points to be pro­jected onto a plane <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span></span></span></span> dis­tance away.
If you are a pho­tog­ra­pher, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span></span></span></span> will be the fo­cal length of the cam­era.</p>
<figure><img src="/images/projection_comparison.gif" alt="A graphic demonstrating the different projection types" title="Perspective Projection (left) vs Orthographic Projection (right)"><figcaption>Perspective Projection (left) vs Orthographic Projection (right)</figcaption></figure>
<h3>Orthographic Projection</h3>
<p>In the case of or­tho­graphic pro­jec­tion, this is easy.
Be­cause we have placed the cam­era on the ori­gin, fac­ing the pos­i­tive <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>z</mi></mrow><annotation encoding="application/x-tex">z</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span> axis, we can draw the co­or­di­nates of any given point di­rectly to the screen.
The only con­sid­er­a­tion nec­es­sary per­tains to the points <em>be­hind</em> the cam­era, which we can skip by check­ing the sign of the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>z</mi></mrow><annotation encoding="application/x-tex">z</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span> com­po­nent.</p>
<h3>Homogeneous Coordinates</h3>
<p>This is where <em>ho­mo­ge­neous co­or­di­nates</em> come in.
When work­ing in eu­clid­ean space, we rep­re­sent a given vec­tor or co­or­di­nate us­ing three com­po­nents:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><annotation encoding="application/x-tex">\begin{bmatrix}
  x <span class="small-caps">\\
</span>  y <span class="small-caps">\\
</span>  z
\end{bma­trix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>When we are work­ing in pro­jec­tive space, we can rep­re­sent any given vec­tor or co­or­di­nate us­ing <em>four</em> com­po­nents.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>w</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><annotation encoding="application/x-tex">\begin{bmatrix}
  x <span class="small-caps">\\
</span>  y <span class="small-caps">\\
</span>  z <span class="small-caps">\\
</span>  w
\end{bma­trix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>We can con­vert be­tween these for­mats in­ter­change­ably.
To con­vert a ho­moge­nous co­or­di­nate to eu­clid­ean, di­vide all other com­po­nents by <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>w</mi></mrow><annotation encoding="application/x-tex">w</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span></span>:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>eu­clid­ean co­or­di­nate</mtext><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>x</mi><mi mathvariant="normal">/</mi><mi>w</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>y</mi><mi mathvariant="normal">/</mi><mi>w</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>z</mi><mi mathvariant="normal">/</mi><mi>w</mi></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\text{euclidean co­or­di­nate} = \begin{bmatrix}
  x / w <span class="small-caps">\\
</span>  y / w <span class="small-caps">\\
</span>  z / w
\end{bma­trix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord text"><span class="mord">eu­clid­ean co­or­di­nate</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>We can trans­form any eu­clid­ean co­or­di­nate to a ho­moge­nous co­or­di­nate by set­ting <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>w</mi><mo>=</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">w = 1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span></p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>ho­moge­nous co­or­di­nate</mtext><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\text{homogenous co­or­di­nate} = \begin{bmatrix}
  x <span class="small-caps">\\
</span>  y <span class="small-caps">\\
</span>  z <span class="small-caps">\\
</span>  1
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">ho­moge­nous co­or­di­nate</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>Just like we can per­form var­i­ous trans­for­ma­tions on eu­clid­ean co­or­di­nates, we can per­form sim­i­lar ones on ho­moge­nous co­or­di­nates.
The ma­jor dif­fer­ence: in­stead of re­quir­ing con­ven­tional ad­di­tions or sub­trac­tions, ho­moge­nous co­or­di­nates can be trans­lated via ma­trix mul­ti­pli­ca­tion.</p>
<p>For ex­am­ple, let’s say we have a point at the ori­gin, and we want to per­form both a trans­la­tion and ro­ta­tion.
If we were us­ing eu­clid­ean co­or­di­nates, we would have to trans­late it via ad­di­tion, then ro­tate it sep­a­rately.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mn>1</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">(</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>+</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>2</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo fence="true">)</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>2</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mn>1</mn></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">
\begin{bmatrix}
  0 &#x26; 1 &#x26; 0 <span class="small-caps">\\
</span>  -1 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>  0 &#x26; 0 &#x26; 1 \\
\end{bmatrix}

\left(
  \begin{bmatrix}
    0 <span class="small-caps">\\
</span>    0 <span class="small-caps">\\
</span>    0
  \end{bmatrix}
  +
  \begin{bmatrix}
    1 <span class="small-caps">\\
</span>    2 <span class="small-caps">\\
</span>    0
  \end{bmatrix}
\right)

=

\begin{bmatrix}
  2 <span class="small-caps">\\
</span>  -1 <span class="small-caps">\\
</span>  0
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord">1</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.875em" height="3.600em" viewbox="0 0 875 3600"><path d="M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1
c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,
-36,557 l0,84c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,
949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9
c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,
-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189
l0,-92c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,
-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.875em" height="3.600em" viewbox="0 0 875 3600"><path d="M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,
63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5
c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,9
c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664
c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11
c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17
c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558
l0,-144c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,
-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord">1</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewbox="0 0 667 3600"><path d="M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v0 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>In ho­moge­nous co­or­di­nates, we can do it with a sin­gle ma­trix op­er­a­tion by prepar­ing the ma­trix ahead of time:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>T</mi><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mn>1</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>2</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">T = \begin{bmatrix}
  0 &#x26; 1 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>  -1 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>  0 &#x26; 0 &#x26; 1 &#x26; 0 <span class="small-caps">\\
</span>  0 &#x26; 0 &#x26; 0 &#x26; 1
\end{bmatrix}

\begin{bmatrix}
  1 &#x26; 0 &#x26; 0 &#x26; 1 <span class="small-caps">\\
</span>  0 &#x26; 1 &#x26; 0 &#x26; 2 <span class="small-caps">\\
</span>  0 &#x26; 0 &#x26; 1 &#x26; 0 <span class="small-caps">\\
</span>  0 &#x26; 0 &#x26; 0 &#x26; 1
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>T</mi><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>2</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mn>1</mn></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">T \begin{bmatrix}
  0 <span class="small-caps">\\
</span>  0 <span class="small-caps">\\
</span>  0 <span class="small-caps">\\
</span>  1
\end{bmatrix}

=

\begin{bmatrix}
  2 <span class="small-caps">\\
</span>  -1 <span class="small-caps">\\
</span>  0 <span class="small-caps">\\
</span>  1
\end{bmatrix}

</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>By con­dens­ing the en­tire trans­for­ma­tion into a sin­gle ma­trix, we are able to save a ton of com­put­ing time.</p>
<h3>Perspective Projection</h3>
<p>The es­sen­tial idea of per­spec­tive pro­jec­tion is sim­ple: we want points fur­ther from the cam­era to ap­pear closer to the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>z</mi></mrow><annotation encoding="application/x-tex">z</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span> axis the fur­ther away they are.
Re­mem­ber that <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span></span></span></span> is the sur­face we are pro­ject­ing onto.
This is pos­si­ble with ho­moge­nous co­or­di­nates with the fol­low­ing ma­trix:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>per­spec­tive pro­jec­tion ma­trix</mtext><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mn>1</mn><mi mathvariant="normal">/</mi><mi>d</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\text{perspective pro­jec­tion ma­trix} =

\begin{bmatrix}
  1 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>  0 &#x26; 1 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>  0 &#x26; 0 &#x26; 1 &#x26; 0 <span class="small-caps">\\
</span>  0 &#x26; 0 &#x26; -1 / d &#x26; 1
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8623em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">per­spec­tive pro­jec­tion ma­trix</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord">1/</span><span class="mord mathnormal">d</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="4.800em" viewbox="0 0 667 4800"><path d="M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1200 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>Assuming you have an un­der­stand­ing of ma­trix mul­ti­pli­ca­tion, it should be ap­par­ent why this works.
When the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>w</mi></mrow><annotation encoding="application/x-tex">w</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span></span> com­po­nent of the ma­trix is be­ing com­puted, the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>z</mi></mrow><annotation encoding="application/x-tex">z</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span> com­po­nent will be di­vided by <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span></span></span></span>.
The re­sult then be­comes a di­vi­sor of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>w</mi></mrow><annotation encoding="application/x-tex">w</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span></span>, which af­fects all com­po­nents of the re­sult­ing vec­tor due to the na­ture of ho­moge­nous co­or­di­nates.
In short: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>w</mi><mo>←</mo><mi>w</mi><mo>∗</mo><mo stretchy="false">(</mo><mo>−</mo><mi>z</mi><mi mathvariant="normal">/</mi><mi>d</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">w \leftarrow w * (-z / d)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">←</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.4653em;"></span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord">−</span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="mord">/</span><span class="mord mathnormal">d</span><span class="mclose">)</span></span></span></span></p>
<h3>Color</h3>
<p>Now that we’ve es­tab­lished ex­actly how to pro­ject points in space onto the screen, we need to start col­or­ing in tri­an­gles.
As I said be­fore, I am not go­ing to go into the al­go­rithms that do this.
I want to dis­cuss how to de­ter­mine the color to ﬁll in.</p>
<p>We could just choose one solid color.
As you can see from the demo (by press­ing <code>R</code>), this does­n’t lead to a par­tic­u­larly im­pres­sive or vi­su­ally pleas­ing re­sult.
I want an ad­di­tional way to con­vey depth.</p>
<p>Given the three points that make up a tri­an­gle <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>p</mi><mn>1</mn></msub></mrow><annotation encoding="application/x-tex">p_1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>p</mi><mn>2</mn></msub></mrow><annotation encoding="application/x-tex">p_2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>, and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>p</mi><mn>3</mn></msub></mrow><annotation encoding="application/x-tex">p_3</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>, we can ﬁnd its nor­mal vec­tor (the vec­tor per­pen­dic­u­lar to it’s sur­face), <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mover accent="true"><mi>n</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">\vec{n}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">n</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2355em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span> fairly eas­ily.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><mi>h</mi><mo>⃗</mo></mover><mo>=</mo><mfrac><mrow><msub><mi>p</mi><mn>2</mn></msub><mo>−</mo><msub><mi>p</mi><mn>1</mn></msub></mrow><mrow><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><msub><mi>p</mi><mn>2</mn></msub><mo>−</mo><msub><mi>p</mi><mn>1</mn></msub><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi></mrow></mfrac><mo>×</mo><mfrac><mrow><msub><mi>p</mi><mn>3</mn></msub><mo>−</mo><msub><mi>p</mi><mn>1</mn></msub></mrow><mrow><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><msub><mi>p</mi><mn>3</mn></msub><mo>−</mo><msub><mi>p</mi><mn>1</mn></msub><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">  \vec{h} = \frac{p_2 - p_1}{||p_2 - p_1||} \times \frac{p_3 - p_1}{||p_3 - p_1||}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9774em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.9774em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">h</span></span><span style="top:-3.2634em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2355em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.1963em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.2603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="small-caps">∣∣</span></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="small-caps">∣∣</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:2.1963em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.2603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="small-caps">∣∣</span></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="small-caps">∣∣</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><mi>n</mi><mo>⃗</mo></mover><mo>=</mo><mfrac><mi>h</mi><mrow><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><mi>h</mi><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">  \vec{n} = \frac{h}{||h||}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">n</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2355em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.3074em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="small-caps">∣∣</span></span><span class="mord mathnormal">h</span><span class="mord"><span class="small-caps">∣∣</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span>
<blockquote>
<p><strong>Note:</strong> the ver­ti­cal bars around a vec­tor <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><mover accent="true"><mi>v</mi><mo>⃗</mo></mover><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi></mrow><annotation encoding="application/x-tex">||\vec{v}||</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="small-caps">∣∣</span></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">v</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2077em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mord"><span class="small-caps">∣∣</span></span></span></span></span> sig­nify get­ting the vec­tor’s length.</p>
</blockquote>
<p>Now that we have the tri­an­gles nor­mal, we can ﬁll it in more brightly de­pend­ing on how di­rectly it is fac­ing the cam­era.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>bright­ness</mtext><mo>=</mo><mover accent="true"><mi>n</mi><mo>⃗</mo></mover><mo>⋅</mo><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><mfrac><mrow><msub><mi>p</mi><mn>1</mn></msub><mo>+</mo><msub><mi>p</mi><mn>2</mn></msub><mo>+</mo><msub><mi>p</mi><mn>3</mn></msub></mrow><mn>3</mn></mfrac><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi></mrow><annotation encoding="application/x-tex">\text{brightness} = \vec{n} \cdot ||\frac{p_1 + p_2 + p_3}{3}||</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">bright­ness</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">n</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2355em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.9463em;vertical-align:-0.686em;"></span><span class="mord"><span class="small-caps">∣∣</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.2603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">3</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord"><span class="small-caps">∣∣</span></span></span></span></span></span>
<p>The re­sult­ing shad­ing is the de­fault in the demo.</p>
<p>Alternatively, we can also sim­ply color based on the dis­tance from the cam­era.
The re­sult­ing im­age is called a <em>depth map.</em></p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>bright­ness</mtext><mo>=</mo><mtext>view dis­tance</mtext><mo>−</mo><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><mfrac><mrow><msub><mi>p</mi><mn>1</mn></msub><mo>+</mo><msub><mi>p</mi><mn>2</mn></msub><mo>+</mo><msub><mi>p</mi><mn>3</mn></msub></mrow><mn>3</mn></mfrac><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi></mrow><annotation encoding="application/x-tex">\text{brightness} = \text{view dis­tance} - ||\frac{p_1 + p_2 + p_3}{3}||</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">bright­ness</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord text"><span class="mord">view dis­tance</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.9463em;vertical-align:-0.686em;"></span><span class="mord"><span class="small-caps">∣∣</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.2603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">3</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord"><span class="small-caps">∣∣</span></span></span></span></span></span>
<h3>Sorting</h3>
<p>When the pro­gram is sup­plied a mesh, the faces are not in any spe­ciﬁc or­der.
If we were to just draw each face in the or­der it ar­rives, noth­ing would make sense.</p>
<p>To solve this, we sim­ple sort each face by it’s dis­tance, then draw the fur­thest faces ﬁrst.</p>
<p>If you want to see what it would look like, go to the demo and press <code>O</code> to tog­gle face sort­ing.</p>
<h3>Optimization</h3>
<p>There are count­less ways to op­ti­mize a ren­derer like this.
They all in­volve work-avoid­ance.
The one I want to dis­cuss is of­ten re­ferred to as <em>back­face culling.</em></p>
<p>In most sit­u­a­tions, there is no need to see the in­side of a mesh.
This al­lows us to avoid a lot of work for very lit­tle ef­fort.
By check­ing the align­ment of the point-to-face vec­tor with a face’s nor­mal, we can check if a given face is fac­ing to­ward us or not.</p>
<p>In the demo, you can tog­gle back­face culling with <code>B</code>.</p>
<h2>Conclusion</h2>
<p>When I ini­tially de­signed this pro­ject, I hoped it would al­low me to ap­ply some of the more ad­vanced lin­ear al­ge­bra con­cepts that I’ve learnt in the sec­ond trimester.
In this re­gard, it did not live up to my ex­pec­ta­tions.</p>
<p>While I was al­lowed to ex­plore some con­cepts, like or­thog­o­nal­ity, it was not quite sat­is­fac­tory.</p>
<p>It was not for naught, though.
I learned a lot about the ﬁelds of math and com­put­ers in­te­grate to­gether, as well as how to more ef­fec­tively con­vert math­e­mat­i­cal con­cepts into a work­ing pro­to­type.
I want to con­tinue do­ing pro­jects like this, and can­not wait to re-take Linear Algebra when I go to col­lege.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/star_fox.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Markov Chains Are the Original Language Models]]></title>
            <link>https://elijahpotter.dev/articles/markov_chains_are_the_original_language_models</link>
            <guid>https://elijahpotter.dev/articles/markov_chains_are_the_original_language_models</guid>
            <pubDate>Wed, 31 Jan 2024 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Markov Chains are the Original Language Models</h1>
<img src="/images/old_car.webp" alt="An old car interior">
<blockquote>
<p><strong>Heads Up:</strong> This ar­ti­cle is a re­pub­lished (with some tweaks on spelling, gram­mar and lay­out) ver­sion of
an ar­ti­cle I wrote in my se­nior year of high school for my Linear Algebra class.
As such, the pub­lish date is not quite cor­rect.</p>
</blockquote>
<h2>The <span class="small-caps">AI</span> Buzz is Boring Now</h2>
<p>I’ve come to the con­clu­sion that there are four stages to the cur­rent <span class="small-caps">AI</span> hype cy­cle in an in­di­vid­ual per­son’s brain, at least as it per­tains to large lan­guage mod­els.
At the very least, these are the stages I went through.</p>
<h3>Stage One: Amazement</h3>
<p><span class="pull-double">“</span>Wow! This is so cool! I can con­verse with a com­puter just like a real per­son!”</p>
<p>This is where all the sci­ence ﬁc­tion fan­tasies come to fruition.
The pos­si­bil­i­ties seem end­less.
We can all kick back and re­lax now, right?</p>
<h3>Stage Two: Frustration</h3>
<p><span class="pull-double">“</span>Hmm&hellip; This is­n’t as ef­fec­tive as I orig­i­nally thought.”</p>
<p>It seems like the brand-new tech­nol­ogy is re­ally only ap­plic­a­ble to the kinds of work no one wants to do any­way.
What it <strong>is</strong> able to do does­n’t pro­vide too much value to you.
It gets in­for­ma­tion and logic wrong of­ten enough that it can­not be trusted for just about any­thing.</p>
<h3>Stage Three: Confusion</h3>
<p>After stage two, you start to for­get about it.
But the hype is in­escapable.
Your friends bring it up.
Your par­ents ask you about it when you go home for the hol­i­days.
Even your den­tist tries to ex­tol its virtues.</p>
<p>Even if you moved on it, no one else did.
Could that mean that you were wrong?</p>
<h3>Stage Four: Boredom</h3>
<p>At this point the rate of new lan­guage mod­els ap­pear­ing has be­come faster than rate of new JavaScript frame­works (and just as an­noy­ing).
You want to go back to your roots and start from scratch.
You want the free­dom of know­ing the whole stack from start to ﬁn­ish.
You don’t want any of the in­ef­fec­tive magic.</p>
<p>This is where I am right now.
Want to go back to my roots.
Some peo­ple work on old cars, even though they are less ef­ﬁ­cient.
At the same time though, they are more fun to work on than new cars.
I’ve de­cided to look into Markov chains.</p>
<h2>Markov Chains</h2>
<p>Below is a demon­stra­tion of my im­ple­men­ta­tion of auto-com­ple­tion us­ing Markov Chains.</p>
<p>Though it is writ­ten in Rust and com­piled to WebAssembly, it is not par­tic­u­larly ef­ﬁ­cient. To ﬁnd out why, con­tinue down the page to my de­tailed ex­pla­na­tion of the im­ple­men­ta­tion.</p>
<h2>Controls</h2>
<p>You may use ei­ther<span class="push-double"></span> <span class="pull-double">“</span>Choose Word” or your right ar­row key [→] to let the sys­tem choose the next word. Alternatively, you can tap any of the [Possible Next Words] to do so your­self.</p>
<iframe iframe frameborder="0" style="width: 100%; height: 1100px;" allowfullscreen src="/standalonemarkov.html"></iframe>
<h1>Explanation</h1>
<p>Markov chains, named af­ter their in­ven­tor, Andrey Markov, are of­ten used to model se­quences of prob­a­bilis­tic events. That is, sys­tems that can­not be mod­eled de­ter­min­is­ti­cally.</p>
<h2>Example</h2>
<p>Alice is at the gro­cery store. For every hour she is there, she has a 70% chance of leav­ing and go­ing to the plan­e­tar­ium. Conversely, she has a 30% chance of stay­ing.
If Alice is al­ready at the plan­e­tar­ium, she has a 10% chance of leav­ing and go­ing to the gro­cery store and a 90% chance of stay­ing.
We can rep­re­sent these prob­a­bil­i­ties as a table, where each col­umn be­longs to a start lo­ca­tion, and each row be­longs to a end lo­ca­tion:</p>
<table>
<thead>
<tr>
<th></th>
<th align="right"></th>
<th align="right"></th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td align="right">Start at Grocery Store</td>
<td align="right">Start at Planetarium</td>
</tr>
<tr>
<td>End at Grocery Store</td>
<td align="right">30%</td>
<td align="right">10%</td>
</tr>
<tr>
<td>End at Planetarium</td>
<td align="right">70%</td>
<td align="right">90%</td>
</tr>
</tbody>
</table>
<p>If we al­ready know Alice’s lo­ca­tion for sure, we can sim­ply per­form table lookups to pre­dict her most likely next move.
For ex­am­ple, we <em>know</em> she is at the gro­cery store right now. So by look­ing at row 2, col­umn 1, we can be 70% con­ﬁ­dent she will be at the plan­e­tar­ium next hour.
How­ever, this does­n’t work if we aren’t sure of her lo­ca­tion, or we want to pre­dict more than one hour in ad­vance. How do we pre­dict her next move if we aren’t cer­tain of her cur­rent lo­ca­tion?
In the lat­ter case, we might ex­press her cur­rent lo­ca­tion as an­other table.</p>
<table>
<thead>
<tr>
<th>Location</th>
<th align="right">% Alice Present</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grocery Store</td>
<td align="right">25%</td>
</tr>
<tr>
<td>Planetarium</td>
<td align="right">75%</td>
</tr>
</tbody>
</table>
<p>How do we es­ti­mate Alice’s lo­ca­tion in this new plane of pos­si­bil­ity? In par­tic­u­lar, how likely will Alice be at the Planetarium next hour?
Since there is a 25% prob­a­bil­ity Alice is at the gro­cery store, we mul­ti­ply that with the pro­bil­ity of her tran­si­tion­ing to the Planetarium: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>25</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>75</mn><mi mathvariant="normal">%</mi></mrow><annotation encoding="application/x-tex">25\% * 75\%</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">25%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">75%</span></span></span></span>. Next, we add the re­sult with the prob­a­bil­ity of be­ing at the Planetarium mul­ti­plied with the prob­a­bil­ity of her stay­ing: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>75</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>90</mn><mi mathvariant="normal">%</mi></mrow><annotation encoding="application/x-tex">75\% * 90\%</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">75%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">90%</span></span></span></span>.
In full, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>25</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>75</mn><mi mathvariant="normal">%</mi><mo>+</mo><mn>75</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>90</mn><mi mathvariant="normal">%</mi><mo>=</mo><mn>85</mn><mi mathvariant="normal">%</mi></mrow><annotation encoding="application/x-tex">25\% * 75\% + 75\% * 90\% = 85\%</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">25%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.0833em;"></span><span class="mord">75%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">75%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">90%</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">85%</span></span></span></span>.
To see the prob­a­bil­i­ties as a table:</p>
<table>
<thead>
<tr>
<th>Next Location</th>
<th>Calculation</th>
<th align="right">% Alice Present</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grocery Store</td>
<td><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>25</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>30</mn><mi mathvariant="normal">%</mi><mo>+</mo><mn>75</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>10</mn><mi mathvariant="normal">%</mi></mrow><annotation encoding="application/x-tex">25\% * 30\% + 75\% * 10\%</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">25%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.0833em;"></span><span class="mord">30%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">75%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">10%</span></span></span></span></td>
<td align="right">15%</td>
</tr>
<tr>
<td>Planetarium</td>
<td><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>25</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>70</mn><mi mathvariant="normal">%</mi><mo>+</mo><mn>75</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>90</mn><mi mathvariant="normal">%</mi></mrow><annotation encoding="application/x-tex">25\% * 70\% + 75\% * 90\%</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">25%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.0833em;"></span><span class="mord">70%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">75%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">90%</span></span></span></span></td>
<td align="right">85%</td>
</tr>
</tbody>
</table>
<p>The keen-eyed among you may have no­ticed that these op­er­a­tions look a lot like ma­trix mul­ti­pli­ca­tion.
In­stead of a table, we may rep­re­sent these pos­si­ble tran­si­tions as a ma­trix <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>T</mi></mrow><annotation encoding="application/x-tex">T</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span></span></span></span>, and the Alice’s cur­rent lo­ca­tion as a vec­tor <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mover accent="true"><mi>s</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">\vec{s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span>.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>T</mi><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.3</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.1</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.7</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.9</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">T = \begin{bmatrix}
  0.3 &#x26; 0.1 <span class="small-caps">\\
</span>  0.7 &#x26; 0.9
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.3</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.7</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.9</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span></span></span></span></span>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><mi>s</mi><mo>⃗</mo></mover><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>.25</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>.75</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\vec{s} = \begin{bmatrix}
 &nbsp;.25 <span class="small-caps">\\
</span> &nbsp;.75 \\
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">.25</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">.75</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span></span></span></span></span>
<blockquote>
<p><strong>Note:</strong> The lo­ca­tion of each el­e­ment re­mains the same as the table, even if we aren’t ex­plic­itly la­bel­ing the rows and columns.</p>
</blockquote>
<p>Finding the next state ma­trix be­comes as easy as mul­ti­ply­ing the cur­rent lo­ca­tion vec­tor <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mover accent="true"><mi>s</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">\vec{s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span> by <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>T</mi></mrow><annotation encoding="application/x-tex">T</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span></span></span></span>. To ﬁnd fur­ther hours in the fu­ture, we do it more than once. For ex­am­ple, to es­ti­mate three hours in the fu­ture: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>T</mi><mi>T</mi><mi>T</mi><mover accent="true"><mi>s</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">TTT\vec{s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;"><span class="small-caps">TTT</span></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span>. We can con­dense this with an ex­po­nent: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>T</mi><mn>3</mn></msup><mover accent="true"><mi>s</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">T^3\vec{s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8141em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span></span></span></span></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span> or gen­er­al­ize it to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>n</mi></mrow><annotation encoding="application/x-tex">n</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">n</span></span></span></span> hours with: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>T</mi><mi>n</mi></msup><mover accent="true"><mi>s</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">T^n\vec{s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6644em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span></span></span></span></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span>.</p>
<h2>Application to Text-Completion</h2>
<p>The prin­ci­ples above can be ap­plied to a va­ri­ety of prob­a­bilis­tic sit­u­a­tions. Most rela­vant to this par­tic­u­lar web­page, is text com­ple­tion.
We want to es­ti­mate the most likely next word to the user. Given the last word, what are the most likely next words? First, we need a dic­tio­nary.</p>
<h3>The Dictionary</h3>
<p>It is triv­ial to build a dic­tio­nary from sam­ple text. For the pur­poses of the ex­pla­na­tion, we are go­ing to start with an ar­bi­trary dic­tio­nary.</p>
<table>
<thead>
<tr>
<th>Index</th>
<th>Word</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>or­ange</td>
</tr>
<tr>
<td>1</td>
<td>fruit</td>
</tr>
<tr>
<td>2</td>
<td>pas­sion</td>
</tr>
<tr>
<td>3</td>
<td>cheese</td>
</tr>
<tr>
<td>4</td>
<td>not</td>
</tr>
<tr>
<td>5</td>
<td>is</td>
</tr>
</tbody>
</table>
<h3>Building the Transition Matrix</h3>
<p>To build our tran­si­tion ma­trix, we need to count all the tran­si­tions that oc­cur be­tween pos­si­ble words in our dic­tio­nary.
In the in­ter­est of per­for­mance, my im­ple­men­ta­tion con­verts the dic­tio­nary into a <code>HashMap&#x3C;String, usize></code>.
Next, I go through the train­ing text and match each word to it’s in­dex in the dic­tio­nary, ef­fec­tively trans­form­ing the <code>String</code> into a <code>Vec&#x3C;usize></code>.
For ex­am­ple, the phrase,<span class="push-double"></span> <span class="pull-double">“</span>passion fruit is not or­ange, cheese is or­ange,” be­comes, <code>[ 2, 1, 5, 4, 0, 3, 5, 0 ]</code>.
Next, the im­ple­men­ta­tion it­er­ates through each el­e­ment in this vec­tor, count­ing each tran­si­tion. The counts are stored in an­other <code>HashMap</code> in the in­ter­est of per­for­mance, but is even­tu­ally con­verted into a ma­trix <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>C</mi></mrow><annotation encoding="application/x-tex">C</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span></span></span></span>. Each row is the out­put word’s in­dex, and the col­umn is the in­put word’s in­dex.
For ex­am­ple, the tran­si­tion <code>"fruit" (index 1) -> "is" (index 5)</code> oc­curs ex­actly once, so we record <code>1</code> in col­umn 1, row 5.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>C</mi><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">C = \begin{bmatrix}
    0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 1 &#x26; 1 <span class="small-caps">\\
</span>    0 &#x26; 0 &#x26; 1 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>    0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>    1 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>    0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 1 <span class="small-caps">\\
</span>    0 &#x26; 1 &#x26; 0 &#x26; 1 &#x26; 0 &#x26; 0
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:7.2001em;vertical-align:-3.35em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-5.8499em;"><span class="pstrut" style="height:9.2em;"></span><span style="width:0.667em;height:7.200em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="7.200em" viewbox="0 0 667 7200"><path d="M403 1759 V84 H666 V0 H319 V1759 v3600 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v3600 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-5.8499em;"><span class="pstrut" style="height:9.2em;"></span><span style="width:0.667em;height:7.200em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="7.200em" viewbox="0 0 667 7200"><path d="M347 1759 V0 H0 V84 H263 V1759 v3600 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v3600 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>Not a very in­ter­est­ing ma­trix, is it?</p>
<p>Each el­e­ment needs to be con­verted into a prob­a­bil­ity. Take the sum of each col­umn:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>2</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><annotation encoding="application/x-tex">\begin{bmatrix}
    1 &#x26; 1 &#x26; 1 &#x26; 1 &#x26; 1 &#x26; 2
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2em;vertical-align:-0.35em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">]</span></span></span></span></span></span></span>
<p>Create a di­ag­o­nal ma­trix <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>D</mi></mrow><annotation encoding="application/x-tex">D</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span></span></span></span> com­posed of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mn>1</mn><mtext>col­umn sum</mtext></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{\text{column sum}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1901em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">col­umn sum</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>C</mi><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.5</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.5</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">C = \begin{bmatrix}
    0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 1 &#x26; 0.5 <span class="small-caps">\\
</span>    0 &#x26; 0 &#x26; 1 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>    0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>    1 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>    0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0.5 <span class="small-caps">\\
</span>    0 &#x26; 1 &#x26; 0 &#x26; 1 &#x26; 0 &#x26; 0
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:7.2001em;vertical-align:-3.35em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-5.8499em;"><span class="pstrut" style="height:9.2em;"></span><span style="width:0.667em;height:7.200em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="7.200em" viewbox="0 0 667 7200"><path d="M403 1759 V84 H666 V0 H319 V1759 v3600 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v3600 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.5</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.5</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-5.8499em;"><span class="pstrut" style="height:9.2em;"></span><span style="width:0.667em;height:7.200em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="7.200em" viewbox="0 0 667 7200"><path d="M347 1759 V0 H0 V84 H263 V1759 v3600 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v3600 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>To ﬁ­nal­ize our Markov (a.k.a. tran­si­tion) ma­trix <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>M</mi></mrow><annotation encoding="application/x-tex">M</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span></span></span></span>, we sim­ply per­form:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>M</mi><mo>=</mo><mi>D</mi><mi>C</mi></mrow><annotation encoding="application/x-tex">M = <span class="small-caps">DC</span></annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span></span></span></span></span>
<h3>Using the tran­si­tion ma­trix</h3>
<p>There are two pos­si­ble sit­u­a­tions: the user is in the process of typ­ing, or they have ﬁn­ished their last word.
The lat­ter is the eas­i­est to im­ple­ment.
Scan the user’s text, and iso­late the last word. Perform a lookup on the word list to iden­tify it’s in­dex. Create a new vec­tor con­tain­ing <code>0</code>s ex­cept for that in­dex, which should con­tain a <code>1</code>.
For ex­am­ple, if the last word was<span class="push-single"></span> <span class="pull-single">‘</span>is’,</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><mi>s</mi><mo>⃗</mo></mover><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\vec{s} = \begin{bmatrix}
    0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 1
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.2em;vertical-align:-0.35em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">]</span></span></span></span></span></span></span>
<p>Run it through our tran­si­tion ma­trix:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>M</mi><mover accent="true"><mi>s</mi><mo>⃗</mo></mover><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.5</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.5</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">M\vec{s} = \begin{bmatrix}
  0.5 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0.5 &#x26; 0
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.2em;vertical-align:-0.35em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.5</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.5</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">]</span></span></span></span></span></span></span>
<p>Meaning the most prob­a­ble next choices are at in­dices <code>0</code> and <code>4</code>, which cor­re­spond to<span class="push-double"></span> <span class="pull-double">“</span>orange” and<span class="push-double"></span> <span class="pull-double">“</span>not” re­spec­tively.
This is great for au­to­com­plete. We can sim­ply list the most prob­a­ble op­tions to the user.</p>
<h3>Text-Generation and Steady State</h3>
<p>It would be pretty neat if we could use this method to au­tomag­i­cally gen­er­ate text, right?</p>
<h4>The Naive Solution</h4>
<p>Each it­er­a­tion, choose the most likely word from the set. Maybe ran­dom­ize it a bit: choose a ran­dom word from the top 5 op­tions.
Un­for­tu­nately, there is an is­sue. All Markov chains are guar­an­teed to con­verge on a spe­ciﬁc prob­a­bilis­tic state given enough it­er­a­tions. In or­der to get text gen­er­a­tion to work un­pre­dictably and with­out con­verg­ing, we need some­thing a bit more com­plex.</p>
<h4>My Solution</h4>
<p>Create a square di­ag­o­nal ma­trix <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>R</mi></mrow><annotation encoding="application/x-tex">R</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span></span></span></span> with a side length equal to the length of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mover accent="true"><mi>s</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">\vec{s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span>. Fill the di­ag­o­nal el­e­ments with ran­dom num­bers be­tween <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0</mn></mrow><annotation encoding="application/x-tex">0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0</span></span></span></span> and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn></mrow><annotation encoding="application/x-tex">1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span>. Then choose the word whose in­dex cor­re­sponds with the high­est value of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>R</mi><mover accent="true"><mi>s</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">R\vec{s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span></p>]]></description>
            <content:encoded><![CDATA[<h1>Markov Chains are the Original Language Models</h1>
<img src="/images/old_car.webp" alt="An old car interior">
<blockquote>
<p><strong>Heads Up:</strong> This ar­ti­cle is a re­pub­lished (with some tweaks on spelling, gram­mar and lay­out) ver­sion of
an ar­ti­cle I wrote in my se­nior year of high school for my Linear Algebra class.
As such, the pub­lish date is not quite cor­rect.</p>
</blockquote>
<h2>The <span class="small-caps">AI</span> Buzz is Boring Now</h2>
<p>I’ve come to the con­clu­sion that there are four stages to the cur­rent <span class="small-caps">AI</span> hype cy­cle in an in­di­vid­ual per­son’s brain, at least as it per­tains to large lan­guage mod­els.
At the very least, these are the stages I went through.</p>
<h3>Stage One: Amazement</h3>
<p><span class="pull-double">“</span>Wow! This is so cool! I can con­verse with a com­puter just like a real per­son!”</p>
<p>This is where all the sci­ence ﬁc­tion fan­tasies come to fruition.
The pos­si­bil­i­ties seem end­less.
We can all kick back and re­lax now, right?</p>
<h3>Stage Two: Frustration</h3>
<p><span class="pull-double">“</span>Hmm&hellip; This is­n’t as ef­fec­tive as I orig­i­nally thought.”</p>
<p>It seems like the brand-new tech­nol­ogy is re­ally only ap­plic­a­ble to the kinds of work no one wants to do any­way.
What it <strong>is</strong> able to do does­n’t pro­vide too much value to you.
It gets in­for­ma­tion and logic wrong of­ten enough that it can­not be trusted for just about any­thing.</p>
<h3>Stage Three: Confusion</h3>
<p>After stage two, you start to for­get about it.
But the hype is in­escapable.
Your friends bring it up.
Your par­ents ask you about it when you go home for the hol­i­days.
Even your den­tist tries to ex­tol its virtues.</p>
<p>Even if you moved on it, no one else did.
Could that mean that you were wrong?</p>
<h3>Stage Four: Boredom</h3>
<p>At this point the rate of new lan­guage mod­els ap­pear­ing has be­come faster than rate of new JavaScript frame­works (and just as an­noy­ing).
You want to go back to your roots and start from scratch.
You want the free­dom of know­ing the whole stack from start to ﬁn­ish.
You don’t want any of the in­ef­fec­tive magic.</p>
<p>This is where I am right now.
Want to go back to my roots.
Some peo­ple work on old cars, even though they are less ef­ﬁ­cient.
At the same time though, they are more fun to work on than new cars.
I’ve de­cided to look into Markov chains.</p>
<h2>Markov Chains</h2>
<p>Below is a demon­stra­tion of my im­ple­men­ta­tion of auto-com­ple­tion us­ing Markov Chains.</p>
<p>Though it is writ­ten in Rust and com­piled to WebAssembly, it is not par­tic­u­larly ef­ﬁ­cient. To ﬁnd out why, con­tinue down the page to my de­tailed ex­pla­na­tion of the im­ple­men­ta­tion.</p>
<h2>Controls</h2>
<p>You may use ei­ther<span class="push-double"></span> <span class="pull-double">“</span>Choose Word” or your right ar­row key [→] to let the sys­tem choose the next word. Alternatively, you can tap any of the [Possible Next Words] to do so your­self.</p>
<iframe iframe frameborder="0" style="width: 100%; height: 1100px;" allowfullscreen src="/standalonemarkov.html"></iframe>
<h1>Explanation</h1>
<p>Markov chains, named af­ter their in­ven­tor, Andrey Markov, are of­ten used to model se­quences of prob­a­bilis­tic events. That is, sys­tems that can­not be mod­eled de­ter­min­is­ti­cally.</p>
<h2>Example</h2>
<p>Alice is at the gro­cery store. For every hour she is there, she has a 70% chance of leav­ing and go­ing to the plan­e­tar­ium. Conversely, she has a 30% chance of stay­ing.
If Alice is al­ready at the plan­e­tar­ium, she has a 10% chance of leav­ing and go­ing to the gro­cery store and a 90% chance of stay­ing.
We can rep­re­sent these prob­a­bil­i­ties as a table, where each col­umn be­longs to a start lo­ca­tion, and each row be­longs to a end lo­ca­tion:</p>
<table>
<thead>
<tr>
<th></th>
<th align="right"></th>
<th align="right"></th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td align="right">Start at Grocery Store</td>
<td align="right">Start at Planetarium</td>
</tr>
<tr>
<td>End at Grocery Store</td>
<td align="right">30%</td>
<td align="right">10%</td>
</tr>
<tr>
<td>End at Planetarium</td>
<td align="right">70%</td>
<td align="right">90%</td>
</tr>
</tbody>
</table>
<p>If we al­ready know Alice’s lo­ca­tion for sure, we can sim­ply per­form table lookups to pre­dict her most likely next move.
For ex­am­ple, we <em>know</em> she is at the gro­cery store right now. So by look­ing at row 2, col­umn 1, we can be 70% con­ﬁ­dent she will be at the plan­e­tar­ium next hour.
How­ever, this does­n’t work if we aren’t sure of her lo­ca­tion, or we want to pre­dict more than one hour in ad­vance. How do we pre­dict her next move if we aren’t cer­tain of her cur­rent lo­ca­tion?
In the lat­ter case, we might ex­press her cur­rent lo­ca­tion as an­other table.</p>
<table>
<thead>
<tr>
<th>Location</th>
<th align="right">% Alice Present</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grocery Store</td>
<td align="right">25%</td>
</tr>
<tr>
<td>Planetarium</td>
<td align="right">75%</td>
</tr>
</tbody>
</table>
<p>How do we es­ti­mate Alice’s lo­ca­tion in this new plane of pos­si­bil­ity? In par­tic­u­lar, how likely will Alice be at the Planetarium next hour?
Since there is a 25% prob­a­bil­ity Alice is at the gro­cery store, we mul­ti­ply that with the pro­bil­ity of her tran­si­tion­ing to the Planetarium: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>25</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>75</mn><mi mathvariant="normal">%</mi></mrow><annotation encoding="application/x-tex">25\% * 75\%</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">25%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">75%</span></span></span></span>. Next, we add the re­sult with the prob­a­bil­ity of be­ing at the Planetarium mul­ti­plied with the prob­a­bil­ity of her stay­ing: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>75</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>90</mn><mi mathvariant="normal">%</mi></mrow><annotation encoding="application/x-tex">75\% * 90\%</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">75%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">90%</span></span></span></span>.
In full, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>25</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>75</mn><mi mathvariant="normal">%</mi><mo>+</mo><mn>75</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>90</mn><mi mathvariant="normal">%</mi><mo>=</mo><mn>85</mn><mi mathvariant="normal">%</mi></mrow><annotation encoding="application/x-tex">25\% * 75\% + 75\% * 90\% = 85\%</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">25%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.0833em;"></span><span class="mord">75%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">75%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">90%</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">85%</span></span></span></span>.
To see the prob­a­bil­i­ties as a table:</p>
<table>
<thead>
<tr>
<th>Next Location</th>
<th>Calculation</th>
<th align="right">% Alice Present</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grocery Store</td>
<td><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>25</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>30</mn><mi mathvariant="normal">%</mi><mo>+</mo><mn>75</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>10</mn><mi mathvariant="normal">%</mi></mrow><annotation encoding="application/x-tex">25\% * 30\% + 75\% * 10\%</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">25%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.0833em;"></span><span class="mord">30%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">75%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">10%</span></span></span></span></td>
<td align="right">15%</td>
</tr>
<tr>
<td>Planetarium</td>
<td><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>25</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>70</mn><mi mathvariant="normal">%</mi><mo>+</mo><mn>75</mn><mi mathvariant="normal">%</mi><mo>∗</mo><mn>90</mn><mi mathvariant="normal">%</mi></mrow><annotation encoding="application/x-tex">25\% * 70\% + 75\% * 90\%</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">25%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.0833em;"></span><span class="mord">70%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">75%</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em;"></span><span class="mord">90%</span></span></span></span></td>
<td align="right">85%</td>
</tr>
</tbody>
</table>
<p>The keen-eyed among you may have no­ticed that these op­er­a­tions look a lot like ma­trix mul­ti­pli­ca­tion.
In­stead of a table, we may rep­re­sent these pos­si­ble tran­si­tions as a ma­trix <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>T</mi></mrow><annotation encoding="application/x-tex">T</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span></span></span></span>, and the Alice’s cur­rent lo­ca­tion as a vec­tor <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mover accent="true"><mi>s</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">\vec{s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span>.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>T</mi><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.3</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.1</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.7</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.9</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">T = \begin{bmatrix}
  0.3 &#x26; 0.1 <span class="small-caps">\\
</span>  0.7 &#x26; 0.9
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.3</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.7</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.9</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span></span></span></span></span>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><mi>s</mi><mo>⃗</mo></mover><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>.25</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>.75</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\vec{s} = \begin{bmatrix}
 &nbsp;.25 <span class="small-caps">\\
</span> &nbsp;.75 \\
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">.25</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">.75</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span></span></span></span></span>
<blockquote>
<p><strong>Note:</strong> The lo­ca­tion of each el­e­ment re­mains the same as the table, even if we aren’t ex­plic­itly la­bel­ing the rows and columns.</p>
</blockquote>
<p>Finding the next state ma­trix be­comes as easy as mul­ti­ply­ing the cur­rent lo­ca­tion vec­tor <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mover accent="true"><mi>s</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">\vec{s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span> by <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>T</mi></mrow><annotation encoding="application/x-tex">T</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span></span></span></span>. To ﬁnd fur­ther hours in the fu­ture, we do it more than once. For ex­am­ple, to es­ti­mate three hours in the fu­ture: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>T</mi><mi>T</mi><mi>T</mi><mover accent="true"><mi>s</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">TTT\vec{s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;"><span class="small-caps">TTT</span></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span>. We can con­dense this with an ex­po­nent: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>T</mi><mn>3</mn></msup><mover accent="true"><mi>s</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">T^3\vec{s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8141em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span></span></span></span></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span> or gen­er­al­ize it to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>n</mi></mrow><annotation encoding="application/x-tex">n</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">n</span></span></span></span> hours with: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>T</mi><mi>n</mi></msup><mover accent="true"><mi>s</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">T^n\vec{s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6644em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span></span></span></span></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span>.</p>
<h2>Application to Text-Completion</h2>
<p>The prin­ci­ples above can be ap­plied to a va­ri­ety of prob­a­bilis­tic sit­u­a­tions. Most rela­vant to this par­tic­u­lar web­page, is text com­ple­tion.
We want to es­ti­mate the most likely next word to the user. Given the last word, what are the most likely next words? First, we need a dic­tio­nary.</p>
<h3>The Dictionary</h3>
<p>It is triv­ial to build a dic­tio­nary from sam­ple text. For the pur­poses of the ex­pla­na­tion, we are go­ing to start with an ar­bi­trary dic­tio­nary.</p>
<table>
<thead>
<tr>
<th>Index</th>
<th>Word</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>or­ange</td>
</tr>
<tr>
<td>1</td>
<td>fruit</td>
</tr>
<tr>
<td>2</td>
<td>pas­sion</td>
</tr>
<tr>
<td>3</td>
<td>cheese</td>
</tr>
<tr>
<td>4</td>
<td>not</td>
</tr>
<tr>
<td>5</td>
<td>is</td>
</tr>
</tbody>
</table>
<h3>Building the Transition Matrix</h3>
<p>To build our tran­si­tion ma­trix, we need to count all the tran­si­tions that oc­cur be­tween pos­si­ble words in our dic­tio­nary.
In the in­ter­est of per­for­mance, my im­ple­men­ta­tion con­verts the dic­tio­nary into a <code>HashMap&#x3C;String, usize></code>.
Next, I go through the train­ing text and match each word to it’s in­dex in the dic­tio­nary, ef­fec­tively trans­form­ing the <code>String</code> into a <code>Vec&#x3C;usize></code>.
For ex­am­ple, the phrase,<span class="push-double"></span> <span class="pull-double">“</span>passion fruit is not or­ange, cheese is or­ange,” be­comes, <code>[ 2, 1, 5, 4, 0, 3, 5, 0 ]</code>.
Next, the im­ple­men­ta­tion it­er­ates through each el­e­ment in this vec­tor, count­ing each tran­si­tion. The counts are stored in an­other <code>HashMap</code> in the in­ter­est of per­for­mance, but is even­tu­ally con­verted into a ma­trix <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>C</mi></mrow><annotation encoding="application/x-tex">C</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span></span></span></span>. Each row is the out­put word’s in­dex, and the col­umn is the in­put word’s in­dex.
For ex­am­ple, the tran­si­tion <code>"fruit" (index 1) -> "is" (index 5)</code> oc­curs ex­actly once, so we record <code>1</code> in col­umn 1, row 5.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>C</mi><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">C = \begin{bmatrix}
    0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 1 &#x26; 1 <span class="small-caps">\\
</span>    0 &#x26; 0 &#x26; 1 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>    0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>    1 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>    0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 1 <span class="small-caps">\\
</span>    0 &#x26; 1 &#x26; 0 &#x26; 1 &#x26; 0 &#x26; 0
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:7.2001em;vertical-align:-3.35em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-5.8499em;"><span class="pstrut" style="height:9.2em;"></span><span style="width:0.667em;height:7.200em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="7.200em" viewbox="0 0 667 7200"><path d="M403 1759 V84 H666 V0 H319 V1759 v3600 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v3600 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-5.8499em;"><span class="pstrut" style="height:9.2em;"></span><span style="width:0.667em;height:7.200em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="7.200em" viewbox="0 0 667 7200"><path d="M347 1759 V0 H0 V84 H263 V1759 v3600 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v3600 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>Not a very in­ter­est­ing ma­trix, is it?</p>
<p>Each el­e­ment needs to be con­verted into a prob­a­bil­ity. Take the sum of each col­umn:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>2</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><annotation encoding="application/x-tex">\begin{bmatrix}
    1 &#x26; 1 &#x26; 1 &#x26; 1 &#x26; 1 &#x26; 2
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2em;vertical-align:-0.35em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">]</span></span></span></span></span></span></span>
<p>Create a di­ag­o­nal ma­trix <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>D</mi></mrow><annotation encoding="application/x-tex">D</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span></span></span></span> com­posed of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mn>1</mn><mtext>col­umn sum</mtext></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{\text{column sum}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1901em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">col­umn sum</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>C</mi><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.5</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.5</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">C = \begin{bmatrix}
    0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 1 &#x26; 0.5 <span class="small-caps">\\
</span>    0 &#x26; 0 &#x26; 1 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>    0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>    1 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 <span class="small-caps">\\
</span>    0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0.5 <span class="small-caps">\\
</span>    0 &#x26; 1 &#x26; 0 &#x26; 1 &#x26; 0 &#x26; 0
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:7.2001em;vertical-align:-3.35em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-5.8499em;"><span class="pstrut" style="height:9.2em;"></span><span style="width:0.667em;height:7.200em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="7.200em" viewbox="0 0 667 7200"><path d="M403 1759 V84 H666 V0 H319 V1759 v3600 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v3600 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-6.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.5</span></span></span><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.5</span></span></span><span style="top:-0.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85em;"><span style="top:-5.8499em;"><span class="pstrut" style="height:9.2em;"></span><span style="width:0.667em;height:7.200em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="7.200em" viewbox="0 0 667 7200"><path d="M347 1759 V0 H0 V84 H263 V1759 v3600 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v3600 v1759 h84z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:3.35em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>To ﬁ­nal­ize our Markov (a.k.a. tran­si­tion) ma­trix <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>M</mi></mrow><annotation encoding="application/x-tex">M</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span></span></span></span>, we sim­ply per­form:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>M</mi><mo>=</mo><mi>D</mi><mi>C</mi></mrow><annotation encoding="application/x-tex">M = <span class="small-caps">DC</span></annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span></span></span></span></span>
<h3>Using the tran­si­tion ma­trix</h3>
<p>There are two pos­si­ble sit­u­a­tions: the user is in the process of typ­ing, or they have ﬁn­ished their last word.
The lat­ter is the eas­i­est to im­ple­ment.
Scan the user’s text, and iso­late the last word. Perform a lookup on the word list to iden­tify it’s in­dex. Create a new vec­tor con­tain­ing <code>0</code>s ex­cept for that in­dex, which should con­tain a <code>1</code>.
For ex­am­ple, if the last word was<span class="push-single"></span> <span class="pull-single">‘</span>is’,</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><mi>s</mi><mo>⃗</mo></mover><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\vec{s} = \begin{bmatrix}
    0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 1
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.2em;vertical-align:-0.35em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">]</span></span></span></span></span></span></span>
<p>Run it through our tran­si­tion ma­trix:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>M</mi><mover accent="true"><mi>s</mi><mo>⃗</mo></mover><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.5</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.5</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">M\vec{s} = \begin{bmatrix}
  0.5 &#x26; 0 &#x26; 0 &#x26; 0 &#x26; 0.5 &#x26; 0
\end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.2em;vertical-align:-0.35em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.5</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.5</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">]</span></span></span></span></span></span></span>
<p>Meaning the most prob­a­ble next choices are at in­dices <code>0</code> and <code>4</code>, which cor­re­spond to<span class="push-double"></span> <span class="pull-double">“</span>orange” and<span class="push-double"></span> <span class="pull-double">“</span>not” re­spec­tively.
This is great for au­to­com­plete. We can sim­ply list the most prob­a­ble op­tions to the user.</p>
<h3>Text-Generation and Steady State</h3>
<p>It would be pretty neat if we could use this method to au­tomag­i­cally gen­er­ate text, right?</p>
<h4>The Naive Solution</h4>
<p>Each it­er­a­tion, choose the most likely word from the set. Maybe ran­dom­ize it a bit: choose a ran­dom word from the top 5 op­tions.
Un­for­tu­nately, there is an is­sue. All Markov chains are guar­an­teed to con­verge on a spe­ciﬁc prob­a­bilis­tic state given enough it­er­a­tions. In or­der to get text gen­er­a­tion to work un­pre­dictably and with­out con­verg­ing, we need some­thing a bit more com­plex.</p>
<h4>My Solution</h4>
<p>Create a square di­ag­o­nal ma­trix <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>R</mi></mrow><annotation encoding="application/x-tex">R</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span></span></span></span> with a side length equal to the length of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mover accent="true"><mi>s</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">\vec{s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span>. Fill the di­ag­o­nal el­e­ments with ran­dom num­bers be­tween <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0</mn></mrow><annotation encoding="application/x-tex">0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0</span></span></span></span> and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn></mrow><annotation encoding="application/x-tex">1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span>. Then choose the word whose in­dex cor­re­sponds with the high­est value of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>R</mi><mover accent="true"><mi>s</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">R\vec{s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">s</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.1799em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span></p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/andrei_markov.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[The Easiest Way to Run Llms Locally]]></title>
            <link>https://elijahpotter.dev/articles/the_easiest_way_to_run_llms_locally</link>
            <guid>https://elijahpotter.dev/articles/the_easiest_way_to_run_llms_locally</guid>
            <pubDate>Mon, 18 Dec 2023 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Easiest Way to Run LLMs Locally</h1>
<img src="/images/llama.webp" alt="A Goofy Lookin&#x27; Llama">
<h2>LLMs</h2>
<p>Unless you’ve been liv­ing un­der a rock for the past year, you al­ready know what LLMs are.
If you <em>do</em> hap­pen to be one of the lucky few un­aware of the cur­rent hype around these things, I’ll go through it real quick.</p>
<p>A large lan­guage model (or <span class="small-caps">LLM</span>) is a sta­tis­ti­cal model ca­pa­ble of<span class="push-double"></span> <span class="pull-double">“</span>predicting” a sub­se­quent word or let­ter, given a body of text.
Es­sen­tially, it is a com­puter pro­gram ca­pa­ble of ﬁll­ing in the blank.
If you let it pre­dict the next word, then feed the re­sult back in, you can get some pretty hu­man-look­ing text.</p>
<h2>Let’s Be Clear</h2>
<p>I hold a lot of skep­ti­cism on the prac­ti­cal ap­pli­ca­tions of LLMs as a tool.
As a blan­ket rule, I never use LLMs or any sim­i­lar tech­nol­ogy in my ed­u­ca­tion.</p>
<p>I know some peo­ple ask LLMs ques­tions like<span class="push-double"></span> <span class="pull-double">“</span>explain the fun­da­men­tal the­o­rem of cal­cu­lus to me like I’m ﬁve.”
While they may get good re­sults for ques­tions, I do not want to lean on them as a crutch.
Col­lege is not only an op­por­tu­nity to learn the raw ma­te­r­ial, but also an op­por­tu­nity to learn how to learn.
If we know any­thing about LLMs, it’s that its abil­ity to an­swer com­plex ques­tions break down as you move to more spe­cial­ized classes.</p>
<p>Which is all to say: I did not in­ves­ti­gate this with the in­ten­tion of us­ing it as a tool, I just wanted to play around.</p>
<h2>My Circumstance</h2>
<p>I use <a href="https://archlinux.org/">arch, btw.</a>
While I en­joy the level of con­trol it pro­vides, I don’t think it’s for every­body.
This is partly be­cause some things are quite dif­ﬁ­cult to set up.</p>
<p>For ex­am­ple, <span class="small-caps">GPU</span> sup­port is lim­ited and ﬁnicky, es­pe­cially if you run an Intel Arc card, like I do.
While it works per­fectly for some apps, like <a href="https://www.blender.org/">blender</a>, it does­n’t work so well for other things.
My card only has <span class="small-caps">3 GB</span> of <span class="small-caps">VRAM</span>, so it would­n’t be able to ﬁt most mod­els any­way.</p>
<p>So when I took on the task of run­ning an <span class="small-caps">LLM</span> on my lo­cal ma­chine, I started at look­ing at CPU-only so­lu­tions.</p>
<p>Initially, I tried to raw-dog <a href="https://github.com/ggerganov/llama.cpp">llama.cpp</a>.
That <em>worked</em> but only so.
The com­mand-line in­ter­face left a lot to be de­sired, and the process of down­load­ing and load­ing var­i­ous mod­els was te­dious and con­fus­ing.</p>
<h2>Ollama</h2>
<p>That’s when I dis­cov­ered <a href="https://ollama.ai/">Ollama</a>.
Installing it was as easy as run­ning:</p>
<pre><code class="hljs language-bash"><span class="hljs-built_in">sudo</span> pacman -S ollama
</code></pre>
<p>To avoid wast­ing re­sources on mul­ti­ple in­stances of each model, Ollama uses a server ar­chi­tec­ture.
You can start the server by run­ning</p>
<pre><code class="hljs language-bash">ollama serve
</code></pre>
<p>Then, you can down­load an start chat­ting with a model with:</p>
<pre><code class="hljs language-bash">ollama run llama2
<span class="hljs-comment"># Or:</span>
ollama run mistral
</code></pre>
<h2>That’s It</h2>
<p>That’s it!
It re­ally is that sim­ple.</p>
<p>Again, you might have no rea­son to do any of this.
Es­pe­cially if you are happy with the pri­vacy night­mare that is OpenAI, Google or Anthropic, or if you al­ready have a sys­tem that works for you.</p>]]></description>
            <content:encoded><![CDATA[<h1>The Easiest Way to Run LLMs Locally</h1>
<img src="/images/llama.webp" alt="A Goofy Lookin&#x27; Llama">
<h2>LLMs</h2>
<p>Unless you’ve been liv­ing un­der a rock for the past year, you al­ready know what LLMs are.
If you <em>do</em> hap­pen to be one of the lucky few un­aware of the cur­rent hype around these things, I’ll go through it real quick.</p>
<p>A large lan­guage model (or <span class="small-caps">LLM</span>) is a sta­tis­ti­cal model ca­pa­ble of<span class="push-double"></span> <span class="pull-double">“</span>predicting” a sub­se­quent word or let­ter, given a body of text.
Es­sen­tially, it is a com­puter pro­gram ca­pa­ble of ﬁll­ing in the blank.
If you let it pre­dict the next word, then feed the re­sult back in, you can get some pretty hu­man-look­ing text.</p>
<h2>Let’s Be Clear</h2>
<p>I hold a lot of skep­ti­cism on the prac­ti­cal ap­pli­ca­tions of LLMs as a tool.
As a blan­ket rule, I never use LLMs or any sim­i­lar tech­nol­ogy in my ed­u­ca­tion.</p>
<p>I know some peo­ple ask LLMs ques­tions like<span class="push-double"></span> <span class="pull-double">“</span>explain the fun­da­men­tal the­o­rem of cal­cu­lus to me like I’m ﬁve.”
While they may get good re­sults for ques­tions, I do not want to lean on them as a crutch.
Col­lege is not only an op­por­tu­nity to learn the raw ma­te­r­ial, but also an op­por­tu­nity to learn how to learn.
If we know any­thing about LLMs, it’s that its abil­ity to an­swer com­plex ques­tions break down as you move to more spe­cial­ized classes.</p>
<p>Which is all to say: I did not in­ves­ti­gate this with the in­ten­tion of us­ing it as a tool, I just wanted to play around.</p>
<h2>My Circumstance</h2>
<p>I use <a href="https://archlinux.org/">arch, btw.</a>
While I en­joy the level of con­trol it pro­vides, I don’t think it’s for every­body.
This is partly be­cause some things are quite dif­ﬁ­cult to set up.</p>
<p>For ex­am­ple, <span class="small-caps">GPU</span> sup­port is lim­ited and ﬁnicky, es­pe­cially if you run an Intel Arc card, like I do.
While it works per­fectly for some apps, like <a href="https://www.blender.org/">blender</a>, it does­n’t work so well for other things.
My card only has <span class="small-caps">3 GB</span> of <span class="small-caps">VRAM</span>, so it would­n’t be able to ﬁt most mod­els any­way.</p>
<p>So when I took on the task of run­ning an <span class="small-caps">LLM</span> on my lo­cal ma­chine, I started at look­ing at CPU-only so­lu­tions.</p>
<p>Initially, I tried to raw-dog <a href="https://github.com/ggerganov/llama.cpp">llama.cpp</a>.
That <em>worked</em> but only so.
The com­mand-line in­ter­face left a lot to be de­sired, and the process of down­load­ing and load­ing var­i­ous mod­els was te­dious and con­fus­ing.</p>
<h2>Ollama</h2>
<p>That’s when I dis­cov­ered <a href="https://ollama.ai/">Ollama</a>.
Installing it was as easy as run­ning:</p>
<pre><code class="hljs language-bash"><span class="hljs-built_in">sudo</span> pacman -S ollama
</code></pre>
<p>To avoid wast­ing re­sources on mul­ti­ple in­stances of each model, Ollama uses a server ar­chi­tec­ture.
You can start the server by run­ning</p>
<pre><code class="hljs language-bash">ollama serve
</code></pre>
<p>Then, you can down­load an start chat­ting with a model with:</p>
<pre><code class="hljs language-bash">ollama run llama2
<span class="hljs-comment"># Or:</span>
ollama run mistral
</code></pre>
<h2>That’s It</h2>
<p>That’s it!
It re­ally is that sim­ple.</p>
<p>Again, you might have no rea­son to do any of this.
Es­pe­cially if you are happy with the pri­vacy night­mare that is OpenAI, Google or Anthropic, or if you al­ready have a sys­tem that works for you.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/llama.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Do Not Type Your Notes]]></title>
            <link>https://elijahpotter.dev/articles/do_not_type_your_notes</link>
            <guid>https://elijahpotter.dev/articles/do_not_type_your_notes</guid>
            <pubDate>Sun, 29 Oct 2023 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Do <em>Not</em> Type Your Notes</h1>
<img src="/images/pen_paper.webp" alt="A man taking notes on pen and paper">
<p>I feel it nec­es­sary to make it clear who I am speak­ing to.
First and fore­most, I am speak­ing to any­one who is con­sid­er­ing switch­ing to a typed note-tak­ing sys­tem for school.
If you al­ready value hand­writ­ten notes, none of what I say will likely ap­ply to you.</p>
<p>For the last two years, I’ve typed my notes.
It has worked well for me.
<em>Really well.</em>
For a time, I be­lieved that it gave me a leg-up com­pared to my peers who hand­wrote their notes.
I still think that is true, for that spe­ciﬁc con­text.
A con­text I no longer re­side in.</p>
<p>This ar­ti­cle is also a re­ﬂec­tion.
I will re­turn to the fact that any kind of note-tak­ing is a per­sonal, cus­tomized process.
I want to look back at what worked, why, and why it does­n’t work any­more.</p>
<p>In his books, Cal Newport has rec­om­mended to stu­dents tak­ing non-tech­ni­cal courses to type their notes.
This is for a num­ber of rea­sons.
For one, typ­ing is pretty much uni­ver­sally faster than hand­writ­ing, to the point that <a href="https://radiolab.org/podcast/wubi-effect">some be­lieve</a> it was a ma­jor eco­nomic ad­van­tage to cul­tures who spoke lan­guages with smaller char­ac­ter sets.
If I can write down more in­for­ma­tion, I will have a bet­ter record of the lec­ture, and thus an eas­ier time study­ing later.
If I was tak­ing his­tory, cre­ative writ­ing, psy­chol­ogy, or busi­ness, and I in­ten­tion­ally para­phrase all my notes, I can paste them into my es­say out­line and use it as a frame­work.
Fur­ther, get­ting good at <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX</mtext></mrow><annotation encoding="application/x-tex">\LaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span> and edit-fo­cused text ed­i­tors like <a href="https://neovim.io/">NeoVim</a>, I found I could dec­i­mate my re­vi­sion time.</p>
<p>Unfortunately, this only ap­plies to hu­man­i­ties classes.
If you are fo­cused on any of the classes men­tioned above, this ar­ti­cles does­n’t ap­ply to you.</p>
<h2>It’s Hard to Keep Up</h2>
<p>Yes.
I just said that typed notes al­lows me to record more in­for­ma­tion.
This is true for hu­man­i­ties classes that are work­ing pri­mar­ily in English.
This is <em>not</em> true for tech­ni­cal classes that are work­ing pri­mar­ily in <em>math no­ta­tion.</em></p>
<p>Pretty much the only way to re­li­ably record math­e­mat­i­cal ex­pres­sions is via <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX</mtext></mrow><annotation encoding="application/x-tex">\LaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span>.
Sound sim­ple?
<strong>No.</strong></p>
<h3>An Example</h3>
<p>You can per­form ideal gas com­pu­ta­tions us­ing the for­mula <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>P</mi><mi>V</mi><mo>=</mo><mi>n</mi><mi>R</mi><mi>T</mi></mrow><annotation encoding="application/x-tex"><span class="small-caps">PV</span> = nRT</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.13889em;"><span class="small-caps">RT</span></span></span></span></span>.
If you want to ﬁnd the to­tal weight of a gas given the vol­ume, tem­per­a­ture and pres­sure, you can re­arrange to form the ex­pres­sion <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mi>P</mi><mi>V</mi></mrow><mrow><mi>R</mi><mi>T</mi></mrow></mfrac><mo>∗</mo><mtext>mol­e­c­u­lar weight</mtext><mo>=</mo><mtext>to­tal weight</mtext></mrow><annotation encoding="application/x-tex">\frac{PV}{RT} * \text{molecular weight} = \text{total weight}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2173em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8723em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;"><span class="small-caps">RT</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">P</span><span class="mord mathnormal mtight" style="margin-right:0.22222em;">V</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">mol­e­c­u­lar weight</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">to­tal weight</span></span></span></span></span>.</p>
<p>So far these ex­pres­sions have been pretty sim­ple.
The one above looks like:</p>
<pre><code class="hljs language-latex">\frac{PV}{RT} * \text{molecular weight} = \text{total weight}
</code></pre>
<p>Using a com­bi­na­tion of manic typ­ing and <a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> short­cuts, I can man­age to gen­er­ate the for­mu­las dur­ing the lec­ture with a lit­tle time to spare.
Once you start plug­ging in val­ues, how­ever, it gets dif­ﬁ­cult.</p>
<p>If we are look­ing at hy­dro­gen, and we use val­ues of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>V</mi><mo>=</mo><mn>27.0</mn><mtext> liters</mtext></mrow><annotation encoding="application/x-tex">V = 27.0 \text{ liters}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord">27.0</span><span class="mord text"><span class="mord"> liters</span></span></span></span></span>, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>P</mi><mo>=</mo><mn>755</mn><mtext> torr</mtext></mrow><annotation encoding="application/x-tex">P = 755 \text{ torr}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">755</span><span class="mord text"><span class="mord"> torr</span></span></span></span></span>, and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>T</mi><mo>=</mo><mn>31.3</mn><mtext> celsius</mtext></mrow><annotation encoding="application/x-tex">T = 31.3 \text{ cel­sius}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord">31.3</span><span class="mord text"><span class="mord"> celsius</span></span></span></span></span>, the full ex­pres­sion looks like:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mo stretchy="false">(</mo><mn>27.0</mn><mo stretchy="false">)</mo><mo stretchy="false">(</mo><mfrac><mn>755</mn><mn>760</mn></mfrac><mo stretchy="false">)</mo></mrow><mrow><mo stretchy="false">(</mo><mn>31.3</mn><mo>+</mo><mn>273.15</mn><mo stretchy="false">)</mo><mo stretchy="false">(</mo><mn>0.08206</mn><mo stretchy="false">)</mo></mrow></mfrac><mo>×</mo><mo stretchy="false">(</mo><mn>2.016</mn><mo stretchy="false">)</mo><mo>=</mo><mn>2.16</mn></mrow><annotation encoding="application/x-tex">\frac{(27.0)(\frac{755}{760})}{(31.3 + 273.15)(0.08206)} \times (2.016) = 2.16</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.5161em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.5801em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mopen">(</span><span class="mord">31.3</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">273.15</span><span class="mclose">)</span><span class="mopen">(</span><span class="mord">0.08206</span><span class="mclose">)</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.735em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mopen">(</span><span class="mord">27.0</span><span class="mclose">)</span><span class="mopen">(</span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">760</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">755</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose">)</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord">2.016</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2.16</span></span></span></span></span>
<p>If that still looks sim­ple to you, take a look at the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX</mtext></mrow><annotation encoding="application/x-tex">\LaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span>:</p>
<pre><code class="hljs language-latex">\frac{(27.0)(\frac{755}{760})}{(31.3 + 273.15)(0.08206)} * (2.016) = 2.16
</code></pre>
<p>There are two sep­a­rate prob­lems here.</p>
<ol>
<li>The <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX</mtext></mrow><annotation encoding="application/x-tex">\LaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span> is not quite self de­scrib­ing.
In or­der to get a good grasp of what it rep­re­sents, I have to ren­der it out to ei­ther <span class="small-caps">HTML</span> or in a <code>pdf</code>.</li>
<li>It is ex­tra­or­di­nar­ily dif­ﬁ­cult to write and re­vise.
Even with snip­pets and a good grasp of the key­board, even mi­nor ed­its are la­bo­ri­ous.</li>
</ol>
<p>The net re­sult: lec­tures be­come manic, un­suc­cess­ful at­tempt at repli­cat­ing the chalk­board in <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX</mtext></mrow><annotation encoding="application/x-tex">\LaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span>, not an ac­tual learn­ing ex­pe­ri­ence.</p>
<h2>Computers are Distracting</h2>
<p>I’ve <a href="/videos">spo­ken</a> on the neg­a­tive ef­fects of so­cial me­dia be­fore.
Need­less to say, so­cial me­dia has a <a href="https://doi.org/10.5817/CP2019-1-4">sig­nif­i­cant ef­fect</a> on aca­d­e­mic per­for­mance across the board.
An enor­mous pile of sci­en­tiﬁc ev­i­dent has proven that re­duc­ing so­cial me­dia use makes you a hap­pier, health­ier, more pro­duc­tive hu­man be­ing.
Tak­ing notes on the same de­vice I use for en­ter­tain­ment is a recipe for dis­trac­tion.
This is why I don’t like iPads.</p>
<p>There are dis­trac­tions on lap­tops too.
I found my­self Googling my ques­tions in­stead of ask­ing the pro­fes­sor.
Email was con­stant dis­tur­bance, in­ter­rupt­ing at what seemed like the most op­por­tune time.
If I had a stray thought about any of my side pro­jects, I in­vari­ably found my­self work­ing on them in­stead of par­tic­i­pat­ing in the lec­ture.</p>
<p>These are ex­treme ex­am­ples, and they did­n’t oc­cur of­ten, but when they did, they were sig­nif­i­cant.</p>
<h2>Customize</h2>
<p>Books like <em>Deep Work</em> by Cal Newport and <em>The Happiness Advantage</em> by Shawn Anchor claim to be able to help read­ers be­come a hap­pier, more pro­duc­tive in­di­vid­ual.
Both books start by ac­knowl­edg­ing that the most suc­cess­ful peo­ple cus­tomize their work strate­gies to what is op­ti­mal for <em>them</em>.</p>
<p>When I asked other mem­bers of the NeoVim com­mu­nity, their re­sponse was pretty sim­i­lar.
By cre­at­ing ﬂex­i­bil­ity in my study and work process, I can shift around and ﬁnd what works best for me.
Typed notes do not eas­ily pro­vide this ﬂex­i­bil­ity.</p>
<h3>The Flexibility of Handwritten Notes</h3>
<p>Personally, when­ever I type my notes, they be­come a form of graph, al­most al­ways a tree.</p>
<img src="/images/china_history_graph.webp" alt="A subset of Chinese history expressed as knowledge graph">
<p>The above is what I am in­tend­ing to write in my mind.
But given that I am work­ing in un­for­mat­ted text, it ends up look­ing like:</p>
<pre><code class="hljs language-markdown"># Chinese History

## Warring States Period

### Lao Tzu

This is some information on Lao Tzu

### Confucius

This is some information on Confucius

## Civil Service Exam

The civil service exam was initally based on Confucian values, and resulted in a highly educated government.
</code></pre>
<p>While this is a non-tech­ni­cal ex­am­ple, it does well to rep­re­sent the prob­lem I am speak­ing to.
With a pen and pa­per, you are phys­i­cally writ­ing out the as­so­ci­a­tions be­tween con­cepts.
When typ­ing notes, you must do so lin­guis­ti­cally.
When hand­writ­ing notes, you have the op­por­tu­nity to in­clude nav­i­ga­tional com­po­nents.
You can phys­i­cally place dif­fer­ent con­cepts in dif­fer­ent places, and in­ter­re­late them sim­i­larly.
By tran­scrib­ing your full two-di­menin­sional men­tal map, you are start­ing to uti­lize the parts of your brain nor­mally re­served for nav­i­ga­tion.</p>
<p>I sus­pect this is why re­ten­tion is of­ten so much higher with hand­writ­ten notes.
Hu­man nav­i­ga­tional mem­ory is the most ac­cu­rate and long-stand­ing kind.
This likely comes down the nav­i­ga­tional chal­lenges of early hunter-gath­er­ers.
Sport mem­o­riz­ers in­vari­ably turn to this kind of mem­ory to re­mem­ber long se­quences of seem­ingly mean­ing­less in­for­ma­tion.</p>
<p>By as­so­ci­at­ing the phys­i­cal lo­ca­tion of in­for­ma­tion in my notes with the in­for­ma­tion it­self, I am able to re­tain the in­for­ma­tion more ac­cu­rately.</p>
<h2>Final Thoughts</h2>
<p>Do I re­gret not switch­ing to hand­writ­ten notes sooner?
<strong>No.</strong>
Will I con­tinue typ­ing my notes in hu­man­i­ties classes?
<strong>Yes.</strong></p>
<p>I know I phrased the ti­tle in a pri­mar­ily in­struc­tive way, but there is only one thing I want you to re­mem­ber: tak­ing notes is an ul­ti­mately per­sonal process.
<strong>Do what works for you.</strong>
Similarly, I am just now en­ter­ing what feels like a new world pos­si­bil­ity.
It may <em>not</em> end up go­ing as well as I the­o­rize.
Ei­ther way, you will hear from me again.</p>]]></description>
            <content:encoded><![CDATA[<h1>Do <em>Not</em> Type Your Notes</h1>
<img src="/images/pen_paper.webp" alt="A man taking notes on pen and paper">
<p>I feel it nec­es­sary to make it clear who I am speak­ing to.
First and fore­most, I am speak­ing to any­one who is con­sid­er­ing switch­ing to a typed note-tak­ing sys­tem for school.
If you al­ready value hand­writ­ten notes, none of what I say will likely ap­ply to you.</p>
<p>For the last two years, I’ve typed my notes.
It has worked well for me.
<em>Really well.</em>
For a time, I be­lieved that it gave me a leg-up com­pared to my peers who hand­wrote their notes.
I still think that is true, for that spe­ciﬁc con­text.
A con­text I no longer re­side in.</p>
<p>This ar­ti­cle is also a re­ﬂec­tion.
I will re­turn to the fact that any kind of note-tak­ing is a per­sonal, cus­tomized process.
I want to look back at what worked, why, and why it does­n’t work any­more.</p>
<p>In his books, Cal Newport has rec­om­mended to stu­dents tak­ing non-tech­ni­cal courses to type their notes.
This is for a num­ber of rea­sons.
For one, typ­ing is pretty much uni­ver­sally faster than hand­writ­ing, to the point that <a href="https://radiolab.org/podcast/wubi-effect">some be­lieve</a> it was a ma­jor eco­nomic ad­van­tage to cul­tures who spoke lan­guages with smaller char­ac­ter sets.
If I can write down more in­for­ma­tion, I will have a bet­ter record of the lec­ture, and thus an eas­ier time study­ing later.
If I was tak­ing his­tory, cre­ative writ­ing, psy­chol­ogy, or busi­ness, and I in­ten­tion­ally para­phrase all my notes, I can paste them into my es­say out­line and use it as a frame­work.
Fur­ther, get­ting good at <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX</mtext></mrow><annotation encoding="application/x-tex">\LaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span> and edit-fo­cused text ed­i­tors like <a href="https://neovim.io/">NeoVim</a>, I found I could dec­i­mate my re­vi­sion time.</p>
<p>Unfortunately, this only ap­plies to hu­man­i­ties classes.
If you are fo­cused on any of the classes men­tioned above, this ar­ti­cles does­n’t ap­ply to you.</p>
<h2>It’s Hard to Keep Up</h2>
<p>Yes.
I just said that typed notes al­lows me to record more in­for­ma­tion.
This is true for hu­man­i­ties classes that are work­ing pri­mar­ily in English.
This is <em>not</em> true for tech­ni­cal classes that are work­ing pri­mar­ily in <em>math no­ta­tion.</em></p>
<p>Pretty much the only way to re­li­ably record math­e­mat­i­cal ex­pres­sions is via <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX</mtext></mrow><annotation encoding="application/x-tex">\LaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span>.
Sound sim­ple?
<strong>No.</strong></p>
<h3>An Example</h3>
<p>You can per­form ideal gas com­pu­ta­tions us­ing the for­mula <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>P</mi><mi>V</mi><mo>=</mo><mi>n</mi><mi>R</mi><mi>T</mi></mrow><annotation encoding="application/x-tex"><span class="small-caps">PV</span> = nRT</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.13889em;"><span class="small-caps">RT</span></span></span></span></span>.
If you want to ﬁnd the to­tal weight of a gas given the vol­ume, tem­per­a­ture and pres­sure, you can re­arrange to form the ex­pres­sion <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mi>P</mi><mi>V</mi></mrow><mrow><mi>R</mi><mi>T</mi></mrow></mfrac><mo>∗</mo><mtext>mol­e­c­u­lar weight</mtext><mo>=</mo><mtext>to­tal weight</mtext></mrow><annotation encoding="application/x-tex">\frac{PV}{RT} * \text{molecular weight} = \text{total weight}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2173em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8723em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;"><span class="small-caps">RT</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">P</span><span class="mord mathnormal mtight" style="margin-right:0.22222em;">V</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">mol­e­c­u­lar weight</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">to­tal weight</span></span></span></span></span>.</p>
<p>So far these ex­pres­sions have been pretty sim­ple.
The one above looks like:</p>
<pre><code class="hljs language-latex">\frac{PV}{RT} * \text{molecular weight} = \text{total weight}
</code></pre>
<p>Using a com­bi­na­tion of manic typ­ing and <a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> short­cuts, I can man­age to gen­er­ate the for­mu­las dur­ing the lec­ture with a lit­tle time to spare.
Once you start plug­ging in val­ues, how­ever, it gets dif­ﬁ­cult.</p>
<p>If we are look­ing at hy­dro­gen, and we use val­ues of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>V</mi><mo>=</mo><mn>27.0</mn><mtext> liters</mtext></mrow><annotation encoding="application/x-tex">V = 27.0 \text{ liters}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord">27.0</span><span class="mord text"><span class="mord"> liters</span></span></span></span></span>, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>P</mi><mo>=</mo><mn>755</mn><mtext> torr</mtext></mrow><annotation encoding="application/x-tex">P = 755 \text{ torr}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">755</span><span class="mord text"><span class="mord"> torr</span></span></span></span></span>, and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>T</mi><mo>=</mo><mn>31.3</mn><mtext> celsius</mtext></mrow><annotation encoding="application/x-tex">T = 31.3 \text{ cel­sius}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord">31.3</span><span class="mord text"><span class="mord"> celsius</span></span></span></span></span>, the full ex­pres­sion looks like:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mo stretchy="false">(</mo><mn>27.0</mn><mo stretchy="false">)</mo><mo stretchy="false">(</mo><mfrac><mn>755</mn><mn>760</mn></mfrac><mo stretchy="false">)</mo></mrow><mrow><mo stretchy="false">(</mo><mn>31.3</mn><mo>+</mo><mn>273.15</mn><mo stretchy="false">)</mo><mo stretchy="false">(</mo><mn>0.08206</mn><mo stretchy="false">)</mo></mrow></mfrac><mo>×</mo><mo stretchy="false">(</mo><mn>2.016</mn><mo stretchy="false">)</mo><mo>=</mo><mn>2.16</mn></mrow><annotation encoding="application/x-tex">\frac{(27.0)(\frac{755}{760})}{(31.3 + 273.15)(0.08206)} \times (2.016) = 2.16</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.5161em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.5801em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mopen">(</span><span class="mord">31.3</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">273.15</span><span class="mclose">)</span><span class="mopen">(</span><span class="mord">0.08206</span><span class="mclose">)</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.735em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mopen">(</span><span class="mord">27.0</span><span class="mclose">)</span><span class="mopen">(</span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">760</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">755</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose">)</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord">2.016</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2.16</span></span></span></span></span>
<p>If that still looks sim­ple to you, take a look at the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX</mtext></mrow><annotation encoding="application/x-tex">\LaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span>:</p>
<pre><code class="hljs language-latex">\frac{(27.0)(\frac{755}{760})}{(31.3 + 273.15)(0.08206)} * (2.016) = 2.16
</code></pre>
<p>There are two sep­a­rate prob­lems here.</p>
<ol>
<li>The <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX</mtext></mrow><annotation encoding="application/x-tex">\LaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span> is not quite self de­scrib­ing.
In or­der to get a good grasp of what it rep­re­sents, I have to ren­der it out to ei­ther <span class="small-caps">HTML</span> or in a <code>pdf</code>.</li>
<li>It is ex­tra­or­di­nar­ily dif­ﬁ­cult to write and re­vise.
Even with snip­pets and a good grasp of the key­board, even mi­nor ed­its are la­bo­ri­ous.</li>
</ol>
<p>The net re­sult: lec­tures be­come manic, un­suc­cess­ful at­tempt at repli­cat­ing the chalk­board in <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX</mtext></mrow><annotation encoding="application/x-tex">\LaTeX</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span></span></span></span>, not an ac­tual learn­ing ex­pe­ri­ence.</p>
<h2>Computers are Distracting</h2>
<p>I’ve <a href="/videos">spo­ken</a> on the neg­a­tive ef­fects of so­cial me­dia be­fore.
Need­less to say, so­cial me­dia has a <a href="https://doi.org/10.5817/CP2019-1-4">sig­nif­i­cant ef­fect</a> on aca­d­e­mic per­for­mance across the board.
An enor­mous pile of sci­en­tiﬁc ev­i­dent has proven that re­duc­ing so­cial me­dia use makes you a hap­pier, health­ier, more pro­duc­tive hu­man be­ing.
Tak­ing notes on the same de­vice I use for en­ter­tain­ment is a recipe for dis­trac­tion.
This is why I don’t like iPads.</p>
<p>There are dis­trac­tions on lap­tops too.
I found my­self Googling my ques­tions in­stead of ask­ing the pro­fes­sor.
Email was con­stant dis­tur­bance, in­ter­rupt­ing at what seemed like the most op­por­tune time.
If I had a stray thought about any of my side pro­jects, I in­vari­ably found my­self work­ing on them in­stead of par­tic­i­pat­ing in the lec­ture.</p>
<p>These are ex­treme ex­am­ples, and they did­n’t oc­cur of­ten, but when they did, they were sig­nif­i­cant.</p>
<h2>Customize</h2>
<p>Books like <em>Deep Work</em> by Cal Newport and <em>The Happiness Advantage</em> by Shawn Anchor claim to be able to help read­ers be­come a hap­pier, more pro­duc­tive in­di­vid­ual.
Both books start by ac­knowl­edg­ing that the most suc­cess­ful peo­ple cus­tomize their work strate­gies to what is op­ti­mal for <em>them</em>.</p>
<p>When I asked other mem­bers of the NeoVim com­mu­nity, their re­sponse was pretty sim­i­lar.
By cre­at­ing ﬂex­i­bil­ity in my study and work process, I can shift around and ﬁnd what works best for me.
Typed notes do not eas­ily pro­vide this ﬂex­i­bil­ity.</p>
<h3>The Flexibility of Handwritten Notes</h3>
<p>Personally, when­ever I type my notes, they be­come a form of graph, al­most al­ways a tree.</p>
<img src="/images/china_history_graph.webp" alt="A subset of Chinese history expressed as knowledge graph">
<p>The above is what I am in­tend­ing to write in my mind.
But given that I am work­ing in un­for­mat­ted text, it ends up look­ing like:</p>
<pre><code class="hljs language-markdown"># Chinese History

## Warring States Period

### Lao Tzu

This is some information on Lao Tzu

### Confucius

This is some information on Confucius

## Civil Service Exam

The civil service exam was initally based on Confucian values, and resulted in a highly educated government.
</code></pre>
<p>While this is a non-tech­ni­cal ex­am­ple, it does well to rep­re­sent the prob­lem I am speak­ing to.
With a pen and pa­per, you are phys­i­cally writ­ing out the as­so­ci­a­tions be­tween con­cepts.
When typ­ing notes, you must do so lin­guis­ti­cally.
When hand­writ­ing notes, you have the op­por­tu­nity to in­clude nav­i­ga­tional com­po­nents.
You can phys­i­cally place dif­fer­ent con­cepts in dif­fer­ent places, and in­ter­re­late them sim­i­larly.
By tran­scrib­ing your full two-di­menin­sional men­tal map, you are start­ing to uti­lize the parts of your brain nor­mally re­served for nav­i­ga­tion.</p>
<p>I sus­pect this is why re­ten­tion is of­ten so much higher with hand­writ­ten notes.
Hu­man nav­i­ga­tional mem­ory is the most ac­cu­rate and long-stand­ing kind.
This likely comes down the nav­i­ga­tional chal­lenges of early hunter-gath­er­ers.
Sport mem­o­riz­ers in­vari­ably turn to this kind of mem­ory to re­mem­ber long se­quences of seem­ingly mean­ing­less in­for­ma­tion.</p>
<p>By as­so­ci­at­ing the phys­i­cal lo­ca­tion of in­for­ma­tion in my notes with the in­for­ma­tion it­self, I am able to re­tain the in­for­ma­tion more ac­cu­rately.</p>
<h2>Final Thoughts</h2>
<p>Do I re­gret not switch­ing to hand­writ­ten notes sooner?
<strong>No.</strong>
Will I con­tinue typ­ing my notes in hu­man­i­ties classes?
<strong>Yes.</strong></p>
<p>I know I phrased the ti­tle in a pri­mar­ily in­struc­tive way, but there is only one thing I want you to re­mem­ber: tak­ing notes is an ul­ti­mately per­sonal process.
<strong>Do what works for you.</strong>
Similarly, I am just now en­ter­ing what feels like a new world pos­si­bil­ity.
It may <em>not</em> end up go­ing as well as I the­o­rize.
Ei­ther way, you will hear from me again.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/pen_paper.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Quantifying Hope on a Global Scale]]></title>
            <link>https://elijahpotter.dev/articles/quantifying_hope_on_a_global_scale</link>
            <guid>https://elijahpotter.dev/articles/quantifying_hope_on_a_global_scale</guid>
            <pubDate>Fri, 20 Oct 2023 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Quantifying Hope on a Global Scale</h1>
<p><strong><em>Hope</em></strong> is a some­what neb­u­lous word.</p>
<p>For some, it is an ex­pec­ta­tion of what the fu­ture will be.
For oth­ers, it is a goal for what the fu­ture <em>should</em> be.
The word it­self means some­thing dif­fer­ent to each per­son.</p>
<p>Most gen­er­ally though, hope de­scribes how one per­ceives the mo­men­tum of the world.
Do things seem to be gen­er­ally get­ting bet­ter, or do they gen­er­ally seem to be get­ting worse?
Per­son­ally, I think things are rapidly get­ting <strong>much, much</strong> bet­ter.</p>
<h2>We <em>Should</em> Be Hopeful</h2>
<p>First, let’s de­ﬁne our met­ric.
The <strong>world</strong> is bet­ter off when <strong>peo­ple</strong> are bet­ter off.
The more peo­ple who are able to live long, mean­ing­ful lives, the bet­ter.</p>
<p>In his posthu­mously pub­lished book, <a href="https://www.amazon.com/Factfulness-Reasons-World-Things-Better/dp/1250123828/"><em>Factfulness: Ten Reasons We’re Wrong About the World&thinsp;&mdash;&thinsp;and Why Things Are Better Than You Think</em></a>, Hans Rosling made the case that the qual­ity and length of hu­man life has been trend­ing up­ward for more than a cen­tury.
Life ex­pectancy at birth has <a href="https://ourworldindata.org/life-expectancy">more than dou­bled</a> in the last cen­tury alone.
The por­tion of the global pop­u­la­tion liv­ing in poverty is lower than <a href="https://ourworldindata.org/poverty">any point in his­tory</a>.</p>
<blockquote>
<p><strong>Side note:</strong> a <em>great</em> re­source to learn about this is Our World In Data’s <a href="https://ourworldindata.org/extreme-poverty-in-brief">com­pre­hen­sive, yet ap­proach­able re­port</a>.</p>
</blockquote>
<p>All of this progress does <strong>not</strong> mean we should just stop here and say the work is<span class="push-double"></span> <span class="pull-double">“</span>done.”
However, I be­lieve that of­ten the best ap­proaches to solv­ing world-scale prob­lems (like poverty) start by ﬁrst look­ing at the so­lu­tions that al­ready work.</p>
<p>In the book, Rosling tells how he is of­ten de­scribed as an<span class="push-double"></span> <span class="pull-double">“</span>optimist.”
He thinks it is a mis­nomer, since op­ti­mists are of­ten por­trayed as fool­ish and mis­in­formed.
He, how­ever was likely more qual­i­ﬁed than any­one to speak on the pos­si­bil­i­ties of the likely good fu­ture, so his pos­i­tive out­look was en­tirely jus­ti­ﬁed.
For this rea­son, he de­scribed him­self a<span class="push-double"></span> <span class="pull-double">“</span>possibilist”.
While I don’t even ap­proach his qual­i­ﬁ­ca­tions, I would sim­i­larly de­scribe my world­view as<span class="push-double"></span> <span class="pull-double">“</span>possibilist.”</p>
<h2>The Solutions That Exist</h2>
<p>I’ve spo­ken on the topic of so­lu­tion-fo­cused think­ing <a href="/articles/the_climate_change_progress_bar">be­fore</a>, but I think it war­rants re­vis­it­ing.
I don’t want this to be the fo­cus of my writ­ing to­day, but I do want to pro­vide a brief ex­am­ple of the kind of so­lu­tions that ex­ist <strong>to­day</strong> that are hav­ing pro­found, real-world im­pacts.</p>
<p>One of the crit­i­cal en­vi­ron­men­tal prob­lems is re­lated to re­cy­cling.
Every year, <a href="https://ourworldindata.org/how-much-plastic-waste-ends-up-in-the-ocean">18 mil­lion tonnes</a> of trash is leaked to the en­vi­ron­ment, in­clud­ing our oceans and rivers.
Ac­cord­ing to <a href="https://www.amprobotics.com/"><span class="small-caps">AMP</span> Robotics</a> founder Matanya Horowitz, this trash can ac­tu­ally be a hugely prof­itable com­mod­ity.
De­pend­ing on mar­ket con­di­tions, one ton of re­cy­clable ma­te­r­ial can be worth up to $100.
The is­sue: the cost of sort­ing the ma­te­r­ial into a us­able form is of­ten also around $100, most of which is spent on hu­man la­bor.
Since the cost of pro­cess­ing the ma­te­r­ial to <em>pre­pare</em> to sell is about the same as it’s sold price, there is lit­tle eco­nomic in­cen­tive to do much re­cy­cling.
In the case of plas­tics re­cy­cling, the is­sue is <strong>not</strong> that peo­ple don’t re­cy­cle enough.
It is that the cost of re­cy­cling the re­sult­ing ma­te­r­ial is too high.</p>
<p>So what does <span class="small-caps">AMP</span> Robotics do?
They have au­to­mated the process of re­cy­cling, bring­ing down the costs pre­cip­i­tously.
With ro­bot­ics, the cost of re­cy­cling (to the waste com­pany) is now much lower than the ac­tual sold price of ma­te­r­ial, mak­ing it an ex­tremely prof­itable busi­ness.</p>
<p>I think this is a great ex­am­ple for two rea­sons.
First and fore­most, we are of­ten told, with­out much sub­stan­ti­at­ing ev­i­dence, that the prob­lem of ocean plas­tics and pol­lu­tion are the re­sult of in­di­vid­ual peo­ple choos­ing not to re­cy­cle.
In re­al­ity, how­ever, the lim­it­ing fac­tor is not on the in­di­vid­ual, but the waste man­age­ment com­pa­ny’s ef­ﬁ­ciency.</p>
<h2>Quantifying Hope</h2>
<p>I’ve es­tab­lished that the world <strong>is</strong> be­com­ing a bet­ter place to live for al­most every­body, all the time.
I’ve also made clear that this pos­i­tive change is the re­sult of so­lu­tions that get re­ported on (by the news) less fre­quently than the prob­lems they solve.
With that done, I think it is time to re­turn to the topic of hope.</p>
<p>My end goal was to au­to­mate the process of ﬁnd­ing so­lu­tions that worked (like <span class="small-caps">AMP</span> Robotics).
I un­der­stood that this would be an enor­mously dif­ﬁ­cult nat­ural lan­guage pro­cess­ing task, so I de­cided to start sim­ple.
The plan: I would ﬁrst quan­tify how hope­ful a given news ar­ti­cle was, then work from there.</p>
<h3>Web Scraping</h3>
<p>The ﬁrst step was to write a sys­tem that could re­trieve re­cent, im­por­tant news ar­ti­cles re­li­ably.
There is al­ready in­fra­struc­ture on most news web­sites that makes this pretty easy: RSS.
By reg­u­larly it­er­at­ing through a list of <span class="small-caps">RSS</span> feeds, we can get pretty good news cov­er­age for most of the world.
Ad­di­tion­ally, news or­ga­ni­za­tions pri­or­i­tize the con­tent of their <span class="small-caps">RSS</span> based on what <em>they</em> think is im­por­tant.</p>
<p>My ini­tial im­ple­men­ta­tion was in Rust, which I will show here.
As you can see, I spent some time par­al­leliz­ing the scrape job.
This was pri­mar­ily to re­duce the amount of time it would take to it­er­ate on the hope quan­ti­fy­ing al­go­rithm, so I could get close to real-time feed­back on it when I made changes.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">use</span> rss::{Channel, Item};
<span class="hljs-keyword">use</span> tokio::sync::mpsc::unbounded_channel;

<span class="hljs-comment">/// Download all items from an RSS Feed</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_feed_url_items</span>(url: &#x26;<span class="hljs-type">str</span>) <span class="hljs-punctuation">-></span> anyhow::<span class="hljs-type">Result</span>&#x3C;<span class="hljs-type">Vec</span>&#x3C;Item>> {
    <span class="hljs-keyword">let</span> <span class="hljs-variable">content</span> = reqwest::<span class="hljs-title function_ invoke__">get</span>(url).<span class="hljs-keyword">await</span>?.<span class="hljs-title function_ invoke__">bytes</span>().<span class="hljs-keyword">await</span>?;

    <span class="hljs-keyword">let</span> <span class="hljs-variable">channel</span> = Channel::<span class="hljs-title function_ invoke__">read_from</span>(&#x26;content[..])?;
    <span class="hljs-title function_ invoke__">Ok</span>(channel.<span class="hljs-title function_ invoke__">into_items</span>())
}

<span class="hljs-comment">/// Download all items from a list of RSS feeds, in parallel.</span>
<span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_all_feed_urls</span>(urls: <span class="hljs-keyword">impl</span> <span class="hljs-title class_">IntoIterator</span>&#x3C;Item = &#x26;<span class="hljs-type">str</span>>) <span class="hljs-punctuation">-></span> anyhow::<span class="hljs-type">Result</span>&#x3C;<span class="hljs-type">Vec</span>&#x3C;Item>> {
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">items_receiver</span> = {
        <span class="hljs-keyword">let</span> (items_sender, items_receiver) = <span class="hljs-title function_ invoke__">unbounded_channel</span>();

        <span class="hljs-keyword">for</span> <span class="hljs-variable">url</span> <span class="hljs-keyword">in</span> urls {
            <span class="hljs-keyword">let</span> <span class="hljs-variable">url</span> = url.<span class="hljs-title function_ invoke__">to_string</span>();
            <span class="hljs-keyword">let</span> <span class="hljs-variable">items_sender</span> = items_sender.<span class="hljs-title function_ invoke__">clone</span>();
            tokio::<span class="hljs-title function_ invoke__">spawn</span>(<span class="hljs-keyword">async</span> <span class="hljs-keyword">move</span> {
                items_sender.<span class="hljs-title function_ invoke__">send</span>(<span class="hljs-title function_ invoke__">get_feed_url_items</span>(&#x26;url).<span class="hljs-keyword">await</span>).<span class="hljs-title function_ invoke__">unwrap</span>();
            });
        }
        items_receiver
    };

    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">res</span> = <span class="hljs-type">Vec</span>::<span class="hljs-title function_ invoke__">new</span>();

    <span class="hljs-keyword">while</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Some</span>(items_res) = items_receiver.<span class="hljs-title function_ invoke__">recv</span>().<span class="hljs-keyword">await</span> {
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">items</span> = items_res?;
        res.<span class="hljs-title function_ invoke__">append</span>(&#x26;<span class="hljs-keyword">mut</span> items);
    }

    <span class="hljs-title function_ invoke__">Ok</span>(res)
}
</code></pre>
<h3>Rewriting into&nbsp;.<span class="small-caps">NET</span> Core</h3>
<p>Now that I’ve found a rel­a­tively high qual­ity source of raw data to es­ti­mate pub­lic opin­ion, I need to con­dense it into a sin­gle num­ber, and look at how it changes over time.
It was at this point I rewrote most of the code into C# and <a href="https://dotnet.microsoft.com/en-us/apps/aspnet">ASP.NET Core</a>.
I had four rea­sons for this:</p>
<ol>
<li>While I’ve used ASP.NET Core and Entity Framework Core in the past, I haven’t used it re­cently.
I wanted to see how the ecosys­tem has changed in the in­ter­ven­ing time.</li>
<li>Evaluate the tech stack for <a href="/articles/the_climate_change_progress_bar">other pro­jects</a>.</li>
<li>Entity Framework Core is a great <span class="small-caps">ORM</span>, and I may be able to ap­ply its ide­olo­gies to other tech­nolo­gies, even if I don’t con­tinue us­ing it di­rectly.</li>
<li>In or­der to eval­u­ate pos­i­tiv­ity over time, I need as much his­tor­i­cal data as pos­si­ble, which should be stored in a per­sis­tent data­base.</li>
</ol>
<p>One main draw­back of ASP.NET Core is the amount of boil­er­plate nec­es­sary.
As a re­sult, I am hes­i­tant to show any clip­pings from the rewrite, as it takes a lot of space with­out adding much value to the dis­cus­sion here.
How­ever, all the code for this pro­ject is open source and avail­able on <a href="https://github.com/elijah-potter/hope-meter">GitHub</a>.</p>
<p>Here is the gist of the over­all process the re-writ­ten app goes through:</p>
<ol>
<li>Scrape a num­ber of <span class="small-caps">RSS</span> feeds with the method al­ready dis­cussed.</li>
<li>Store head­lines in a table in an SQLite data­base, along with time­stamp of the re­trieval and the source <span class="small-caps">URL</span>.</li>
<li>Repeat the ﬁrst two steps every two min­utes, check­ing and re­mov­ing du­pli­cates.</li>
</ol>
<h3>Finally: The Hope Algorithm</h3>
<p>Finally, we can dis­cuss the ac­tual al­go­rithm.</p>
<p>First, I would run the <a href="https://doi.org/10.1609/icwsm.v8i1.14550"><span class="small-caps">VADER</span> sen­ti­ment analy­sis al­go­rithm</a> over each head­line from the last month to eval­u­ate their pos­i­tiv­ity.
Since <span class="small-caps">VADER</span> re­turns three val­ues, a<span class="push-double"></span> <span class="pull-double">“</span>positivity” rat­ing, a<span class="push-double"></span> <span class="pull-double">“</span>negativity” rat­ing, and a<span class="push-double"></span> <span class="pull-double">“</span>neutral” rat­ing, I con­sol­i­dated them into a sin­gle num­ber by sim­ply sub­tract­ing neg­a­tiv­ity from pos­i­tiv­ity.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>head­line rat­ing</mtext><mo>=</mo><mtext>pos­i­tiv­ity</mtext><mo>−</mo><mtext>neg­a­tiv­ity</mtext></mrow><annotation encoding="application/x-tex">\text{headline rat­ing} = \text{positivity} - \text{negativity}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">head­line rat­ing</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8623em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">pos­i­tiv­ity</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8623em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">neg­a­tiv­ity</span></span></span></span></span></span>
<p>Next, I ﬁnd the av­er­age of the sub­set of the pos­i­tiv­ity score from the past two hours and sub­tract the av­er­age from the last month.
Fi­nally, I use the mon­th’s stan­dard de­vi­a­tion to place it on a scale of 0&thinsp;&ndash;&thinsp;100, where 50 is av­er­age.
Here is the for­mula, where <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mover accent="true"><mi>x</mi><mo>ˉ</mo></mover><mi>h</mi></msub></mrow><annotation encoding="application/x-tex">\bar{x}_h</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7178em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.5678em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">x</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2222em;"><span class="mord">ˉ</span></span></span></span></span></span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> is the pos­i­tiv­ity mean from the last two hours and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mover accent="true"><mi>x</mi><mo>ˉ</mo></mover><mi>m</mi></msub></mrow><annotation encoding="application/x-tex">\bar{x}_m</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7178em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.5678em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">x</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2222em;"><span class="mord">ˉ</span></span></span></span></span></span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">m</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> is the pos­i­tiv­ity mean from the last month.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>hope score</mtext><mo>=</mo><mn>200</mn><mrow><mo fence="true">(</mo><mfrac><mrow><msub><mover accent="true"><mi>x</mi><mo>ˉ</mo></mover><mi>h</mi></msub><mo>−</mo><msub><mover accent="true"><mi>x</mi><mo>ˉ</mo></mover><mi>m</mi></msub></mrow><msub><mi>σ</mi><mi>m</mi></msub></mfrac><mo fence="true">)</mo></mrow><mo>+</mo><mn>50</mn></mrow><annotation encoding="application/x-tex">\text{hope score} = 200\left(\frac{\bar{x}_h - \bar{x}_m}{\sigma_m} \right) + 50</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">hope score</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="mord">200</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.2603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">σ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">m</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.5678em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">x</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2222em;"><span class="mord">ˉ</span></span></span></span></span></span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.5678em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">x</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2222em;"><span class="mord">ˉ</span></span></span></span></span></span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">m</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.836em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">50</span></span></span></span></span>
<p>Naturally, there are a few things to note about this for­mula.
I as­sumed that the pos­i­tiv­ity score is a nor­mal dis­tri­b­u­tion among news head­lines (I did not have time to eval­u­ate whether this is ac­tu­ally true).
The score will also of­ten fall <em>out­side</em> the range of 0&thinsp;&ndash;&thinsp;100.
This is ex­pected, since val­ues out­side that range will be less ac­cu­rate any­way.
In those sit­u­a­tions, I sim­ply clamp to within the range 0&thinsp;&ndash;&thinsp;100.</p>
<h2>Communicating Hope</h2>
<p>I wanted to ex­plore and get feed­back on a cou­ple dif­fer­ent ways of com­mu­ni­cat­ing ab­stract con­cepts of hope.
First, I threw to­gether a quick web­site us­ing <a href="https://svelte.dev/">Svelte</a> and <a href="https://tailwindcss.com/">TailwindCSS</a>.</p>
<img src="/images/hope_screenshot.webp" alt="A screenshot of my website prototype">
<p>I ran the web­site for a cou­ple of days and had friends, fam­ily, and peers test it out to get feed­back on the con­cept.</p>
<h3>Feedback</h3>
<p>The peo­ple I spoke to had a lot to say on the dial it­self.
No­tably, the lack of +/- sig­nal­ing was con­fus­ing.
Peo­ple could­n’t tell what a<span class="push-double"></span> <span class="pull-double">“</span>good” or a<span class="push-double"></span> <span class="pull-double">“</span>bad” read­ing on the dial looked like.
One per­son sug­gested I in­clude a happy face on the hope­ful side and a sad face on the de­spair side.</p>
<p>We brain­stormed the dif­fer­ent places we could put a hope-me­ter.
IOS and Android both sup­port home-screen wid­gets.
Maybe we could put a global hope-me­ter on one of those, right next to the clock?
Maybe you could click on a spe­ciﬁc spot on the hope me­ter, and you would be served a news ar­ti­cle with that amount of hope?
The pos­si­bil­i­ties were end­less.
I was pushed to look into what a phys­i­cal hope me­ter would look like, so I sketched it out.</p>
<img src="/images/hope_sketch.webp" alt="A quick little sketch of a physical hope meter">
<h3>Moving To the Real World</h3>
<p>I got hooked on the phys­i­cal dial.
I wanted some­thing tan­gi­ble that I could put some­where.
Some­thing whose phys­i­cal weight would give an amount of sub­stance to the thing it mea­sured.</p>
<p>I de­cided to keep riff­ing on the sketches I had drawn out.
I loaded the web server I used for the orig­i­nal site onto a Raspberry Pi 3, plan­ning on us­ing the ex­ist­ing <span class="small-caps">HTTP</span> end­point to con­trol a servo, which in turn, moved a hand on a dial.</p>
<p>The only ma­te­r­ial I needed to buy was a <a href="https://www.adafruit.com/product/154"><span class="small-caps">FS5103R</span> con­tin­u­ous ro­ta­tion servo</a>.
I al­ready had ac­cess to every­thing else.
While I was wait­ing on that part to ar­rive, I mod­eled out what the phys­i­cal de­vice <em>could</em> look like.
Ini­tially, I imag­ined some­thing like this:</p>
<img src="/images/hope_model.jpg" alt="A Blender render of the initial Hope meter 3D model">
<p>All the elec­tron­ics would be placed in­side the bot­tle, and the servo would be at­tached to the cap.
The en­tire con­trap­tion would rest on it’s side.
In hind­sight, there are a cou­ple of is­sues with this con­cept.
The cir­cu­lar body would cause it to roll all over the place, and the com­plete lack of ac­cess to the in­side would make main­te­nance quite dif­ﬁ­cult.
That is­n’t even to men­tion there is­n’t a sin­gle hole for a power cord in this model for the elec­tron­ics.</p>
<img src="/images/hope_model_face.webp" alt="Final Hope Meter Face Model">
<p>In the ﬁ­nal model (pictured above), I in­cluded fa­cial ex­pres­sions to dif­fer­en­ti­ate hope and de­spair on the dial.
I also com­pletely re­moved the en­clo­sure, in­stead plan­ning on di­rectly at­tach­ing the Raspberry Pi and servo to the back of the dial.</p>
<h4>The Print</h4>
<p>With the model com­plete, I went to my lo­cal mak­er­space, and printed it out.
How did it go?
Not so well.</p>
<img src="/images/failed_hope_print.webp" alt="My first (failed) 3D print">
<p>The ﬁrst print failed spec­tac­u­larly.
Parts were com­ing off, and it looked like it was drawn with one of those <span class="small-caps">3D</span> pens.
Turns out, the is­sue was that the un­der­ly­ing <span class="small-caps">3D</span> model had not been ex­ported out of Blender (the <span class="small-caps">3D</span> mod­el­ing soft­ware I used) prop­erly, and the geom­e­try was screwed up be­fore it even en­tered the slic­ing soft­ware.
Garbage in, garbage out, I sup­pose.</p>
<video controls>
  <source src="/videos/hope_demo.webm" type="video/webm">
</video>
<p>The sec­ond print, how­ever, turned out great.
I hot-glued the Raspberry Pi to the back, plugged in the servo, and we were off to the races.
As you can see above, I pro­grammed a lit­tle servo con­trol script in Python, which in­cluded a lit­tle startup mo­tion.
Af­ter the mo­tion is com­pleted, it moves to the cur­rent hope value.</p>
<p>The video was recorded right af­ter the <a href="https://www.nytimes.com/live/2023/10/11/world/israel-news-hamas-war">Isreal-Hamas War</a> be­gan, so global hope val­ues were not do­ing so hot.
Al­though it truly is a tragedy, if any­thing, this is ev­i­dence that the sys­tem works.</p>
<h2>Reﬂection</h2>
<p>This pro­ject stretched my abil­i­ties in a cou­ple key ways.</p>
<p>After the ini­tial pro­to­typ­ing phase, <em>all</em> the code was in pro­gram­ming en­vi­ron­ments I am rel­a­tively un­fa­mil­iar with.
While I have a lot of ex­pe­ri­ence with Node.js, Rust and React, I felt some dif­ﬁ­culty when work­ing with ASP.NET Core and Python.
The dif­ﬁ­cul­ties were ex­ac­er­bated when try­ing to run every­thing on a Raspberry Pi, which I have never worked with in this ca­pac­ity.</p>
<p>Additionally, I found it pretty dif­ﬁ­cult to get the servo work­ing re­li­ably.
Ap­par­ently, ser­vos rely on a po­ten­tiome­ter to stay cal­i­brated.
If the po­ten­tiome­ter moves to an am­bi­ent en­vi­ron­ment with a dif­fer­ent tem­per­a­ture than it was cal­i­brated in, the servo starts to move in un­ex­pected ways.
This re­sulted in fre­quently in­ac­cu­rate read­ings.
If I could go back and re-do this pro­ject, I would have used a dif­fer­ent kind of mo­tor, maybe a step­per mo­tor.</p>
<p>Part of my mo­ti­va­tion for do­ing this pro­ject was to gather more in­for­ma­tion on the best way to im­ple­ment a <a href="/articles/the_climate_change_progress_bar">cli­mate change progress bar</a>.
To that end, I found this quite suc­cess­ful.
I now have a bet­ter un­der­stand­ing of what kind of server and data­base ar­chi­tec­ture I want to use.
I also now know what kinds of for­mats peo­ple are more likely to see global met­rics like hope or cli­mate progress (home screen wid­gets and push no­ti­ﬁ­ca­tions are at the top of the list).</p>]]></description>
            <content:encoded><![CDATA[<h1>Quantifying Hope on a Global Scale</h1>
<p><strong><em>Hope</em></strong> is a some­what neb­u­lous word.</p>
<p>For some, it is an ex­pec­ta­tion of what the fu­ture will be.
For oth­ers, it is a goal for what the fu­ture <em>should</em> be.
The word it­self means some­thing dif­fer­ent to each per­son.</p>
<p>Most gen­er­ally though, hope de­scribes how one per­ceives the mo­men­tum of the world.
Do things seem to be gen­er­ally get­ting bet­ter, or do they gen­er­ally seem to be get­ting worse?
Per­son­ally, I think things are rapidly get­ting <strong>much, much</strong> bet­ter.</p>
<h2>We <em>Should</em> Be Hopeful</h2>
<p>First, let’s de­ﬁne our met­ric.
The <strong>world</strong> is bet­ter off when <strong>peo­ple</strong> are bet­ter off.
The more peo­ple who are able to live long, mean­ing­ful lives, the bet­ter.</p>
<p>In his posthu­mously pub­lished book, <a href="https://www.amazon.com/Factfulness-Reasons-World-Things-Better/dp/1250123828/"><em>Factfulness: Ten Reasons We’re Wrong About the World&thinsp;&mdash;&thinsp;and Why Things Are Better Than You Think</em></a>, Hans Rosling made the case that the qual­ity and length of hu­man life has been trend­ing up­ward for more than a cen­tury.
Life ex­pectancy at birth has <a href="https://ourworldindata.org/life-expectancy">more than dou­bled</a> in the last cen­tury alone.
The por­tion of the global pop­u­la­tion liv­ing in poverty is lower than <a href="https://ourworldindata.org/poverty">any point in his­tory</a>.</p>
<blockquote>
<p><strong>Side note:</strong> a <em>great</em> re­source to learn about this is Our World In Data’s <a href="https://ourworldindata.org/extreme-poverty-in-brief">com­pre­hen­sive, yet ap­proach­able re­port</a>.</p>
</blockquote>
<p>All of this progress does <strong>not</strong> mean we should just stop here and say the work is<span class="push-double"></span> <span class="pull-double">“</span>done.”
However, I be­lieve that of­ten the best ap­proaches to solv­ing world-scale prob­lems (like poverty) start by ﬁrst look­ing at the so­lu­tions that al­ready work.</p>
<p>In the book, Rosling tells how he is of­ten de­scribed as an<span class="push-double"></span> <span class="pull-double">“</span>optimist.”
He thinks it is a mis­nomer, since op­ti­mists are of­ten por­trayed as fool­ish and mis­in­formed.
He, how­ever was likely more qual­i­ﬁed than any­one to speak on the pos­si­bil­i­ties of the likely good fu­ture, so his pos­i­tive out­look was en­tirely jus­ti­ﬁed.
For this rea­son, he de­scribed him­self a<span class="push-double"></span> <span class="pull-double">“</span>possibilist”.
While I don’t even ap­proach his qual­i­ﬁ­ca­tions, I would sim­i­larly de­scribe my world­view as<span class="push-double"></span> <span class="pull-double">“</span>possibilist.”</p>
<h2>The Solutions That Exist</h2>
<p>I’ve spo­ken on the topic of so­lu­tion-fo­cused think­ing <a href="/articles/the_climate_change_progress_bar">be­fore</a>, but I think it war­rants re­vis­it­ing.
I don’t want this to be the fo­cus of my writ­ing to­day, but I do want to pro­vide a brief ex­am­ple of the kind of so­lu­tions that ex­ist <strong>to­day</strong> that are hav­ing pro­found, real-world im­pacts.</p>
<p>One of the crit­i­cal en­vi­ron­men­tal prob­lems is re­lated to re­cy­cling.
Every year, <a href="https://ourworldindata.org/how-much-plastic-waste-ends-up-in-the-ocean">18 mil­lion tonnes</a> of trash is leaked to the en­vi­ron­ment, in­clud­ing our oceans and rivers.
Ac­cord­ing to <a href="https://www.amprobotics.com/"><span class="small-caps">AMP</span> Robotics</a> founder Matanya Horowitz, this trash can ac­tu­ally be a hugely prof­itable com­mod­ity.
De­pend­ing on mar­ket con­di­tions, one ton of re­cy­clable ma­te­r­ial can be worth up to $100.
The is­sue: the cost of sort­ing the ma­te­r­ial into a us­able form is of­ten also around $100, most of which is spent on hu­man la­bor.
Since the cost of pro­cess­ing the ma­te­r­ial to <em>pre­pare</em> to sell is about the same as it’s sold price, there is lit­tle eco­nomic in­cen­tive to do much re­cy­cling.
In the case of plas­tics re­cy­cling, the is­sue is <strong>not</strong> that peo­ple don’t re­cy­cle enough.
It is that the cost of re­cy­cling the re­sult­ing ma­te­r­ial is too high.</p>
<p>So what does <span class="small-caps">AMP</span> Robotics do?
They have au­to­mated the process of re­cy­cling, bring­ing down the costs pre­cip­i­tously.
With ro­bot­ics, the cost of re­cy­cling (to the waste com­pany) is now much lower than the ac­tual sold price of ma­te­r­ial, mak­ing it an ex­tremely prof­itable busi­ness.</p>
<p>I think this is a great ex­am­ple for two rea­sons.
First and fore­most, we are of­ten told, with­out much sub­stan­ti­at­ing ev­i­dence, that the prob­lem of ocean plas­tics and pol­lu­tion are the re­sult of in­di­vid­ual peo­ple choos­ing not to re­cy­cle.
In re­al­ity, how­ever, the lim­it­ing fac­tor is not on the in­di­vid­ual, but the waste man­age­ment com­pa­ny’s ef­ﬁ­ciency.</p>
<h2>Quantifying Hope</h2>
<p>I’ve es­tab­lished that the world <strong>is</strong> be­com­ing a bet­ter place to live for al­most every­body, all the time.
I’ve also made clear that this pos­i­tive change is the re­sult of so­lu­tions that get re­ported on (by the news) less fre­quently than the prob­lems they solve.
With that done, I think it is time to re­turn to the topic of hope.</p>
<p>My end goal was to au­to­mate the process of ﬁnd­ing so­lu­tions that worked (like <span class="small-caps">AMP</span> Robotics).
I un­der­stood that this would be an enor­mously dif­ﬁ­cult nat­ural lan­guage pro­cess­ing task, so I de­cided to start sim­ple.
The plan: I would ﬁrst quan­tify how hope­ful a given news ar­ti­cle was, then work from there.</p>
<h3>Web Scraping</h3>
<p>The ﬁrst step was to write a sys­tem that could re­trieve re­cent, im­por­tant news ar­ti­cles re­li­ably.
There is al­ready in­fra­struc­ture on most news web­sites that makes this pretty easy: RSS.
By reg­u­larly it­er­at­ing through a list of <span class="small-caps">RSS</span> feeds, we can get pretty good news cov­er­age for most of the world.
Ad­di­tion­ally, news or­ga­ni­za­tions pri­or­i­tize the con­tent of their <span class="small-caps">RSS</span> based on what <em>they</em> think is im­por­tant.</p>
<p>My ini­tial im­ple­men­ta­tion was in Rust, which I will show here.
As you can see, I spent some time par­al­leliz­ing the scrape job.
This was pri­mar­ily to re­duce the amount of time it would take to it­er­ate on the hope quan­ti­fy­ing al­go­rithm, so I could get close to real-time feed­back on it when I made changes.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">use</span> rss::{Channel, Item};
<span class="hljs-keyword">use</span> tokio::sync::mpsc::unbounded_channel;

<span class="hljs-comment">/// Download all items from an RSS Feed</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_feed_url_items</span>(url: &#x26;<span class="hljs-type">str</span>) <span class="hljs-punctuation">-></span> anyhow::<span class="hljs-type">Result</span>&#x3C;<span class="hljs-type">Vec</span>&#x3C;Item>> {
    <span class="hljs-keyword">let</span> <span class="hljs-variable">content</span> = reqwest::<span class="hljs-title function_ invoke__">get</span>(url).<span class="hljs-keyword">await</span>?.<span class="hljs-title function_ invoke__">bytes</span>().<span class="hljs-keyword">await</span>?;

    <span class="hljs-keyword">let</span> <span class="hljs-variable">channel</span> = Channel::<span class="hljs-title function_ invoke__">read_from</span>(&#x26;content[..])?;
    <span class="hljs-title function_ invoke__">Ok</span>(channel.<span class="hljs-title function_ invoke__">into_items</span>())
}

<span class="hljs-comment">/// Download all items from a list of RSS feeds, in parallel.</span>
<span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_all_feed_urls</span>(urls: <span class="hljs-keyword">impl</span> <span class="hljs-title class_">IntoIterator</span>&#x3C;Item = &#x26;<span class="hljs-type">str</span>>) <span class="hljs-punctuation">-></span> anyhow::<span class="hljs-type">Result</span>&#x3C;<span class="hljs-type">Vec</span>&#x3C;Item>> {
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">items_receiver</span> = {
        <span class="hljs-keyword">let</span> (items_sender, items_receiver) = <span class="hljs-title function_ invoke__">unbounded_channel</span>();

        <span class="hljs-keyword">for</span> <span class="hljs-variable">url</span> <span class="hljs-keyword">in</span> urls {
            <span class="hljs-keyword">let</span> <span class="hljs-variable">url</span> = url.<span class="hljs-title function_ invoke__">to_string</span>();
            <span class="hljs-keyword">let</span> <span class="hljs-variable">items_sender</span> = items_sender.<span class="hljs-title function_ invoke__">clone</span>();
            tokio::<span class="hljs-title function_ invoke__">spawn</span>(<span class="hljs-keyword">async</span> <span class="hljs-keyword">move</span> {
                items_sender.<span class="hljs-title function_ invoke__">send</span>(<span class="hljs-title function_ invoke__">get_feed_url_items</span>(&#x26;url).<span class="hljs-keyword">await</span>).<span class="hljs-title function_ invoke__">unwrap</span>();
            });
        }
        items_receiver
    };

    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">res</span> = <span class="hljs-type">Vec</span>::<span class="hljs-title function_ invoke__">new</span>();

    <span class="hljs-keyword">while</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Some</span>(items_res) = items_receiver.<span class="hljs-title function_ invoke__">recv</span>().<span class="hljs-keyword">await</span> {
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">items</span> = items_res?;
        res.<span class="hljs-title function_ invoke__">append</span>(&#x26;<span class="hljs-keyword">mut</span> items);
    }

    <span class="hljs-title function_ invoke__">Ok</span>(res)
}
</code></pre>
<h3>Rewriting into&nbsp;.<span class="small-caps">NET</span> Core</h3>
<p>Now that I’ve found a rel­a­tively high qual­ity source of raw data to es­ti­mate pub­lic opin­ion, I need to con­dense it into a sin­gle num­ber, and look at how it changes over time.
It was at this point I rewrote most of the code into C# and <a href="https://dotnet.microsoft.com/en-us/apps/aspnet">ASP.NET Core</a>.
I had four rea­sons for this:</p>
<ol>
<li>While I’ve used ASP.NET Core and Entity Framework Core in the past, I haven’t used it re­cently.
I wanted to see how the ecosys­tem has changed in the in­ter­ven­ing time.</li>
<li>Evaluate the tech stack for <a href="/articles/the_climate_change_progress_bar">other pro­jects</a>.</li>
<li>Entity Framework Core is a great <span class="small-caps">ORM</span>, and I may be able to ap­ply its ide­olo­gies to other tech­nolo­gies, even if I don’t con­tinue us­ing it di­rectly.</li>
<li>In or­der to eval­u­ate pos­i­tiv­ity over time, I need as much his­tor­i­cal data as pos­si­ble, which should be stored in a per­sis­tent data­base.</li>
</ol>
<p>One main draw­back of ASP.NET Core is the amount of boil­er­plate nec­es­sary.
As a re­sult, I am hes­i­tant to show any clip­pings from the rewrite, as it takes a lot of space with­out adding much value to the dis­cus­sion here.
How­ever, all the code for this pro­ject is open source and avail­able on <a href="https://github.com/elijah-potter/hope-meter">GitHub</a>.</p>
<p>Here is the gist of the over­all process the re-writ­ten app goes through:</p>
<ol>
<li>Scrape a num­ber of <span class="small-caps">RSS</span> feeds with the method al­ready dis­cussed.</li>
<li>Store head­lines in a table in an SQLite data­base, along with time­stamp of the re­trieval and the source <span class="small-caps">URL</span>.</li>
<li>Repeat the ﬁrst two steps every two min­utes, check­ing and re­mov­ing du­pli­cates.</li>
</ol>
<h3>Finally: The Hope Algorithm</h3>
<p>Finally, we can dis­cuss the ac­tual al­go­rithm.</p>
<p>First, I would run the <a href="https://doi.org/10.1609/icwsm.v8i1.14550"><span class="small-caps">VADER</span> sen­ti­ment analy­sis al­go­rithm</a> over each head­line from the last month to eval­u­ate their pos­i­tiv­ity.
Since <span class="small-caps">VADER</span> re­turns three val­ues, a<span class="push-double"></span> <span class="pull-double">“</span>positivity” rat­ing, a<span class="push-double"></span> <span class="pull-double">“</span>negativity” rat­ing, and a<span class="push-double"></span> <span class="pull-double">“</span>neutral” rat­ing, I con­sol­i­dated them into a sin­gle num­ber by sim­ply sub­tract­ing neg­a­tiv­ity from pos­i­tiv­ity.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>head­line rat­ing</mtext><mo>=</mo><mtext>pos­i­tiv­ity</mtext><mo>−</mo><mtext>neg­a­tiv­ity</mtext></mrow><annotation encoding="application/x-tex">\text{headline rat­ing} = \text{positivity} - \text{negativity}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">head­line rat­ing</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8623em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">pos­i­tiv­ity</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8623em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">neg­a­tiv­ity</span></span></span></span></span></span>
<p>Next, I ﬁnd the av­er­age of the sub­set of the pos­i­tiv­ity score from the past two hours and sub­tract the av­er­age from the last month.
Fi­nally, I use the mon­th’s stan­dard de­vi­a­tion to place it on a scale of 0&thinsp;&ndash;&thinsp;100, where 50 is av­er­age.
Here is the for­mula, where <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mover accent="true"><mi>x</mi><mo>ˉ</mo></mover><mi>h</mi></msub></mrow><annotation encoding="application/x-tex">\bar{x}_h</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7178em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.5678em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">x</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2222em;"><span class="mord">ˉ</span></span></span></span></span></span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> is the pos­i­tiv­ity mean from the last two hours and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mover accent="true"><mi>x</mi><mo>ˉ</mo></mover><mi>m</mi></msub></mrow><annotation encoding="application/x-tex">\bar{x}_m</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7178em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.5678em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">x</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2222em;"><span class="mord">ˉ</span></span></span></span></span></span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">m</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> is the pos­i­tiv­ity mean from the last month.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>hope score</mtext><mo>=</mo><mn>200</mn><mrow><mo fence="true">(</mo><mfrac><mrow><msub><mover accent="true"><mi>x</mi><mo>ˉ</mo></mover><mi>h</mi></msub><mo>−</mo><msub><mover accent="true"><mi>x</mi><mo>ˉ</mo></mover><mi>m</mi></msub></mrow><msub><mi>σ</mi><mi>m</mi></msub></mfrac><mo fence="true">)</mo></mrow><mo>+</mo><mn>50</mn></mrow><annotation encoding="application/x-tex">\text{hope score} = 200\left(\frac{\bar{x}_h - \bar{x}_m}{\sigma_m} \right) + 50</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">hope score</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="mord">200</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.2603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">σ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">m</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.5678em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">x</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2222em;"><span class="mord">ˉ</span></span></span></span></span></span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.5678em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">x</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2222em;"><span class="mord">ˉ</span></span></span></span></span></span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">m</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.836em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">50</span></span></span></span></span>
<p>Naturally, there are a few things to note about this for­mula.
I as­sumed that the pos­i­tiv­ity score is a nor­mal dis­tri­b­u­tion among news head­lines (I did not have time to eval­u­ate whether this is ac­tu­ally true).
The score will also of­ten fall <em>out­side</em> the range of 0&thinsp;&ndash;&thinsp;100.
This is ex­pected, since val­ues out­side that range will be less ac­cu­rate any­way.
In those sit­u­a­tions, I sim­ply clamp to within the range 0&thinsp;&ndash;&thinsp;100.</p>
<h2>Communicating Hope</h2>
<p>I wanted to ex­plore and get feed­back on a cou­ple dif­fer­ent ways of com­mu­ni­cat­ing ab­stract con­cepts of hope.
First, I threw to­gether a quick web­site us­ing <a href="https://svelte.dev/">Svelte</a> and <a href="https://tailwindcss.com/">TailwindCSS</a>.</p>
<img src="/images/hope_screenshot.webp" alt="A screenshot of my website prototype">
<p>I ran the web­site for a cou­ple of days and had friends, fam­ily, and peers test it out to get feed­back on the con­cept.</p>
<h3>Feedback</h3>
<p>The peo­ple I spoke to had a lot to say on the dial it­self.
No­tably, the lack of +/- sig­nal­ing was con­fus­ing.
Peo­ple could­n’t tell what a<span class="push-double"></span> <span class="pull-double">“</span>good” or a<span class="push-double"></span> <span class="pull-double">“</span>bad” read­ing on the dial looked like.
One per­son sug­gested I in­clude a happy face on the hope­ful side and a sad face on the de­spair side.</p>
<p>We brain­stormed the dif­fer­ent places we could put a hope-me­ter.
IOS and Android both sup­port home-screen wid­gets.
Maybe we could put a global hope-me­ter on one of those, right next to the clock?
Maybe you could click on a spe­ciﬁc spot on the hope me­ter, and you would be served a news ar­ti­cle with that amount of hope?
The pos­si­bil­i­ties were end­less.
I was pushed to look into what a phys­i­cal hope me­ter would look like, so I sketched it out.</p>
<img src="/images/hope_sketch.webp" alt="A quick little sketch of a physical hope meter">
<h3>Moving To the Real World</h3>
<p>I got hooked on the phys­i­cal dial.
I wanted some­thing tan­gi­ble that I could put some­where.
Some­thing whose phys­i­cal weight would give an amount of sub­stance to the thing it mea­sured.</p>
<p>I de­cided to keep riff­ing on the sketches I had drawn out.
I loaded the web server I used for the orig­i­nal site onto a Raspberry Pi 3, plan­ning on us­ing the ex­ist­ing <span class="small-caps">HTTP</span> end­point to con­trol a servo, which in turn, moved a hand on a dial.</p>
<p>The only ma­te­r­ial I needed to buy was a <a href="https://www.adafruit.com/product/154"><span class="small-caps">FS5103R</span> con­tin­u­ous ro­ta­tion servo</a>.
I al­ready had ac­cess to every­thing else.
While I was wait­ing on that part to ar­rive, I mod­eled out what the phys­i­cal de­vice <em>could</em> look like.
Ini­tially, I imag­ined some­thing like this:</p>
<img src="/images/hope_model.jpg" alt="A Blender render of the initial Hope meter 3D model">
<p>All the elec­tron­ics would be placed in­side the bot­tle, and the servo would be at­tached to the cap.
The en­tire con­trap­tion would rest on it’s side.
In hind­sight, there are a cou­ple of is­sues with this con­cept.
The cir­cu­lar body would cause it to roll all over the place, and the com­plete lack of ac­cess to the in­side would make main­te­nance quite dif­ﬁ­cult.
That is­n’t even to men­tion there is­n’t a sin­gle hole for a power cord in this model for the elec­tron­ics.</p>
<img src="/images/hope_model_face.webp" alt="Final Hope Meter Face Model">
<p>In the ﬁ­nal model (pictured above), I in­cluded fa­cial ex­pres­sions to dif­fer­en­ti­ate hope and de­spair on the dial.
I also com­pletely re­moved the en­clo­sure, in­stead plan­ning on di­rectly at­tach­ing the Raspberry Pi and servo to the back of the dial.</p>
<h4>The Print</h4>
<p>With the model com­plete, I went to my lo­cal mak­er­space, and printed it out.
How did it go?
Not so well.</p>
<img src="/images/failed_hope_print.webp" alt="My first (failed) 3D print">
<p>The ﬁrst print failed spec­tac­u­larly.
Parts were com­ing off, and it looked like it was drawn with one of those <span class="small-caps">3D</span> pens.
Turns out, the is­sue was that the un­der­ly­ing <span class="small-caps">3D</span> model had not been ex­ported out of Blender (the <span class="small-caps">3D</span> mod­el­ing soft­ware I used) prop­erly, and the geom­e­try was screwed up be­fore it even en­tered the slic­ing soft­ware.
Garbage in, garbage out, I sup­pose.</p>
<video controls>
  <source src="/videos/hope_demo.webm" type="video/webm">
</video>
<p>The sec­ond print, how­ever, turned out great.
I hot-glued the Raspberry Pi to the back, plugged in the servo, and we were off to the races.
As you can see above, I pro­grammed a lit­tle servo con­trol script in Python, which in­cluded a lit­tle startup mo­tion.
Af­ter the mo­tion is com­pleted, it moves to the cur­rent hope value.</p>
<p>The video was recorded right af­ter the <a href="https://www.nytimes.com/live/2023/10/11/world/israel-news-hamas-war">Isreal-Hamas War</a> be­gan, so global hope val­ues were not do­ing so hot.
Al­though it truly is a tragedy, if any­thing, this is ev­i­dence that the sys­tem works.</p>
<h2>Reﬂection</h2>
<p>This pro­ject stretched my abil­i­ties in a cou­ple key ways.</p>
<p>After the ini­tial pro­to­typ­ing phase, <em>all</em> the code was in pro­gram­ming en­vi­ron­ments I am rel­a­tively un­fa­mil­iar with.
While I have a lot of ex­pe­ri­ence with Node.js, Rust and React, I felt some dif­ﬁ­culty when work­ing with ASP.NET Core and Python.
The dif­ﬁ­cul­ties were ex­ac­er­bated when try­ing to run every­thing on a Raspberry Pi, which I have never worked with in this ca­pac­ity.</p>
<p>Additionally, I found it pretty dif­ﬁ­cult to get the servo work­ing re­li­ably.
Ap­par­ently, ser­vos rely on a po­ten­tiome­ter to stay cal­i­brated.
If the po­ten­tiome­ter moves to an am­bi­ent en­vi­ron­ment with a dif­fer­ent tem­per­a­ture than it was cal­i­brated in, the servo starts to move in un­ex­pected ways.
This re­sulted in fre­quently in­ac­cu­rate read­ings.
If I could go back and re-do this pro­ject, I would have used a dif­fer­ent kind of mo­tor, maybe a step­per mo­tor.</p>
<p>Part of my mo­ti­va­tion for do­ing this pro­ject was to gather more in­for­ma­tion on the best way to im­ple­ment a <a href="/articles/the_climate_change_progress_bar">cli­mate change progress bar</a>.
To that end, I found this quite suc­cess­ful.
I now have a bet­ter un­der­stand­ing of what kind of server and data­base ar­chi­tec­ture I want to use.
I also now know what kinds of for­mats peo­ple are more likely to see global met­rics like hope or cli­mate progress (home screen wid­gets and push no­ti­ﬁ­ca­tions are at the top of the list).</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/hope_sketch.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[The Climate Change Progress Bar]]></title>
            <link>https://elijahpotter.dev/articles/the_climate_change_progress_bar</link>
            <guid>https://elijahpotter.dev/articles/the_climate_change_progress_bar</guid>
            <pubDate>Thu, 12 Oct 2023 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>The Climate Change Progress Bar</h1>
<img src="/images/blue_marble.jpg" alt="The Blue Marble">
<h2>The Problem</h2>
<p>Over the last cou­ple years, I’ve no­ticed a grow­ing dis­tinc­tion be­tween two groups of peo­ple.</p>
<p>On one hand, we have those who deny cli­mate change is a prob­lem.
Some be­lieve it is a <a href="https://en.wikipedia.org/wiki/Climate_change_conspiracy_theory">hoax</a>, while oth­ers merely don’t grasp the grav­ity of the sit­u­a­tion.
This per­spec­tive is mis­in­formed.</p>
<p>On the other hand, there are some who be­lieve cli­mate change is a prob­lem be­yond their abil­i­ties to im­pact.
They be­lieve that, as mere in­di­vid­u­als, there is not a sin­gle thing they can do that could have a mean­ing­ful ef­fect to­ward re­duc­ing car­bon emis­sions.
This per­spec­tive is equally mis­in­formed.</p>
<p>In re­al­ity, the cli­mate prob­lem is <em>huge.</em>
It <em>will</em> re­sult in mas­sive habi­tat losses for hu­man life and wildlife.
In all like­li­hood, hun­dreds of mil­lions of peo­ple will lose the abil­ity to farm their land, ei­ther from drought, ﬂoods, lost soil fer­til­ity or wild­ﬁres.</p>
<blockquote>
<p><strong>Side Note:</strong> If you want to learn more about hu­man mi­gra­tion in the 21st cen­tury, I highly rec­om­mend the doc­u­men­tary <em>Climate Refugees</em>.</p>
</blockquote>
<p>The point of this ar­ti­cle is <em>not</em> to con­vince you that cli­mate change is a real prob­lem that has real so­lu­tions.
At this point, I don’t need to tell you that the cli­mate prob­lem is ac­tively be­ing solved by some of the most coura­geous, in­ﬂu­en­tial, in­tel­li­gent minds in the world.
There <em>are</em> so­lu­tions out there that are hav­ing a <a href="https://www.ted.com/talks/al_gore_the_case_for_optimism_on_climate_change"><strong>huge</strong> im­pact</a>, but peo­ple don’t have a straight­for­ward way to dis­cover them.</p>
<p>The prob­lem <strong>I</strong> want to solve is this: there is no sin­gle met­ric or dash­board to see the progress of cli­mate change and its so­lu­tions.</p>
<h2>What’s the Pitch?</h2>
<p>As I am sure you ﬁg­ured out from the ti­tle, I want to make a progress bar for cli­mate change.
A sin­gle place peo­ple can go to see where we, as a planet and as a species, stand.
Func­tion­ally, I want to repli­cate how we can view <a href="https://www.usdebtclock.org/">fed­eral gov­ern­men­t’s debt in real time</a>, but for cli­mate change.</p>
<p>I’ve con­versed with a friend of mine, Anyll Markevich, who knows much more about cli­mate is­sues than I do.
While we would ob­vi­ously need mul­ti­ple rounds of peer re­view be­fore we can put any­thing of sub­stance out, Anyll has rec­om­mended I start my re­search in a cou­ple spe­ciﬁc ar­eas.</p>
<h2>Planetary Boundaries</h2>
<p>There ex­ist some pro­jects and tools that hold a sim­i­lar goal as the cli­mate change progress bar.
The clos­est I could ﬁnd was the Planetary Boundaries pro­ject.</p>
<p>When hav­ing an ar­gu­ment with some­one, there are some lines you just can­not cross.
Some lines are un­avoid­able.
God­win’s law states that<span class="push-double"></span> <span class="pull-double">“</span>as an on­line dis­cus­sion grows longer, the prob­a­bil­ity of a com­par­i­son to Hitler ap­proaches 1.”
While com­par­ing your ar­gu­men­ta­tive op­po­nent to a geno­ci­dal dic­ta­tor is al­most never nec­es­sary or use­ful, the line gets crossed even­tu­ally.</p>
<p>The plan­et’s ecosys­tem is sim­i­lar.
There are cer­tain eco­log­i­cal lines that can­not not be crossed, like hav­ing more than 10 ex­tinc­tions per mil­lion species in a year, with­out sig­nif­i­cant im­pact on global eco­log­i­cal health.
The planet can han­dle a cer­tain amount of car­bon diox­ide in the air, but it can­not han­dle more than 350 parts per mil­lion with­out sig­nif­i­cant dam­age oc­cur­ring. At the time of writ­ing, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>C</mi><msub><mi>O</mi><mn>2</mn></msub></mrow><annotation encoding="application/x-tex">CO_2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0278em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> is cur­rently at 418 ppm.</p>
<img src="/images/planetary_boundaries.webp" alt="The Planetary Boundaries Graph, Credit Azote for Stockholm Resilience Centre, based on analysis in Richardson et al 2023.">
<p>The above graph shows where the planet is on sev­eral plan­e­tary bound­aries.
As you can see, we’ve ex­ceeded most of them.</p>
<p>While plan­e­tary bound­aries are a great way of com­mu­ni­cat­ing where we are on a bunch of en­vi­ron­men­tal met­rics, it falls short of our goal in a few key ways.
For one, it does­n’t spe­cial­ize in cli­mate change.
While it <em>does</em> show cur­rent car­bon diox­ide lev­els, it does­n’t in­clude any other green­house gases, or how deepy they are ef­fect­ing global tem­per­a­tures.
Fi­nally, it is only up­dated an­nu­ally.
The Climate Change Progress Bar’s key trait is that it is real-time, so peo­ple can see how we stand in the cli­mate bat­tle <em>right now.</em></p>
<h2>Possible Data Sources</h2>
<p>This is some­thing I need to speak to more cli­mate ex­perts on.
How­ever, at the bare min­i­mum there are a few el­e­ments our tech­nol­ogy can look at.</p>
<h3>Real Time Carbon Data</h3>
<p>Anyll sug­gested I look into the <a href="https://www.nature.com/articles/s41597-020-00708-7">Carbon Monitor</a>, a real-time car­bon emis­sions’ dataset.
Since it does­n’t look like their <span class="small-caps">API</span> is ac­ces­si­ble, but their code <em>is</em>, it looks like we can just run in on our in­fra­struc­ture.
I need to look into the tech­ni­cal im­ple­men­ta­tion de­tails a bit more, but ini­tial prospects are good.</p>
<h3><a href="https://en.wikipedia.org/wiki/Normalized_difference_vegetation_index">Normalized Difference Vegetation Index</a></h3>
<p><span class="small-caps">NDVI</span> is an in­dus­try-stan­dard way of mea­sur­ing the pho­to­syn­thetic pro­duc­tiv­ity of plants around the globe.
It works by ap­ply­ing some sim­ple math on two bands of satel­lite im­agery, both of which are widely avail­able.
The com­par­a­tive low com­pu­ta­tional cost of get­ting the <span class="small-caps">NDVI</span> of an area is a boon for our real-time goals.
It seems like its main draw­back is that it does­n’t give us many met­rics on bio­di­ver­sity.</p>
<h2>Getting Started</h2>
<p>We want to get started on an <span class="small-caps">MVP</span> as quickly as pos­si­ble.
The rel­a­tive sim­plic­ity of the <span class="small-caps">NVDI</span> gives us a great way to set up and test a real-time data pro­cess­ing pipeline.
We aim to in­clude other met­rics, like pop­u­la­tion growth and clean in­vest­ing, but we want to pre­pare a pipeline ﬁrst.</p>
<h2>Feedback</h2>
<p>This post, as well as all other posts re­lated to this pro­ject, ex­ist to serve two pur­poses:</p>
<ol>
<li>Bookkeeping and white­board­ing de­vice.</li>
<li>To get feed­back on every stage of the process.</li>
</ol>
<p>In other words, I want to hear from you!
If you have any ideas to how we can make the Climate Change Progress bar bet­ter, let us know by email­ing <a href="mailto:feedback@elijahpotter.dev">feed­back@eli­jah­pot­ter.dev</a>
If there are <strong>bet­ter</strong> met­rics we should be in­clud­ing in our in­dex, tell us!</p>]]></description>
            <content:encoded><![CDATA[<h1>The Climate Change Progress Bar</h1>
<img src="/images/blue_marble.jpg" alt="The Blue Marble">
<h2>The Problem</h2>
<p>Over the last cou­ple years, I’ve no­ticed a grow­ing dis­tinc­tion be­tween two groups of peo­ple.</p>
<p>On one hand, we have those who deny cli­mate change is a prob­lem.
Some be­lieve it is a <a href="https://en.wikipedia.org/wiki/Climate_change_conspiracy_theory">hoax</a>, while oth­ers merely don’t grasp the grav­ity of the sit­u­a­tion.
This per­spec­tive is mis­in­formed.</p>
<p>On the other hand, there are some who be­lieve cli­mate change is a prob­lem be­yond their abil­i­ties to im­pact.
They be­lieve that, as mere in­di­vid­u­als, there is not a sin­gle thing they can do that could have a mean­ing­ful ef­fect to­ward re­duc­ing car­bon emis­sions.
This per­spec­tive is equally mis­in­formed.</p>
<p>In re­al­ity, the cli­mate prob­lem is <em>huge.</em>
It <em>will</em> re­sult in mas­sive habi­tat losses for hu­man life and wildlife.
In all like­li­hood, hun­dreds of mil­lions of peo­ple will lose the abil­ity to farm their land, ei­ther from drought, ﬂoods, lost soil fer­til­ity or wild­ﬁres.</p>
<blockquote>
<p><strong>Side Note:</strong> If you want to learn more about hu­man mi­gra­tion in the 21st cen­tury, I highly rec­om­mend the doc­u­men­tary <em>Climate Refugees</em>.</p>
</blockquote>
<p>The point of this ar­ti­cle is <em>not</em> to con­vince you that cli­mate change is a real prob­lem that has real so­lu­tions.
At this point, I don’t need to tell you that the cli­mate prob­lem is ac­tively be­ing solved by some of the most coura­geous, in­ﬂu­en­tial, in­tel­li­gent minds in the world.
There <em>are</em> so­lu­tions out there that are hav­ing a <a href="https://www.ted.com/talks/al_gore_the_case_for_optimism_on_climate_change"><strong>huge</strong> im­pact</a>, but peo­ple don’t have a straight­for­ward way to dis­cover them.</p>
<p>The prob­lem <strong>I</strong> want to solve is this: there is no sin­gle met­ric or dash­board to see the progress of cli­mate change and its so­lu­tions.</p>
<h2>What’s the Pitch?</h2>
<p>As I am sure you ﬁg­ured out from the ti­tle, I want to make a progress bar for cli­mate change.
A sin­gle place peo­ple can go to see where we, as a planet and as a species, stand.
Func­tion­ally, I want to repli­cate how we can view <a href="https://www.usdebtclock.org/">fed­eral gov­ern­men­t’s debt in real time</a>, but for cli­mate change.</p>
<p>I’ve con­versed with a friend of mine, Anyll Markevich, who knows much more about cli­mate is­sues than I do.
While we would ob­vi­ously need mul­ti­ple rounds of peer re­view be­fore we can put any­thing of sub­stance out, Anyll has rec­om­mended I start my re­search in a cou­ple spe­ciﬁc ar­eas.</p>
<h2>Planetary Boundaries</h2>
<p>There ex­ist some pro­jects and tools that hold a sim­i­lar goal as the cli­mate change progress bar.
The clos­est I could ﬁnd was the Planetary Boundaries pro­ject.</p>
<p>When hav­ing an ar­gu­ment with some­one, there are some lines you just can­not cross.
Some lines are un­avoid­able.
God­win’s law states that<span class="push-double"></span> <span class="pull-double">“</span>as an on­line dis­cus­sion grows longer, the prob­a­bil­ity of a com­par­i­son to Hitler ap­proaches 1.”
While com­par­ing your ar­gu­men­ta­tive op­po­nent to a geno­ci­dal dic­ta­tor is al­most never nec­es­sary or use­ful, the line gets crossed even­tu­ally.</p>
<p>The plan­et’s ecosys­tem is sim­i­lar.
There are cer­tain eco­log­i­cal lines that can­not not be crossed, like hav­ing more than 10 ex­tinc­tions per mil­lion species in a year, with­out sig­nif­i­cant im­pact on global eco­log­i­cal health.
The planet can han­dle a cer­tain amount of car­bon diox­ide in the air, but it can­not han­dle more than 350 parts per mil­lion with­out sig­nif­i­cant dam­age oc­cur­ring. At the time of writ­ing, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>C</mi><msub><mi>O</mi><mn>2</mn></msub></mrow><annotation encoding="application/x-tex">CO_2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0278em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> is cur­rently at 418 ppm.</p>
<img src="/images/planetary_boundaries.webp" alt="The Planetary Boundaries Graph, Credit Azote for Stockholm Resilience Centre, based on analysis in Richardson et al 2023.">
<p>The above graph shows where the planet is on sev­eral plan­e­tary bound­aries.
As you can see, we’ve ex­ceeded most of them.</p>
<p>While plan­e­tary bound­aries are a great way of com­mu­ni­cat­ing where we are on a bunch of en­vi­ron­men­tal met­rics, it falls short of our goal in a few key ways.
For one, it does­n’t spe­cial­ize in cli­mate change.
While it <em>does</em> show cur­rent car­bon diox­ide lev­els, it does­n’t in­clude any other green­house gases, or how deepy they are ef­fect­ing global tem­per­a­tures.
Fi­nally, it is only up­dated an­nu­ally.
The Climate Change Progress Bar’s key trait is that it is real-time, so peo­ple can see how we stand in the cli­mate bat­tle <em>right now.</em></p>
<h2>Possible Data Sources</h2>
<p>This is some­thing I need to speak to more cli­mate ex­perts on.
How­ever, at the bare min­i­mum there are a few el­e­ments our tech­nol­ogy can look at.</p>
<h3>Real Time Carbon Data</h3>
<p>Anyll sug­gested I look into the <a href="https://www.nature.com/articles/s41597-020-00708-7">Carbon Monitor</a>, a real-time car­bon emis­sions’ dataset.
Since it does­n’t look like their <span class="small-caps">API</span> is ac­ces­si­ble, but their code <em>is</em>, it looks like we can just run in on our in­fra­struc­ture.
I need to look into the tech­ni­cal im­ple­men­ta­tion de­tails a bit more, but ini­tial prospects are good.</p>
<h3><a href="https://en.wikipedia.org/wiki/Normalized_difference_vegetation_index">Normalized Difference Vegetation Index</a></h3>
<p><span class="small-caps">NDVI</span> is an in­dus­try-stan­dard way of mea­sur­ing the pho­to­syn­thetic pro­duc­tiv­ity of plants around the globe.
It works by ap­ply­ing some sim­ple math on two bands of satel­lite im­agery, both of which are widely avail­able.
The com­par­a­tive low com­pu­ta­tional cost of get­ting the <span class="small-caps">NDVI</span> of an area is a boon for our real-time goals.
It seems like its main draw­back is that it does­n’t give us many met­rics on bio­di­ver­sity.</p>
<h2>Getting Started</h2>
<p>We want to get started on an <span class="small-caps">MVP</span> as quickly as pos­si­ble.
The rel­a­tive sim­plic­ity of the <span class="small-caps">NVDI</span> gives us a great way to set up and test a real-time data pro­cess­ing pipeline.
We aim to in­clude other met­rics, like pop­u­la­tion growth and clean in­vest­ing, but we want to pre­pare a pipeline ﬁrst.</p>
<h2>Feedback</h2>
<p>This post, as well as all other posts re­lated to this pro­ject, ex­ist to serve two pur­poses:</p>
<ol>
<li>Bookkeeping and white­board­ing de­vice.</li>
<li>To get feed­back on every stage of the process.</li>
</ol>
<p>In other words, I want to hear from you!
If you have any ideas to how we can make the Climate Change Progress bar bet­ter, let us know by email­ing <a href="mailto:feedback@elijahpotter.dev">feed­back@eli­jah­pot­ter.dev</a>
If there are <strong>bet­ter</strong> met­rics we should be in­clud­ing in our in­dex, tell us!</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/blue_marble.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[A Case for Procrastination]]></title>
            <link>https://elijahpotter.dev/articles/a_case_for_procrastination</link>
            <guid>https://elijahpotter.dev/articles/a_case_for_procrastination</guid>
            <pubDate>Wed, 27 Sep 2023 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>A Case for Procrastination</h1>
<img src="/images/model_t.jpg" alt="A linup of Model T Fords">
<p>The most valu­able, unique as­pect of soft­ware de­vel­op­ment is the speed at which we can it­er­ate.
Soft­ware pro­jects that don’t it­er­ate quickly and fre­quently usu­ally stag­nate and fail, while pro­jects that work with user feed­back on an al­most real-time ba­sis are the most suc­cess­ful.
I’ve no­ticed the dif­fer­ence per­son­ally, with my work at <a href="https://archytasinc.com/">Archytas Automation</a>.
Most re­cently, I’ve read <em>The Lean Startup</em>, <em>Zen and the Art of Motorcycle Maintenance</em> and <em>The Pragmatic Programmer</em>.
While each uses their own ter­mi­nol­ogy, both go into depth on the depth on the topic of fast it­er­a­tion cy­cles.</p>
<h2>Lean Manufacturing and Lean Thinking</h2>
<p>Lean man­u­fac­tur­ing, as ex­plained in <em>The Lean Startup</em> is a pro­duc­tion method­ol­ogy fo­cused on avoid­ing waste.
The book’s pri­mary ex­am­ple is the Toyota Production System.</p>
<p>In the mid 1930s, the world’s largest car man­u­fac­turer was Ford.
Ford’s fac­to­ries pro­duced thou­sands of unique parts, which to­gether could form a car.
They pro­duced these parts in bulk, and each dis­tinct part had an en­tire ma­chine ded­i­cated to pro­duc­ing it.
There was a ma­chine that made springs, a ma­chine that made nuts, and a ma­chine that made bolts.
No ma­chine could make any part other than the one it was de­signed for.</p>
<p>This was great: it meant that cars would be cheaper than ever, and more peo­ple could ex­pe­ri­ence car own­er­ship.</p>
<p>Around this time, Toyota en­tered the au­to­mo­tive in­dus­try.
How­ever, they did not have the time, nor the money to de­sign and build the same kind of hy­per-spe­ciﬁc man­u­fac­tur­ing ma­chines that Ford used.
In­stead, they chose to pur­chase com­par­a­tively few hy­per-<strong>gen­er­al­ized</strong> ma­chines.
Their strat­egy was to build one car at time, and sell it as soon as pos­si­ble (ideally the mo­ment it rolled off of the fac­tory ﬂoor).
This way, if there is an is­sue with the ve­hi­cle, the de­sign can be mod­i­ﬁed be­fore too many faulty cars are as­sem­bled.</p>
<p>The Toyota Production System em­pha­sized ﬂex­i­bil­ity over ef­ﬁ­ciency.
By stay­ing ﬂex­i­ble, Toyota gave it­self the abil­ity to op­ti­mize all as­pects of the busi­ness in the mid­dle of a pro­duc­tion run.</p>
<p>On one hand, the Ford process looked like this:</p>
<ul>
<li>Get ma­te­ri­als for one hun­dred cars.</li>
<li>Fabricate over a thou­sand dis­tinct parts re­quired for each car (likely more than a hun­dred thou­sand to­tal parts).</li>
<li>Assemble one hun­dred cars&nbsp;.</li>
<li>Sell one hun­dred cars.</li>
<li>Receive feed­back on ve­hi­cle and pro­duc­tion process.</li>
<li>Perform all pos­si­ble changes to the ve­hi­cle’s de­sign and pro­duc­tion process (which would be very lit­tle).</li>
</ul>
<p>With this many steps, each tak­ing place over a com­par­a­tively long time, the it­er­a­tion cy­cles are few and far be­tween.
On the other hand, the Toyota Production System looked like this:</p>
<ul>
<li>Get ma­te­ri­als for one car</li>
<li>Fabricate the rel­a­tively few dis­tinct parts re­quired for the car</li>
<li>If there is an is­sue with the pro­duc­tion process (a ma­chine breaks, some­one gets hurt, etc.).
Stop the whole process and ﬁx the is­sue, so it will not af­fect fu­ture cars.</li>
<li>Assemble one car.</li>
<li>Sell one car.</li>
<li>Receive feed­back on ve­hi­cle and pro­duc­tion process.</li>
<li>Perform all pos­si­ble changes to the ve­hi­cle’s de­sign and pro­duc­tion process (which could be quite a lot, given the ﬂex­i­bil­ity of both).</li>
</ul>
<p>Counterintuitively, per­form­ing each step with a batch size of one ac­tu­ally ended up be­ing <em>more</em> ef­ﬁ­cient.
Even though Ford ex­pe­ri­enced a higher level of ef­ﬁ­cient within each batch, their process led to many more wasted parts be­ing man­u­fac­tured,
as well as a lack of com­mu­ni­ca­tion be­tween steps, which dra­mat­i­cally in­creased the amount of wasted ef­fort.
Ad­di­tion­ally, be­cause the Toyota Production System al­lowed com­par­a­tively rapid it­er­a­tion, Toyota ve­hi­cles were sim­ply bet­ter: more durable, and more use­ful to their cus­tomers.</p>
<h3>Lean Thinking In Software</h3>
<p>In soft­ware de­vel­op­ment, it can be tempt­ing to go the Ford route:
cre­ate a de­tailed plan of all the fea­tures you wish to add to your ser­vice,
then, de­velop the fea­tures, and de­liver them all at once.
Only then do you ac­cept feed­back and eval­u­ate the use­ful­ness of each fea­ture.</p>
<p>In <em>The Lean Startup</em> how­ever, Eric Ries sug­gests an al­ter­na­tive method­ol­ogy, in­spired by the Toyota Production System.
Before de­cid­ing on which fea­tures to in­clude in your ser­vice in an all-at-once, shot-in-the-dark man­ner, per­form ex­per­i­ments.
Cre­ate an ab­solutely min­i­mal pro­to­type of the fea­ture, give to a cus­tomer, and ob­serve how they use it.
It is pos­si­ble they ﬁnd the fea­ture un­help­ful, or they use it in a way you did­n’t ex­pect.
By run­ning the ex­per­i­ment early on, there are far fewer un­help­ful fea­tures in­tro­duced, and the cus­tomer gets a prod­uct more closely tied to their needs.</p>
<p>Further, it is pos­si­ble that your un­der­stand­ing of the prob­lem do­main is mis­in­formed.
Plan­ning out the en­tire prod­uct re­lease months in ad­vance is prob­lem­atic when the re­quire­ments of the pro­ject change (and they will, <strong>of­ten</strong>).</p>
<h2>Why Interpreted Languages Can Be So Productive</h2>
<p>Interpreted pro­gram­ming lan­guages are al­most laugh­ably per­va­sive.
Of the top ten most pop­u­lar pro­gram­ming lan­guages on the 2023 StackOverﬂow Developer sur­vey, six were in­ter­preted.
That count does not in­clude TypeScript, which can be used in a semi-in­ter­preted man­ner.
In­ter­preted lan­guage pop­u­lar­ity is of­ten chalked up to their ease-of-use, ab­strac­tion, and sim­ple, English-like syn­tax.
How­ever, I be­lieve there is an ad­di­tional rea­son: they al­low one to it­er­ate much, much faster.</p>
<h2>Micro-iterations</h2>
<p>Iterations can hap­pen on any timescale.
<strong>Micro-iterations</strong> oc­cur hap­pen over the short­est time scale.
These are small, in­cre­men­tal changes that oc­cur (usually) when de­bug­ging or op­ti­miz­ing code.
It is the amount of time (and by ex­ten­sion, men­tal over­head) that re­sults from a change.
The longer it takes for tests to com­pile and run, the longer it takes to de­bug.</p>
<p>I be­lieve you can model it like so:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>time to de­bug</mtext><mo>=</mo><mo stretchy="false">(</mo><mtext>time to test</mtext><msup><mo stretchy="false">)</mo><mn>2</mn></msup><mo>∗</mo><mtext>text edit­ing</mtext></mrow><annotation encoding="application/x-tex">\text{time to de­bug} = (\text{time to test})^2 * \text{text edit­ing}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">time to de­bug</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.1141em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord text"><span class="mord">time to test</span></span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">text edit­ing</span></span></span></span></span></span>
<blockquote>
<p>This is <strong>not</strong> an ex­act, rep­re­sen­ta­tive equa­tion, merely an ab­strac­tion to il­lus­trate my point, based on per­sonal ex­pe­ri­ence.</p>
</blockquote>
<p>In other words, the to­tal de­bug­ging time in­creases su­per-lin­early as the time it takes to <em>test</em> a change to the pro­gram in­creases.
No­tice how, as the <code>time to test</code> in­creases, the ac­tual amount of time <code>text editing</code> falls to a neg­li­gi­ble ra­tio of the to­tal.
I be­lieve this comes from the fact that as com­pi­la­tion time in­creases, the amount of in­for­ma­tion gleaned per mi­cro-it­er­a­tion tends to de­crease.</p>
<h3><span class="small-caps">JIT</span> Compilation and Hot Reload</h3>
<p>This is where we get to the ti­tle of the ar­ti­cle.</p>
<p>The con­cepts of lean think­ing ex­tend into com­piler tech­nol­ogy.
The Toyota Production System in­cludes the con­cept of<span class="push-double"></span> <span class="pull-double">“</span>Just-in-time”, mean­ing,
“to only pro­duce ex­actly what is needed for the prod­uct, ex­actly when in the process it is re­quired.”
Avoid plan­ning re­source re­quire­ments ahead of time and build sys­tems that only pro­duce what is ac­tu­ally needed.</p>
<p>While the mean­ing of<span class="push-double"></span> <span class="pull-double">“</span>Just-in-time” <strong>is dif­fer­ent</strong> when we re­fer to com­pil­ers, the line of think­ing is ul­ti­mately the same.
In­stead of wast­ing valu­able, hu­man-scale time on com­pi­la­tion and op­ti­miza­tion of ir­rel­e­vant code, just com­pile the code you need, when you need it.
This dras­ti­cally re­duces the amount of time to test, and ul­ti­mately, the time to de­bug.</p>
<p>Further, tech­nolo­gies like React and Flutter’s Hot Reload make it pos­si­ble to com­plete these mi­cro-it­er­a­tions with­out hav­ing to restart your pro­gram.
I be­lieve this is partly why in­ter­preted lan­guages are so be­gin­ner-friendly (other than the high level of ab­strac­tion they typ­i­cally pro­vide).
New­com­ers to pro­gram­ming spend most of their time on syn­tac­tic is­sues and sim­ple run­time er­rors.
By mak­ing mi­cro-it­er­a­tions hap­pen as fast as pos­si­ble, you make these is­sues less an­noy­ing to deal with.</p>
<h3>Procrastination</h3>
<p>If you are in uni­ver­sity (like I cur­rently am), or any other level of ed­u­ca­tion, it can ac­tu­ally be ben­e­ﬁ­cial to hold off on do­ing your home­work.
It is not un­com­mon (at least in my ex­pe­ri­ence) for the pro­ject or pa­per re­quire­ments to change at the last minute.
Some of your peers may choose to com­plete the pro­ject or pa­per as soon as pos­si­ble.
If this is the case, you may ben­e­ﬁt from their ex­pe­ri­ence.
If a cer­tain chain of logic or analy­sis is con­fus­ing or mis­lead­ing, they can warn you to avoid los­ing time.
As­sum­ing they are will­ing, they can also pro­vide in­sight­ful feed­back on your work mid-way through your writ­ing or study process.</p>
<h2>Conclusion</h2>
<p>Iteration is in­cred­i­bly im­por­tant to any de­sign and de­vel­op­ment cy­cle, es­pe­cially in soft­ware.
Since I have em­braced it­er­a­tion, I’ve seen a dra­matic im­prove­ment in my pro­duc­tiv­ity and ef­fec­tive­ness.
So I en­cour­age you: if your build process takes 30 min­utes, spend some time on im­prov­ing it.
If you are work­ing on a pro­ject that has­n’t been put in front of a cus­tomer, try show­ing it to a fam­ily mem­ber or non-tech­ni­cal friend.
Ask them for bru­tal hon­esty, and your pro­ject will be <em>much</em> bet­ter off.</p>]]></description>
            <content:encoded><![CDATA[<h1>A Case for Procrastination</h1>
<img src="/images/model_t.jpg" alt="A linup of Model T Fords">
<p>The most valu­able, unique as­pect of soft­ware de­vel­op­ment is the speed at which we can it­er­ate.
Soft­ware pro­jects that don’t it­er­ate quickly and fre­quently usu­ally stag­nate and fail, while pro­jects that work with user feed­back on an al­most real-time ba­sis are the most suc­cess­ful.
I’ve no­ticed the dif­fer­ence per­son­ally, with my work at <a href="https://archytasinc.com/">Archytas Automation</a>.
Most re­cently, I’ve read <em>The Lean Startup</em>, <em>Zen and the Art of Motorcycle Maintenance</em> and <em>The Pragmatic Programmer</em>.
While each uses their own ter­mi­nol­ogy, both go into depth on the depth on the topic of fast it­er­a­tion cy­cles.</p>
<h2>Lean Manufacturing and Lean Thinking</h2>
<p>Lean man­u­fac­tur­ing, as ex­plained in <em>The Lean Startup</em> is a pro­duc­tion method­ol­ogy fo­cused on avoid­ing waste.
The book’s pri­mary ex­am­ple is the Toyota Production System.</p>
<p>In the mid 1930s, the world’s largest car man­u­fac­turer was Ford.
Ford’s fac­to­ries pro­duced thou­sands of unique parts, which to­gether could form a car.
They pro­duced these parts in bulk, and each dis­tinct part had an en­tire ma­chine ded­i­cated to pro­duc­ing it.
There was a ma­chine that made springs, a ma­chine that made nuts, and a ma­chine that made bolts.
No ma­chine could make any part other than the one it was de­signed for.</p>
<p>This was great: it meant that cars would be cheaper than ever, and more peo­ple could ex­pe­ri­ence car own­er­ship.</p>
<p>Around this time, Toyota en­tered the au­to­mo­tive in­dus­try.
How­ever, they did not have the time, nor the money to de­sign and build the same kind of hy­per-spe­ciﬁc man­u­fac­tur­ing ma­chines that Ford used.
In­stead, they chose to pur­chase com­par­a­tively few hy­per-<strong>gen­er­al­ized</strong> ma­chines.
Their strat­egy was to build one car at time, and sell it as soon as pos­si­ble (ideally the mo­ment it rolled off of the fac­tory ﬂoor).
This way, if there is an is­sue with the ve­hi­cle, the de­sign can be mod­i­ﬁed be­fore too many faulty cars are as­sem­bled.</p>
<p>The Toyota Production System em­pha­sized ﬂex­i­bil­ity over ef­ﬁ­ciency.
By stay­ing ﬂex­i­ble, Toyota gave it­self the abil­ity to op­ti­mize all as­pects of the busi­ness in the mid­dle of a pro­duc­tion run.</p>
<p>On one hand, the Ford process looked like this:</p>
<ul>
<li>Get ma­te­ri­als for one hun­dred cars.</li>
<li>Fabricate over a thou­sand dis­tinct parts re­quired for each car (likely more than a hun­dred thou­sand to­tal parts).</li>
<li>Assemble one hun­dred cars&nbsp;.</li>
<li>Sell one hun­dred cars.</li>
<li>Receive feed­back on ve­hi­cle and pro­duc­tion process.</li>
<li>Perform all pos­si­ble changes to the ve­hi­cle’s de­sign and pro­duc­tion process (which would be very lit­tle).</li>
</ul>
<p>With this many steps, each tak­ing place over a com­par­a­tively long time, the it­er­a­tion cy­cles are few and far be­tween.
On the other hand, the Toyota Production System looked like this:</p>
<ul>
<li>Get ma­te­ri­als for one car</li>
<li>Fabricate the rel­a­tively few dis­tinct parts re­quired for the car</li>
<li>If there is an is­sue with the pro­duc­tion process (a ma­chine breaks, some­one gets hurt, etc.).
Stop the whole process and ﬁx the is­sue, so it will not af­fect fu­ture cars.</li>
<li>Assemble one car.</li>
<li>Sell one car.</li>
<li>Receive feed­back on ve­hi­cle and pro­duc­tion process.</li>
<li>Perform all pos­si­ble changes to the ve­hi­cle’s de­sign and pro­duc­tion process (which could be quite a lot, given the ﬂex­i­bil­ity of both).</li>
</ul>
<p>Counterintuitively, per­form­ing each step with a batch size of one ac­tu­ally ended up be­ing <em>more</em> ef­ﬁ­cient.
Even though Ford ex­pe­ri­enced a higher level of ef­ﬁ­cient within each batch, their process led to many more wasted parts be­ing man­u­fac­tured,
as well as a lack of com­mu­ni­ca­tion be­tween steps, which dra­mat­i­cally in­creased the amount of wasted ef­fort.
Ad­di­tion­ally, be­cause the Toyota Production System al­lowed com­par­a­tively rapid it­er­a­tion, Toyota ve­hi­cles were sim­ply bet­ter: more durable, and more use­ful to their cus­tomers.</p>
<h3>Lean Thinking In Software</h3>
<p>In soft­ware de­vel­op­ment, it can be tempt­ing to go the Ford route:
cre­ate a de­tailed plan of all the fea­tures you wish to add to your ser­vice,
then, de­velop the fea­tures, and de­liver them all at once.
Only then do you ac­cept feed­back and eval­u­ate the use­ful­ness of each fea­ture.</p>
<p>In <em>The Lean Startup</em> how­ever, Eric Ries sug­gests an al­ter­na­tive method­ol­ogy, in­spired by the Toyota Production System.
Before de­cid­ing on which fea­tures to in­clude in your ser­vice in an all-at-once, shot-in-the-dark man­ner, per­form ex­per­i­ments.
Cre­ate an ab­solutely min­i­mal pro­to­type of the fea­ture, give to a cus­tomer, and ob­serve how they use it.
It is pos­si­ble they ﬁnd the fea­ture un­help­ful, or they use it in a way you did­n’t ex­pect.
By run­ning the ex­per­i­ment early on, there are far fewer un­help­ful fea­tures in­tro­duced, and the cus­tomer gets a prod­uct more closely tied to their needs.</p>
<p>Further, it is pos­si­ble that your un­der­stand­ing of the prob­lem do­main is mis­in­formed.
Plan­ning out the en­tire prod­uct re­lease months in ad­vance is prob­lem­atic when the re­quire­ments of the pro­ject change (and they will, <strong>of­ten</strong>).</p>
<h2>Why Interpreted Languages Can Be So Productive</h2>
<p>Interpreted pro­gram­ming lan­guages are al­most laugh­ably per­va­sive.
Of the top ten most pop­u­lar pro­gram­ming lan­guages on the 2023 StackOverﬂow Developer sur­vey, six were in­ter­preted.
That count does not in­clude TypeScript, which can be used in a semi-in­ter­preted man­ner.
In­ter­preted lan­guage pop­u­lar­ity is of­ten chalked up to their ease-of-use, ab­strac­tion, and sim­ple, English-like syn­tax.
How­ever, I be­lieve there is an ad­di­tional rea­son: they al­low one to it­er­ate much, much faster.</p>
<h2>Micro-iterations</h2>
<p>Iterations can hap­pen on any timescale.
<strong>Micro-iterations</strong> oc­cur hap­pen over the short­est time scale.
These are small, in­cre­men­tal changes that oc­cur (usually) when de­bug­ging or op­ti­miz­ing code.
It is the amount of time (and by ex­ten­sion, men­tal over­head) that re­sults from a change.
The longer it takes for tests to com­pile and run, the longer it takes to de­bug.</p>
<p>I be­lieve you can model it like so:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>time to de­bug</mtext><mo>=</mo><mo stretchy="false">(</mo><mtext>time to test</mtext><msup><mo stretchy="false">)</mo><mn>2</mn></msup><mo>∗</mo><mtext>text edit­ing</mtext></mrow><annotation encoding="application/x-tex">\text{time to de­bug} = (\text{time to test})^2 * \text{text edit­ing}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">time to de­bug</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.1141em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord text"><span class="mord">time to test</span></span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">text edit­ing</span></span></span></span></span></span>
<blockquote>
<p>This is <strong>not</strong> an ex­act, rep­re­sen­ta­tive equa­tion, merely an ab­strac­tion to il­lus­trate my point, based on per­sonal ex­pe­ri­ence.</p>
</blockquote>
<p>In other words, the to­tal de­bug­ging time in­creases su­per-lin­early as the time it takes to <em>test</em> a change to the pro­gram in­creases.
No­tice how, as the <code>time to test</code> in­creases, the ac­tual amount of time <code>text editing</code> falls to a neg­li­gi­ble ra­tio of the to­tal.
I be­lieve this comes from the fact that as com­pi­la­tion time in­creases, the amount of in­for­ma­tion gleaned per mi­cro-it­er­a­tion tends to de­crease.</p>
<h3><span class="small-caps">JIT</span> Compilation and Hot Reload</h3>
<p>This is where we get to the ti­tle of the ar­ti­cle.</p>
<p>The con­cepts of lean think­ing ex­tend into com­piler tech­nol­ogy.
The Toyota Production System in­cludes the con­cept of<span class="push-double"></span> <span class="pull-double">“</span>Just-in-time”, mean­ing,
“to only pro­duce ex­actly what is needed for the prod­uct, ex­actly when in the process it is re­quired.”
Avoid plan­ning re­source re­quire­ments ahead of time and build sys­tems that only pro­duce what is ac­tu­ally needed.</p>
<p>While the mean­ing of<span class="push-double"></span> <span class="pull-double">“</span>Just-in-time” <strong>is dif­fer­ent</strong> when we re­fer to com­pil­ers, the line of think­ing is ul­ti­mately the same.
In­stead of wast­ing valu­able, hu­man-scale time on com­pi­la­tion and op­ti­miza­tion of ir­rel­e­vant code, just com­pile the code you need, when you need it.
This dras­ti­cally re­duces the amount of time to test, and ul­ti­mately, the time to de­bug.</p>
<p>Further, tech­nolo­gies like React and Flutter’s Hot Reload make it pos­si­ble to com­plete these mi­cro-it­er­a­tions with­out hav­ing to restart your pro­gram.
I be­lieve this is partly why in­ter­preted lan­guages are so be­gin­ner-friendly (other than the high level of ab­strac­tion they typ­i­cally pro­vide).
New­com­ers to pro­gram­ming spend most of their time on syn­tac­tic is­sues and sim­ple run­time er­rors.
By mak­ing mi­cro-it­er­a­tions hap­pen as fast as pos­si­ble, you make these is­sues less an­noy­ing to deal with.</p>
<h3>Procrastination</h3>
<p>If you are in uni­ver­sity (like I cur­rently am), or any other level of ed­u­ca­tion, it can ac­tu­ally be ben­e­ﬁ­cial to hold off on do­ing your home­work.
It is not un­com­mon (at least in my ex­pe­ri­ence) for the pro­ject or pa­per re­quire­ments to change at the last minute.
Some of your peers may choose to com­plete the pro­ject or pa­per as soon as pos­si­ble.
If this is the case, you may ben­e­ﬁt from their ex­pe­ri­ence.
If a cer­tain chain of logic or analy­sis is con­fus­ing or mis­lead­ing, they can warn you to avoid los­ing time.
As­sum­ing they are will­ing, they can also pro­vide in­sight­ful feed­back on your work mid-way through your writ­ing or study process.</p>
<h2>Conclusion</h2>
<p>Iteration is in­cred­i­bly im­por­tant to any de­sign and de­vel­op­ment cy­cle, es­pe­cially in soft­ware.
Since I have em­braced it­er­a­tion, I’ve seen a dra­matic im­prove­ment in my pro­duc­tiv­ity and ef­fec­tive­ness.
So I en­cour­age you: if your build process takes 30 min­utes, spend some time on im­prov­ing it.
If you are work­ing on a pro­ject that has­n’t been put in front of a cus­tomer, try show­ing it to a fam­ily mem­ber or non-tech­ni­cal friend.
Ask them for bru­tal hon­esty, and your pro­ject will be <em>much</em> bet­ter off.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/model_t.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[I Designed My Own Pen Plotter]]></title>
            <link>https://elijahpotter.dev/articles/i_designed_my_own_pen_plotter</link>
            <guid>https://elijahpotter.dev/articles/i_designed_my_own_pen_plotter</guid>
            <pubDate>Tue, 18 Oct 2022 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>How I Designed (and built) My Own Pen Plotter</h1>
<p>For the last few months, af­ter read­ing Preslav Rachev’s book <a href="https://p5v.gumroad.com/l/generative-art-in-golang">Generative Art in Go</a>, I have been play­ing around with writ­ing al­go­rithms that cre­ate in­ter­est­ing graph­ics. I even­tu­ally ended up build­ing a <a href="https://github.com/elijah-potter/denim">mod­u­lar can­vas li­brary</a> for Rust to make that eas­ier to do, for me and other peo­ple.</p>
<p>After mak­ing a few al­go­rithms and post­ing them to the web, I started look­ing around for more in­spi­ra­tion. I hap­pened upon a video with this pen plot­ter:</p>
<img src="/images/reddit_pen_plotter.png" alt="A screencap of a pen plotter from Reddit">
<p>I was amazed. A ro­bot that could draw <em>vec­tor graph­ics</em>? I had been nerd sniped.</p>
<img src="/images/xkcd_nerd_sniping.png" alt="XKCD Comic #356">
<h2>The Math</h2>
<p>The math­e­mat­ics in­volved in­ter­ested my es­pe­cially. How do you trans­late <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><annotation encoding="application/x-tex">\begin{bmatrix} x &#x26; y \end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2em;vertical-align:-0.35em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">]</span></span></span></span></span></span> to ro­ta­tions of mo­tors? It in­tim­i­dated me a lit­tle at ﬁrst, but once I sat down and worked it out, I re­al­ized it was sur­pris­ingly sim­ple.</p>
<img src="/images/plotter_math_diagram.png" alt="A diagram of the math involved to create the plotter">
<p>We con­sider that the two mo­tors are sim­ply length­en­ing or short­en­ing each string a spe­ciﬁc amount. Here are the equa­tions that dic­tate the length of each string, given a point <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><annotation encoding="application/x-tex">\begin{bmatrix} x &#x26; y \end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2em;vertical-align:-0.35em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">]</span></span></span></span></span></span>, and a dis­tance <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>m</mi></mrow><annotation encoding="application/x-tex">m</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">m</span></span></span></span> be­tween the two mo­tors.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msqrt><mrow><msup><mi>x</mi><mn>2</mn></msup><mo>+</mo><msup><mi>y</mi><mn>2</mn></msup></mrow></msqrt><mo>=</mo><msub><mi>r</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">\sqrt{x^2+y^2} = r_0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.24em;vertical-align:-0.2333em;"></span><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.0067em;"><span class="svg-align" style="top:-3.2em;"><span class="pstrut" style="height:3.2em;"></span><span class="mord" style="padding-left:1em;"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7401em;"><span style="top:-2.989em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7401em;"><span style="top:-2.989em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span><span style="top:-2.9667em;"><span class="pstrut" style="height:3.2em;"></span><span class="hide-tail" style="min-width:1.02em;height:1.28em;"><svg xmlns="http://www.w3.org/2000/svg" width="400em" height="1.28em" viewbox="0 0 400000 1296" preserveaspectratio="xMinYMin slice"><path d="M263,681c0.7,0,18,39.7,52,119
c34,79.3,68.167,158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120
c340,-704.7,510.7,-1060.3,512,-1067
l0 -0
c4.7,-7.3,11,-11,19,-11
H40000v40H1012.3
s-271.3,567,-271.3,567c-38.7,80.7,-84,175,-136,283c-52,108,-89.167,185.3,-111.5,232
c-22.3,46.7,-33.8,70.3,-34.5,71c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1
s-109,-253,-109,-253c-72.7,-168,-109.3,-252,-110,-252c-10.7,8,-22,16.7,-34,26
c-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26s76,-59,76,-59s76,-60,76,-60z
M1001 80h400000v40h-400000z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2333em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.5806em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0278em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msqrt><mrow><mo stretchy="false">(</mo><mi>x</mi><mo>−</mo><mi>m</mi><msup><mo stretchy="false">)</mo><mn>2</mn></msup><mo>+</mo><msup><mi>y</mi><mn>2</mn></msup></mrow></msqrt><mo>=</mo><msub><mi>r</mi><mn>1</mn></msub></mrow><annotation encoding="application/x-tex">\sqrt{(x-m)^2+y^2} = r_1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.24em;vertical-align:-0.2561em;"></span><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.9839em;"><span class="svg-align" style="top:-3.2em;"><span class="pstrut" style="height:3.2em;"></span><span class="mord" style="padding-left:1em;"><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal">m</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7401em;"><span style="top:-2.989em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7401em;"><span style="top:-2.989em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span><span style="top:-2.9439em;"><span class="pstrut" style="height:3.2em;"></span><span class="hide-tail" style="min-width:1.02em;height:1.28em;"><svg xmlns="http://www.w3.org/2000/svg" width="400em" height="1.28em" viewbox="0 0 400000 1296" preserveaspectratio="xMinYMin slice"><path d="M263,681c0.7,0,18,39.7,52,119
c34,79.3,68.167,158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120
c340,-704.7,510.7,-1060.3,512,-1067
l0 -0
c4.7,-7.3,11,-11,19,-11
H40000v40H1012.3
s-271.3,567,-271.3,567c-38.7,80.7,-84,175,-136,283c-52,108,-89.167,185.3,-111.5,232
c-22.3,46.7,-33.8,70.3,-34.5,71c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1
s-109,-253,-109,-253c-72.7,-168,-109.3,-252,-110,-252c-10.7,8,-22,16.7,-34,26
c-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26s76,-59,76,-59s76,-60,76,-60z
M1001 80h400000v40h-400000z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2561em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.5806em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0278em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span>
<h2>Is This Actually Going to Happen?</h2>
<p>I ini­tially sat down and did the math out of cu­rios­ity. I was­n’t plan­ning on build­ing any­thing. After re­al­iz­ing the el­e­gant sim­plicty of the math, I falt em­pow­ered to go through with it. I threw to­gether a parts list.</p>
<img src="/images/pen_plotter_parts.png" alt="A visual of all the parts needed for the plotter">
<h3>Materials</h3>
<ul>
<li>Whiteboard</li>
<li>Expo Marker</li>
<li>2 Stepper Motors</li>
<li><span class="small-caps">3D</span> Printer Timing Belt</li>
<li>Arduino</li>
<li>Adafruit Motor Shield</li>
<li><span class="small-caps">12V</span> <span class="small-caps">2A</span> Power Adapter</li>
<li>Duct Tape (as any pro­ject re­quires)</li>
</ul>
<p>I wanted to build this from scratch, with­out us­ing any­one else’s de­signs or soft­ware.</p>
<p>I or­der the ma­te­ri­als off Amazon and soon enough, I had every­thing I needed.</p>
<h2>Putting It All Together</h2>
<p>The ﬁrst step was to sol­der the mo­tor shield onto the Arduino, and wiring up step­per mo­tors. I used my school’s laser cut­ter to make a lit­tle bracket.</p>
<img src="/images/mounted_motors_pen_plotter.jpeg" alt="The stepper motors mounted to a bracket">
<p>I am us­ing weights to bal­ance the belts on the mo­tors. I mounted it to a white­board so I can it­er­ate in soft­ware faster/​eas­ier.</p>
<img src="/images/mounted_bracket_on_whiteboard_pen_plotter.jpeg" alt="Everything taped onto an old whiteboard. It&#x27;s super rough, but that&#x27;s OK">
<p>I ini­tially did­n’t have the tim­ing belt, so I pro­to­typed with an old length of wire. This is the last pic­ture be­fore I wrote all the soft­ware, which I want to talk about be­fore I show you that ver­sion.</p>
<h2>The Software</h2>
<p>There are two, sep­a­rate pro­grams that, to­gether, make the plot­ter work. The ﬁrst is run­ning on the Arduino, ac­cept­ing com­mands over se­r­ial from the sec­ond, which is run­ning on a USB-connected com­puter (my lap­top).</p>
<p>There are three main rea­sons there needs to be a lap­top in the sys­tem:</p>
<ol>
<li>The Arduino does­n’t have enough pro­gram mem­ory</li>
<li>The Arduino can­not eas­ily ac­cept ﬁles (like SVGs)</li>
<li>It takes for­ever for an Arduino pro­gram to com­pile and up­load, which makes it­er­a­tion frus­trat­ing.</li>
</ol>
<h3>Arduino</h3>
<p>The Arduino is run­ning a very sim­ple loop:</p>
<ol>
<li>Receive two 32 bit signed in­te­gers over se­r­ial (one for each mo­tor)</li>
<li>Linear in­ter­po­late the step­per mo­tors to po­si­tions de­scribed by re­ceived in­te­gers</li>
</ol>
<p>That’s it. It’s im­por­tant that the time it takes for each mo­tor to reach it’s des­ti­na­tion is the same.</p>
<h3>Laptop</h3>
<p>The lap­top is do­ing all the math.</p>
<p>I am used my can­vas li­brary <a href="https://github.com/elijah-potter/denim">Denim</a> to do all the vir­tual draw­ing. I just added a ren­derer that:</p>
<ol>
<li>Converts all points to a se­quence of belt-lengths</li>
<li>Converts belt-lengths to se­quence of mo­tor move­ments</li>
</ol>
<p>And sent the re­sult­ing mo­tor move­ments over se­r­ial to the Arduino.</p>
<p>I also wrote a quick lit­tle parser us­ing <a href="https://github.com/Geal/nom">nom</a> to parse and ex­e­cute the math com­mands you can ﬁnd in­side <span class="small-caps">SVG</span> <code>&#x3C;path></code> el­e­ments.</p>
<h2>Final Results</h2>
<img src="/images/pen_plotter_drawing_a.jpeg" alt="The first drawing on the plotter: the letter A">
<p>As you can see, I also made a big, rec­tan­gu­lar box, to hold the marker. It worked al­right, but not great. You also prob­a­bly no­ticed the main lim­i­ta­tion of my de­sign: it can­not lift the marker off the white­board.</p>
<p>I ﬁg­ured the rough­ness was mainly due to un­bal­anced weights and the slant of the white­board. I ad­justed every­thing and tried again.</p>
<h3>Take Two</h3>
<img src="/images/pen_plotter_drawing_a2.jpeg" alt="A slightly improved letter A">
<p>This one is much bet­ter. The lines are crisp, and ex­actly where they are sup­posed to be. At this point I wanted to try some­thing a lit­tle more ad­vanced out. Something a lit­tle more <strong>gen­er­a­tive</strong>.</p>
<h3>The First Hilbert Curve</h3>
<img src="/images/pen_plotter_drawing_hilbert.jpeg" alt="An attempt at drawing a hilbert curve">
<p>After see­ing the plot­ter draw a Hilbert Curve, I felt proud <em>and</em> a lit­tle dis­ap­pointed. The cor­ners aren’t crisp, the lines aren’t straight. Frankly, it looks like it was drawn by a two-year old.</p>
<h3>Solving the Issue</h3>
<img src="/images/pen_plotter_drawing_hilbert2.jpeg" alt="The final attempt at a Hilbert Curve">
<p>It worked! Now all the lines are pre­cise, crisp, smooth. The biggest is­sue is that the marker it­self ro­tates as it moves. That would­n’t nor­mally be an is­sue, but the marker is chis­eled, so it re­sults in vary­ing lev­els of pres­sure on the white­board.</p>
<h2>Conclusion</h2>
<p>This was a re­ally cool pro­ject. I learned a lot. I am not very ex­pe­ri­enced with ro­bot­ics, so this re­ally chal­lenged me. I in­tend on con­tin­u­ing work­ing on it. I want to add the abil­ity for the pen to lift off the can­vas, and re­ally so­lid­ify the marker holder. Most of all, I look for­ward to tak­ing my pro­jects into the <em>real</em> world.</p>]]></description>
            <content:encoded><![CDATA[<h1>How I Designed (and built) My Own Pen Plotter</h1>
<p>For the last few months, af­ter read­ing Preslav Rachev’s book <a href="https://p5v.gumroad.com/l/generative-art-in-golang">Generative Art in Go</a>, I have been play­ing around with writ­ing al­go­rithms that cre­ate in­ter­est­ing graph­ics. I even­tu­ally ended up build­ing a <a href="https://github.com/elijah-potter/denim">mod­u­lar can­vas li­brary</a> for Rust to make that eas­ier to do, for me and other peo­ple.</p>
<p>After mak­ing a few al­go­rithms and post­ing them to the web, I started look­ing around for more in­spi­ra­tion. I hap­pened upon a video with this pen plot­ter:</p>
<img src="/images/reddit_pen_plotter.png" alt="A screencap of a pen plotter from Reddit">
<p>I was amazed. A ro­bot that could draw <em>vec­tor graph­ics</em>? I had been nerd sniped.</p>
<img src="/images/xkcd_nerd_sniping.png" alt="XKCD Comic #356">
<h2>The Math</h2>
<p>The math­e­mat­ics in­volved in­ter­ested my es­pe­cially. How do you trans­late <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><annotation encoding="application/x-tex">\begin{bmatrix} x &#x26; y \end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2em;vertical-align:-0.35em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">]</span></span></span></span></span></span> to ro­ta­tions of mo­tors? It in­tim­i­dated me a lit­tle at ﬁrst, but once I sat down and worked it out, I re­al­ized it was sur­pris­ingly sim­ple.</p>
<img src="/images/plotter_math_diagram.png" alt="A diagram of the math involved to create the plotter">
<p>We con­sider that the two mo­tors are sim­ply length­en­ing or short­en­ing each string a spe­ciﬁc amount. Here are the equa­tions that dic­tate the length of each string, given a point <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><annotation encoding="application/x-tex">\begin{bmatrix} x &#x26; y \end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2em;vertical-align:-0.35em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">]</span></span></span></span></span></span>, and a dis­tance <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>m</mi></mrow><annotation encoding="application/x-tex">m</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">m</span></span></span></span> be­tween the two mo­tors.</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msqrt><mrow><msup><mi>x</mi><mn>2</mn></msup><mo>+</mo><msup><mi>y</mi><mn>2</mn></msup></mrow></msqrt><mo>=</mo><msub><mi>r</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">\sqrt{x^2+y^2} = r_0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.24em;vertical-align:-0.2333em;"></span><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.0067em;"><span class="svg-align" style="top:-3.2em;"><span class="pstrut" style="height:3.2em;"></span><span class="mord" style="padding-left:1em;"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7401em;"><span style="top:-2.989em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7401em;"><span style="top:-2.989em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span><span style="top:-2.9667em;"><span class="pstrut" style="height:3.2em;"></span><span class="hide-tail" style="min-width:1.02em;height:1.28em;"><svg xmlns="http://www.w3.org/2000/svg" width="400em" height="1.28em" viewbox="0 0 400000 1296" preserveaspectratio="xMinYMin slice"><path d="M263,681c0.7,0,18,39.7,52,119
c34,79.3,68.167,158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120
c340,-704.7,510.7,-1060.3,512,-1067
l0 -0
c4.7,-7.3,11,-11,19,-11
H40000v40H1012.3
s-271.3,567,-271.3,567c-38.7,80.7,-84,175,-136,283c-52,108,-89.167,185.3,-111.5,232
c-22.3,46.7,-33.8,70.3,-34.5,71c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1
s-109,-253,-109,-253c-72.7,-168,-109.3,-252,-110,-252c-10.7,8,-22,16.7,-34,26
c-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26s76,-59,76,-59s76,-60,76,-60z
M1001 80h400000v40h-400000z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2333em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.5806em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0278em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msqrt><mrow><mo stretchy="false">(</mo><mi>x</mi><mo>−</mo><mi>m</mi><msup><mo stretchy="false">)</mo><mn>2</mn></msup><mo>+</mo><msup><mi>y</mi><mn>2</mn></msup></mrow></msqrt><mo>=</mo><msub><mi>r</mi><mn>1</mn></msub></mrow><annotation encoding="application/x-tex">\sqrt{(x-m)^2+y^2} = r_1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.24em;vertical-align:-0.2561em;"></span><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.9839em;"><span class="svg-align" style="top:-3.2em;"><span class="pstrut" style="height:3.2em;"></span><span class="mord" style="padding-left:1em;"><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal">m</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7401em;"><span style="top:-2.989em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7401em;"><span style="top:-2.989em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span><span style="top:-2.9439em;"><span class="pstrut" style="height:3.2em;"></span><span class="hide-tail" style="min-width:1.02em;height:1.28em;"><svg xmlns="http://www.w3.org/2000/svg" width="400em" height="1.28em" viewbox="0 0 400000 1296" preserveaspectratio="xMinYMin slice"><path d="M263,681c0.7,0,18,39.7,52,119
c34,79.3,68.167,158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120
c340,-704.7,510.7,-1060.3,512,-1067
l0 -0
c4.7,-7.3,11,-11,19,-11
H40000v40H1012.3
s-271.3,567,-271.3,567c-38.7,80.7,-84,175,-136,283c-52,108,-89.167,185.3,-111.5,232
c-22.3,46.7,-33.8,70.3,-34.5,71c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1
s-109,-253,-109,-253c-72.7,-168,-109.3,-252,-110,-252c-10.7,8,-22,16.7,-34,26
c-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26s76,-59,76,-59s76,-60,76,-60z
M1001 80h400000v40h-400000z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2561em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.5806em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0278em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span>
<h2>Is This Actually Going to Happen?</h2>
<p>I ini­tially sat down and did the math out of cu­rios­ity. I was­n’t plan­ning on build­ing any­thing. After re­al­iz­ing the el­e­gant sim­plicty of the math, I falt em­pow­ered to go through with it. I threw to­gether a parts list.</p>
<img src="/images/pen_plotter_parts.png" alt="A visual of all the parts needed for the plotter">
<h3>Materials</h3>
<ul>
<li>Whiteboard</li>
<li>Expo Marker</li>
<li>2 Stepper Motors</li>
<li><span class="small-caps">3D</span> Printer Timing Belt</li>
<li>Arduino</li>
<li>Adafruit Motor Shield</li>
<li><span class="small-caps">12V</span> <span class="small-caps">2A</span> Power Adapter</li>
<li>Duct Tape (as any pro­ject re­quires)</li>
</ul>
<p>I wanted to build this from scratch, with­out us­ing any­one else’s de­signs or soft­ware.</p>
<p>I or­der the ma­te­ri­als off Amazon and soon enough, I had every­thing I needed.</p>
<h2>Putting It All Together</h2>
<p>The ﬁrst step was to sol­der the mo­tor shield onto the Arduino, and wiring up step­per mo­tors. I used my school’s laser cut­ter to make a lit­tle bracket.</p>
<img src="/images/mounted_motors_pen_plotter.jpeg" alt="The stepper motors mounted to a bracket">
<p>I am us­ing weights to bal­ance the belts on the mo­tors. I mounted it to a white­board so I can it­er­ate in soft­ware faster/​eas­ier.</p>
<img src="/images/mounted_bracket_on_whiteboard_pen_plotter.jpeg" alt="Everything taped onto an old whiteboard. It&#x27;s super rough, but that&#x27;s OK">
<p>I ini­tially did­n’t have the tim­ing belt, so I pro­to­typed with an old length of wire. This is the last pic­ture be­fore I wrote all the soft­ware, which I want to talk about be­fore I show you that ver­sion.</p>
<h2>The Software</h2>
<p>There are two, sep­a­rate pro­grams that, to­gether, make the plot­ter work. The ﬁrst is run­ning on the Arduino, ac­cept­ing com­mands over se­r­ial from the sec­ond, which is run­ning on a USB-connected com­puter (my lap­top).</p>
<p>There are three main rea­sons there needs to be a lap­top in the sys­tem:</p>
<ol>
<li>The Arduino does­n’t have enough pro­gram mem­ory</li>
<li>The Arduino can­not eas­ily ac­cept ﬁles (like SVGs)</li>
<li>It takes for­ever for an Arduino pro­gram to com­pile and up­load, which makes it­er­a­tion frus­trat­ing.</li>
</ol>
<h3>Arduino</h3>
<p>The Arduino is run­ning a very sim­ple loop:</p>
<ol>
<li>Receive two 32 bit signed in­te­gers over se­r­ial (one for each mo­tor)</li>
<li>Linear in­ter­po­late the step­per mo­tors to po­si­tions de­scribed by re­ceived in­te­gers</li>
</ol>
<p>That’s it. It’s im­por­tant that the time it takes for each mo­tor to reach it’s des­ti­na­tion is the same.</p>
<h3>Laptop</h3>
<p>The lap­top is do­ing all the math.</p>
<p>I am used my can­vas li­brary <a href="https://github.com/elijah-potter/denim">Denim</a> to do all the vir­tual draw­ing. I just added a ren­derer that:</p>
<ol>
<li>Converts all points to a se­quence of belt-lengths</li>
<li>Converts belt-lengths to se­quence of mo­tor move­ments</li>
</ol>
<p>And sent the re­sult­ing mo­tor move­ments over se­r­ial to the Arduino.</p>
<p>I also wrote a quick lit­tle parser us­ing <a href="https://github.com/Geal/nom">nom</a> to parse and ex­e­cute the math com­mands you can ﬁnd in­side <span class="small-caps">SVG</span> <code>&#x3C;path></code> el­e­ments.</p>
<h2>Final Results</h2>
<img src="/images/pen_plotter_drawing_a.jpeg" alt="The first drawing on the plotter: the letter A">
<p>As you can see, I also made a big, rec­tan­gu­lar box, to hold the marker. It worked al­right, but not great. You also prob­a­bly no­ticed the main lim­i­ta­tion of my de­sign: it can­not lift the marker off the white­board.</p>
<p>I ﬁg­ured the rough­ness was mainly due to un­bal­anced weights and the slant of the white­board. I ad­justed every­thing and tried again.</p>
<h3>Take Two</h3>
<img src="/images/pen_plotter_drawing_a2.jpeg" alt="A slightly improved letter A">
<p>This one is much bet­ter. The lines are crisp, and ex­actly where they are sup­posed to be. At this point I wanted to try some­thing a lit­tle more ad­vanced out. Something a lit­tle more <strong>gen­er­a­tive</strong>.</p>
<h3>The First Hilbert Curve</h3>
<img src="/images/pen_plotter_drawing_hilbert.jpeg" alt="An attempt at drawing a hilbert curve">
<p>After see­ing the plot­ter draw a Hilbert Curve, I felt proud <em>and</em> a lit­tle dis­ap­pointed. The cor­ners aren’t crisp, the lines aren’t straight. Frankly, it looks like it was drawn by a two-year old.</p>
<h3>Solving the Issue</h3>
<img src="/images/pen_plotter_drawing_hilbert2.jpeg" alt="The final attempt at a Hilbert Curve">
<p>It worked! Now all the lines are pre­cise, crisp, smooth. The biggest is­sue is that the marker it­self ro­tates as it moves. That would­n’t nor­mally be an is­sue, but the marker is chis­eled, so it re­sults in vary­ing lev­els of pres­sure on the white­board.</p>
<h2>Conclusion</h2>
<p>This was a re­ally cool pro­ject. I learned a lot. I am not very ex­pe­ri­enced with ro­bot­ics, so this re­ally chal­lenged me. I in­tend on con­tin­u­ing work­ing on it. I want to add the abil­ity for the pen to lift off the can­vas, and re­ally so­lid­ify the marker holder. Most of all, I look for­ward to tak­ing my pro­jects into the <em>real</em> world.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/pen_plotter_drawing_hilbert2.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[3 Awesome Ways Computers Generate Randomness]]></title>
            <link>https://elijahpotter.dev/articles/3_awesome_ways_computers_generate_randomness</link>
            <guid>https://elijahpotter.dev/articles/3_awesome_ways_computers_generate_randomness</guid>
            <pubDate>Fri, 04 Mar 2022 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>What Is Randomness?</h1>
<img src="/images/numbers_on_a_screen.jpg" alt="Numbers on a screen">
<p>In case you were born yes­ter­day, let’s go over it.</p>
<p>Randomness is, at the most ba­sic level, some­thing that can­not be pre­dicted.
In com­puter sci­ence, when we talk about ran­dom­ness, we are usu­ally talk­ing about ran­dom num­bers and the tools we use to get them, ran­dom num­ber gen­er­a­tors (RNGs).</p>
<h2>Pseudorandomness</h2>
<p>We say an <span class="small-caps">RNG</span> is<span class="push-double"></span> <span class="pull-double">“</span>pseudorandom” when we have to give it a ﬁxed seed, and it gen­er­ates ran­dom num­bers based on that seed.
If we give it the same seed, we will get the same num­bers.
It is de­ter­min­is­tic.</p>
<p>I thought, for a very long time, that com­put­ers could only cre­ate pseudo­ran­dom num­bers.
The main rea­son I thought this, and why you might too, is be­cause util­i­ties like the Random class in&nbsp;.<span class="small-caps">NET</span> are pseudo­ran­dom and are seeded by some ar­bi­trary in­for­ma­tion, like the cur­rent time.</p>
<p>Computers aren’t use­ful if they aren’t de­ter­min­is­tic.
In a per­fect, en­closed sys­tem, it would be im­pos­si­ble for a com­puter to gen­er­ate truly ran­dom num­bers.</p>
<h2>True Randomness</h2>
<p>The phrase<span class="push-double"></span> <span class="pull-double">“</span>true ran­dom­ness” is used to de­scribe things that are im­pos­si­ble to pre­dict. Provably so. This is dif­fer­ent from chaotic sys­tems, which are pre­dictable un­der short spans of time.
True ran­dom­ness is im­pos­si­ble to pre­dict on all scales.</p>
<h2>The Everyday Method</h2>
<p>Allow me to in­tro­duce you to <a href="https://en.wikipedia.org/wiki/RDRAND"><span class="small-caps">RDRAND</span></a> and <a href="https://en.wikipedia.org/wiki/RDRAND#RDSEED"><span class="small-caps">RDSEED</span></a>, two <span class="small-caps">CPU</span> in­struc­tions orig­i­nally in­tro­duced by Intel, that al­low pro­grams ac­cess to truly ran­dom num­bers.
These in­struc­tions gather data from an on-chip en­tropy source to pro­vide ran­dom num­bers.</p>
<p>These in­struc­tions uti­lize ther­mal noise to pro­duce white noise, which is used to gen­er­ate said ran­dom num­bers.
The ad­van­tage of ther­mal noise is that it pro­duces ac­tu­ally ran­dom val­ues, as ex­plained here.
Its also fast, which means it can be used for every­day things, like gen­er­at­ing <span class="small-caps">SSL</span> or <span class="small-caps">TLS</span> keys and the like.</p>
<h3>Do It Yourself</h3>
<p>If you hap­pen to be on a Linux sys­tem, you can ac­tu­ally use these in­struc­tions your­self. Just run this com­mand:</p>
<pre><code class="hljs language-bash"><span class="hljs-built_in">dd</span> <span class="hljs-keyword">if</span>=/dev/random count=4 bs=1 status=none | <span class="hljs-built_in">od</span> -An --format=dI
</code></pre>
<p>This com­mand uses <code>dd</code> to gen­er­ate 4 ran­dom bytes.
Then it pipes those bytes into <code>od</code>&nbsp;, which will for­mat those 4 bytes into hu­man-read­able text as a signed 32-bit in­te­ger.</p>
<h2>The Nuclear Method</h2>
<img src="/images/nuclear_plant.jpg" alt="A Nuclear Plant">
<p>One of the big ad­vance­ments of the 20th Century was the cre­ation of a branch of re­search called quan­tum physics.
One of the things we learned was that on the scale of in­di­vid­ual par­ti­cles, it be­comes prov­ably im­pos­si­ble to pre­dict state.
Every time a mea­sure­ment is taken of a sub­atomic par­ti­cle, its state changes ran­domly.</p>
<p>One way we could sam­ple sub­atomic par­ti­cles is by plac­ing a Geiger counter next to a bit of ra­dioac­tive ore.
Be­cause whether an in­di­vid­ual atom will de­cay at any given mo­ment, we can know that the time be­tween ticks of the Geiger counter is ran­dom.</p>
<h2>The Cloudﬂare Method</h2>
<img src="/images/lava_lamp.jpg" alt="A Lava Lamp">
<p>Cloudﬂare uses some es­pe­cially in­ter­est­ing sources of en­tropy to seed its ran­dom num­ber gen­er­a­tors.
One way is via lava lamps.
In their lobby, they have a large ar­ray of lava lamps.
There is a cam­era run­ning a live feed of the lava lamps to their servers.
There are two main sources of ran­dom­ness.</p>
<p><strong>First</strong>, the lava lamps them­selves.
The move­ment of even a sin­gle lava lamp, if we for­get the sec­ond law of ther­mo­dy­nam­ics, is far too chaotic to pre­dict.
If we in­clude the sec­ond law of ther­mo­dy­nam­ics, it be­comes true ran­dom­ness.</p>
<p><strong>Second</strong>, the cam­era sen­sor’s noise.
Thanks to the pho­to­elec­tric ef­fect the noise that ap­pears on a cam­er­a’s sen­sor, how­ever un­no­tice­able, is truly ran­dom.</p>
<p>By com­bin­ing these two sources, you get a pool of truly ran­dom num­bers ~60 times a sec­ond.
If that does­n’t ﬁt your needs, you can use those num­bers to feed a cryp­to­graph­i­cally se­cure pseudo­ran­dom num­ber gen­er­a­tor (<span class="small-caps">CSPRNG</span>) to get as many as you want.</p>]]></description>
            <content:encoded><![CDATA[<h1>What Is Randomness?</h1>
<img src="/images/numbers_on_a_screen.jpg" alt="Numbers on a screen">
<p>In case you were born yes­ter­day, let’s go over it.</p>
<p>Randomness is, at the most ba­sic level, some­thing that can­not be pre­dicted.
In com­puter sci­ence, when we talk about ran­dom­ness, we are usu­ally talk­ing about ran­dom num­bers and the tools we use to get them, ran­dom num­ber gen­er­a­tors (RNGs).</p>
<h2>Pseudorandomness</h2>
<p>We say an <span class="small-caps">RNG</span> is<span class="push-double"></span> <span class="pull-double">“</span>pseudorandom” when we have to give it a ﬁxed seed, and it gen­er­ates ran­dom num­bers based on that seed.
If we give it the same seed, we will get the same num­bers.
It is de­ter­min­is­tic.</p>
<p>I thought, for a very long time, that com­put­ers could only cre­ate pseudo­ran­dom num­bers.
The main rea­son I thought this, and why you might too, is be­cause util­i­ties like the Random class in&nbsp;.<span class="small-caps">NET</span> are pseudo­ran­dom and are seeded by some ar­bi­trary in­for­ma­tion, like the cur­rent time.</p>
<p>Computers aren’t use­ful if they aren’t de­ter­min­is­tic.
In a per­fect, en­closed sys­tem, it would be im­pos­si­ble for a com­puter to gen­er­ate truly ran­dom num­bers.</p>
<h2>True Randomness</h2>
<p>The phrase<span class="push-double"></span> <span class="pull-double">“</span>true ran­dom­ness” is used to de­scribe things that are im­pos­si­ble to pre­dict. Provably so. This is dif­fer­ent from chaotic sys­tems, which are pre­dictable un­der short spans of time.
True ran­dom­ness is im­pos­si­ble to pre­dict on all scales.</p>
<h2>The Everyday Method</h2>
<p>Allow me to in­tro­duce you to <a href="https://en.wikipedia.org/wiki/RDRAND"><span class="small-caps">RDRAND</span></a> and <a href="https://en.wikipedia.org/wiki/RDRAND#RDSEED"><span class="small-caps">RDSEED</span></a>, two <span class="small-caps">CPU</span> in­struc­tions orig­i­nally in­tro­duced by Intel, that al­low pro­grams ac­cess to truly ran­dom num­bers.
These in­struc­tions gather data from an on-chip en­tropy source to pro­vide ran­dom num­bers.</p>
<p>These in­struc­tions uti­lize ther­mal noise to pro­duce white noise, which is used to gen­er­ate said ran­dom num­bers.
The ad­van­tage of ther­mal noise is that it pro­duces ac­tu­ally ran­dom val­ues, as ex­plained here.
Its also fast, which means it can be used for every­day things, like gen­er­at­ing <span class="small-caps">SSL</span> or <span class="small-caps">TLS</span> keys and the like.</p>
<h3>Do It Yourself</h3>
<p>If you hap­pen to be on a Linux sys­tem, you can ac­tu­ally use these in­struc­tions your­self. Just run this com­mand:</p>
<pre><code class="hljs language-bash"><span class="hljs-built_in">dd</span> <span class="hljs-keyword">if</span>=/dev/random count=4 bs=1 status=none | <span class="hljs-built_in">od</span> -An --format=dI
</code></pre>
<p>This com­mand uses <code>dd</code> to gen­er­ate 4 ran­dom bytes.
Then it pipes those bytes into <code>od</code>&nbsp;, which will for­mat those 4 bytes into hu­man-read­able text as a signed 32-bit in­te­ger.</p>
<h2>The Nuclear Method</h2>
<img src="/images/nuclear_plant.jpg" alt="A Nuclear Plant">
<p>One of the big ad­vance­ments of the 20th Century was the cre­ation of a branch of re­search called quan­tum physics.
One of the things we learned was that on the scale of in­di­vid­ual par­ti­cles, it be­comes prov­ably im­pos­si­ble to pre­dict state.
Every time a mea­sure­ment is taken of a sub­atomic par­ti­cle, its state changes ran­domly.</p>
<p>One way we could sam­ple sub­atomic par­ti­cles is by plac­ing a Geiger counter next to a bit of ra­dioac­tive ore.
Be­cause whether an in­di­vid­ual atom will de­cay at any given mo­ment, we can know that the time be­tween ticks of the Geiger counter is ran­dom.</p>
<h2>The Cloudﬂare Method</h2>
<img src="/images/lava_lamp.jpg" alt="A Lava Lamp">
<p>Cloudﬂare uses some es­pe­cially in­ter­est­ing sources of en­tropy to seed its ran­dom num­ber gen­er­a­tors.
One way is via lava lamps.
In their lobby, they have a large ar­ray of lava lamps.
There is a cam­era run­ning a live feed of the lava lamps to their servers.
There are two main sources of ran­dom­ness.</p>
<p><strong>First</strong>, the lava lamps them­selves.
The move­ment of even a sin­gle lava lamp, if we for­get the sec­ond law of ther­mo­dy­nam­ics, is far too chaotic to pre­dict.
If we in­clude the sec­ond law of ther­mo­dy­nam­ics, it be­comes true ran­dom­ness.</p>
<p><strong>Second</strong>, the cam­era sen­sor’s noise.
Thanks to the pho­to­elec­tric ef­fect the noise that ap­pears on a cam­er­a’s sen­sor, how­ever un­no­tice­able, is truly ran­dom.</p>
<p>By com­bin­ing these two sources, you get a pool of truly ran­dom num­bers ~60 times a sec­ond.
If that does­n’t ﬁt your needs, you can use those num­bers to feed a cryp­to­graph­i­cally se­cure pseudo­ran­dom num­ber gen­er­a­tor (<span class="small-caps">CSPRNG</span>) to get as many as you want.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/numbers_on_a_screen.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Build a Wordle Solver Using Rust]]></title>
            <link>https://elijahpotter.dev/articles/build_a_wordle_solver_using_rust</link>
            <guid>https://elijahpotter.dev/articles/build_a_wordle_solver_using_rust</guid>
            <pubDate>Tue, 01 Mar 2022 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Build a Wordle Solver Using Rust</h1>
<h2>The Game</h2>
<p><a href="https://www.nytimes.com/games/wordle/index.html">Wordle</a> is a rel­a­tively sim­ple game. If you have ever played Mastermind, it should sound fa­mil­iar. The goal is to ﬁg­ure out a mys­tery word with as few guesses as pos­si­ble. The mys­tery word changes each day. Here are two ex­am­ple guesses.</p>
<img src="/images/wordle_example.png" alt="Two example guesses from the game Wordle">
<p>After a guess, each let­ter’s color changes.</p>
<p>Green — The let­ter is cor­rect.
Yel­low— The let­ter ex­ists in the word, but not in that space.
Gray — The let­ter does not ex­ist in the word.</p>
<p>As you can see, there are a max­i­mum of six guesses. If you can­not ﬁnd the mys­tery word within six guesses, you lose. I have been com­pet­ing with my grand­mother each day to ﬁnd the word in as few guesses as pos­si­ble.</p>
<img src="/images/xkcd_nerd_sniping.png" alt="XKCD Comic #356">
<p>This to­tally nerd-sniped me. I felt an over­whelm­ing urge to build an app that could, con­ceiv­ably ﬁnd the mys­tery word in as few guesses as pos­si­ble.</p>
<h2>How I Did It</h2>
<p>First things ﬁrst, we need a list of English words. I ini­tially used the <a href="http://www.mieliestronk.com/wordlist.html">corn­cob</a> list, but I found greater suc­cess with <a href="https://github.com/dwyl/english-words/">dwyl’s</a> list.</p>
<p>For this pro­ject, I de­cided to use Rust, just be­cause I felt most con­ﬁ­dent in my abil­ity to make an <span class="small-caps">MVP</span> quickly.</p>
<p>Both the word lists I used are for­mat­ted as a se­quence of in­di­vid­ual words, sep­a­rated by <code>\n</code> char­ac­ters. On Windows (which is what I am us­ing), they also have pesky those lit­tle <code>\r</code> char­ac­ters.</p>
<p>Wordle is heav­ily fo­cused on let­ters. I can re­move items from the word list based on what let­ters I know aren’t in the mys­tery word (these are gray let­ters in-game), and I can re­move items based what let­ters I know are in the mys­tery word (the or­ange or green let­ters), but in a lot of cases that still leaves a lot of pos­si­ble words. I need to way to sort words based on how likely their let­ters are.</p>
<p>To do this, I count how fre­quently each let­ter ap­pears in the word list, and give each word a score based on how fre­quently its com­po­nents ap­pear.</p>
<p>The ﬁrst step in the pro­gram is to load the word list and count the let­ters:</p>
<pre><code class="hljs language-rust"><span class="hljs-comment">// Store the total number of times a letter appears.</span>
<span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">letter_scores</span> = HashMap::<span class="hljs-title function_ invoke__">new</span>();
<span class="hljs-comment">// The final list of words. It will make like easier later in the program to store the words as Vec&#x3C;char>.</span>
<span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">word_list</span> = <span class="hljs-type">Vec</span>::<span class="hljs-title function_ invoke__">new</span>();
<span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">last_word</span> = <span class="hljs-type">Vec</span>::<span class="hljs-title function_ invoke__">new</span>();
<span class="hljs-keyword">let</span> <span class="hljs-variable">file</span> = std::fs::<span class="hljs-title function_ invoke__">read</span>(<span class="hljs-string">"corncob_lowercase.txt"</span>)?;

<span class="hljs-comment">// Iterate through all the bytes in the wordlist file, ignoring all `\r` instances.</span>
<span class="hljs-keyword">for</span> <span class="hljs-variable">letter</span> <span class="hljs-keyword">in</span> file {
    <span class="hljs-keyword">let</span> <span class="hljs-variable">letter</span> = letter <span class="hljs-keyword">as</span> <span class="hljs-type">char</span>;
    <span class="hljs-keyword">match</span> letter {
        <span class="hljs-string">'<span class="hljs-char escape_">\n</span>'</span> => {
            word_list.<span class="hljs-title function_ invoke__">push</span>(last_word);
            last_word = <span class="hljs-type">Vec</span>::<span class="hljs-title function_ invoke__">new</span>();
        }
        <span class="hljs-string">'<span class="hljs-char escape_">\r</span>'</span> => (),
        _ => {
            <span class="hljs-keyword">let</span> <span class="hljs-variable">entry</span> = letter_scores.<span class="hljs-title function_ invoke__">entry</span>(letter).<span class="hljs-title function_ invoke__">or_default</span>();
            *entry += <span class="hljs-number">1</span>;
            last_word.<span class="hljs-title function_ invoke__">push</span>(letter);
        }
    }
}
</code></pre>
<p>Using the de­fault <code>HashMap</code> (which uses <code>SipHash</code>, which is­n’t great for sin­gle-char­ac­ter lookup), prob­a­bly is­n’t the best, per­for­mance-wise, but this is just a toy pro­gram, and does­n’t need to be the fastest thing in the world.</p>
<p>Next, we need to go through the word list, and elim­i­nate words that con­tain gray let­ters. Here is a func­tion that helps do that:</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">matches_found</span>(
    word: &#x26;[<span class="hljs-type">char</span>],
    found: &#x26;[<span class="hljs-type">char</span>],
    not: &#x26;[<span class="hljs-type">char</span>],
    must: &#x26;[<span class="hljs-type">char</span>],
    masks: &#x26;[<span class="hljs-type">Vec</span>&#x3C;<span class="hljs-type">char</span>>],
) <span class="hljs-punctuation">-></span> <span class="hljs-type">bool</span> {
    <span class="hljs-comment">// Check if the word contains a letter we know *isn't* in the mystery. &#x3C;-- The gray letters.</span>
    <span class="hljs-keyword">for</span> <span class="hljs-variable">c</span> <span class="hljs-keyword">in</span> not {
        <span class="hljs-keyword">if</span> word.<span class="hljs-title function_ invoke__">contains</span>(c) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }
    }

    <span class="hljs-comment">// Check if the word contains the letters we don't know the positions of, but know they are in the mystery word.. &#x3C;-- The orange letters.</span>
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">found_letters</span> = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">for</span> <span class="hljs-variable">c</span> <span class="hljs-keyword">in</span> must {
        <span class="hljs-keyword">if</span> word.<span class="hljs-title function_ invoke__">contains</span>(c) {
            found_letters += <span class="hljs-number">1</span>;
        }
    }
    <span class="hljs-keyword">if</span> found_letters &#x3C; must.<span class="hljs-title function_ invoke__">len</span>() {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }

    <span class="hljs-comment">// Check if the word has letters we know exist in the word, but not at the right spots. &#x3C;-- The orange letters.</span>
    <span class="hljs-keyword">for</span> <span class="hljs-variable">mask</span> <span class="hljs-keyword">in</span> masks {
        <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<span class="hljs-title function_ invoke__">min</span>(word.<span class="hljs-title function_ invoke__">len</span>(), mask.<span class="hljs-title function_ invoke__">len</span>()) {
            <span class="hljs-keyword">if</span> word[i] == mask[i] {
                <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
            }
        }
    }

    <span class="hljs-comment">// Check if the word contains the already found (green) letters.</span>
    <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<span class="hljs-title function_ invoke__">min</span>(word.<span class="hljs-title function_ invoke__">len</span>(), found.<span class="hljs-title function_ invoke__">len</span>()) {
        <span class="hljs-keyword">if</span> found[i] != <span class="hljs-string">' '</span> &#x26;&#x26; word[i] != found[i] {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }
    }

    <span class="hljs-literal">true</span>
}
</code></pre>
<p>It ac­cepts a few dif­fer­ent char slices:</p>
<ul>
<li><strong><em>Word</em></strong>: the word we want to check.
Found: this is a slice con­tain­ing the let­ters we have found (the green ones), with<span class="push-double"></span> <span class="pull-double">“</span><span class="push-double"></span> <span class="pull-double">“</span> (space) char­ac­ters in the lo­ca­tions we don’t know the char­ac­ter of.</li>
<li><strong><em>Not</em></strong>: this is a slice con­tain­ing the let­ters we know aren’t in the mys­tery word.</li>
<li><strong><em>Must</em></strong>: this is a slice con­tain­ing the let­ter we know are in the mys­tery word, but we don’t know the po­si­tion of.</li>
<li><strong><em>Masks</em></strong>: this is a se­ries of masks. We re­move every word that has let­ters that match any mask here. This is use­ful for elim­i­nat­ing words in the wordlist that con­tain punc­tu­a­tion and for elim­i­nat­ing words that con­tain or­ange let­ters, but in po­si­tions we know they aren’t.</li>
</ul>
<p>Now all we have to do is run each word in the word list and see if it matches our al­ready known char­ac­ters, up­dat­ing the con­tents of each slice with new in­for­ma­tion af­ter each guess.</p>
<h2>Why You Should Care</h2>
<p>This sounds like a use­less prob­lem. It is. There is no way this will ben­e­ﬁt any­one other than me, and I def­i­nitely won’t use this when I’m ac­tu­ally com­pet­ing with my grand­mother.</p>
<p>Then why did you do it?</p>
<p>Useless an­swers to use­less prob­lems are use­ful. They teach us how to im­prove, with­out the pres­sure of real stakes. They are also just plain fun.</p>
<p>It’s also a re­ﬂec­tion. How would you have ap­proached this prob­lem in the past? How has your think­ing im­proved. Maybe it’s a bit mag­nan­i­mous to say this lit­tle Wordle solver is the key to self re­ﬂec­tion, but I don’t think it’s that far off.</p>
<hr>
<h2>A Reﬂection From Months Later</h2>
<p>Hi! I am re­turn­ing to this pro­ject months later with a few thoughts.</p>
<p>When I ﬁrst wrote this ar­ti­cle, I com­pletely ne­glected to share my ﬁt­ness test for each word. In hind­sight, it’s a good thing I did­n’t. It was the ex­act method 3Blue1Brown de­scribed as<span class="push-double"></span> <span class="pull-double">“</span>naive” in his (fantastic) <a href="https://youtu.be/v68zYyaEmEA">video</a> on this very topic.</p>]]></description>
            <content:encoded><![CDATA[<h1>Build a Wordle Solver Using Rust</h1>
<h2>The Game</h2>
<p><a href="https://www.nytimes.com/games/wordle/index.html">Wordle</a> is a rel­a­tively sim­ple game. If you have ever played Mastermind, it should sound fa­mil­iar. The goal is to ﬁg­ure out a mys­tery word with as few guesses as pos­si­ble. The mys­tery word changes each day. Here are two ex­am­ple guesses.</p>
<img src="/images/wordle_example.png" alt="Two example guesses from the game Wordle">
<p>After a guess, each let­ter’s color changes.</p>
<p>Green — The let­ter is cor­rect.
Yel­low— The let­ter ex­ists in the word, but not in that space.
Gray — The let­ter does not ex­ist in the word.</p>
<p>As you can see, there are a max­i­mum of six guesses. If you can­not ﬁnd the mys­tery word within six guesses, you lose. I have been com­pet­ing with my grand­mother each day to ﬁnd the word in as few guesses as pos­si­ble.</p>
<img src="/images/xkcd_nerd_sniping.png" alt="XKCD Comic #356">
<p>This to­tally nerd-sniped me. I felt an over­whelm­ing urge to build an app that could, con­ceiv­ably ﬁnd the mys­tery word in as few guesses as pos­si­ble.</p>
<h2>How I Did It</h2>
<p>First things ﬁrst, we need a list of English words. I ini­tially used the <a href="http://www.mieliestronk.com/wordlist.html">corn­cob</a> list, but I found greater suc­cess with <a href="https://github.com/dwyl/english-words/">dwyl’s</a> list.</p>
<p>For this pro­ject, I de­cided to use Rust, just be­cause I felt most con­ﬁ­dent in my abil­ity to make an <span class="small-caps">MVP</span> quickly.</p>
<p>Both the word lists I used are for­mat­ted as a se­quence of in­di­vid­ual words, sep­a­rated by <code>\n</code> char­ac­ters. On Windows (which is what I am us­ing), they also have pesky those lit­tle <code>\r</code> char­ac­ters.</p>
<p>Wordle is heav­ily fo­cused on let­ters. I can re­move items from the word list based on what let­ters I know aren’t in the mys­tery word (these are gray let­ters in-game), and I can re­move items based what let­ters I know are in the mys­tery word (the or­ange or green let­ters), but in a lot of cases that still leaves a lot of pos­si­ble words. I need to way to sort words based on how likely their let­ters are.</p>
<p>To do this, I count how fre­quently each let­ter ap­pears in the word list, and give each word a score based on how fre­quently its com­po­nents ap­pear.</p>
<p>The ﬁrst step in the pro­gram is to load the word list and count the let­ters:</p>
<pre><code class="hljs language-rust"><span class="hljs-comment">// Store the total number of times a letter appears.</span>
<span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">letter_scores</span> = HashMap::<span class="hljs-title function_ invoke__">new</span>();
<span class="hljs-comment">// The final list of words. It will make like easier later in the program to store the words as Vec&#x3C;char>.</span>
<span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">word_list</span> = <span class="hljs-type">Vec</span>::<span class="hljs-title function_ invoke__">new</span>();
<span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">last_word</span> = <span class="hljs-type">Vec</span>::<span class="hljs-title function_ invoke__">new</span>();
<span class="hljs-keyword">let</span> <span class="hljs-variable">file</span> = std::fs::<span class="hljs-title function_ invoke__">read</span>(<span class="hljs-string">"corncob_lowercase.txt"</span>)?;

<span class="hljs-comment">// Iterate through all the bytes in the wordlist file, ignoring all `\r` instances.</span>
<span class="hljs-keyword">for</span> <span class="hljs-variable">letter</span> <span class="hljs-keyword">in</span> file {
    <span class="hljs-keyword">let</span> <span class="hljs-variable">letter</span> = letter <span class="hljs-keyword">as</span> <span class="hljs-type">char</span>;
    <span class="hljs-keyword">match</span> letter {
        <span class="hljs-string">'<span class="hljs-char escape_">\n</span>'</span> => {
            word_list.<span class="hljs-title function_ invoke__">push</span>(last_word);
            last_word = <span class="hljs-type">Vec</span>::<span class="hljs-title function_ invoke__">new</span>();
        }
        <span class="hljs-string">'<span class="hljs-char escape_">\r</span>'</span> => (),
        _ => {
            <span class="hljs-keyword">let</span> <span class="hljs-variable">entry</span> = letter_scores.<span class="hljs-title function_ invoke__">entry</span>(letter).<span class="hljs-title function_ invoke__">or_default</span>();
            *entry += <span class="hljs-number">1</span>;
            last_word.<span class="hljs-title function_ invoke__">push</span>(letter);
        }
    }
}
</code></pre>
<p>Using the de­fault <code>HashMap</code> (which uses <code>SipHash</code>, which is­n’t great for sin­gle-char­ac­ter lookup), prob­a­bly is­n’t the best, per­for­mance-wise, but this is just a toy pro­gram, and does­n’t need to be the fastest thing in the world.</p>
<p>Next, we need to go through the word list, and elim­i­nate words that con­tain gray let­ters. Here is a func­tion that helps do that:</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">matches_found</span>(
    word: &#x26;[<span class="hljs-type">char</span>],
    found: &#x26;[<span class="hljs-type">char</span>],
    not: &#x26;[<span class="hljs-type">char</span>],
    must: &#x26;[<span class="hljs-type">char</span>],
    masks: &#x26;[<span class="hljs-type">Vec</span>&#x3C;<span class="hljs-type">char</span>>],
) <span class="hljs-punctuation">-></span> <span class="hljs-type">bool</span> {
    <span class="hljs-comment">// Check if the word contains a letter we know *isn't* in the mystery. &#x3C;-- The gray letters.</span>
    <span class="hljs-keyword">for</span> <span class="hljs-variable">c</span> <span class="hljs-keyword">in</span> not {
        <span class="hljs-keyword">if</span> word.<span class="hljs-title function_ invoke__">contains</span>(c) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }
    }

    <span class="hljs-comment">// Check if the word contains the letters we don't know the positions of, but know they are in the mystery word.. &#x3C;-- The orange letters.</span>
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">found_letters</span> = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">for</span> <span class="hljs-variable">c</span> <span class="hljs-keyword">in</span> must {
        <span class="hljs-keyword">if</span> word.<span class="hljs-title function_ invoke__">contains</span>(c) {
            found_letters += <span class="hljs-number">1</span>;
        }
    }
    <span class="hljs-keyword">if</span> found_letters &#x3C; must.<span class="hljs-title function_ invoke__">len</span>() {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }

    <span class="hljs-comment">// Check if the word has letters we know exist in the word, but not at the right spots. &#x3C;-- The orange letters.</span>
    <span class="hljs-keyword">for</span> <span class="hljs-variable">mask</span> <span class="hljs-keyword">in</span> masks {
        <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<span class="hljs-title function_ invoke__">min</span>(word.<span class="hljs-title function_ invoke__">len</span>(), mask.<span class="hljs-title function_ invoke__">len</span>()) {
            <span class="hljs-keyword">if</span> word[i] == mask[i] {
                <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
            }
        }
    }

    <span class="hljs-comment">// Check if the word contains the already found (green) letters.</span>
    <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<span class="hljs-title function_ invoke__">min</span>(word.<span class="hljs-title function_ invoke__">len</span>(), found.<span class="hljs-title function_ invoke__">len</span>()) {
        <span class="hljs-keyword">if</span> found[i] != <span class="hljs-string">' '</span> &#x26;&#x26; word[i] != found[i] {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }
    }

    <span class="hljs-literal">true</span>
}
</code></pre>
<p>It ac­cepts a few dif­fer­ent char slices:</p>
<ul>
<li><strong><em>Word</em></strong>: the word we want to check.
Found: this is a slice con­tain­ing the let­ters we have found (the green ones), with<span class="push-double"></span> <span class="pull-double">“</span><span class="push-double"></span> <span class="pull-double">“</span> (space) char­ac­ters in the lo­ca­tions we don’t know the char­ac­ter of.</li>
<li><strong><em>Not</em></strong>: this is a slice con­tain­ing the let­ters we know aren’t in the mys­tery word.</li>
<li><strong><em>Must</em></strong>: this is a slice con­tain­ing the let­ter we know are in the mys­tery word, but we don’t know the po­si­tion of.</li>
<li><strong><em>Masks</em></strong>: this is a se­ries of masks. We re­move every word that has let­ters that match any mask here. This is use­ful for elim­i­nat­ing words in the wordlist that con­tain punc­tu­a­tion and for elim­i­nat­ing words that con­tain or­ange let­ters, but in po­si­tions we know they aren’t.</li>
</ul>
<p>Now all we have to do is run each word in the word list and see if it matches our al­ready known char­ac­ters, up­dat­ing the con­tents of each slice with new in­for­ma­tion af­ter each guess.</p>
<h2>Why You Should Care</h2>
<p>This sounds like a use­less prob­lem. It is. There is no way this will ben­e­ﬁt any­one other than me, and I def­i­nitely won’t use this when I’m ac­tu­ally com­pet­ing with my grand­mother.</p>
<p>Then why did you do it?</p>
<p>Useless an­swers to use­less prob­lems are use­ful. They teach us how to im­prove, with­out the pres­sure of real stakes. They are also just plain fun.</p>
<p>It’s also a re­ﬂec­tion. How would you have ap­proached this prob­lem in the past? How has your think­ing im­proved. Maybe it’s a bit mag­nan­i­mous to say this lit­tle Wordle solver is the key to self re­ﬂec­tion, but I don’t think it’s that far off.</p>
<hr>
<h2>A Reﬂection From Months Later</h2>
<p>Hi! I am re­turn­ing to this pro­ject months later with a few thoughts.</p>
<p>When I ﬁrst wrote this ar­ti­cle, I com­pletely ne­glected to share my ﬁt­ness test for each word. In hind­sight, it’s a good thing I did­n’t. It was the ex­act method 3Blue1Brown de­scribed as<span class="push-double"></span> <span class="pull-double">“</span>naive” in his (fantastic) <a href="https://youtu.be/v68zYyaEmEA">video</a> on this very topic.</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/wordle_example.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Why Rust May Be More Attractive than JavaScript]]></title>
            <link>https://elijahpotter.dev/articles/why_rust_may_be_more_attractive_than_javascript</link>
            <guid>https://elijahpotter.dev/articles/why_rust_may_be_more_attractive_than_javascript</guid>
            <pubDate>Fri, 25 Feb 2022 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1>Why Rust Might Attract More Developers Than Java and JavaScript</h1>
<p>Rust is un­de­ni­ably a greatly ap­pre­ci­ated lan­guage, af­ter all, it has placed as<span class="push-double"></span> <span class="pull-double">“</span>#1 Most Loved Programming Language” on the StackOverﬂow Developer Survey for 6th year run­ning.</p>
<p>Reading this, you are prob­a­bly very aware of why so many peo­ple like it, but I will be go­ing over those rea­sons any­way, at least be­fore I re­veal its se­cret weapon.</p>
<p><strong>It’s fast, very fast</strong>. While it may not be a great bench­mark, The Computer Language Benchmarks Game, mea­sures that it falls just a hair be­hind the fastest lan­guage, <strong>C</strong>.</p>
<p>That makes sense, given that Rust was meant to re­place C/C++. Even bet­ter than the bench­mark, it has been shown that in many sit­u­a­tions, the Rust com­piler is able to op­ti­mize code sig­nif­i­cantly bet­ter than C or C++, just be­cause it has more in­for­ma­tion about the de­vel­op­ers in­tent.</p>
<p><strong>It’s con­sis­tent</strong>. For more than pretty much any other lan­guage, Rust de­vel­op­ers can ex­pect that, if their code com­piles, it works. When it does­n’t com­pile, the com­piler gives bet­ter feed­back and ad­vice than any other lan­guage. It can even check if your arith­metic is wrong.</p>
<p><strong>It’s com­pli­cated</strong>, but only where it needs to be. The lan­guage it­self, as well as its stan­dard li­brary are built very con­sciously, by its users. Most places where you might ﬁnd you have to write te­dious boil­er­plate, Rust makes it easy. It also gives you the free­dom to write code that does ex­actly what you want, ex­actly how you want. We will get back to this later.</p>
<h2>The Problem</h2>
<p>When start­ing a new pro­ject what is the ﬁrst thing you are go­ing to do? It de­pends on what stack you are us­ing.</p>
<p>If you want to use NodeJS, it might look some­thing like:</p>
<pre><code class="hljs language-bash"><span class="hljs-built_in">mkdir</span> new_project
<span class="hljs-built_in">touch</span> index.js
npm install
</code></pre>
<p>That is just the bare min­i­mum. If you want to add any de­pen­den­cies, you have to go through an un­rea­son­able num­ber of hoops, and the stan­dard pack­age repos­i­tory, npm, is known for be­ing quite in­se­cure, not to men­tion it be­ing con­trolled by a pri­vate cor­po­ra­tion.</p>
<p>If you want to use Java, you have to use some kind of tem­plate, oth­er­wise it’s im­pos­si­ble to cre­ate a pro­ject from scratch. Even then, you have to de­cide be­tween two or three pack­age man­agers, and deal with it when li­braries don’t sup­port your choice.</p>
<p>If you want to use C or C++ for­get it. As a be­gin­ner, you are go­ing to be stuck ﬁg­ur­ing out how to use the lo­cal linker and com­piler to get any li­brary to work.</p>
<h2>The Rusty Solution</h2>
<p><strong>Cargo</strong> is one of the sim­plest, eas­i­est to use build tools out there. It does every­thing you might need, and it may be why Rust may soon be­come a ma­jor player. 99% of the time, you can just copy and paste a given li­brary’s string di­rectly from <a href="https://crates.io">crates.io</a>, which is a repos­i­tory owned and op­er­ated by the Rust Foundation, and you are off to the races.</p>
<p>Testing is easy too, just run <code>cargo test</code>, and you’re done. It cov­ers both unit tests, and in­te­gra­tion tests if you need it.</p>
<p>`cargo doc`` pro­vides a con­sis­tent, and easy way to doc­u­ment code. It’s also great for users, al­low­ing de­vel­op­ers ac­cess to a straight­for­ward rep­re­sen­ta­tion of what a li­brary looks like.</p>
<p><strong>It’s ex­ten­si­ble</strong>, some of the great­est bi­nary crates out there ex­ist to ex­tend the func­tion­al­ity. So, in the few ar­eas where <code>cargo</code> on its own does­n’t cut it, there are ad­di­tional crates to ﬁll the gaps.</p>
<h2>Closing</h2>
<p>At least for the mo­ment, of all the great rea­sons to use Rust, <code>cargo</code> will be the most sig­nif­i­cant rea­son it will in­crease in pop­u­lar­ity.</p>
<img src="/images/most_loved_stackoverflow_2021.png" alt="Most loved programming languages, according to the 2021 StackOverflow Developer Survey">
<p>Rust can be tough to learn, es­pe­cially for peo­ple who are used to dy­namic lan­guages like JavaScript. But what Rust has beat for most lan­guages is the build sys­tem. <code>cargo</code> alone makes Rust more ap­proach­able than C/C++, Java and many oth­ers.</p>
<p>More and more new de­vel­op­ers will be go­ing to Rust in­stead of those other lan­guages, be­cause it is eas­ier to start with, eas­ier to learn, and has a great com­mu­nity to re­quest help from.</p>]]></description>
            <content:encoded><![CDATA[<h1>Why Rust Might Attract More Developers Than Java and JavaScript</h1>
<p>Rust is un­de­ni­ably a greatly ap­pre­ci­ated lan­guage, af­ter all, it has placed as<span class="push-double"></span> <span class="pull-double">“</span>#1 Most Loved Programming Language” on the StackOverﬂow Developer Survey for 6th year run­ning.</p>
<p>Reading this, you are prob­a­bly very aware of why so many peo­ple like it, but I will be go­ing over those rea­sons any­way, at least be­fore I re­veal its se­cret weapon.</p>
<p><strong>It’s fast, very fast</strong>. While it may not be a great bench­mark, The Computer Language Benchmarks Game, mea­sures that it falls just a hair be­hind the fastest lan­guage, <strong>C</strong>.</p>
<p>That makes sense, given that Rust was meant to re­place C/C++. Even bet­ter than the bench­mark, it has been shown that in many sit­u­a­tions, the Rust com­piler is able to op­ti­mize code sig­nif­i­cantly bet­ter than C or C++, just be­cause it has more in­for­ma­tion about the de­vel­op­ers in­tent.</p>
<p><strong>It’s con­sis­tent</strong>. For more than pretty much any other lan­guage, Rust de­vel­op­ers can ex­pect that, if their code com­piles, it works. When it does­n’t com­pile, the com­piler gives bet­ter feed­back and ad­vice than any other lan­guage. It can even check if your arith­metic is wrong.</p>
<p><strong>It’s com­pli­cated</strong>, but only where it needs to be. The lan­guage it­self, as well as its stan­dard li­brary are built very con­sciously, by its users. Most places where you might ﬁnd you have to write te­dious boil­er­plate, Rust makes it easy. It also gives you the free­dom to write code that does ex­actly what you want, ex­actly how you want. We will get back to this later.</p>
<h2>The Problem</h2>
<p>When start­ing a new pro­ject what is the ﬁrst thing you are go­ing to do? It de­pends on what stack you are us­ing.</p>
<p>If you want to use NodeJS, it might look some­thing like:</p>
<pre><code class="hljs language-bash"><span class="hljs-built_in">mkdir</span> new_project
<span class="hljs-built_in">touch</span> index.js
npm install
</code></pre>
<p>That is just the bare min­i­mum. If you want to add any de­pen­den­cies, you have to go through an un­rea­son­able num­ber of hoops, and the stan­dard pack­age repos­i­tory, npm, is known for be­ing quite in­se­cure, not to men­tion it be­ing con­trolled by a pri­vate cor­po­ra­tion.</p>
<p>If you want to use Java, you have to use some kind of tem­plate, oth­er­wise it’s im­pos­si­ble to cre­ate a pro­ject from scratch. Even then, you have to de­cide be­tween two or three pack­age man­agers, and deal with it when li­braries don’t sup­port your choice.</p>
<p>If you want to use C or C++ for­get it. As a be­gin­ner, you are go­ing to be stuck ﬁg­ur­ing out how to use the lo­cal linker and com­piler to get any li­brary to work.</p>
<h2>The Rusty Solution</h2>
<p><strong>Cargo</strong> is one of the sim­plest, eas­i­est to use build tools out there. It does every­thing you might need, and it may be why Rust may soon be­come a ma­jor player. 99% of the time, you can just copy and paste a given li­brary’s string di­rectly from <a href="https://crates.io">crates.io</a>, which is a repos­i­tory owned and op­er­ated by the Rust Foundation, and you are off to the races.</p>
<p>Testing is easy too, just run <code>cargo test</code>, and you’re done. It cov­ers both unit tests, and in­te­gra­tion tests if you need it.</p>
<p>`cargo doc`` pro­vides a con­sis­tent, and easy way to doc­u­ment code. It’s also great for users, al­low­ing de­vel­op­ers ac­cess to a straight­for­ward rep­re­sen­ta­tion of what a li­brary looks like.</p>
<p><strong>It’s ex­ten­si­ble</strong>, some of the great­est bi­nary crates out there ex­ist to ex­tend the func­tion­al­ity. So, in the few ar­eas where <code>cargo</code> on its own does­n’t cut it, there are ad­di­tional crates to ﬁll the gaps.</p>
<h2>Closing</h2>
<p>At least for the mo­ment, of all the great rea­sons to use Rust, <code>cargo</code> will be the most sig­nif­i­cant rea­son it will in­crease in pop­u­lar­ity.</p>
<img src="/images/most_loved_stackoverflow_2021.png" alt="Most loved programming languages, according to the 2021 StackOverflow Developer Survey">
<p>Rust can be tough to learn, es­pe­cially for peo­ple who are used to dy­namic lan­guages like JavaScript. But what Rust has beat for most lan­guages is the build sys­tem. <code>cargo</code> alone makes Rust more ap­proach­able than C/C++, Java and many oth­ers.</p>
<p>More and more new de­vel­op­ers will be go­ing to Rust in­stead of those other lan­guages, be­cause it is eas­ier to start with, eas­ier to learn, and has a great com­mu­nity to re­quest help from.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Write a Discord Bot in Rust]]></title>
            <link>https://elijahpotter.dev/articles/how_to_write_a_discord_bot_in_rust</link>
            <guid>https://elijahpotter.dev/articles/how_to_write_a_discord_bot_in_rust</guid>
            <pubDate>Wed, 02 Jun 2021 06:00:00 GMT</pubDate>
            <description><![CDATA[<h1>How to Write a Discord Bot in Rust</h1>
<p>Discord is an in­stant mes­sage plat­form with more than 150 mil­lion monthly ac­tive users. The main ap­peal seems to a com­bi­na­tion of the wide reach that plat­forms like StackOverﬂow have, as well as the in­stant de­liv­ery of the in­stant mes­sag­ing con­cept. One of the best fea­tures of Discord is it’s seem­ingly un­lim­ited hack­a­bil­ity of the plat­form via it’s<span class="push-double"></span> <span class="pull-double">“</span>Bot” sys­tem. The Bot sys­tem al­lows de­vel­op­ers to add func­tion­al­ity to Discord com­mu­ni­ties by writ­ing soft­ware that in­ter­faces in a sim­i­lar way that peo­ple do. I want to give you an in­tro­duc­tion on how to do that in Rust.</p>
<img src="/images/flat_ferris.png" alt="A Flat Render of Rust&#x27;s Mascot, Ferris">
<h2>Interaction</h2>
<p>Most in­ter­ac­tion with Discord bots hap­pens via com­mands, not dis­sim­i­lar to ter­mi­nal ap­pli­ca­tions. Commands may look like <code>!play Eat it by Weird Al</code>.</p>
<img src="/images/ping_pong.png" alt="Ping! Pong!">
<p>The bot we are go­ing to make now will sim­ply re­spond to <code>!ping</code> with<span class="push-double"></span> <span class="pull-double">“</span>Pong!”.</p>
<h2>Template</h2>
<img src="https://github.com/elijah-potter/discord-bot-template" alt="cargo generator template">
<h2>Setup the Project</h2>
<p>Using a func­tion­ing Rust en­vi­ron­ment, use cargo to cre­ate a new pro­ject. E.g:</p>
<pre><code class="hljs language-bash">cargo new tutorial-bot
</code></pre>
<p>Next, we have to add <a href="https://github.com/serenity-rs/serenity">Serenity</a>, the li­brary for cre­at­ing Discord bots in Rust. We also have to drop in Tokio, be­cause Serenity takes ad­van­tage of it’s async run­time.</p>
<p>You can do this ei­ther via cargo-edit:</p>
<pre><code class="hljs language-bash">cargo add serenity
cargo add tokio --features full
</code></pre>
<p>or by just adding them to Cargo.toml:</p>
<pre><code class="hljs language-toml">[dependencies]
serenity = "0.10.5"
tokio = { version = "1.5.0", features = ["full"] }
</code></pre>
<h2>Setting up the Standard Framework</h2>
<p>Serenity has a lot of ﬂex­i­bil­ity. You have ac­cess to a event han­dler that al­lows ﬁne grain con­trol of events. You also have ac­cess to a stan­dard frame­work that makes it ridicu­lously easy to re­spond to com­mands.
Be­fore we do any­thing else, we have to make our main func­tion async. It is su­per easy to do that, just re­place it with:</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[tokio::main]</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {
}
</code></pre>
<p>First, we want to get our bot to­ken in. In an ac­tual bot, please ob­tain it via an en­vi­ron­ment vari­able or some other method. We are only do­ing it this way for sim­plic­ity.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">let</span> <span class="hljs-variable">token</span> = <span class="hljs-string">"{your bot token}"</span>;
</code></pre>
<p>If you do not know how to get a bot to­ken, please fol­low <a href="https://www.getdroidtips.com/discord-bot-token/">this tu­to­r­ial</a>.</p>
<p>The Serenity Standard Framework splits your bot’s com­mands into groups. Each group can have mul­ti­ple com­mands. For ex­am­ple, a bot might have two groups: one fo­cused on fun and one fo­cused on math. The for­mer has com­mands like !meme, while the lat­ter may have var­i­ous math func­tions, like sin!. This is also how we will add com­mands to our bot.</p>
<p>First, add the needed structs and macros to the ﬁle:</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">use</span> serenity::{Client, client::Context, framework::{StandardFramework, standard::{CommandResult, macros::{group, command}}}, model::channel::Message};
</code></pre>
<p>Next, cre­ate a struct that we will at­tach our com­mands to:</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[group]</span>
<span class="hljs-meta">#[commands()]</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">HelloWorld</span>;
</code></pre>
<p>Once we have added our com­mands, we will en­ter them into the com­mands sub-macro.</p>
<p>Create an in­stance of <code>StandardFramework</code> and add our group to it. We can also con­ﬁg­ure our com­mand pre­ﬁx now.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">let</span> <span class="hljs-variable">framework</span> = StandardFramework::<span class="hljs-title function_ invoke__">new</span>()
    .<span class="hljs-title function_ invoke__">configure</span>(|c|{
        c.<span class="hljs-title function_ invoke__">prefix</span>(<span class="hljs-string">"!"</span>)
    })
    .<span class="hljs-title function_ invoke__">group</span>(&#x26;HELLOWORLD_GROUP);
</code></pre>
<p>Notice that we used a ref­er­ence to a sta­tic struct called HELLOWORLD_GROUP in­stead of just adding our group. This is the out­put of the #[group] macro.</p>
<p>Now that we have cre­ated our frame­work, we have to at­tach it to a Discord client.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">client</span> = Client::<span class="hljs-title function_ invoke__">builder</span>(token).<span class="hljs-title function_ invoke__">framework</span>(framework).<span class="hljs-keyword">await</span>.<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Could not start Discord"</span>);
</code></pre>
<p>Start it.</p>
<pre><code class="hljs language-rust">client.<span class="hljs-title function_ invoke__">start</span>().<span class="hljs-keyword">await</span>.<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"The bot stopped"</span>);
</code></pre>
<h2>Adding the Command</h2>
<p>Now that we have the frame­work set up, let’s add a com­mand.</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[command]</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">ping</span>(ctx: &#x26;Context, msg: &#x26;Message) <span class="hljs-punctuation">-></span> CommandResult{
}
</code></pre>
<p>This com­mand only needs the Discord clien­t’s Context, the mes­sage that con­tains the com­mand, and re­turns a <code>CommandResult</code>.
 Do not for­get to add the com­mand to the group:</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[group]</span>
<span class="hljs-meta">#[commands(ping)]</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">HelloWorld</span>;
</code></pre>
<p>If you don’t, it will sim­ply not get run.</p>
<p>We want the bot to re­ply to the !ping com­mand with<span class="push-double"></span> <span class="pull-double">“</span>Pong!”, so let’s add that to the in­side of the ping func­tion:</p>
<pre><code class="hljs language-rust">msg.<span class="hljs-title function_ invoke__">reply</span>(ctx, <span class="hljs-string">"Pong!"</span>).<span class="hljs-keyword">await</span>?;
<span class="hljs-title function_ invoke__">Ok</span>(())
</code></pre>
<h2>Full Code</h2>
<p>Here is the full code for the bot:</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">use</span> serenity::{Client, client::Context, framework::{StandardFramework, standard::{CommandResult, macros::{group, command}}}, model::channel::Message};
<span class="hljs-meta">#[tokio::main]</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {
    <span class="hljs-keyword">let</span> <span class="hljs-variable">token</span> = <span class="hljs-string">"Your bot token"</span>;
    <span class="hljs-keyword">let</span> <span class="hljs-variable">framework</span> = StandardFramework::<span class="hljs-title function_ invoke__">new</span>()
    .<span class="hljs-title function_ invoke__">configure</span>(|c|{
        c.<span class="hljs-title function_ invoke__">prefix</span>(<span class="hljs-string">"!"</span>)
    })
    .<span class="hljs-title function_ invoke__">group</span>(&#x26;HELLOWORLD_GROUP);
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">client</span> = Client::<span class="hljs-title function_ invoke__">builder</span>(token).<span class="hljs-title function_ invoke__">framework</span>(framework).<span class="hljs-keyword">await</span>.<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Could not start Discord"</span>);
    client.<span class="hljs-title function_ invoke__">start</span>().<span class="hljs-keyword">await</span>.<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"The bot stopped"</span>);
}
<span class="hljs-meta">#[group]</span>
<span class="hljs-meta">#[commands(ping)]</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">HelloWorld</span>;
<span class="hljs-meta">#[command]</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">ping</span>(ctx: &#x26;Context, msg: &#x26;Message) <span class="hljs-punctuation">-></span> CommandResult{
    msg.<span class="hljs-title function_ invoke__">reply</span>(ctx, <span class="hljs-string">"Pong!"</span>).<span class="hljs-keyword">await</span>?;
    <span class="hljs-title function_ invoke__">Ok</span>(())
}
</code></pre>
<h2>That’s it</h2>
<p>If you build and run your app, you should have a func­tion­ing Discord bot!
Seren­ity is an amaz­ing crate and is an ab­solute joy to work with. I hope you learned some­thing. There is a ton more stuff that I did not cover here. Feel free to look at the <a href="https://docs.rs/serenity/latest/serenity/">Serenity docs</a> and ex­am­ples to learn more!</p>]]></description>
            <content:encoded><![CDATA[<h1>How to Write a Discord Bot in Rust</h1>
<p>Discord is an in­stant mes­sage plat­form with more than 150 mil­lion monthly ac­tive users. The main ap­peal seems to a com­bi­na­tion of the wide reach that plat­forms like StackOverﬂow have, as well as the in­stant de­liv­ery of the in­stant mes­sag­ing con­cept. One of the best fea­tures of Discord is it’s seem­ingly un­lim­ited hack­a­bil­ity of the plat­form via it’s<span class="push-double"></span> <span class="pull-double">“</span>Bot” sys­tem. The Bot sys­tem al­lows de­vel­op­ers to add func­tion­al­ity to Discord com­mu­ni­ties by writ­ing soft­ware that in­ter­faces in a sim­i­lar way that peo­ple do. I want to give you an in­tro­duc­tion on how to do that in Rust.</p>
<img src="/images/flat_ferris.png" alt="A Flat Render of Rust&#x27;s Mascot, Ferris">
<h2>Interaction</h2>
<p>Most in­ter­ac­tion with Discord bots hap­pens via com­mands, not dis­sim­i­lar to ter­mi­nal ap­pli­ca­tions. Commands may look like <code>!play Eat it by Weird Al</code>.</p>
<img src="/images/ping_pong.png" alt="Ping! Pong!">
<p>The bot we are go­ing to make now will sim­ply re­spond to <code>!ping</code> with<span class="push-double"></span> <span class="pull-double">“</span>Pong!”.</p>
<h2>Template</h2>
<img src="https://github.com/elijah-potter/discord-bot-template" alt="cargo generator template">
<h2>Setup the Project</h2>
<p>Using a func­tion­ing Rust en­vi­ron­ment, use cargo to cre­ate a new pro­ject. E.g:</p>
<pre><code class="hljs language-bash">cargo new tutorial-bot
</code></pre>
<p>Next, we have to add <a href="https://github.com/serenity-rs/serenity">Serenity</a>, the li­brary for cre­at­ing Discord bots in Rust. We also have to drop in Tokio, be­cause Serenity takes ad­van­tage of it’s async run­time.</p>
<p>You can do this ei­ther via cargo-edit:</p>
<pre><code class="hljs language-bash">cargo add serenity
cargo add tokio --features full
</code></pre>
<p>or by just adding them to Cargo.toml:</p>
<pre><code class="hljs language-toml">[dependencies]
serenity = "0.10.5"
tokio = { version = "1.5.0", features = ["full"] }
</code></pre>
<h2>Setting up the Standard Framework</h2>
<p>Serenity has a lot of ﬂex­i­bil­ity. You have ac­cess to a event han­dler that al­lows ﬁne grain con­trol of events. You also have ac­cess to a stan­dard frame­work that makes it ridicu­lously easy to re­spond to com­mands.
Be­fore we do any­thing else, we have to make our main func­tion async. It is su­per easy to do that, just re­place it with:</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[tokio::main]</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {
}
</code></pre>
<p>First, we want to get our bot to­ken in. In an ac­tual bot, please ob­tain it via an en­vi­ron­ment vari­able or some other method. We are only do­ing it this way for sim­plic­ity.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">let</span> <span class="hljs-variable">token</span> = <span class="hljs-string">"{your bot token}"</span>;
</code></pre>
<p>If you do not know how to get a bot to­ken, please fol­low <a href="https://www.getdroidtips.com/discord-bot-token/">this tu­to­r­ial</a>.</p>
<p>The Serenity Standard Framework splits your bot’s com­mands into groups. Each group can have mul­ti­ple com­mands. For ex­am­ple, a bot might have two groups: one fo­cused on fun and one fo­cused on math. The for­mer has com­mands like !meme, while the lat­ter may have var­i­ous math func­tions, like sin!. This is also how we will add com­mands to our bot.</p>
<p>First, add the needed structs and macros to the ﬁle:</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">use</span> serenity::{Client, client::Context, framework::{StandardFramework, standard::{CommandResult, macros::{group, command}}}, model::channel::Message};
</code></pre>
<p>Next, cre­ate a struct that we will at­tach our com­mands to:</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[group]</span>
<span class="hljs-meta">#[commands()]</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">HelloWorld</span>;
</code></pre>
<p>Once we have added our com­mands, we will en­ter them into the com­mands sub-macro.</p>
<p>Create an in­stance of <code>StandardFramework</code> and add our group to it. We can also con­ﬁg­ure our com­mand pre­ﬁx now.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">let</span> <span class="hljs-variable">framework</span> = StandardFramework::<span class="hljs-title function_ invoke__">new</span>()
    .<span class="hljs-title function_ invoke__">configure</span>(|c|{
        c.<span class="hljs-title function_ invoke__">prefix</span>(<span class="hljs-string">"!"</span>)
    })
    .<span class="hljs-title function_ invoke__">group</span>(&#x26;HELLOWORLD_GROUP);
</code></pre>
<p>Notice that we used a ref­er­ence to a sta­tic struct called HELLOWORLD_GROUP in­stead of just adding our group. This is the out­put of the #[group] macro.</p>
<p>Now that we have cre­ated our frame­work, we have to at­tach it to a Discord client.</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">client</span> = Client::<span class="hljs-title function_ invoke__">builder</span>(token).<span class="hljs-title function_ invoke__">framework</span>(framework).<span class="hljs-keyword">await</span>.<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Could not start Discord"</span>);
</code></pre>
<p>Start it.</p>
<pre><code class="hljs language-rust">client.<span class="hljs-title function_ invoke__">start</span>().<span class="hljs-keyword">await</span>.<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"The bot stopped"</span>);
</code></pre>
<h2>Adding the Command</h2>
<p>Now that we have the frame­work set up, let’s add a com­mand.</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[command]</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">ping</span>(ctx: &#x26;Context, msg: &#x26;Message) <span class="hljs-punctuation">-></span> CommandResult{
}
</code></pre>
<p>This com­mand only needs the Discord clien­t’s Context, the mes­sage that con­tains the com­mand, and re­turns a <code>CommandResult</code>.
 Do not for­get to add the com­mand to the group:</p>
<pre><code class="hljs language-rust"><span class="hljs-meta">#[group]</span>
<span class="hljs-meta">#[commands(ping)]</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">HelloWorld</span>;
</code></pre>
<p>If you don’t, it will sim­ply not get run.</p>
<p>We want the bot to re­ply to the !ping com­mand with<span class="push-double"></span> <span class="pull-double">“</span>Pong!”, so let’s add that to the in­side of the ping func­tion:</p>
<pre><code class="hljs language-rust">msg.<span class="hljs-title function_ invoke__">reply</span>(ctx, <span class="hljs-string">"Pong!"</span>).<span class="hljs-keyword">await</span>?;
<span class="hljs-title function_ invoke__">Ok</span>(())
</code></pre>
<h2>Full Code</h2>
<p>Here is the full code for the bot:</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">use</span> serenity::{Client, client::Context, framework::{StandardFramework, standard::{CommandResult, macros::{group, command}}}, model::channel::Message};
<span class="hljs-meta">#[tokio::main]</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {
    <span class="hljs-keyword">let</span> <span class="hljs-variable">token</span> = <span class="hljs-string">"Your bot token"</span>;
    <span class="hljs-keyword">let</span> <span class="hljs-variable">framework</span> = StandardFramework::<span class="hljs-title function_ invoke__">new</span>()
    .<span class="hljs-title function_ invoke__">configure</span>(|c|{
        c.<span class="hljs-title function_ invoke__">prefix</span>(<span class="hljs-string">"!"</span>)
    })
    .<span class="hljs-title function_ invoke__">group</span>(&#x26;HELLOWORLD_GROUP);
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">client</span> = Client::<span class="hljs-title function_ invoke__">builder</span>(token).<span class="hljs-title function_ invoke__">framework</span>(framework).<span class="hljs-keyword">await</span>.<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Could not start Discord"</span>);
    client.<span class="hljs-title function_ invoke__">start</span>().<span class="hljs-keyword">await</span>.<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"The bot stopped"</span>);
}
<span class="hljs-meta">#[group]</span>
<span class="hljs-meta">#[commands(ping)]</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">HelloWorld</span>;
<span class="hljs-meta">#[command]</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">ping</span>(ctx: &#x26;Context, msg: &#x26;Message) <span class="hljs-punctuation">-></span> CommandResult{
    msg.<span class="hljs-title function_ invoke__">reply</span>(ctx, <span class="hljs-string">"Pong!"</span>).<span class="hljs-keyword">await</span>?;
    <span class="hljs-title function_ invoke__">Ok</span>(())
}
</code></pre>
<h2>That’s it</h2>
<p>If you build and run your app, you should have a func­tion­ing Discord bot!
Seren­ity is an amaz­ing crate and is an ab­solute joy to work with. I hope you learned some­thing. There is a ton more stuff that I did not cover here. Feel free to look at the <a href="https://docs.rs/serenity/latest/serenity/">Serenity docs</a> and ex­am­ples to learn more!</p>]]></content:encoded>
            <enclosure url="https://elijahpotter.dev/images/flat_ferris.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>