Unplanned ObsolescenceA blog about trying to write software that doesn't rotZola2026-03-13T00:00:00+00:00https://unplannedobsolescence.com/atom.xmlXML is a Cheap DSL2026-03-13T00:00:00+00:002026-03-13T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/blog/xml-cheap-dsl/<p>Yesterday, the IRS <a rel="external" href="https://www.irs.gov/newsroom/updated-tax-withholding-estimator-lets-millions-of-taxpayers-take-one-big-beautiful-bill-changes-into-account-when-calculating-their-withholding">announced the release</a> of the project I’ve been engineering leading since this summer, its new <a rel="external" href="https://www.irs.gov/individuals/tax-withholding-estimator">Tax Withholding Estimator</a> (TWE).
Taxpayers enter in their income, expected deductions, and other relevant info to estimate what they’ll owe in taxes at the end of the year, and adjust the withholdings on their paycheck.
It’s free, open source, and, in a major first for the IRS, <a rel="external" href="https://github.com/IRS-Public/tax-withholding-estimator">open for public contributions</a>.</p>
<p>TWE is full of exciting learnings about the field of public sector software. Being me, I’m going to start by writing about by far the driest one: XML.</p>
<p>(I am writing this in my personal capacity, based on the open source release, not in my position as a federal employee.)</p>
<p>XML is widely considered clunky at best, obsolete at worst.
It evokes memories of SOAP configs and J2EE (it’s fine, even good, if those acronyms don’t mean anything to you).
My experience with the Tax Withholding Estimator, however, has taught me that XML absolutely has a place in modern software development, and it should be considered a leading option for any cross-platform declarative specification.</p>
<h2 id="the-fact-graph"><a class="zola-anchor" href="#the-fact-graph" aria-label="Anchor link for: the-fact-graph">The Fact Graph</a></h2>
<p>TWE is a static site generated from <em>two</em> XML configurations.
The first of these configs is the Fact Dictionary, our representation of the US Tax Code; the second will be the subject of a later blog post.</p>
<p>We use the Fact Graph, a logic engine, to calculate the taxpayer’s tax obligations (and their withholdings) based on the facts defined in the Fact Dictionary.
The Fact Graph was originally built for <a rel="external" href="https://github.com/IRS-Public/direct-file/">IRS Direct File</a> and now we use it for TWE.
I’m going to introduce you to the Fact Graph the way that I was introduced to it: by <s>fire</s> example.</p>
<p>Put aside any preconceptions you might have about XML for a moment and ask yourself what this fact describes, and how well it describes it.</p>
<pre class="giallo z-code" data-lang="xml"><code data-lang="xml"><span class="giallo-l"><span><</span><span class="z-entity z-name z-tag">Fact</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/totalOwed</span><span class="z-punctuation z-definition z-string">"></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Derived</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Subtract</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Minuend</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dependency</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/totalTax</span><span class="z-punctuation z-definition z-string">"/></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Minuend</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Subtrahends</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dependency</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/totalPayments</span><span class="z-punctuation z-definition z-string">"/></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Subtrahends</span><span>></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Subtract</span><span>></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Derived</span><span>></span></span>
<span class="giallo-l"><span></</span><span class="z-entity z-name z-tag">Fact</span><span>></span></span></code></pre>
<p>This fact describes a <code>/totalOwed</code> value that’s derived by subtracting <code>/totalPayments</code> from <code>/totalTax</code>.
In tax terms, this is the amount you will need to pay the IRS at the end of the year.
That amount, “total owed,” is the difference between the total taxes due for your income (“total tax”) and the amount you’ve already paid (“total payments”).</p>
<p>My initial reaction to way of representing taxes was that it’s quite verbose, but also reasonably clear.
That’s more or less how I still feel.</p>
<p>You only need to look at a few of these to intuit the structure.
Take the refundable credits calculation, for example.
A refundable credit is a tax credit that can lead to a negative tax balance—if you qualify for more refundable credits than you owe in taxes, the government just gives you some money.
TWE calculates the total value of refundable credits by adding up the values of the Earned Income Credit, the Child Tax Credit (CTC), American Opportunity Credit, the refundable portion of the Adoption Credit, and some other stuff from the Schedule 3.</p>
<aside>
This blog is about programming interfaces and paradigms, but the application is about taxes.
So yes, this section is going to involve a lot of talk about taxes.
</aside>
<pre class="giallo z-code" data-lang="xml"><code data-lang="xml"><span class="giallo-l"><span><</span><span class="z-entity z-name z-tag">Fact</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/totalRefundableCredits</span><span class="z-punctuation z-definition z-string">"></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Description</span><span>></span></span>
<span class="giallo-l"><span> Form 1040 Line 32. Schedule 3 Line 15 + EITC,ACTC, AOTC,</span></span>
<span class="giallo-l"><span> refundable portion of Adoption</span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Description</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Derived</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Add</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dependency</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/earnedIncomeCredit</span><span class="z-punctuation z-definition z-string">"/></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dependency</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/additionalCtc</span><span class="z-punctuation z-definition z-string">"/></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dependency</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/americanOpportunityCredit</span><span class="z-punctuation z-definition z-string">"/></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dependency</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/adoptionCreditRefundable</span><span class="z-punctuation z-definition z-string">"/></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dependency</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/schedule3OtherPaymentsAndRefundableCreditsTotal</span><span class="z-punctuation z-definition z-string">"/></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Add</span><span>></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Derived</span><span>></span></span>
<span class="giallo-l"><span></</span><span class="z-entity z-name z-tag">Fact</span><span>></span></span></code></pre><aside>
These facts are only lightly edited from the <a href="https://github.com/IRS-Public/tax-withholding-estimator/tree/d6be307aa3d4a3eeb8f7564764fa770accbdd9aa/src/main/resources/twe/facts">actual facts</a> in TWE.
I just removed some descriptions and placeholders.
This is real code that's handling real complexity.
</aside>
<p>By contrast, non-refundable tax credits can bring your tax burden down to zero, but won’t ever make it negative.
TWE models that by subtracting non-refundable credits from the tentative tax burden while making sure it can’t go below zero,
using the <code><GreaterOf></code> operator.</p>
<pre class="giallo z-code" data-lang="xml"><code data-lang="xml"><span class="giallo-l"><span><</span><span class="z-entity z-name z-tag">Fact</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/tentativeTaxNetNonRefundableCredits</span><span class="z-punctuation z-definition z-string">"></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Description</span><span>></span></span>
<span class="giallo-l"><span> Total tentative tax after applying non-refundable credits, but before</span></span>
<span class="giallo-l"><span> applying refundable credits.</span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Description</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Derived</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">GreaterOf</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dollar</span><span>>0</</span><span class="z-entity z-name z-tag">Dollar</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Subtract</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Minuend</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dependency</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/totalTentativeTax</span><span class="z-punctuation z-definition z-string">"/></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Minuend</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Subtrahends</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dependency</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/totalNonRefundableCredits</span><span class="z-punctuation z-definition z-string">"/></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Subtrahends</span><span>></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Subtract</span><span>></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">GreaterOf</span><span>></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Derived</span><span>></span></span>
<span class="giallo-l"><span></</span><span class="z-entity z-name z-tag">Fact</span><span>></span></span></code></pre>
<p>While admittedly <em>very</em> verbose, the nesting is straightforward to follow.
The tax after non-refundable credits is derived by saying “give me the greater of these two numbers: zero, or the difference between tentative tax and the non-refundable credits.”</p>
<p>Finally, what about inputs?
Obviously we need places for the taxpayer to provide information, so that we can calculate all the other values.</p>
<pre class="giallo z-code" data-lang="xml"><code data-lang="xml"><span class="giallo-l"><span><</span><span class="z-entity z-name z-tag">Fact</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/totalEstimatedTaxesPaid</span><span class="z-punctuation z-definition z-string">"></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Writable</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dollar</span><span>/></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Writable</span><span>></span></span>
<span class="giallo-l"><span></</span><span class="z-entity z-name z-tag">Fact</span><span>></span></span></code></pre>
<p>Okay, so instead of <code><Derived></code> we use <code><Writable></code>.
Because the value is… writable.
Fair enough.
The <code><Dollar/></code> denotes what type of value this fact takes.
True-or-false questions use <code><Boolean/></code>, like this one that records whether the taxpayer is 65 or older.</p>
<pre class="giallo z-code" data-lang="xml"><code data-lang="xml"><span class="giallo-l"><span><</span><span class="z-entity z-name z-tag">Fact</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/primaryFilerAge65OrOlder</span><span class="z-punctuation z-definition z-string">"></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Writable</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Boolean</span><span>/></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Writable</span><span>></span></span>
<span class="giallo-l"><span></</span><span class="z-entity z-name z-tag">Fact</span><span>></span></span></code></pre>
<p>There are some (much) longer facts, but these are a fair representation of what the median fact looks like.
Facts depend on other facts, sometimes derived and sometimes writable, and they all add up to some final tax numbers at the end.
But why encode math this way when it seems far clunkier than traditional notation?</p>
<h2 id="tax-logic-needs-a-declarative-specification"><a class="zola-anchor" href="#tax-logic-needs-a-declarative-specification" aria-label="Anchor link for: tax-logic-needs-a-declarative-specification">Tax logic needs a declarative specification</a></h2>
<p>Countless mainstream programming languages would instead let you write this calculation in a notation that looks more like normal math.
Take this JavaScript example, which looks like elementary algebra:</p>
<pre class="giallo z-code" data-lang="javascript"><code data-lang="javascript"><span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> totalOwed = totalTax - totalPayments</span></span></code></pre>
<p>That seems better!
It’s far more concise, easier to read, and doesn’t make you explicitly label the “minuend” and “subtrahend.”</p>
<p>Let’s add in the definitions for <code>totalTax</code> and <code>totalPayments</code>.</p>
<pre class="giallo z-code" data-lang="javascript"><code data-lang="javascript"><span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> totalTax = tentativeTaxNetNonRefundableCredits + totalOtherTaxes</span></span>
<span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> totalPayments = totalEstimatedTaxesPaid +</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> totalTaxesPaidOnSocialSecurityIncome +</span></span>
<span class="giallo-l"><span> totalRefundableCredits</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> totalOwed = totalTax - totalPayments</span></span></code></pre>
<p>Still not too bad.
Total tax is calculated by adding the tax after non-refundable credits (discussed earlier) to whatever’s in “other taxes.”
Total payments is the sum of estimated taxes you’ve already paid, taxes you’ve paid on social security, and any refundable credits.</p>
<p>The problem with the JavaScript representation is that it’s <em>imperative</em>.
It describes actions you take in a sequence, and once the sequence is done, the intermediate steps are lost.
The issues with this get more obvious when you go another level deeper, adding the definitions of all the values that <code>totalTax</code> and <code>totalPayments</code> depend on.</p>
<pre class="giallo z-code" data-lang="javascript"><code data-lang="javascript"><span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">// Total tax calculation</span></span>
<span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> totalOtherTaxes = selfEmploymentTax + additionalMedicareTax + netInvestmentIncomeTax</span></span>
<span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> tentativeTaxNetNonRefundableCredits = Math.</span><span class="z-entity z-name z-function">max</span><span class="z-keyword z-operator">(totalTentativeTax - totalNonRefundableCredits,</span><span class="z-constant z-numeric"> 0</span><span>)</span></span>
<span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> totalTax = tentativeTaxNetNonRefundableCredits + totalOtherTaxes</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">// Total payments calculation</span></span>
<span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> totalEstimatedTaxesPaid =</span><span class="z-entity z-name z-function"> getInput</span><span>()</span></span>
<span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> totalTaxesPaidOnSocialSecurityIncome = socialSecuritySources</span></span>
<span class="giallo-l"><span> .</span><span class="z-entity z-name z-function">map</span><span>(</span><span class="z-variable z-parameter">source</span><span class="z-storage"> =></span><span> source.totalTaxesPaid)</span></span>
<span class="giallo-l"><span> .</span><span class="z-entity z-name z-function">reduce</span><span class="z-punctuation z-definition z-parameters">((</span><span class="z-variable z-parameter">acc</span><span>,</span><span class="z-variable z-parameter"> val</span><span class="z-punctuation z-definition z-parameters">)</span><span class="z-storage"> =></span><span> {</span><span class="z-keyword"> return</span><span class="z-keyword z-operator"> acc+val },</span><span class="z-constant z-numeric"> 0</span><span>)</span></span>
<span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> totalRefundableCredits = earnedIncomeCredit +</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> additionalCtc +</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> americanOpportunityCredit +</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> adoptionCreditRefundable +</span></span>
<span class="giallo-l"><span> schedule3OtherPaymentsAndRefundableCreditsTotal</span></span>
<span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> totalPayments = totalEstimatedTaxesPaid +</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> totalTaxesPaidOnSocialSecurityIncome +</span></span>
<span class="giallo-l"><span> totalRefundableCredits</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">// Total owed</span></span>
<span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> totalOwed = totalTax - totalPayments</span></span></code></pre>
<p>We are quickly arriving at a situation that has a lot of subtle problems.</p>
<!-- We're using `Math.max` to ensure that non-refundable credits don't make your tax balance negative. -->
<p>One problem is the execution order.
The hypothetical <code>getInput()</code> function solicits an answer from the taxpayer, which has to happen before the program can continue.
Calculations that don’t depend on knowing “total estimated taxes” are still held up waiting for the user;
calculations that <em>do</em> depend on knowing that value had better be specified after it.</p>
<aside>
You can't just ask all the questions up top either, because it might not make sense to ask certain questions based on your earlier answers.
The application doesn't need to know your spouse's income if you don't have a spouse or are Married Filing Separately.
</aside>
<p>Or, take a close look at how we add up all the social security income:</p>
<pre class="giallo z-code" data-lang="javascript"><code data-lang="javascript"><span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> totalTaxesPaidOnSocialSecurityIncome = socialSecuritySources</span></span>
<span class="giallo-l"><span> .</span><span class="z-entity z-name z-function">map</span><span>(</span><span class="z-variable z-parameter">source</span><span class="z-storage"> =></span><span> source.totalTaxesPaid)</span></span>
<span class="giallo-l"><span> .</span><span class="z-entity z-name z-function">reduce</span><span class="z-punctuation z-definition z-parameters">((</span><span class="z-variable z-parameter">acc</span><span>,</span><span class="z-variable z-parameter"> val</span><span class="z-punctuation z-definition z-parameters">)</span><span class="z-storage"> =></span><span> {</span><span class="z-keyword"> return</span><span class="z-keyword z-operator"> acc+val },</span><span class="z-constant z-numeric"> 0</span><span>)</span></span></code></pre>
<p>All of a sudden we are really in the weeds with JavaScript.
These are not complicated code concepts—map and reduce are both in the standard library and basic functional paradigms are widespread these days—but they are not tax math concepts.
Instead, they are implementation details.</p>
<p>Compare it to the Fact representation of that same value.</p>
<pre class="giallo z-code" data-lang="xml"><code data-lang="xml"><span class="giallo-l"><span><</span><span class="z-entity z-name z-tag">Fact</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/totalTaxesPaidOnSocialSecurityIncome</span><span class="z-punctuation z-definition z-string">"></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Derived</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">CollectionSum</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dependency</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/socialSecuritySources/*/totalFederalTaxesPaid</span><span class="z-punctuation z-definition z-string">"/></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">CollectionSum</span><span>></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Derived</span><span>></span></span>
<span class="giallo-l"><span></</span><span class="z-entity z-name z-tag">Fact</span><span>></span></span></code></pre>
<p>This isn’t perfect—the <code>*</code> that represents each social security source is a little hacky—but the meaning is much clearer.
What are the total taxes paid on social security income?
The sum of the taxes paid on <em>each</em> social security income.
How do you add all the items in a collection?
With <code><CollectionSum></code>.</p>
<p>Plus, it reads like all the other facts; needing to add up all items in a collection didn’t suddenly kick us into a new conceptual realm.</p>
<p>The philosophical difference between these two is that, unlike JavaScript, which is <em>imperative</em>, the Fact Dictionary is <em>declarative</em>.
It doesn’t describe exactly what steps the computer will take or in what order;
it describes a bunch of named calculations and how they depend on each other.
The engine decides automatically how to execute that calculation.</p>
<!-- that's why the engine is called the Fact Graph. -->
<p>Besides being (relatively) friendlier to read, the most important benefit of a declarative tax model is that you can ask the program how it calculated something.
Per the Fact Graph’s original author, <a rel="external" href="https://chrisgiven.com/2025/06/peeking-behind-the-code/">Chris Given</a>:</p>
<blockquote>
<p>The Fact Graph provides us with a means of proving that none of the unasked questions would have changed the bottom line of your tax return and that you’re getting every tax benefit to which you’re entitled.</p>
</blockquote>
<!-- I'm a programmer and I use map/reduce all the time, but they are -->
<p>Suppose you get a value for <code>totalOwed</code> that doesn’t seem right.
You can’t ask the JavaScript version “how did you arrive at that number?” because those intermediate values have already been discarded.
Imperative programs are generally debugged by adding log statements or stepping through with a debugger, pausing to check each value.
This works fine when the number of intermediate values is small;
it does not scale at all for the US Tax Code, where the final value is calculated based on hundreds upon hundreds of calculations of intermediate values.</p>
<aside>
"The actual problem is that the US Tax Code is too complicated."
Your Nobel is in the mail.
</aside>
<p>With a declarative graph representation, we get auditability and introspection for free, for every single calculation.</p>
<p>Intuit, the company behind TurboTax, came to the same conclusion, and published <a rel="external" href="https://arxiv.org/pdf/2009.06103">a whitepaper about their “Tax Knowledge Graph”</a> in 2020.
Their implementation is not open source, however (or least I can’t find it).
The <a rel="external" href="https://github.com/IRS-Public/fact-graph">IRS Fact Graph</a> is open source and public domain, so it can be studied, shared, and extended by the public.</p>
<aside>
Do not use the Tax Withholding Estimator's facts to file your taxes, though.
Those facts are intended for estimating your withholdings.
(It's in the name.)
</aside>
<h2 id="xml-is-much-better-at-this-than-json"><a class="zola-anchor" href="#xml-is-much-better-at-this-than-json" aria-label="Anchor link for: xml-is-much-better-at-this-than-json">XML is much better at this than JSON</a></h2>
<p>If we accept the need for a declarative data representation of the tax code, what should it be?</p>
<p>In many of the places where people used to encounter XML, such network data transfer and configuration files, it has been replaced by JSON.
I find JSON to be a reasonably good wire format and a painful configuration format, but in neither case would I rather be using XML (although it’s a close call on the latter).</p>
<p>The Fact Dictionary is different.
It’s not a pile of settings or key-value pairs.
It’s a custom language that models a unique and complex problem space.
In programming we call this a domain-specific language, or DSL for short.</p>
<aside>
People sometimes deride the creation of a DSL as over-engineering.
I'm sure that's true in many cases—this is not one of them.
</aside>
<p>As an exercise, I tried to come up with a plausible JSON representation of the <code>/tentativeTaxNetNonRefundableCredits</code> fact from earlier.</p>
<pre class="giallo z-code" data-lang="json"><code data-lang="json"><span class="giallo-l"><span>{</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "description"</span><span class="z-punctuation z-definition z-string">: "</span><span class="z-string">Total tentative tax after applying non-refundable credits, but before applying refundable credits.</span><span class="z-punctuation z-definition z-string">",</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "definition"</span><span>: {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "type"</span><span class="z-punctuation z-definition z-string">: "</span><span class="z-string">Expression</span><span class="z-punctuation z-definition z-string">",</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "kind"</span><span class="z-punctuation z-definition z-string">: "</span><span class="z-string">GreaterOf</span><span class="z-punctuation z-definition z-string">",</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "children"</span><span class="z-punctuation z-definition z-array">: [</span></span>
<span class="giallo-l"><span> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "type"</span><span class="z-punctuation z-definition z-string">: "</span><span class="z-string">Value</span><span class="z-punctuation z-definition z-string">",</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "kind"</span><span class="z-punctuation z-definition z-string">: "</span><span class="z-string">Dollar</span><span class="z-punctuation z-definition z-string">",</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "value"</span><span>:</span><span class="z-constant z-numeric"> 0</span></span>
<span class="giallo-l"><span> },</span></span>
<span class="giallo-l"><span> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "type"</span><span class="z-punctuation z-definition z-string">: "</span><span class="z-string">Expression</span><span class="z-punctuation z-definition z-string">",</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "kind"</span><span class="z-punctuation z-definition z-string">: "</span><span class="z-string">Subtract</span><span class="z-punctuation z-definition z-string">",</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "minuend"</span><span>: {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "type"</span><span class="z-punctuation z-definition z-string">: "</span><span class="z-string">Dependency</span><span class="z-punctuation z-definition z-string">",</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "path"</span><span class="z-punctuation z-definition z-string">: "</span><span class="z-string">/totalTentativeTax</span><span class="z-punctuation z-definition z-string">"</span></span>
<span class="giallo-l"><span> },</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "subtrahend"</span><span>: {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "type"</span><span class="z-punctuation z-definition z-string">: "</span><span class="z-string">Dependency</span><span class="z-punctuation z-definition z-string">",</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> "path"</span><span class="z-punctuation z-definition z-string">: "</span><span class="z-string">/totalNonRefundableCredits</span><span class="z-punctuation z-definition z-string">"</span></span>
<span class="giallo-l"><span> }</span></span>
<span class="giallo-l"><span> }</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-array"> ]</span></span>
<span class="giallo-l"><span> }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>This is not a terribly complicated fact, but it’s immediately apparent that JSON does not handle arbitrary nested expressions well.
The only complex data structure <a rel="external" href="https://www.json.org/json-en.html">available in JSON</a> is an object, so every child object has to declare what kind of object it is.
Contrast that with XML, where the “kind” of the object is embedded in its delimiters.</p>
<aside>
I'm sure there are more-precise academic terms for what I'm trying to describe here and I would welcome an email pointing me in the direction of relevant scholarship.
</aside>
<pre class="giallo z-code" data-lang="xml"><code data-lang="xml"><span class="giallo-l"><span><</span><span class="z-entity z-name z-tag">Fact</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/tentativeTaxNetNonRefundableCredits</span><span class="z-punctuation z-definition z-string">"></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Description</span><span>></span></span>
<span class="giallo-l"><span> Total tentative tax after applying non-refundable credits, but before</span></span>
<span class="giallo-l"><span> applying refundable credits.</span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Description</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Derived</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">GreaterOf</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dollar</span><span>>0</</span><span class="z-entity z-name z-tag">Dollar</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Subtract</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Minuend</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dependency</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/totalTentativeTax</span><span class="z-punctuation z-definition z-string">"/></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Minuend</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Subtrahends</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dependency</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/totalNonRefundableCredits</span><span class="z-punctuation z-definition z-string">"/></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Subtrahends</span><span>></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Subtract</span><span>></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">GreaterOf</span><span>></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Derived</span><span>></span></span>
<span class="giallo-l"><span></</span><span class="z-entity z-name z-tag">Fact</span><span>></span></span></code></pre>
<p>I think this XML representation could be improved, but even in its current form, it is clearly better than JSON.
(It’s also, amusingly, a couple lines shorter.)
Attributes and named children give you just enough expressive power to make choices about what your language should or should not emphasize.
Not being tied to specific set of data types makes it reasonable to define your own, such as a distinction between “dollars” and “integers.”</p>
<p>A lot of minor frustrations we’ve all internalized as inevitable with JSON are actually JSON-specific.
XML has comments, for instance.
That’s nice.
It also has sane whitespace and newline handling, which is important when your descriptions are often long.
For text that has any length or shape to it, XML is far more pleasant to read and edit by hand than JSON.</p>
<p>There are still verbosity gains to be had, particularly with switch statements (omitted here out of respect for page length).
I’d certainly remove the explicit “minuend” and “subtrahend,” for starters.</p>
<pre class="giallo z-code" data-lang="xml"><code data-lang="xml"><span class="giallo-l"><span><</span><span class="z-entity z-name z-tag">Fact</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/tentativeTaxNetNonRefundableCredits</span><span class="z-punctuation z-definition z-string">"></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Description</span><span>></span></span>
<span class="giallo-l"><span> Total tentative tax after applying non-refundable credits, but before</span></span>
<span class="giallo-l"><span> applying refundable credits.</span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Description</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Derived</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">GreaterOf</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dollar</span><span>>0</</span><span class="z-entity z-name z-tag">Dollar</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Subtract</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dependency</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/totalTentativeTax</span><span class="z-punctuation z-definition z-string">"/></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">Dependency</span><span class="z-entity z-other z-attribute-name"> path</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/totalNonRefundableCredits</span><span class="z-punctuation z-definition z-string">"/></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Subtract</span><span>></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">GreaterOf</span><span>></span></span>
<span class="giallo-l"><span> </</span><span class="z-entity z-name z-tag">Derived</span><span>></span></span>
<span class="giallo-l"><span></</span><span class="z-entity z-name z-tag">Fact</span><span>></span></span></code></pre>
<p>I believe that the original team didn’t do this because they didn’t want the order of the children to have semantic consequence.
I get it, but order <em>is</em> guaranteed in XML and I think the additional nesting and words do more harm then good.</p>
<p>What about YAML? <a rel="external" href="https://chrisgiven.com/2025/06/saying-goodbye/">Chris Given again</a>:</p>
<blockquote>
<p>whatever you do, don’t try to express the logic of the Internal Revenue Code as YAML</p>
</blockquote>
<h2 id="xml-is-cheap-and-universal"><a class="zola-anchor" href="#xml-is-cheap-and-universal" aria-label="Anchor link for: xml-is-cheap-and-universal">XML is cheap and universal</a></h2>
<p>Finally, there’s a good case to made that you could build this DSL with s-expressions.
In a lot of ways, this is nicest syntax to read and edit.</p>
<pre class="giallo z-code" data-lang="common-lisp"><code data-lang="common-lisp"><span class="giallo-l"><span>(Fact</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string"> (Path "</span><span class="z-string">/tentativeTaxNetNonRefundableCredits</span><span class="z-punctuation z-definition z-string">")</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string"> (Description "</span><span class="z-string">Total tentative tax after applying non-refundable</span></span>
<span class="giallo-l"><span class="z-string"> credits, but before applying refundable credits.</span><span class="z-punctuation z-definition z-string">")</span></span>
<span class="giallo-l"><span> (Derived</span></span>
<span class="giallo-l"><span> (GreaterOf</span></span>
<span class="giallo-l"><span> (Dollar </span><span class="z-constant z-numeric">0</span><span>)</span></span>
<span class="giallo-l"><span> (Subtract</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string"> (Minuend (Dependency "</span><span class="z-string">/totalTentativeTax</span><span class="z-punctuation z-definition z-string">"))</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string"> (Subtrahends (Dependency "</span><span class="z-string">/totalNonRefundableCredits</span><span class="z-punctuation z-definition z-string">"))))))</span></span></code></pre>
<p>HackerNews user <a rel="external" href="https://news.ycombinator.com/item?id=45599836">ok123456 asks</a>: “Why would I want to use this over Prolog/Datalog?”
<a href="https://unplannedobsolescence.com/blog/prolog-basics-pokemon/">I’m a Prolog fan</a>!
This is also possible.</p>
<pre class="giallo z-code" data-lang="prolog"><code data-lang="prolog"><span class="giallo-l"><span class="z-entity z-name z-function">fact</span><span class="z-punctuation z-definition z-parameters">(</span></span>
<span class="giallo-l"><span> path(</span><span class="z-string">"/tentativeTaxNetNonRefundableCredits"</span><span>),</span></span>
<span class="giallo-l"><span> description(</span><span class="z-string">"Total tentative tax after applying non-refundable credits, but before applying refundable credits."</span><span>),</span></span>
<span class="giallo-l"><span> derived(</span></span>
<span class="giallo-l"><span> greaterOf(</span></span>
<span class="giallo-l"><span> dollar(</span><span class="z-constant z-numeric">0</span><span>),</span></span>
<span class="giallo-l"><span> subtract(</span></span>
<span class="giallo-l"><span> minued(dependency(</span><span class="z-string">"/totalTentativeTax"</span><span>)),</span></span>
<span class="giallo-l"><span> subtrahends(dependency(</span><span class="z-string">"/totalNonRefundableCredits"</span><span>))))))</span></span></code></pre>
<p>My friend Deniz couldn’t help but rewrite it in <a rel="external" href="https://kdl.dev/">KDL</a>, a cool thing I had to look up.</p>
<pre class="giallo z-code" data-lang="kdl"><code data-lang="kdl"><span class="giallo-l"><span class="z-entity z-name z-tag">fact</span><span> /</span><span class="z-string">tentativeTaxNetNonRefundableCredits</span><span> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag"> description</span><span class="z-string"> """</span></span>
<span class="giallo-l"><span class="z-string"> Total tentative tax after applying non-refundable credits, but before</span></span>
<span class="giallo-l"><span class="z-string"> applying refundable credits.</span></span>
<span class="giallo-l"><span class="z-string"> """</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag"> derived</span><span> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag"> greater-of</span><span> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag"> dollar</span><span class="z-constant z-numeric"> 0</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag"> subtract</span><span> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag"> dependency</span><span> /</span><span class="z-string">totalTentativeTax</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag"> dependency</span><span> /</span><span class="z-string">totalNonRefundableCredits</span></span>
<span class="giallo-l"><span> }</span></span>
<span class="giallo-l"><span> }</span></span>
<span class="giallo-l"><span> }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>At least to my eye, all of these feel more pleasant than the XML version.
When I started working on the Fact Graph, I strongly considered proposing a transition to s-expressions.
I even half-jokingly included it in a draft design document.
The process of actually building on top of the Fact Graph, however, taught me something very important about the value of XML.</p>
<p>Using XML gives you a parser and a universal tooling ecosystem for free.</p>
<p>Take Prolog for instance.
You can relate XML to Prolog terms with <a rel="external" href="https://www.scryer.pl/sgml">a single predicate</a>.
If I want to explore Fact Dictionaries in Prolog—or even make a whole alternative implementation of the Fact Graph—I basically get the Prolog representation out of the box.</p>
<p>S-expressions work great in Lisp and Prolog terms work great in Prolog.
XML can be transformed, more or less natively, into anything.
That makes it a great canonical, cross-platform data format.</p>
<p>XML is rivaled only by JSON in the maturity and availability of its tooling.
At one point I had the idea that it would be helpful to fuzzy search for Fact definitions by path.
I’d like to just type “overtime” and see all the facts related to overtime.
Regular searches of the codebase were cluttered with references and dependencies.</p>
<p>This was possible entirely with shell commands I already had on my computer.</p>
<pre class="giallo z-code" data-lang="plain"><code data-lang="plain"><span class="giallo-l"><span>cat facts.xml | xpath -q -e '//Fact/@path' | grep -o '/[^"]*' | fzf</span></span></code></pre>
<p>This uses <a rel="external" href="https://devhints.io/xpath">XPath</a> to query all the fact paths, <code>grep</code> to clean up the output, and <code>fzf</code> to interactively search the results.
I solved my problem with a trivial bash one-liner.
I kept going and said: not only do I want to search the paths, I’d like <em>selecting</em> one of the paths to show me the definition.</p>
<p>Easy. Just take the result of the first command, which is a path attribute, and use it in a second XPath query.</p>
<pre class="giallo z-code" data-lang="plain"><code data-lang="plain"><span class="giallo-l"><span>path=$(cat facts.xml | xpath -q -e '//Fact/@path' | grep -o '/[^"]*' | fzf)</span></span>
<span class="giallo-l"><span>cat facts.xml | xpath -q -e "//Fact[@path=\"$path\"]" | format</span></span></code></pre>
<p>I got a little carried away <a rel="external" href="https://github.com/IRS-Public/tax-withholding-estimator">building this out</a> into a “$0 Dispatch Pattern” script of the kind <a rel="external" href="https://www.oilshell.org/blog/2021/07/blog-backlog-1.html#shell-programming-patterns">described by Andy Chu</a>.
(Andy is a blogging icon, by the way.)
I also added dependency search—not only can you query the definition of a fact, but you can go <em>up</em> the dependency chain by asking what facts depend on it.</p>
<p><video src="./fgs-demo.mp4" type="video/mp4" controls></video></p>
<p>Try it yourself by cloning <a rel="external" href="https://github.com/IRS-Public/tax-withholding-estimator">the repo</a> and running <code>./scripts/fgs.sh</code> (you need <code>fzf</code> installed).
The error handling is janky but it’s pretty solid for 60 lines of bash I wrote in an afternoon.
I use it almost daily.</p>
<p>I’m not sure how many people used my script, but multiple other team members put together similarly quick, powerful debugging tools that became part of everyone’s workflow.
All of these tools relied on being able to trivially parse the XML representation and work with it in the language that best suited the problem they were trying to solve, without touching the Fact Graph’s actual implementation in Scala.</p>
<p>The lesson I took from this is that a universal data representation is worth its weight in gold.
There are exactly two options in this category.
In most cases you should choose JSON.
If you need a DSL though, XML is by far the cheapest one, and the cost-efficiency of building on it will empower your team to spend their innovation budget elsewhere.</p>
<p><em>Thanks to <a rel="external" href="https://chrisgiven.com/">Chris Given</a> and <a rel="external" href="https://deniz.aksimsek.tr">Deniz Akşimşek</a> for their feedback on a draft of this blog.</em></p>
<h1 id="notes"><a class="zola-anchor" href="#notes" aria-label="Anchor link for: notes">Notes</a></h1>
<ul>
<li>I had never heard of XPath before 2023, when Deniz <a rel="external" href="https://deniz.aksimsek.tr/2023/xpath/">figured out an XPath query</a> that made <a rel="external" href="https://github.com/bigskysoftware/htmx/pull/1489">my first htmx PR</a> possible.</li>
<li>Another reason to use XML is that humans who aren’t programmers can read it. They usually don’t like it, but, if you did a good-enough job designing the schema, they can read it in a pinch. Do them a favor and build an alternative view, though. Because you’re using XML, this is pretty easy.</li>
<li>It’s probably just because I’ve started to use it—buy a Jeep Grand Cherokee and suddenly the roadways seem full of them—but lately I have noticed an uptick in XML interest.
Fellow <a rel="external" href="https://www.recurse.com/scout/click?t=044d120abf1c334d0b2a3132634eb025">Spring ’24 Recurser</a> Jake Low recently wrote <a rel="external" href="https://www.jakelow.com/blog/introducing-grex">a tool called <code>grex</code></a> which turns XML documents into a flat, line-oriented representation.
Martijn Faassen has been working on <a rel="external" href="https://blog.startifact.com/posts/xee/">a modern XPath and XSLT engine in Rust</a>.</li>
<li>I’m not sure it’s fair to call JSON “lobotomized” but I thought <a rel="external" href="https://marcosmagueta.com/blog/the-lost-art-of-xml/">this article</a> was largely correct about the problems XML can solve. The binary format is especially interesting to me.</li>
</ul>
Prolog Basics Explained with Pokémon2026-01-05T00:00:00+00:002026-01-05T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/blog/prolog-basics-pokemon/<style>
.inline-figure {
display: flex;
justify-content: space-around;
margin: 1rem;
}
.move {
text-transform: lowercase;
font-variant-caps: small-caps;
}
pre[data-name=top-level] {
/*
* Trust me I'm not proud of this, but Zola, the static-site generator I'm using,
* inserts the code block background-color as a "style" attribute on the <pre> tag.
* I can either break open my Zola syntax highlighting implementation or I can suck it up
* and use the !important tag. I do still feel the need to acknowledge it though.
* Call it a point of professional pride.
*/
background-color: var(--aside-color) !important;
}
figure img {
margin: 0 auto;
}
</style>
<p>The project that inspired this post is a little silly—I am about to describe the mechanics of a children’s video game in great detail—but this particular problem is what finally made Prolog click for me, an epiphany I’ve been hunting for ever since reading Bruce Tate’s “Seven Languages in Seven Weeks.”</p>
<p>This exercise has taught me a lot about the kinds of interfaces I’m trying to build in <a rel="external" href="https://alexanderpetros.com/triptych/">somewhat more practical domains</a>.
For certain kinds of relationships, logic programming is by far the most concise and expressive programming system I’ve ever used.</p>
<p>To understand why, let’s talk about Pokémon.</p>
<h2 id="pokemon-basics"><a class="zola-anchor" href="#pokemon-basics" aria-label="Anchor link for: pokemon-basics">Pokémon basics</a></h2>
<p>Pokémon is a video game series/multimedia franchise/lifestyle brand set in a world where humans live alongside a menagerie of colorful animal characters.</p>
<p>“Pokémon” is both the name of the franchise and the generic term for the animal characters themselves, which all have their own individual species names.
There are over a thousand distinct species of Pokémon, from Bulbasaur (<a rel="external" href="https://bulbapedia.bulbagarden.net/wiki/Bulbasaur_(Pok%C3%A9mon)">#1</a>) to Pecharunt (<a rel="external" href="https://bulbapedia.bulbagarden.net/wiki/Pecharunt_(game)">#1025</a>).</p>
<figure>
<div class="inline-figure">
<img src="./images/pikachu.png" height=100 width=100 alt="the pokemon pikachu, an electric mouse">
<img src="./images/archeops.png" height=100 width=100 alt="the pokemon archeops, a colorfully-feathered rock bird">
<img src="./images/dipplin.png" height=100, width=100 alt="the pokemon dipplin, which kind of looks like a candy apple that fell on the ground">
</div>
<figcaption>
Popular Pokémon include (from left to right):<br>
Pikachu <a href="https://bulbapedia.bulbagarden.net/wiki/Pikachu_(Pok%C3%A9mon)">(#25)</a>, Archeops
<a href="https://bulbapedia.bulbagarden.net/wiki/Archeops_(Pok%C3%A9mon)">(#567)</a>
, and Dipplin <a href="https://bulbapedia.bulbagarden.net/wiki/Dipplin_(Pok%C3%A9mon)">(#1101)</a>.
</figcaption>
</figure>
<aside>I rarely include images on this blog and I am very excited that this post warrants them.</aside>
<p>There are all sorts of Pokémon games now, but the main series has always been about catching and battling them.
During a battle, your team of six Pokémon faces off against another team.
Each Pokémon is equipped with four moves that it can choose to (usually) do damage to their opponent.
You need to reduce the HP (Hit Points) of all your opponent’s Pokémon to zero before they are able to do so to you.</p>
<p>Each Pokémon has unique traits that affects how it battles.
They have a set of base stats, a large pool of possible moves, a handful of abilities, and a typing.
As you will see in a moment, the immense number of combinations here is the motivation for trying to track this with software.</p>
<figure>
<img src="./images/scizor-stats.png" alt="A screenshot showing Scizor's abilities, typing, and stats">
<figcaption>Scizor is a Bug/Steel type with high Attack and low Speed (via <a href="https://www.smogon.com/dex/bw/pokemon/scizor/">Smogon</a>)</figcaption>
</figure>
<aside>
Speed controls which move will go first;
Attack and Special Attack affect how much damage they do with Physical and Special moves, respectively;
Defense and Special Defense affect how much damage they take.
</aside>
<p>Typing is especially important.
Moves have a type, like Fire or Rock, and Pokémon can have up to two types.
A move with a type that is Super Effective against the opposing Pokémon will do double damage; a move that is Not Very Effective will do half damage.</p>
<p>It’s a little more intuitive with examples.
The Fire-type move <span class="move">Flamethrower</span> will do 2x to Grass-type Pokémon, because Grass is weak to Fire, but the Water-type move <span class="move">Surf</span> will only do ½ damage to them, because Grass resists Water.</p>
<figure>
<img src="./images/surf.png" alt="Miltoic using Surf against a Lunatone in a Generation 3 Pokémon game.">
<figcaption>
<a href="https://bulbapedia.bulbagarden.net/wiki/Lunatone_(Pok%C3%A9mon)">Lunatone</a> is a Rock/Psychic Type. Rock is weak to Water, and Psychic is neutral to it, so <span class=move>Surf</span> will do 2x damage.
</figcaption>
</figure>
<p>Type modifiers can stack.
<a href="https://bulbapedia.bulbagarden.net/wiki/Scizor_(Pok%C3%A9mon)">Scizor</a> is a Bug/Steel type, and <em>both</em> Bug and Steel are weak to Fire, so Fire moves will do 4x damage to Scizor.
Electric is weak to Water, but Ground is immune, so if you use an Electric type move against Water/Ground <a rel="external" href="https://bulbapedia.bulbagarden.net/wiki/Swampert_(Pok%C3%A9mon)">Swampert</a>, you’ll do zero damage, since 0×2 is still 0.</p>
<p>Naturally, there is a chart to help you keep track.</p>
<figure>
<img src="./images/type-chart.svg" alt="The Pokémon type chart.">
<figcaption>
Pokémon Type Chart (via <a href="https://commons.wikimedia.org/wiki/File:Pokemon_Type_Chart.svg">Wikimedia</a>)
</figcaption>
</figure>
<p>Those are effectively the mechanics of the Pokémon video games as I understood them when I was 8.
Click moves to do damage, try to click moves with good type matchups.
These games are for children and, at the surface level, they’re not very hard.</p>
<h2 id="prolog-basics"><a class="zola-anchor" href="#prolog-basics" aria-label="Anchor link for: prolog-basics">Prolog basics</a></h2>
<p>Before I explain how wonky the Pokémon mechanics can get under the hood, I first need to explain how logic programming works.
Pokémon is a great fit for logic programming because Pokémon battles are essentially an extremely intricate rules engine.</p>
<p>Let’s start by creating a file with a bunch of facts.</p>
<pre class="giallo z-code" data-lang="prolog"><code data-lang="prolog"><span class="giallo-l"><span class="z-entity z-name z-function">pokemon</span><span class="z-punctuation z-definition z-parameters z-constant">(bulbasaur)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">pokemon</span><span class="z-punctuation z-definition z-parameters z-constant">(ivysaur)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">pokemon</span><span class="z-punctuation z-definition z-parameters z-constant">(venusaur)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">pokemon</span><span class="z-punctuation z-definition z-parameters z-constant">(charmander)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">pokemon</span><span class="z-punctuation z-definition z-parameters z-constant">(charmeleon)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">pokemon</span><span class="z-punctuation z-definition z-parameters z-constant">(charizard)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">pokemon</span><span class="z-punctuation z-definition z-parameters z-constant">(squirtle)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">pokemon</span><span class="z-punctuation z-definition z-parameters z-constant">(wartortle)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">pokemon</span><span class="z-punctuation z-definition z-parameters z-constant">(blastoise)</span><span class="z-keyword">.</span></span></code></pre>
<p>In Prolog, we declare “predicates.”
Predicates define relationships: <code>bulbasaur</code> is a <code>pokemon</code>, <code>charmander</code> is a <code>pokemon</code>, and so on.
We refer to this predicate as <code>pokemon/1</code>, because the name of the predicate is <code>pokemon</code> and it has one argument.</p>
<p>These facts are loaded into an interactive prompt called the “top-level.”
You query the top-level by typing a statement into the prompt;
Prolog tries to find all the ways to make that statement true.
When there’s more than one possible solution, the top-level displays the first solution and then awaits user input.
You can then have it display one more solution, all the solutions, or stop entirely.</p>
<p>In this first example, we type <code>pokemon(squirtle).</code> and hit Enter.
The top-level replies <code>true.</code>
<a rel="external" href="https://bulbapedia.bulbagarden.net/wiki/Squirtle_(Pok%C3%A9mon)">Squirtle</a> is, in fact, a Pokémon.</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- pokemon(squirtle).</span></span>
<span class="giallo-l"><span> true.</span></span></code></pre><aside>
If you want to follow along at home, check out <a href="https://github.com/alexpetros/prologdex">the repo</a>, which has quick setup instructions.
</aside>
<p>Not all things are Pokémon.</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- pokemon(alex).</span></span>
<span class="giallo-l"><span> false.</span></span></code></pre>
<p>Let’s add Pokémon types in there, as the predicate <code>type/2</code>.</p>
<pre class="giallo z-code" data-lang="prolog"><code data-lang="prolog"><span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(bulbasaur, grass)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(bulbasaur, poison)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(ivysaur, grass)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(ivysaur, poison)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(venusaur, grass)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(venusaur, poison)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(charmander, fire)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(charmeleon, fire)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(charizard, fire)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(charizard, flying)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(squirtle, water)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(wartortle, water)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(blastoise, water)</span><span class="z-keyword">.</span></span></code></pre><aside>
The actual dataset includes different forms for Pokémon, like their Mega Evolutions, because they have different stats and typing. Those are omitted here for clarity.
</aside>
<p>Recall that some Pokémon have just one type while others have two.
In the latter case, that’s modeled with two <code>type</code> facts.
<a rel="external" href="https://bulbapedia.bulbagarden.net/wiki/Bulbasaur_(Pok%C3%A9mon)">Bulbasaur</a> is a Grass type, and Bulbasaur is a Poison type; both are true.
The paradigm is similar to a One-To-Many relation in a SQL database.</p>
<p>Interactively, we can confirm whether Squirtle is a water type.</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- type(squirtle, water).</span></span>
<span class="giallo-l"><span> true.</span></span></code></pre>
<p>Can we state that Squirtle is a Grass type?</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- type(squirtle, grass).</span></span>
<span class="giallo-l"><span> false.</span></span></code></pre>
<p>No, because Squirtle is a Water type.</p>
<p>Suppose we didn’t know what type Squirtle was.
We can ask!</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- type(squirtle, Type).</span></span>
<span class="giallo-l"><span> Type = water.</span></span></code></pre>
<p>In Prolog, names that start with an upper-case letter are variables.
Prolog tries to “unify” the predicate with all possible matches for the variable.
There’s only one way to make this particular predicate true though: <code>Type</code> has to be <code>water</code>, because Squirtle’s only type is Water.</p>
<p>For Pokémon with two types, the predicate unifies twice.</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- type(venusaur, Type).</span></span>
<span class="giallo-l"><span> Type = grass</span></span>
<span class="giallo-l"><span>; Type = poison.</span></span></code></pre>
<p>Semantically, that leading semicolon on the third line means “or.”
<code>type(venusaur, Type)</code> is true when <code>Type = grass</code> or when <code>Type = poison</code>.</p>
<p>Any of the terms can be be a variable, which means we can ask questions in any direction.
What are all the Grass types?
Just make the first argument the variable, and set the second argument to <code>grass</code>.</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- type(Pokemon, grass).</span></span>
<span class="giallo-l"><span> Pokemon = bulbasaur</span></span>
<span class="giallo-l"><span>; Pokemon = ivysaur</span></span>
<span class="giallo-l"><span>; Pokemon = venusaur</span></span>
<span class="giallo-l"><span>; Pokemon = oddish</span></span>
<span class="giallo-l"><span>; Pokemon = gloom</span></span>
<span class="giallo-l"><span>; Pokemon = vileplume</span></span>
<span class="giallo-l"><span>; Pokemon = paras</span></span>
<span class="giallo-l"><span>; Pokemon = parasect</span></span>
<span class="giallo-l"><span>; Pokemon = bellsprout</span></span>
<span class="giallo-l"><span>; ... .</span></span></code></pre><aside>
This output is the result of me entering the query, pressing "n" a couple of times to get the first few solutions, and then hitting Enter to exit.
</aside>
<p>I cut it off, but the prompt would happily would list all 164 of them.</p>
<p>Commas can be used to list multiple predicates—Prolog will unify the variables such that all of them are true.
Listing all the Water/Ice types is just a matter of asking what Pokémon exist that unify with both the Water and Ice types.</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- type(Pokemon, water), type(Pokemon, ice).</span></span>
<span class="giallo-l"><span> Pokemon = dewgong</span></span>
<span class="giallo-l"><span>; Pokemon = cloyster</span></span>
<span class="giallo-l"><span>; Pokemon = lapras</span></span>
<span class="giallo-l"><span>; Pokemon = laprasgmax</span></span>
<span class="giallo-l"><span>; Pokemon = spheal</span></span>
<span class="giallo-l"><span>; Pokemon = sealeo</span></span>
<span class="giallo-l"><span>; Pokemon = walrein</span></span>
<span class="giallo-l"><span>; Pokemon = arctovish</span></span>
<span class="giallo-l"><span>; Pokemon = ironbundle</span></span>
<span class="giallo-l"><span>; false.</span></span></code></pre><aside>
Don't be bothered with by the fact that the solutions end with "or false" here.
It's a function of how the search algorithms work; the solver looked for more solutions, then failed.
I'll admit, I don't totally understand why it only sometimes does this, but it's expected.
</aside>
<p>Even though <code>Pokemon</code> is a variable, in the context of the query, both instances of it have to be the same (just like in algebra).
The query only unifies for values of <code>Pokemon</code> where both those predicates hold.
For instance, the Water/Ice type <a rel="external" href="https://bulbapedia.bulbagarden.net/wiki/Dewgong_(Pok%C3%A9mon)">Dewgong</a> is a solution because our program contains the following two facts:</p>
<pre class="giallo z-code" data-lang="prolog"><code data-lang="prolog"><span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(dewgong, water)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">type</span><span class="z-punctuation z-definition z-parameters z-constant">(dewgong, ice)</span><span class="z-keyword">.</span></span></code></pre>
<p>Therefore, subbing in <code>dewgong</code> for the <code>Pokemon</code> variable satisfies the query.
Squirtle, by contrast, is just a Water type: <code>pokemon(squirtle, water)</code> exists, but not <code>pokemon(squirtle, ice)</code>.
The query requires both to unify, so <code>squirtle</code> is not a possible value for <code>Pokemon</code>.</p>
<p>Pokémon have lots of data that you can play around with.
<a rel="external" href="https://bulbapedia.bulbagarden.net/wiki/Iron_Bundle_(Pok%C3%A9mon)">Iron Bundle</a> is a strong Water/Ice-type Pokémon with high Special Attack.
How high exactly?</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- pokemon_spa(ironbundle, SpA).</span></span>
<span class="giallo-l"><span> SpA = 124.</span></span></code></pre>
<p>With Special Attack that high, we want to make use of strong Special moves.
What Special moves does Iron Bundle know?</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- learns(ironbundle, Move), move_category(Move, special).</span></span>
<span class="giallo-l"><span> Move = aircutter</span></span>
<span class="giallo-l"><span>; Move = blizzard</span></span>
<span class="giallo-l"><span>; Move = chillingwater</span></span>
<span class="giallo-l"><span>; Move = freezedry</span></span>
<span class="giallo-l"><span>; Move = hydropump</span></span>
<span class="giallo-l"><span>; Move = hyperbeam</span></span>
<span class="giallo-l"><span>; Move = icebeam</span></span>
<span class="giallo-l"><span>; Move = icywind</span></span>
<span class="giallo-l"><span>; Move = powdersnow</span></span>
<span class="giallo-l"><span>; Move = swift</span></span>
<span class="giallo-l"><span>; Move = terablast</span></span>
<span class="giallo-l"><span>; Move = waterpulse</span></span>
<span class="giallo-l"><span>; Move = whirlpool.</span></span></code></pre>
<p><span class=move>Freeze-Dry</span> is a particularly good Special move.
Here’s a query for all Ice-type Pokémon with Special Attack greater than 120 that learn <span class=move>Freeze-Dry</span>.</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- pokemon_spa(Pokemon, SpA), SpA #> 120, learns(Pokemon, freezedry), type(Pokemon, ice).</span></span>
<span class="giallo-l"><span> Pokemon = glaceon, SpA = 130</span></span>
<span class="giallo-l"><span>; Pokemon = kyurem, SpA = 130</span></span>
<span class="giallo-l"><span>; Pokemon = kyuremwhite, SpA = 170</span></span>
<span class="giallo-l"><span>; Pokemon = ironbundle, SpA = 124</span></span>
<span class="giallo-l"><span>; false.</span></span></code></pre>
<p>One last concept before we move on: Rules.
Rules have a head and a body, and they unify if the body is true.</p>
<p>A move is considered a damaging move if it’s either a Physical Move or a Special Move.
The <code>damaging_move/2</code> predicate defines all the moves that do direct damage.</p>
<pre class="giallo z-code" data-lang="prolog"><code data-lang="prolog"><span class="giallo-l"><span class="z-entity z-name z-function">damaging_move</span><span class="z-punctuation z-definition z-parameters">(</span><span class="z-variable z-parameter">Move</span><span class="z-punctuation z-definition z-parameters">)</span><span class="z-keyword"> :-</span></span>
<span class="giallo-l"><span> move_category(</span><span class="z-variable z-parameter">Move</span><span class="z-constant">, physical)</span></span>
<span class="giallo-l"><span>; move_category(</span><span class="z-variable z-parameter">Move</span><span class="z-constant">, special)</span><span class="z-keyword">.</span></span></code></pre>
<p>This will unify with any moves that do direct damage.</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- damaging_move(tackle).</span></span>
<span class="giallo-l"><span> true.</span></span>
<span class="giallo-l"><span>?- damaging_move(rest).</span></span>
<span class="giallo-l"><span> false.</span></span></code></pre><h2 id="sql-comparison"><a class="zola-anchor" href="#sql-comparison" aria-label="Anchor link for: sql-comparison">SQL comparison</a></h2>
<p>Nothing I’ve shown so far is, logically speaking, very ambitious—just “and” and “or” statements about various facts.
It’s essentially a glorified lookup table.
Still, take a moment to appreciate how much nicer it is to query this database than a plausible alternative, like SQL.</p>
<p>For the facts we’ve seen so far, I would probably set up SQL tables like this:</p>
<pre class="giallo z-code" data-lang="sql"><code data-lang="sql"><span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">-- Omitting the other stats to be concise</span></span>
<span class="giallo-l"><span class="z-keyword">CREATE TABLE</span><span class="z-entity z-name z-function"> pokemon</span><span> (pokemon_name </span><span class="z-storage">TEXT</span><span>, special_attack </span><span class="z-storage">INTEGER</span><span>);</span></span>
<span class="giallo-l"><span class="z-keyword">CREATE TABLE</span><span class="z-entity z-name z-function"> pokemon_types</span><span>(pokemon_name </span><span class="z-storage">TEXT</span><span>, </span><span class="z-keyword z-storage">type TEXT</span><span>);</span></span>
<span class="giallo-l"><span class="z-keyword">CREATE TABLE</span><span class="z-entity z-name z-function"> pokemon_moves</span><span>(pokemon_name </span><span class="z-storage">TEXT</span><span>, </span><span class="z-keyword z-storage">move TEXT</span><span>, category </span><span class="z-storage">TEXT</span><span>);</span></span></code></pre><aside>
It might look contrived to put types in their own table rather than <code>type_1</code>
and <code>type_2</code>. It depends on the use-case, but since it doesn't make a difference which type comes first, you'd have to check both slots each time for a specific type, which I find more error-prone.
</aside>
<p>Then query it like so:</p>
<pre class="giallo z-code" data-lang="sql"><code data-lang="sql"><span class="giallo-l"><span class="z-keyword">SELECT DISTINCT</span><span> pokmeon, special_attack</span></span>
<span class="giallo-l"><span class="z-keyword">FROM</span><span> pokemon </span><span class="z-keyword">as</span><span> p</span></span>
<span class="giallo-l"><span class="z-keyword">WHERE</span></span>
<span class="giallo-l"><span class="z-constant z-keyword z-operator"> p.special_attack ></span><span class="z-constant z-numeric"> 120</span></span>
<span class="giallo-l"><span class="z-keyword"> AND EXISTS</span><span> (</span></span>
<span class="giallo-l"><span class="z-keyword"> SELECT</span><span class="z-constant z-numeric"> 1</span></span>
<span class="giallo-l"><span class="z-keyword"> FROM</span><span> pokemon_moves </span><span class="z-keyword">as</span><span> pm</span></span>
<span class="giallo-l"><span class="z-keyword"> WHERE</span><span class="z-constant z-keyword z-operator"> p.pokemon_name = pm.pokemon_name</span><span class="z-keyword"> AND move</span><span class="z-keyword z-operator z-punctuation z-definition z-string"> = '</span><span class="z-string">freezedry</span><span class="z-punctuation z-definition z-string">'</span></span>
<span class="giallo-l"><span> )</span></span>
<span class="giallo-l"><span class="z-keyword"> AND EXISTS</span><span> (</span></span>
<span class="giallo-l"><span class="z-keyword"> SELECT</span><span class="z-constant z-numeric"> 1</span></span>
<span class="giallo-l"><span class="z-keyword"> FROM</span><span> pokemon_types </span><span class="z-keyword">as</span><span> pt</span></span>
<span class="giallo-l"><span class="z-keyword"> WHERE</span><span class="z-constant z-keyword z-operator"> p.pokemon_name = pt.pokemon_name</span><span class="z-keyword"> AND type</span><span class="z-keyword z-operator z-punctuation z-definition z-string"> = '</span><span class="z-string">ice</span><span class="z-punctuation z-definition z-string">'</span></span>
<span class="giallo-l"><span> );</span></span></code></pre>
<p>For comparison, here’s the equivalent Prolog query again:</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- pokemon_spa(Pokemon, SpA),</span></span>
<span class="giallo-l"><span>SpA #> 120,</span></span>
<span class="giallo-l"><span>learns(Pokemon, freezedry),</span></span>
<span class="giallo-l"><span>type(Pokemon, ice).</span></span></code></pre>
<p>I’m not ripping on SQL—I love SQL—but that’s the best declarative query language most people interact with.
It’s amazing to me how much simpler and more flexible the Prolog version is.
The SQL query would become unmanageably complex if we continued to add clauses, while the Prolog query remains easy to read and edit (once you get the hang of how variables work).</p>
<h2 id="level-up"><a class="zola-anchor" href="#level-up" aria-label="Anchor link for: level-up">Level up</a></h2>
<p>With the basics established, here’s some context on the project I’m working on.</p>
<p>Pokémon battles have an outrageous number of mechanics that all interact in complex and probabilistic ways.
Part of the appeal of these games is the futile attempt to keep them all in your head better than your opponent, using that information to out-predict and out-maneuver their plans.
It’s a sort of like very silly Poker.</p>
<details>
<summary>A small subset of game mechanics I have not yet mentioned</summary>
<ul>
<li> Some moves miss a certain percentage of the time, doing no damage.
<li> Some moves raise or lower a Pokémon's stats.
<li> Pokémon can hold items that have various effects.
<li> Damage calculations aren't constant; moves do normally-distributed damage within the calculated range.
<li> Pokémon can get frozen, burned, paralyzed, poisoned, or fall asleep; these all have various adverse effects.
<li> There are a variety of field effects (like weather, terrain, Trick Room) which alter move damage, turn order, and other things.
<li> Pokémon each have an ability that has various effects i.e Levitate makes you immune to ground moves, Drizzle turns the weather to Rain when the Pokemon switches in, Sheer Force disables a move's side effects but multiplies its damage by 1.3x.
<li> Players have points they (invisibly) allocate to each Pokémon before the game, to boost chosen stats. Depending on they built the team, each Pokemon might do more damage or take hits better than you were expecting.
</ul>
</details>
<p>The challenge, if you want to build software for this game, is to model all that complexity without losing your mind.
Prolog is stunningly good at this, for two main reasons:</p>
<aside>Take a look at the <a href="https://calc.pokemonshowdown.com/">damage calculator</a> to get an idea of what I mean.</aside>
<ol>
<li>The query model excels at describing ad-hoc combinations.</li>
<li>The data model is perfectly suited to layering rules in a consistent way.</li>
</ol>
<aside>
The logicians in the audience would probably like me to note that the query model and the data model are, in fact, the exact same.
</aside>
<p>To illustrate that, here’s how I implemented priority moves for my Pokémon draft league.</p>
<p>Pokémon draft is pretty much what it sounds like.
Pokémon are given a point value based on how good they are, each player is given a certain amount of points to spend, and you draft until every player has spent their points.
Your team ends up with about 8-11 Pokémon and each week you go head to head against another person in the league.
My friend and <a rel="external" href="https://wemakeinter.net/">WMI</a> collaborator <a rel="external" href="https://wttdotm.com/">Morry</a> invited me to his a couple years ago and I’ve been hooked on the format ever since.</p>
<p>The games are 6v6, so a big part of the battle is preparing for all the possible combinations of six your opponent could bring, and putting together six of your own that can handle all of them.</p>
<p>Naturally, you can only build teams with the Pokémon you drafted.
I just made that predicate my name: <code>alex/1</code>.</p>
<pre class="giallo z-code" data-lang="prolog"><code data-lang="prolog"><span class="giallo-l"><span class="z-entity z-name z-function">alex</span><span class="z-punctuation z-definition z-parameters z-constant">(meowscarada)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">alex</span><span class="z-punctuation z-definition z-parameters z-constant">(weezinggalar)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">alex</span><span class="z-punctuation z-definition z-parameters z-constant">(swampertmega)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">alex</span><span class="z-punctuation z-definition z-parameters z-constant">(latios)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">alex</span><span class="z-punctuation z-definition z-parameters z-constant">(volcarona)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">alex</span><span class="z-punctuation z-definition z-parameters z-constant">(tornadus)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">alex</span><span class="z-punctuation z-definition z-parameters z-constant">(politoed)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">alex</span><span class="z-punctuation z-definition z-parameters z-constant">(archaludon)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">alex</span><span class="z-punctuation z-definition z-parameters z-constant">(beartic)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">alex</span><span class="z-punctuation z-definition z-parameters z-constant">(dusclops)</span><span class="z-keyword">.</span></span></code></pre><aside>
I'm not proud of having drafted a rain team.
This is probably the worst draft I've had since I started;
it just happens sometimes.
</aside>
<p>What Pokémon do I have that learn <span class=move>Freeze-Dry</span>?</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- alex(Pokemon), learns(Pokemon, freezedry).</span></span>
<span class="giallo-l"><span> false.</span></span></code></pre>
<p>None. Rats.</p>
<p>One very important type of move is priority moves.
Earlier I mentioned that the Speed stat controls which Pokémon moves first.
Some nuance: the Pokémon that used the move with the highest priority goes first, and if they both selected a move of the same priority, then the one with the higher Speed goes first.</p>
<p>Most moves have a priority of zero.</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- move_priority(Move, P).</span></span>
<span class="giallo-l"><span> Move = '10000000voltthunderbolt', P = 0</span></span>
<span class="giallo-l"><span>; Move = absorb, P = 0</span></span>
<span class="giallo-l"><span>; Move = accelerock, P = 1</span></span>
<span class="giallo-l"><span>; Move = acid, P = 0</span></span>
<span class="giallo-l"><span>; Move = acidarmor, P = 0</span></span>
<span class="giallo-l"><span>; Move = aciddownpour, P = 0</span></span>
<span class="giallo-l"><span>; Move = acidspray, P = 0</span></span>
<span class="giallo-l"><span>; Move = acrobatics, P = 0</span></span>
<span class="giallo-l"><span>; Move = acupressure, P = 0</span></span>
<span class="giallo-l"><span>; Move = aerialace, P = 0</span></span>
<span class="giallo-l"><span>; Move = aeroblast, P = 0</span></span></code></pre>
<p>Ah, but not all! <span class="move">Accelerock</span> has a priority of 1.
A Pokémon that uses <span class="move">Accelerock</span> will move before
any Pokémon that uses a move with priority 0 (or less), even if the latter Pokémon has a higher Speed stat.</p>
<p>I define a <code>learns_priority/3</code> predicate that unifies with a Pokémon, the priority move it learns, and what priority that move is.</p>
<pre class="giallo z-code" data-lang="prolog"><code data-lang="prolog"><span class="giallo-l"><span class="z-entity z-name z-function">learns_priority</span><span class="z-punctuation z-definition z-parameters">(</span><span class="z-variable z-parameter">Pokemon</span><span>, </span><span class="z-variable z-parameter">Move</span><span>, </span><span class="z-variable z-parameter">P</span><span class="z-punctuation z-definition z-parameters">)</span><span class="z-keyword"> :-</span></span>
<span class="giallo-l"><span> learns(</span><span class="z-variable z-parameter">Pokemon</span><span>,</span><span class="z-variable z-parameter"> Move</span><span>),</span></span>
<span class="giallo-l"><span> move_priority(</span><span class="z-variable z-parameter">Move</span><span>,</span><span class="z-variable z-parameter"> P</span><span>),</span></span>
<span class="giallo-l"><span class="z-constant z-keyword z-operator"> move_priority #></span><span class="z-constant z-numeric"> 0</span><span class="z-keyword">.</span></span></code></pre>
<p>A simple query that asks “what priority moves does my team learn” returns a <em>lot</em> of answers.</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- alex(Pokemon), learns_priority(Pokemon, Move, Priority).</span></span>
<span class="giallo-l"><span> Pokemon = meowscarada, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = meowscarada, Move = helpinghand, Priority = 5</span></span>
<span class="giallo-l"><span>; Pokemon = meowscarada, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = meowscarada, Move = quickattack, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = meowscarada, Move = allyswitch, Priority = 2</span></span>
<span class="giallo-l"><span>; Pokemon = meowscarada, Move = suckerpunch, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = weezinggalar, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = weezinggalar, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = swampertmega, Move = bide, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = swampertmega, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = swampertmega, Move = helpinghand, Priority = 5</span></span>
<span class="giallo-l"><span>; Pokemon = swampertmega, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = swampertmega, Move = wideguard, Priority = 3</span></span>
<span class="giallo-l"><span>; Pokemon = latios, Move = allyswitch, Priority = 2</span></span>
<span class="giallo-l"><span>; Pokemon = latios, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = latios, Move = helpinghand, Priority = 5</span></span>
<span class="giallo-l"><span>; Pokemon = latios, Move = magiccoat, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = latios, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = volcarona, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = volcarona, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = volcarona, Move = ragepowder, Priority = 2</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = politoed, Move = detect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = politoed, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = politoed, Move = helpinghand, Priority = 5</span></span>
<span class="giallo-l"><span>; Pokemon = politoed, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = politoed, Move = bide, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = archaludon, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = archaludon, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = beartic, Move = aquajet, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = beartic, Move = bide, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = beartic, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = beartic, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = allyswitch, Priority = 2</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = helpinghand, Priority = 5</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = shadowsneak, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = snatch, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = suckerpunch, Priority = 1</span></span>
<span class="giallo-l"><span>; false.</span></span></code></pre>
<p>Although this is technically correct (the best kind), most of these answers are not actually useful.
<span class="move">Helping Hand</span> and <span class="move">Ally Switch</span> have very high priority, but they only have a purpose in Double Battles, which isn’t the format I’m playing.</p>
<p>To fix this, I define all the Double Battle moves and exclude them.
I’m going to exclude the move <span class=move>Bide</span> too, which is functionally useless. The <code>\+/1</code> predicate means “true if this goal fails”, and <code>dif/2</code> means “these two terms are different.”</p>
<pre class="giallo z-code" data-lang="plain"><code data-lang="plain"><span class="giallo-l"><span>learns_priority(Mon, Move, Priority) :-</span></span>
<span class="giallo-l"><span> learns(Mon, Move),</span></span>
<span class="giallo-l"><span> \+ doubles_move(Move),</span></span>
<span class="giallo-l"><span> dif(Move, bide),</span></span>
<span class="giallo-l"><span> move_priority(Move, Priority),</span></span>
<span class="giallo-l"><span> Priority #> 0.</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>doubles_move(helpinghand).</span></span>
<span class="giallo-l"><span>doubles_move(afteryou).</span></span>
<span class="giallo-l"><span>doubles_move(quash).</span></span>
<span class="giallo-l"><span>doubles_move(allyswitch).</span></span>
<span class="giallo-l"><span>doubles_move(followme).</span></span>
<span class="giallo-l"><span>doubles_move(ragepowder).</span></span>
<span class="giallo-l"><span>doubles_move(aromaticmist).</span></span>
<span class="giallo-l"><span>doubles_move(holdhands).</span></span>
<span class="giallo-l"><span>doubles_move(spotlight).</span></span></code></pre>
<p>We get the following results:</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- alex(Pokemon), learns_priority(Pokemon, Move, Priority).</span></span>
<span class="giallo-l"><span> Pokemon = meowscarada, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = meowscarada, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = meowscarada, Move = quickattack, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = meowscarada, Move = suckerpunch, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = weezinggalar, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = weezinggalar, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = swampertmega, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = swampertmega, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = latios, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = latios, Move = magiccoat, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = latios, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = volcarona, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = volcarona, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = politoed, Move = detect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = politoed, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = politoed, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = archaludon, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = archaludon, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = beartic, Move = aquajet, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = beartic, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = beartic, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = endure, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = protect, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = shadowsneak, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = snatch, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = suckerpunch, Priority = 1</span></span>
<span class="giallo-l"><span>; false.</span></span></code></pre><aside>
Not captured by this write-up is just how <em>fast</em> these results come out.
</aside>
<p>Much better, but there’s a handful of moves in there that go first because they
protect the user from damage or status, like <span class=move>Detect</span>.
That’s not really what I mean by priority move—I’m interested in moves that will surprise my opponent with damage or an adverse side effect, like <span class="move">Quick Attack</span> and <span class="move">Sucker Punch</span>.</p>
<pre class="giallo z-code" data-lang="prolog"><code data-lang="prolog"><span class="giallo-l"><span class="z-entity z-name z-function">learns_priority</span><span class="z-punctuation z-definition z-parameters">(</span><span class="z-variable z-parameter">Mon</span><span>, </span><span class="z-variable z-parameter">Move</span><span>, </span><span class="z-variable z-parameter">Priority</span><span class="z-punctuation z-definition z-parameters">)</span><span class="z-keyword"> :-</span></span>
<span class="giallo-l"><span> learns(</span><span class="z-variable z-parameter">Mon</span><span>,</span><span class="z-variable z-parameter"> Move</span><span>),</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> \+ doubles_move(</span><span class="z-variable z-parameter">Move</span><span>),</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> \+ protection_move(</span><span class="z-variable z-parameter">Move</span><span>),</span></span>
<span class="giallo-l"><span class="z-variable z-parameter"> Move</span><span class="z-keyword z-operator z-constant"> \= bide,</span></span>
<span class="giallo-l"><span> move_priority(</span><span class="z-variable z-parameter">Move</span><span>,</span><span class="z-variable z-parameter"> Priority</span><span>),</span></span>
<span class="giallo-l"><span class="z-variable z-parameter"> Priority</span><span class="z-keyword z-operator"> #></span><span class="z-constant z-numeric"> 0</span><span class="z-keyword">.</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-name z-function">protection_move</span><span class="z-punctuation z-definition z-parameters z-constant">(detect)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">protection_move</span><span class="z-punctuation z-definition z-parameters z-constant">(protect)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">protection_move</span><span class="z-punctuation z-definition z-parameters z-constant">(kingsshield)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">protection_move</span><span class="z-punctuation z-definition z-parameters z-constant">(burningbulwark)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">protection_move</span><span class="z-punctuation z-definition z-parameters z-constant">(spikyshield)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">protection_move</span><span class="z-punctuation z-definition z-parameters z-constant">(banefulbunker)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">protection_move</span><span class="z-punctuation z-definition z-parameters z-constant">(endure)</span><span class="z-keyword">.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">protection_move</span><span class="z-punctuation z-definition z-parameters z-constant">(magiccoat)</span><span class="z-keyword">.</span></span></code></pre><!-- <aside> -->
<!-- What is <code>Move \= bide</code> doing in there? -->
<!-- <span class="move">Bide</span> is technically a damage-dealing move but it's unusable in practice. -->
<!-- It's actually illegal in the current generation games but we're playing with the National Dex movesets. -->
<!-- </aside> -->
<p>With those rules in place, we arrive at a very useful answer!</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- alex(Pokemon), learns_priority(Pokemon, Move, Priority).</span></span>
<span class="giallo-l"><span> Pokemon = meowscarada, Move = quickattack, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = meowscarada, Move = suckerpunch, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = beartic, Move = aquajet, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = shadowsneak, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = snatch, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = suckerpunch, Priority = 1</span></span>
<span class="giallo-l"><span>; false.</span></span></code></pre><aside>
The lack of priority moves is actually a big weakness in my draft.
Dusclops and Beartic are highly situational so they don't usually come to battles.
</aside>
<p>It’s even more useful to look up what priority moves my <em>opponent</em> for the week has.</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- morry(Pokemon), learns_priority(Pokemon, Move, Priority).</span></span>
<span class="giallo-l"><span> Pokemon = mawilemega, Move = snatch, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = mawilemega, Move = suckerpunch, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = walkingwake, Move = aquajet, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = ursaluna, Move = babydolleyes, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = lokix, Move = feint, Priority = 2</span></span>
<span class="giallo-l"><span>; Pokemon = lokix, Move = firstimpression, Priority = 2</span></span>
<span class="giallo-l"><span>; Pokemon = lokix, Move = suckerpunch, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = alakazam, Move = snatch, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = skarmory, Move = feint, Priority = 2</span></span>
<span class="giallo-l"><span>; Pokemon = froslass, Move = iceshard, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = froslass, Move = snatch, Priority = 4</span></span>
<span class="giallo-l"><span>; Pokemon = froslass, Move = suckerpunch, Priority = 1</span></span>
<span class="giallo-l"><span>; Pokemon = dipplin, Move = suckerpunch, Priority = 1.</span></span></code></pre>
<p>At this point, I showed the program to Morry and he hit me with a challenge.
Pokémon with the Prankster ability get an additional +1 priority on their status moves.
Could the rule be extended to note that?</p>
<p>I happen to have one such Pokémon on my team.</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- alex(Pokemon), pokemon_ability(Pokemon, prankster).</span></span>
<span class="giallo-l"><span> Pokemon = tornadus</span></span>
<span class="giallo-l"><span>; false.</span></span></code></pre>
<p>This took me 3 minutes, using Prolog’s if/then construct, <code>->/2</code>.</p>
<pre class="giallo z-code" data-lang="prolog"><code data-lang="prolog"><span class="giallo-l"><span class="z-entity z-name z-function">learns_priority</span><span class="z-punctuation z-definition z-parameters">(</span><span class="z-variable z-parameter">Mon</span><span>, </span><span class="z-variable z-parameter">Move</span><span>, </span><span class="z-variable z-parameter">Priority</span><span class="z-punctuation z-definition z-parameters">)</span><span class="z-keyword"> :-</span></span>
<span class="giallo-l"><span> learns(</span><span class="z-variable z-parameter">Mon</span><span>,</span><span class="z-variable z-parameter"> Move</span><span>),</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> \+ doubles_move(</span><span class="z-variable z-parameter">Move</span><span>),</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> \+ protection_move(</span><span class="z-variable z-parameter">Move</span><span>),</span></span>
<span class="giallo-l"><span class="z-variable z-parameter"> Move</span><span class="z-keyword z-operator z-constant"> \= bide,</span></span>
<span class="giallo-l"><span> move_priority(</span><span class="z-variable z-parameter">Move</span><span>,</span><span class="z-variable z-parameter"> BasePriority</span><span>),</span></span>
<span class="giallo-l"><span> (</span></span>
<span class="giallo-l"><span> pokemon_ability(</span><span class="z-variable z-parameter">Mon</span><span class="z-constant">, prankster), move_category(</span><span class="z-variable z-parameter">Move</span><span class="z-constant">, status)</span><span class="z-keyword"> -></span></span>
<span class="giallo-l"><span class="z-entity z-name z-function"> Priority</span><span> #= </span><span class="z-variable z-parameter">BasePriority</span><span> + </span><span class="z-constant z-numeric">1</span></span>
<span class="giallo-l"><span> ; </span><span class="z-variable z-parameter">Priority</span><span> #= </span><span class="z-variable z-parameter">BasePriority</span></span>
<span class="giallo-l"><span> ),</span></span>
<span class="giallo-l"><span class="z-variable z-parameter"> Priority</span><span> #> </span><span class="z-constant z-numeric">0</span><span class="z-keyword">.</span></span></code></pre><aside>
I basically know how I would express this in SQL, but I am happy I don't have to!
</aside>
<p>Now the same query includes all of Tornadus’ status moves, with their increased priority.</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- alex(Pokemon), learns_priority(Pokemon, Move, P).</span></span>
<span class="giallo-l"><span> Pokemon = meowscarada, Move = quickattack, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = meowscarada, Move = suckerpunch, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = agility, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = attract, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = bulkup, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = confide, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = defog, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = doubleteam, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = embargo, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = leer, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = metronome, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = nastyplot, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = raindance, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = rest, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = roleplay, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = sandstorm, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = scaryface, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = sleeptalk, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = snowscape, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = substitute, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = sunnyday, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = swagger, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = tailwind, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = taunt, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = torment, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = tornadus, Move = toxic, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = beartic, Move = aquajet, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = shadowsneak, P = 1</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = snatch, P = 4</span></span>
<span class="giallo-l"><span>; Pokemon = dusclops, Move = suckerpunch, P = 1</span></span>
<span class="giallo-l"><span>; false.</span></span></code></pre><h2 id="there-s-something-about-spreadsheets"><a class="zola-anchor" href="#there-s-something-about-spreadsheets" aria-label="Anchor link for: there-s-something-about-spreadsheets">There’s something about spreadsheets</a></h2>
<p>At the top, I said that this experience had taught me about the kinds of interfaces I want to build.
One of those lessons is fairly obvious: Prolog can be a little clunky, but it’s an elegant language for expressing and querying relations like the ones described here.
That has implications if you, like me, are interested in the <a rel="external" href="https://alexanderpetros.com/triptych/">judicious use of declarative DSLs</a> for programming.</p>
<p>The other lesson is what kinds of tools work for <em>non</em>-programmers.</p>
<p>I’m not the first person to think “it would be nice to know what priority moves my opponent’s team has.”
The Pokémon community has resources like this, built in the best programming interface of all time: the humble spreadsheet.</p>
<figure>
<img src="./images/matchup-sheet.png" alt="A screenshot of a spreadsheet listing Pokémon that know various useful moves.">
<figcaption>Much prettier to look at, too.</figcaption>
</figure>
<p>I use a copy of <a rel="external" href="https://www.smogon.com/forums/threads/draft-league-resources.3716128/">“Techno’s Prep Doc”</a>, which is one of those spectacularly-advanced Google Sheets you come across in the wild sometimes.
You put in the teams and it generates tons of useful information about the matchup.
It has a great interface, support for a variety of formats, scannable visuals, and even auto-complete.</p>
<p>I was curious about the formula for finding priority moves.
It’s gnarly.</p>
<pre class="giallo z-code" data-lang="plain"><code data-lang="plain"><span class="giallo-l"><span>={IFERROR(ARRAYFORMULA(VLOOKUP(FILTER(INDIRECT(Matchup!$S$3&"!$AV$4:$AV"),INDIRECT(Matchup!$S$3&"!$AT$4:$AT")="X"),{Backend!$L$2:$L,Backend!$F$2:$F},2,FALSE))),IFERROR(FILTER(INDIRECT(Matchup!$S$3&"!$AW$4:$AW"),INDIRECT(Matchup!$S$3&"!$AT$4:$AT")="X"))}</span></span></code></pre>
<p>With a little bit of clicking around, I was basically able to figure out what this does.
There’s a “Backend” sheet that lists all the moves.
It’s effectively a hard-coded version of my Prolog query.</p>
<figure>
<img src="./images/priority-backend-list.png" alt="">
</figure>
<p>The lookup formula does some filtering, VLOOKUP-ing, and kinda-metaprogramming (INDIRECT <a rel="external" href="https://support.google.com/docs/answer/3093377?hl=en">returns a cell reference</a>) to find all the Pokémon on your team that are in that Backend list, and display them.</p>
<p>There are a number of reasons that I, personally, would prefer to work on a version of this database implemented in Prolog instead of one implemented with spreadsheet VLOOKUPs.
I plan to build webapps with this that do things the existing suite of Pokémon tooling can’t. (If I can ever <a rel="external" href="https://github.com/mthom/scryer-prolog/issues/3196">get scryer-prolog to compile to WASM</a>, that is.)</p>
<p>Furthermore, the Prolog paradigm is clearly more extensible.
The spreadsheet backend is a hard-coded list of notable moves;
my database can look up <em>any</em> move.
I still can’t really believe this query, which finds all the Special moves that Tornadus learns which are super-effective against any member of Justin’s team.
Nothing like that exists in any tool that I know of—it’s the kind of thing I normally try to figure out by endlessly switching tabs.
With the grammar established by my program, I put this together in like 30 seconds.</p>
<pre class="giallo z-code" data-lang="plain" data-name="top-level"><code data-lang="plain" data-name="top-level"><span class="giallo-l"><span>?- justin(Target), learns(tornadus, Move), super_effective_move(Move, Target), move_category(Move, special).</span></span>
<span class="giallo-l"><span> Target = charizardmegay, Move = chillingwater</span></span>
<span class="giallo-l"><span>; Target = terapagosterastal, Move = focusblast</span></span>
<span class="giallo-l"><span>; Target = alomomola, Move = grassknot</span></span>
<span class="giallo-l"><span>; Target = scizor, Move = heatwave</span></span>
<span class="giallo-l"><span>; Target = scizor, Move = incinerate</span></span>
<span class="giallo-l"><span>; Target = runerigus, Move = chillingwater</span></span>
<span class="giallo-l"><span>; Target = runerigus, Move = darkpulse</span></span>
<span class="giallo-l"><span>; Target = runerigus, Move = grassknot</span></span>
<span class="giallo-l"><span>; Target = runerigus, Move = icywind</span></span>
<span class="giallo-l"><span>; Target = screamtail, Move = sludgebomb</span></span>
<span class="giallo-l"><span>; Target = screamtail, Move = sludgewave</span></span>
<span class="giallo-l"><span>; Target = trapinch, Move = chillingwater</span></span>
<span class="giallo-l"><span>; Target = trapinch, Move = grassknot</span></span>
<span class="giallo-l"><span>; Target = trapinch, Move = icywind</span></span>
<span class="giallo-l"><span>; false.</span></span>
<span class="giallo-l"><span>?-</span></span></code></pre><aside>
How I managed to encode <a href="https://github.com/alexpetros/prologdex/blob/main/db/type-chart.pl">the type chart</a> is probably worthy of its own blog post.
</aside>
<p>I’m not interested in how structured programming is more extensible than spreadsheets, though.
I already know why I don’t do all my programming in spreadsheets.</p>
<pre class="giallo z-code" data-lang="plain"><code data-lang="plain"><span class="giallo-l"><span>={IFERROR(ARRAYFORMULA(VLOOKUP(FILTER(INDIRECT(Matchup!$S$3&"!$AV$4:$AV"),INDIRECT(Matchup!$S$3&"!$AT$4:$AT")="X"),{Backend!$L$2:$L,Backend!$F$2:$F},2,FALSE))),IFERROR(FILTER(INDIRECT(Matchup!$S$3&"!$AW$4:$AW"),INDIRECT(Matchup!$S$3&"!$AT$4:$AT")="X"))}</span></span></code></pre>
<p>A question I find very important is: What is it about this particular problem, and the kinds of people who were motivated to solve it, where the most well-maintained solution available is a spreadsheet?</p>
<aside>
When I say it's the most well-maintained: Techno's prep doc supports Pokémon that aren't even <em>released</em> yet.
</aside>
<p>I believe there are a great many problems like that in the world, and a lot of improvements on that programming paradigm yet to be properly realized.</p>
<p><em>Thanks to <a rel="external" href="https://wttdotm.com">Morry Kolman</a> for reading a draft of this blog</em>.</p>
<h1 id="notes"><a class="zola-anchor" href="#notes" aria-label="Anchor link for: notes">Notes</a></h1>
<ul>
<li>I joined the draft league in Season 3, lost in finals, then won Seasons 4 and 5. We just started Season 6. <a rel="external" href="https://www.youtube.com/watch?v=jMkhsamX-2I">If you want it, you can have the crown</a>.</li>
<li>There are a number of coders in this draft league and I have gotten precisely zero of them to try out my Prolog program. That’s kind of the point though! It needs to be a website…</li>
<li>The Prolog implementation I’m using is <a rel="external" href="https://www.scryer.pl/">Scryer Prolog</a>, a modern Prolog implementation that emphasizes standards and formal correctness. The creator, Markus Triska, has a terrific online book, <a rel="external" href="https://www.metalevel.at/prolog">“The Power of Prolog,”</a> and accompanying <a rel="external" href="https://www.youtube.com/@ThePowerOfProlog">YouTube channel</a> that has soundtracked my breakfast for weeks.</li>
<li>Scryer Prolog is also designed to encourage more constructs that <a rel="external" href="https://www.youtube.com/watch?v=6G-3DqyJ_l8">preserve logical completeness and monotonicity</a>, which means I’m not really supposed to use the <code>\+/2</code> or <code>->/2</code> predicates. I couldn’t really figure out how to express what I wanted with the replacements offered, though. Happy to edit if anyone wants to help.</li>
<li>Also, <a rel="external" href="https://www.metalevel.at/">on Markus’ website</a>: “My goal is to provide programs that work as intended, reliably and conveniently, with zero surprises. Programs that you can run for multiple decades without any issues such as crashes, resource leaks or other unexpected behaviour.” This guy and I have some <a href="https://unplannedobsolescence.com/talks/building-the-hundred-year-web-service/">similar interests!</a></li>
<li>I did <a rel="external" href="https://github.com/alexpetros/prologdex/blob/6fd8d2ed1e7f9e35f36b76dd60bd2535f70f5164/scripts/generate-dex.js">some fun metaprogrogramming</a> to get all the data into Prolog predicates using the <a rel="external" href="https://github.com/smogon/pokemon-showdown">Pokémon Showdown</a> NodeJS API.</li>
<li>Yes, putting the accent on the “e” everywhere but the code blocks was very annoying.</li>
</ul>
What Dynamic Typing Is For2025-10-12T00:00:00+00:002025-10-12T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/blog/what-dynamic-typing-is-for/<style>
.languages {
grid-template-columns: 1fr 1fr fit-content(60%);
}
</style>
<p><em>Unplanned Obsolescence</em> is a blog is about writing maintainable, long-lasting software.
It also frequently touts—or is, at the very least, not inherently hostile to—writing software in dynamically-typed programming languages.</p>
<p>These two positions are somewhat at odds.</p>
<h2 id="static-typing-is-better-for-maintainability"><a class="zola-anchor" href="#static-typing-is-better-for-maintainability" aria-label="Anchor link for: static-typing-is-better-for-maintainability">Static typing is better for maintainability</a></h2>
<p>Dynamically-typed languages encode less information.
That’s a problem for the person reading the code and trying to figure out what it does.</p>
<p>This is a simplified version of an authentication middleware that I include in most of my web services:
it checks an HTTP request to see if it corresponds to a logged-in user’s session.</p>
<pre class="giallo z-code" data-lang="javascript"><code data-lang="javascript"><span class="giallo-l"><span class="z-storage">function</span><span class="z-entity z-name z-function"> authenticate</span><span class="z-punctuation z-definition z-parameters">(</span><span class="z-variable z-parameter">req</span><span class="z-punctuation z-definition z-parameters">) {</span></span>
<span class="giallo-l"><span class="z-storage"> const</span><span class="z-keyword z-operator z-punctuation z-definition z-string"> cookieToken = req.cookies['</span><span class="z-string">token</span><span class="z-punctuation z-definition z-string">']</span></span>
<span class="giallo-l"><span class="z-storage"> const</span><span class="z-keyword z-operator"> user = req.db.</span><span class="z-entity z-name z-function">get_user_by_token</span><span>(cookieToken)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-keyword"> if</span><span> (user) {</span></span>
<span class="giallo-l"><span class="z-keyword"> return</span><span> user</span></span>
<span class="giallo-l"><span> }</span><span class="z-keyword"> else</span><span> {</span></span>
<span class="giallo-l"><span class="z-keyword"> throw</span><span class="z-keyword z-operator"> new</span><span class="z-entity z-name z-function"> AuthorizationError</span><span>()</span></span>
<span class="giallo-l"><span> }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Pretty straightforward stuff.
The function gets a <code>token</code> cookie from the HTTP request, checks the database to see if that token corresponds to a user, and then returns the user if it does.
Line 2 fetches the cookie from the request, line 3 gets the user from the database, and the rest either returns the user or throw an error.</p>
<!-- For instance, what type is `user`? -->
<!-- If you want the user's database ID, would that be `user.id` or `user.user_id`? -->
<!-- To answer this question you need to look at the implementation of `db.get_user_by_token`, and now we're entering a rabbit hole. -->
<p>There are, however, some problems with this.
What happens if there’s no <code>token</code> cookie included in the HTTP request?
Will it return <code>undefined</code> or an empty string?
Will <code>req.cookies</code> even exist if there’s no cookies at all?
There’s no way to know without looking at the implementation (or, less reliably, the documentation).</p>
<p>That doesn’t mean there isn’t an answer!
A request with no <code>token</code> cookie <a rel="external" href="https://expressjs.com/en/4x/api.html#req.cookies">will return <code>undefined</code></a>.
That results in a <code>get_user_by_token(undefined)</code> call, which returns <code>undefined</code> (the function checks for that).
<code>undefined</code> is a <a rel="external" href="https://developer.mozilla.org/en-US/docs/Glossary/Falsy">falsy</a> value in JavaScript, so the conditional evaluates to false and throws an <code>AuthorizationError</code>.</p>
<p>The code works and it’s very readable, but you have to do a fair amount of digging to ensure that it works reliably.
That’s a cost that gets paid in the future, anytime the “missing token” code path needs to be understood or modified.
That cost reduces the maintainability of the service.</p>
<p>Unsurprisingly, the equivalent Rust code is much more explicit.</p>
<pre class="giallo z-code" data-lang="rust"><code data-lang="rust"><span class="giallo-l"><span class="z-keyword">fn</span><span class="z-entity z-name z-function"> authenticate</span><span class="z-keyword z-operator">(req: Request) -> AuthStatus {</span></span>
<span class="giallo-l"><span class="z-storage"> let</span><span class="z-keyword z-operator"> cookie_token =</span><span class="z-keyword"> match</span><span class="z-keyword z-operator"> req.cookies.</span><span class="z-entity z-name z-function">get</span><span class="z-punctuation z-definition z-string">("</span><span class="z-string">token</span><span class="z-punctuation z-definition z-string">") {</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> Some(token) => token,</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> None =></span><span class="z-keyword"> return</span><span class="z-keyword z-operator"> AuthStatus::</span><span class="z-entity z-name z-function">Failure</span><span class="z-punctuation z-definition z-string">("</span><span class="z-string">Token not included in request</span><span class="z-punctuation z-definition z-string">")</span></span>
<span class="giallo-l"><span> };</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-keyword"> match</span><span class="z-keyword z-operator"> req.db.</span><span class="z-entity z-name z-function">get_user_by_token</span><span>(cookie_token) {</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> Some(user) => AuthStatus::</span><span class="z-entity z-name z-function">Success</span><span>(user),</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> None => AuthStatus::</span><span class="z-entity z-name z-function">Failure</span><span class="z-punctuation z-definition z-string">("</span><span class="z-string">Could not find user for token</span><span class="z-punctuation z-definition z-string">")</span></span>
<span class="giallo-l"><span> }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>In Rust, the tooling can answer a lot more questions for me.
What type is <code>cookie_token</code>?
A simple hover in any code editor with an LSP tells me, definitively, that it’s <code>Option<String></code>.</p>
<p>Because it’s Rust, you have to explicitly check if the token exists;
ditto for whether the user exists.
That’s better for the reader too: they don’t have to wonder whether certain edge cases are handled.</p>
<aside>
It's possible to write sloppier Rust than this, but the baseline is quite a bit higher.
</aside>
<p>Rust is not the only language with a strict, static typing.
At every place I’ve ever worked, the longest-running web services have all been written in Java.
Java is not as good as Rust at forcing you to show your work and handle edge cases, but it’s much better than JavaScript.</p>
<p>Putting aside the question of which one I prefer to write, if I find myself in charge a production web service that someone <em>else</em> wrote, I would much prefer it to be in Java or Rust than JavaScript or Python.</p>
<h2 id="web-development-has-lots-of-dsls"><a class="zola-anchor" href="#web-development-has-lots-of-dsls" aria-label="Anchor link for: web-development-has-lots-of-dsls">Web Development Has Lots of DSLs</a></h2>
<p>Conceding that, <em>ceteris paribus</em>, static typing is good for software maintainability, one of the reasons that I like dynamically-typed languages is that they encourage a style I find important for web services in particular: writing to the DSL.</p>
<p>A DSL (domain-specific language) is programming language that’s designed for a specific problem area.
This is in contrast to what we typically call “general-purpose programming languages” (e.g. Java, JavaScript, Python, Rust), which can reasonably applied to most programming tasks.</p>
<p>Most web services have to contend with at least three DSLs: HTML, CSS, and SQL.
A web service with a JavaScript backend has to interface with, at a <em>minimum</em>, four programming languages: one general-purpose and three DSLs.</p>
<table class="as-grid-table languages">
<tr><th>Language <th>DSL? <th>Purpose
<tr><td>HTML <td>Yes <td>Website layout <a href="/blog/behavior-belongs-in-html">and functionality</a>
<tr><td>SQL <td>Yes <td>Data persistence and retrieval
<tr><td>CSS <td>Yes <td>Website design
<tr><td>JavaScript <td>No <td>Server logic and website functionality missing from HTML
</table>
<p>If you have the audacity to use <a rel="external" href="https://htmx.org/essays/hypermedia-on-whatever-youd-like/">something other than JavaScript</a> on the server, then that number goes up to five, because you still need JavaScript to augment HTML.</p>
<table class="as-grid-table languages">
<tr><th>Language <th>DSL? <th>Purpose
<tr><td>HTML <td>Yes <td>Website layout <a href="/blog/behavior-belongs-in-html">and functionality</a>
<tr><td>SQL <td>Yes <td>Data persistence and retrieval
<tr><td>CSS <td>Yes <td>Website design
<tr><td>Java <td>No <td>Server logic
<tr><td>JavaScript <td>No <td>Website functionality missing from HTML
</table>
<p>That’s a lot of languages!
How are we supposed to find developers who can do <em>all this stuff</em>?</p>
<h2 id="two-approaches-to-language-proliferation"><a class="zola-anchor" href="#two-approaches-to-language-proliferation" aria-label="Anchor link for: two-approaches-to-language-proliferation">Two approaches to language proliferation</a></h2>
<h3 id="expanding-the-bounds"><a class="zola-anchor" href="#expanding-the-bounds" aria-label="Anchor link for: expanding-the-bounds">Expanding the bounds</a></h3>
<p>The answer that a big chunk of the industry settled on is to build APIs so that the domains of the DSLs can be described in the general-purpose programming language.</p>
<p>Instead of writing HTML…</p>
<pre class="giallo z-code" data-lang="html" data-name="HTML"><code data-lang="html" data-name="HTML"><span class="giallo-l"><span><!</span><span class="z-entity z-name z-tag">DOCTYPE</span><span class="z-entity z-other z-attribute-name"> html</span><span>></span></span>
<span class="giallo-l"><span><</span><span class="z-entity z-name z-tag">h1</span><span>>Classic Movies</</span><span class="z-entity z-name z-tag">h1</span><span>></span></span>
<span class="giallo-l"><span><</span><span class="z-entity z-name z-tag">p</span><span>>Louis, I think this is the beginning of a beautiful website.</</span><span class="z-entity z-name z-tag">p</span><span>></span></span></code></pre>
<p>…you can write JSX, a JavaScript syntax extension that supports tags.</p>
<pre class="giallo z-code" data-lang="tsx" data-name="JSX"><code data-lang="tsx" data-name="JSX"><span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> header = <</span><span class="z-entity z-name z-tag">h1</span><span>>Classic Movies</</span><span class="z-entity z-name z-tag">h1</span><span>></span></span>
<span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> paragraph = <</span><span class="z-entity z-name z-tag">p</span><span>></span></span>
<span class="giallo-l"><span> Louis, I think this is the beginning of a beautiful website.</span></span>
<span class="giallo-l"><span></</span><span class="z-entity z-name z-tag">p</span><span>></span></span>
<span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> page = <></span></span>
<span class="giallo-l"><span class="z-punctuation z-section z-embedded"> {</span><span>header</span><span class="z-punctuation z-section z-embedded">}</span></span>
<span class="giallo-l"><span class="z-punctuation z-section z-embedded"> {</span><span>paragraph</span><span class="z-punctuation z-section z-embedded">}</span></span>
<span class="giallo-l"><span></></span></span></code></pre>
<p>This has the important advantage of allowing you to include dynamic JavaScript expressions in your markup.
And now we don’t have to kick out to another DSL to write web pages.
Can we start abstracting away CSS too?</p>
<p>Sure can! This example uses <a rel="external" href="https://styled-components.com/">styled-components</a>.</p>
<pre class="giallo z-code" data-lang="tsx" data-name="JSX"><code data-lang="tsx" data-name="JSX"><span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> Button = styled.</span><span class="z-entity z-name z-function">button</span><span>({</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string"> color: '</span><span class="z-string">gray</span><span class="z-punctuation z-definition z-string">',</span></span>
<span class="giallo-l"><span>})</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> page = <></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">h1</span><span>>Classic Movies</</span><span class="z-entity z-name z-tag">h1</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">p</span><span>>Louis, I think this is the beginning of a beautiful website.</</span><span class="z-entity z-name z-tag">p</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-support z-class">Button</span><span>>Round up the usual suspects.</</span><span class="z-support z-class">Button</span><span>></span></span>
<span class="giallo-l"><span></></span></span></code></pre><aside>Sorry, I just saw Casablanca again recently. That's the last one.</aside>
<p>This is a tactic I call “expanding the bounds” of the programming language.
In an effort to reduce complexity, you try to make one language express everything about the project.
In theory, this reduces the number of languages that one needs to learn to work on it.</p>
<table class="as-grid-table languages">
<tr><th>Language <th>DSL? <th>Purpose
<tr><td>SQL <td>Yes <td>Data persistence and retrieval
<tr><td>JavaScript <td>No <td>Server logic, website layout, <br>website functionality, and website design
</table>
<p>The problem is that it usually doesn’t work.
Expressing DSLs in general-purpose programming syntax does not free you from having to understand the DSL—you can’t actually use styled-components without understanding CSS.
So now a prospective developer has to both understand CSS and a new CSS syntax that only applies to the styled-components library.</p>
<aside>
I got hit with that library in a job interview a while back, and I spent five minutes trying to figure out how to express <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator">child selectors</a> before the interviewer took pity on me and was like "don't worry, I can tell you know CSS."
Which I do!
Let me write it!
</aside>
<table class="as-grid-table languages">
<tr><th>Language <th>DSL? <th>Purpose
<tr><td>HTML <td>Yes <td>Website layout and functionality
<tr><td>JSX <td>Yes <td>Website layout and functionality
<tr><td>SQL <td>Yes <td>Data persistence and retrieval
<tr><td>CSS <td>Yes <td>Website design
<tr><td>styled-components <td>Yes <td>Website design
<tr><td>JavaScript <td>No <td>Server logic
</table>
<aside>
I suspect people are going to be more inclined to defend JSX than CSS-in-JS.
Do you <em>really</em> need to know HTML to write JSX?
Yes, unless you want to re-implement functionality that's built into <code><a></code>, <code><form></code> <code><details></code>, <code><input></code>, etc.
Many people waste enormous amounts of time and money doing exactly that.
</aside>
<p>Not to mention, it is almost always a worse syntax.
CSS is designed to make expressing declarative styles very easy, because that’s the only thing CSS has to do.</p>
<pre class="giallo z-code" data-lang="css" data-name="CSS"><code data-lang="css" data-name="CSS"><span class="giallo-l"><span class="z-entity z-name z-tag">button</span><span> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name"> color</span><span>: gray;</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Expressing this in JavaScript is naturally way clunkier.</p>
<pre class="giallo z-code" data-lang="javascript" data-name="JS"><code data-lang="javascript" data-name="JS"><span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> Button = styled.</span><span class="z-entity z-name z-function">button</span><span>({</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string"> color: '</span><span class="z-string">gray</span><span class="z-punctuation z-definition z-string">',</span></span>
<span class="giallo-l"><span>})</span></span></code></pre>
<p>Plus, you’ve also tossed the web’s backwards compatibility guarantees.
I picked styled-components because it’s very popular.
If you built a website with styled-components in <a rel="external" href="https://styled-components.com/releases#v4.4.0">2019</a>, didn’t think about the styles for a couple years, and then tried to upgrade it in <a rel="external" href="https://styled-components.com/releases#v6.0.2">2023</a>, you would be two major versions behind.
Good luck with the <a rel="external" href="https://styled-components.com/docs/faqs#what-do-i-need-to-do-to-migrate-to-v6">migration guide</a>.
CSS files, on the other hand, <a rel="external" href="https://htmx.org/essays/no-build-step/">are evergreen</a>.</p>
<p>Of course, one of the reasons for introducing JSX or CSS-in-JS is that they add functionality, like dynamic population of values.
That’s an important problem, but I prefer a different solution.</p>
<h3 id="building-good-boundaries"><a class="zola-anchor" href="#building-good-boundaries" aria-label="Anchor link for: building-good-boundaries">Building good boundaries</a></h3>
<p>Instead of expanding the bounds of the general-purpose language so that it can express everything, another strategy is to build strong and simple API boundaries between the DSLs.
Some benefits of this approach include:</p>
<ol>
<li>DSLs are better at expressing their domain, resulting in simpler code</li>
<li>It aids debugging by segmenting bugs into natural categories</li>
<li>The skills gained by writing DSLs are more more transferable</li>
</ol>
<p>The following example uses a JavaScript backend.
A lot of enthusiasm for <a rel="external" href="https://htmx.org/">htmx</a> (the software library I co-maintain) is driven by communities like <a rel="external" href="https://forum.djangoproject.com/t/adding-template-fragments-or-partials-for-the-dtl/21500">Django</a> and <a rel="external" href="https://github.com/wimdeblauwe/htmx-spring-boot">Spring Boot</a> developers, who are thrilled to no longer be bolting on a JavaScript frontend to their website;
that’s a core value proposition for <a rel="external" href="https://htmx.org/essays/hypermedia-driven-applications/">hypermedia-driven development</a>.
I happen to like JavaScript though, and sometimes write services in NodeJS, so, at least in theory, I could still use JSX if I wanted to.</p>
<p>What I prefer, and what I encourage hypermedia-curious NodeJS developers to do, is use a <a rel="external" href="https://htmx.org/essays/web-security-basics-with-htmx/#always-use-an-auto-escaping-template-engine">template engine</a>.
This bit of production code I wrote for an events company uses <a rel="external" href="https://mozilla.github.io/nunjucks/">Nunjucks</a>, a template engine <a href="https://unplannedobsolescence.com/talks/building-the-hundred-year-web-service/">I once (fondly!) called “abandonware” on stage</a>.
Other libraries that support <a rel="external" href="https://jinja.palletsprojects.com/en/stable/">Jinja</a>-like syntax are available in pretty much any programming language.</p>
<pre class="giallo z-code" data-lang="html"><code data-lang="html"><span class="giallo-l"><span><</span><span class="z-entity z-name z-tag">h2</span><span>>Upcoming Events</</span><span class="z-entity z-name z-tag">h2</span><span>></span></span>
<span class="giallo-l"><span><</span><span class="z-entity z-name z-tag">table</span><span>></span></span>
<span class="giallo-l"><span><</span><span class="z-entity z-name z-tag">tr</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">th</span><span>>Name</span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">th</span><span>>Location</span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">th</span><span>>Date</span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">th</span><span>>Registration Deadline</span></span>
<span class="giallo-l"><span></</span><span class="z-entity z-name z-tag">tr</span><span>></span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>{% for event in events %}</span></span>
<span class="giallo-l"><span><</span><span class="z-entity z-name z-tag">tr</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">td</span><span>><</span><span class="z-entity z-name z-tag">a</span><span class="z-entity z-other z-attribute-name"> href</span><span class="z-punctuation z-definition z-string">="</span><span class="z-string">/events/{{ event.event_id }}</span><span class="z-punctuation z-definition z-string">">{{ event.name }}</</span><span class="z-entity z-name z-tag">a</span><span>></span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">td</span><span>>{{ event.location }}</span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">td</span><span>>{{ event.date }}</span></span>
<span class="giallo-l"><span> <</span><span class="z-entity z-name z-tag">td</span><span>>{{ event.registration_deadline }}</span></span>
<span class="giallo-l"><span></</span><span class="z-entity z-name z-tag">tr</span><span>></span></span>
<span class="giallo-l"><span>{% endfor %}</span></span>
<span class="giallo-l"><span></</span><span class="z-entity z-name z-tag">table</span><span>></span></span></code></pre>
<p>This is just HTML with basic loops (<code>{% for event in events %}</code>) and data access (<code>{{ event.name }}</code>).
I get very frustrated when something that is easy in HTML is hard to do because I’m using some wrapper with inferior semantics;
with templates, I can dynamically build content for HTML without abstracting it away.</p>
<aside>
Nunjucks actually has no concept of HTML syntax; it's just providing the curly-brace functionality.
You could template anything you wanted with it.
</aside>
<p>Populating this template in JavaScript is <em>so easy</em>.
You just give it a JavaScript object with an <code>events</code> field.</p>
<pre class="giallo z-code" data-lang="javascript"><code data-lang="javascript"><span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> events = db.</span><span class="z-entity z-name z-function">getUpcomingEvents</span><span>()</span></span>
<span class="giallo-l"><span>req.</span><span class="z-entity z-name z-function">render</span><span class="z-punctuation z-definition z-string">('</span><span class="z-string">events.html</span><span class="z-punctuation z-definition z-string">', { events })</span></span></code></pre>
<p>That’s not particularly special on its own—many languages support serialized key-value pairs.
This strategy really shines when you start stringing it together with SQL.
Let’s replace that database function call with an actual query, using an interface similar to <a rel="external" href="https://github.com/WiseLibs/better-sqlite3"><code>better-sqlite3</code></a>.</p>
<pre class="giallo z-code" data-lang="javascript"><code data-lang="javascript"><span class="giallo-l"><span class="z-storage">function</span><span class="z-entity z-name z-function"> getEvent</span><span class="z-punctuation z-definition z-parameters">(</span><span class="z-variable z-parameter">req</span><span class="z-punctuation z-definition z-parameters">) {</span></span>
<span class="giallo-l"><span class="z-storage"> const</span><span class="z-keyword z-operator"> events = db.</span><span class="z-entity z-name z-function">all</span><span class="z-punctuation z-definition z-string">(`</span></span>
<span class="giallo-l"><span class="z-string"> SELECT event_id, name location, date, registration_deadline</span></span>
<span class="giallo-l"><span class="z-string"> FROM events</span></span>
<span class="giallo-l"><span class="z-string"> WHERE date start_date >= date('now')</span></span>
<span class="giallo-l"><span class="z-string"> ORDER BY start_date ASC</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string"> `</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span> req.</span><span class="z-entity z-name z-function">render</span><span class="z-punctuation z-definition z-string">('</span><span class="z-string">events.html</span><span class="z-punctuation z-definition z-string">', { events })</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>I <em>know</em> the above code is not everybody’s taste, but I think it’s marvelous.
You get to write all parts of the application in the language best suited to each: HTML for the frontend and SQL for the queries.
And if you need to do any additional logic between the database and the template, JavaScript is still right there.</p>
<p>One result of this style is that it increases the percentage of your service that is specified declaratively.
The database schema and query are declarative, as is the HTML template.
The only imperative code in the function is the glue that moves that query result into the template: two statements in total.</p>
<aside>
Declarative languages tend to have better backwards compatibility guarantees, but HTML and SQL in particular are phenomenal in this regard.
</aside>
<p>Debugging is also dramatically easier.
I typically do two quick things to narrow down the location of the bug:</p>
<ol>
<li><strong>CMD+U to View Source</strong> - If the missing data is in the HTML, it’s a frontend problem</li>
<li><strong>Run the query in the database</strong> - If the missing data is in the SQL, it’s a problem with the GET route</li>
</ol>
<aside>
JetBrains IDEs are especially good at running parameterized SQL queries straight from the embedded-string source.
</aside>
<p>Those two steps are easy, can be done in production with no deployments, and provide excellent signal on the location of the error.</p>
<p>Fundamentally, what’s happening here is a quick check at the two hard boundaries of the system:
the one between the server and the client, and the one between the client and the database.
Similar tools are available to you if you abstract over those layers, but they are lessened in usefulness.</p>
<p>Every web service has network requests that can be inspected, but putting most frontend logic in the template means that the HTTP response’s <strong>data</strong> (“does the date ever get send to the frontend”) <strong>and functionality</strong> (“does the date get displayed in the right HTML element?”) can be inspected in one place, with one keystroke.
Every database can be queried, but using the database’s native query language in your server means you can validate both the <strong>stored data</strong> (“did the value get saved?”) <strong>and the query</strong> (“does the code ask for the right value?”) independent of the application.</p>
<p>By pushing so much of the business logic outside the general-purpose programming language, you reduce the likelihood that a bug will exist in the place where it is hardest to track down—runtime server logic.
You’d rather the bug be a malformatted SQL query or HTML template, because those are easy to find and easy to fix.</p>
<p>When combined with the router-driven style described in <a href="https://unplannedobsolescence.com/talks/building-the-hundred-year-web-service/">Building The Hundred-Year Web Service</a>,
you get simple and debuggable web systems.
Each HTTP request is a relatively isolated function call: it takes some parameters, runs an SQL query, and returns some HTML.</p>
<p>In essence, dynamically-typed languages help you write the least amount of server code possible, leaning heavily on the DSLs that define web programming while validating small amounts of server code via means other than static type checking.</p>
<h2 id="raising-the-inference-bar"><a class="zola-anchor" href="#raising-the-inference-bar" aria-label="Anchor link for: raising-the-inference-bar">Raising the inference bar</a></h2>
<p>To finish, let’s take a look at the equivalent code in Rust, using <a rel="external" href="https://github.com/rusqlite/rusqlite">rusqlite</a>, <a rel="external" href="https://github.com/mitsuhiko/minijinja">minjina</a>, and a quasi-hypothetical server implementation:</p>
<pre class="giallo z-code" data-lang="rust"><code data-lang="rust"><span class="giallo-l"><span>#[derive(Serialize, Deserialize)]</span></span>
<span class="giallo-l"><span class="z-storage">struct</span><span> Event {</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> event_id: String,</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> name: String,</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> date: String,</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> registration_deadline: String,</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-keyword">pub async fn</span><span class="z-entity z-name z-function"> get_events</span><span class="z-keyword z-operator">(req: Request) -> ServerResult {</span></span>
<span class="giallo-l"><span class="z-storage"> let</span><span class="z-keyword z-operator"> events = req.db.</span><span class="z-entity z-name z-function">query_map</span><span class="z-punctuation z-definition z-string">("</span></span>
<span class="giallo-l"><span class="z-string"> SELECT event_id, name location, date, registration_deadline</span></span>
<span class="giallo-l"><span class="z-string"> FROM events</span></span>
<span class="giallo-l"><span class="z-string"> WHERE date start_date >= date('now')</span></span>
<span class="giallo-l"><span class="z-string"> ORDER BY start_date ASC</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string"> ",</span></span>
<span class="giallo-l"><span> [],</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> |row| {</span></span>
<span class="giallo-l"><span class="z-storage"> let</span><span class="z-keyword z-operator"> event = Event {</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> event_id: row.</span><span class="z-entity z-name z-function">get</span><span class="z-punctuation z-definition z-string">("</span><span class="z-string">event_id</span><span class="z-punctuation z-definition z-string z-keyword z-operator">")?,</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> name: row.</span><span class="z-entity z-name z-function">get</span><span class="z-punctuation z-definition z-string">("</span><span class="z-string">name</span><span class="z-punctuation z-definition z-string z-keyword z-operator">")?,</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> date: row.</span><span class="z-entity z-name z-function">get</span><span class="z-punctuation z-definition z-string">("</span><span class="z-string">date</span><span class="z-punctuation z-definition z-string z-keyword z-operator">")?,</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> registration_deadline: row.</span><span class="z-entity z-name z-function">get</span><span class="z-punctuation z-definition z-string">("</span><span class="z-string">registration_deadline</span><span class="z-punctuation z-definition z-string z-keyword z-operator">")?,</span></span>
<span class="giallo-l"><span> };</span></span>
<span class="giallo-l"><span> Ok(event)</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> })?;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-storage"> let</span><span class="z-keyword z-operator"> context =</span><span class="z-entity z-name z-function"> context!</span><span> { events };</span></span>
<span class="giallo-l"><span class="z-storage"> let</span><span class="z-keyword z-operator"> body = req.</span><span class="z-entity z-name z-function">render</span><span class="z-punctuation z-definition z-string">("</span><span class="z-string">events.html</span><span class="z-punctuation z-definition z-string z-keyword z-operator">", context)?;</span></span>
<span class="giallo-l"><span class="z-keyword z-operator"> req.</span><span class="z-entity z-name z-function">send</span><span>(body)</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span></code></pre>
<p>I am again obfuscating some implementation details (Are we storing human-readable dates in the database? What’s that universal result type?).
The important part is that this blows.</p>
<p>Most of the complexity comes from the need to tell Rust exactly how to unpack that SQL result into a typed data structure, and then into an HTML template.
The <code>Event</code> struct is declared so that Rust knows to expect a <code>String</code> for <code>event_id</code>.
The derive macros create a representation that minijinja knows how to serialize.
It’s tedious.</p>
<!-- But you also lose the obligation of specifying those conversions manually, which is very handy because SQL, JavaScript, and HTML all have different type systems (a topic for another blog post), and specifying those conversions is quite tedious. -->
<p>Worse, after all that work, the compiler still doesn’t do the most useful thing: check whether <code>String</code> is the correct type for <code>event_id</code>.
If it turns out that <code>event_id</code> can’t be represented as a <code>String</code> (maybe it’s a <a rel="external" href="https://sqlite.org/datatype3.html">blob</a>), the query will compile correctly and then fail at runtime.
From a safety standpoint, we’re not really in a much better spot than we were with JavaScript:
we don’t know if it works until we run the code.</p>
<p>Speaking of JavaScript, remember that code? That was great!</p>
<pre class="giallo z-code" data-lang="javascript"><code data-lang="javascript"><span class="giallo-l"><span class="z-storage">const</span><span class="z-keyword z-operator"> events = db.</span><span class="z-entity z-name z-function">all</span><span class="z-punctuation z-definition z-string">(`</span></span>
<span class="giallo-l"><span class="z-string"> SELECT event_id, name, location, date, registration_deadline</span></span>
<span class="giallo-l"><span class="z-string"> FROM events</span></span>
<span class="giallo-l"><span class="z-string"> WHERE date start_date >= date('now')</span></span>
<span class="giallo-l"><span class="z-string"> ORDER BY start_date ASC</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string">`)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>req.</span><span class="z-entity z-name z-function">render</span><span class="z-punctuation z-definition z-string">('</span><span class="z-string">events.html</span><span class="z-punctuation z-definition z-string">', { events })</span></span></code></pre>
<p>Now we have no idea what any of these types are, but if we run the code and we see some output, it’s probably fine.
By writing the JavaScript version, you are banking that you’ve made the code so highly auditable by hand that the compile-time checks become less necessary.
In the long run, this is always a bad bet, but at least I’m not writing 150% more code for 10% more compile-time safety.</p>
<p>The “expand the bounds” solution to this is to pull everything into the language’s type system: the database schema, the template engine, everything.
Many have trod that path; I believe it leads to madness (and toolchain lock-in).
Is there a better one?</p>
<p>I believe there is.
The compiler should understand the DSLs I’m writing and automatically map them to types it understands.
If it needs more information—like a database schema—to figure that out, that information can be provided.</p>
<p>Queries correspond to columns with known types—the programming language can infer that <code>events.name</code> is of type <code>SQLITE_TEXT</code>.
HTML has <a rel="external" href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#output-encoding">context-dependent escaping rules</a>—the programming language can validate that <code>events.name</code> is being used in a valid element and escape it correctly.</p>
<p>With this functionality in the compiler, if I make a database migration that would render my usage of a dependent variable in my HTML template invalid, the compiler will show an error.
All without losing the advantages of writing the expressive, interoperable, and backwards-compatible DSLs the comprise web development.</p>
<p>Dynamically-typed languages show us how easy web development can be when we ditch the unnecessary abstractions.
Now we need tooling to make it just as easy in statically-typed languages too.</p>
<h1 id="notes"><a class="zola-anchor" href="#notes" aria-label="Anchor link for: notes">Notes</a></h1>
<p><em>Thanks to <a rel="external" href="https://mlog.nektro.net/">Meghan Denny</a> for her feedback on a draft of this blog.</em></p>
<ul>
<li>Language extensions that just translate the syntax are alright by me, like generating HTML with <a rel="external" href="https://github.com/weavejester/hiccup">s-expressions</a>, <a rel="external" href="https://yawaramin.github.io/dream-html/">ocaml functions</a>, or <a rel="external" href="https://github.com/nektro/zig-pek">zig comptime functions</a>. I tend to end up just using templates, but language-native HTML syntax can be done tastefully, and they are probably helpful in the road to achieving the DX I’m describing; I’ve never seen them done well for SQL.</li>
<li><a rel="external" href="https://github.com/launchbadge/sqlx">Sqlx</a> and <a rel="external" href="https://docs.sqlc.dev">sqlc</a> seem to have the right idea, but I haven’t used either because I to stick to SQLite-specific libraries to avoid async database calls.</li>
<li>I don’t know as much about compilers as I’d like to, so I have no idea what kind of infrastructure would be required to make this work with existing languages in an extensible way. I assume it would be hard.</li>
</ul>
What's Left for Frontend Engineers?2025-08-12T00:00:00+00:002025-08-12T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/talks/whats-left-for-frontend-engineers/<style>
iframe {
display: block;
margin: 10px auto;
}
</style>
<p>I went back to Bozeman!</p>The Server Doesn't Render Anything2025-06-17T00:00:00+00:002025-06-17T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/blog/the-server-doesnt-render/<p>When I advise people on how they should structure a web service, I always start from the same place:
make a server that responds to HTTP requests with HTML text.</p>The Mostly True Naming Rule2025-04-19T00:00:00+00:002025-04-19T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/blog/mostly-true-naming-rule/<p>Naming things properly is <a rel="external" href="https://martinfowler.com/bliki/TwoHardThings.html">very hard to do</a>, so, as programmers, we come up with little rules to help us.
These rules are often inconsistent.</p>Why Insist on a Word2025-02-03T00:00:00+00:002025-02-03T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/blog/why-insist-on-a-word/<p>A central concept to HTML, and hypertext theory more generally, is something called Representational State Transfer, a.k.a. REST.
Over at htmx, a lot of the <a rel="external" href="https://htmx.org/essays">writing we do</a> is based on REST theory.</p>
<p><a rel="external" href="https://htmx.org/essays/how-did-rest-come-to-mean-the-opposite-of-rest/">REST is a widely misunderstood term</a>,
and if you point that out to people, you will be told, repeatedly and sometimes quite irately: who cares?
REST has a new meaning now—use words the way people understand them and spare us the lecture.</p>Building the Hundred-Year Web Service2024-10-11T00:00:00+00:002024-10-11T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/talks/building-the-hundred-year-web-service/<style>
iframe {
display: block;
margin: 10px auto;
}
</style>
<p>My <a rel="external" href="https://utahjs.com/">UtahJS</a> talk, <a rel="external" href="https://www.youtube.com/watch?v=lASLZ9TgXyc">“Building the Hundred-Year Web Service”</a>, was put online this week!
It’s about how to build software infrastructure that lasts a very long time.</p>Less htmx is More2024-10-02T00:00:00+00:002024-10-02T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/blog/less-htmx-is-more/<p>It’s been two years since I wrote my first production webservice with <a rel="external" href="https://htmx.org">htmx</a>.
Two years is not a very long time, but early indicators suggest that the software projects I’ve written with htmx are a much better experience for users, and orders of magnitude easier to maintain, than the software projects they replaced.
They are likely to remain useful for longer than anything else I’ve ever written (so far).
Pretty good!</p>
<p>Like any new tool, especially a tool that got popular <a rel="external" href="https://risingstars.js.org/2023/en#section-framework">as quickly as htmx</a>, there are differing schools of thought on how best to use it.
My approach—which I believe necessary to achieve the results described above—requires you to internalize something that htmx certainly hints at, but doesn’t enforce: use plain HTML <em>wherever possible</em>.</p>The Messy Pile2024-08-01T00:00:00+00:002024-08-01T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/blog/messy-pile-css/<p>A couple months ago I was sitting next to <a rel="external" href="https://ivywong.dev">Ivy Wong</a> and I saw them working on a dropdown menu so cute that I immediately asked how they did it.</p>Who's Afraid of a Hard Page Load?2024-07-16T00:00:00+00:002024-07-16T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/blog/hard-page-load/<p>While I’m not going to settle the Single-Page Web Application (SPA) debate in a blog post, there is
one claim about SPAs that routinely goes unchallenged, and it drives me nuts: that users prefer
them because of the “modern,” responsive feel.</p>The Life & Death of htmx2024-06-13T00:00:00+00:002024-06-13T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/talks/life-and-death-of-htmx/<style>
iframe {
display: block;
margin: 10px auto;
}
</style>
<p>This past weekend, I gave a talk entitled “The Life & Death of htmx” at <a rel="external" href="https://bigskydevcon.com/">Big Sky Dev Con</a>.</p>The Best "Hello World" in Web Development2024-02-29T00:00:00+00:002024-02-29T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/blog/best-hello-world-web-development/<span id="continue-reading"></span><h2 id="the-classic-hello-world"><a class="zola-anchor" href="#the-classic-hello-world" aria-label="Anchor link for: the-classic-hello-world">The Classic Hello World</a></h2>
<p>Here’s how you make a webpage that says “Hello World” in PHP:</p>
<pre class="giallo z-code" data-lang="php"><code data-lang="php"><span class="giallo-l"><span class="z-constant">Hello World</span></span></code></pre>
<p>Name that file <code>index.php</code> and you’re set. Awesome. <a href="./hello-world-v1.html">Version 1</a> of our website looks like this:</p>
<iframe title="Hello World Page" width=300 height=200 src="./hello-world-v1.html">
</iframe>
<p>Okay, we can do a little better. Let’s add the HTML doctype and <code><title></code> element to make it a <a rel="external" href="http://lofi.limo/blog/write-html-right">legal HTML5 page</a>, an <code><h1></code> header to give the “Hello World” some heft, and a <code><p></code> paragraph to tell our visitor where they are.</p>
<pre class="giallo z-code" data-lang="php"><code data-lang="php"><span class="giallo-l"><span class="z-keyword z-operator z-constant"><!DOCTYPE html></span></span>
<span class="giallo-l"><span class="z-keyword z-operator z-constant"><title>Hello, World!</title></span></span>
<span class="giallo-l"><span class="z-keyword z-operator z-constant"><h1>Hello, World!</h1></span></span>
<span class="giallo-l"><span class="z-keyword z-operator z-constant z-punctuation z-definition z-string"><p>Welcome to Alex'</span><span class="z-string">s website :)</p></span></span></code></pre>
<p>This is a complete webpage! If you host this single file at one of the many places available to host PHP code, it will show that webpage to everyone who visits your website. Here’s <a href="./hello-world-v2.html">Version 2</a>:</p>
<iframe title="Hello World Page" width=300 height=200 src="./hello-world-v2.html">
</iframe>
<p>Now let’s make <a href="https://unplannedobsolescence.com/blog/best-hello-world-web-development/./hello-world-v3.html">Version 3</a> comic sans! And baby blue! We just have to add a style tag:</p>
<pre class="giallo z-code" data-lang="php"><code data-lang="php"><span class="giallo-l"><span class="z-keyword z-operator z-constant"><!DOCTYPE html></span></span>
<span class="giallo-l"><span class="z-keyword z-operator z-constant"><title>Hello, World!</title></span></span>
<span class="giallo-l"><span class="z-keyword z-operator z-constant"><style></span></span>
<span class="giallo-l"><span class="z-constant">body {</span></span>
<span class="giallo-l"><span class="z-constant z-keyword z-operator"> background-color: lightblue;</span></span>
<span class="giallo-l"><span class="z-constant z-keyword z-operator z-punctuation z-definition z-string"> font-family: "</span><span class="z-string">Comic Sans MS</span><span class="z-punctuation z-definition z-string">", "</span><span class="z-string">Comic Sans</span><span class="z-punctuation z-definition z-string z-constant">", cursive;</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"><span class="z-keyword z-operator z-constant"></style></span></span>
<span class="giallo-l"><span class="z-keyword z-operator z-constant"><h1>Hello, World!</h1></span></span>
<span class="giallo-l"><span class="z-keyword z-operator z-constant z-punctuation z-definition z-string"><p>Welcome to Alex'</span><span class="z-string">s website :)</p></span></span></code></pre><iframe title="Hello World Page" width=300 height=200 src="./hello-world-v3.html">
</iframe>
<p>Already our webpage has a little bit of personality, and we’ve spent just a couple minutes on it. At each step we could see the website in a browser, and keep adding to it. We haven’t even used any PHP yet—all this is plain old HTML, which is much easier to understand than PHP.</p>
<p>This is the best “Hello World” in web development, and possibly all of programming.</p>
<h2 id="contemporary-hello-worlds"><a class="zola-anchor" href="#contemporary-hello-worlds" aria-label="Anchor link for: contemporary-hello-worlds">Contemporary Hello Worlds</a></h2>
<p>The thing that first got me interested in PHP in the first place is a comment that Ruby on Rails creator <a rel="external" href="https://corecursive.com/045-david-heinemeier-hansson-software-contrarian/">David Heinemeier Hansson made on the “CoRecursive” podcast</a>, about PHP’s influence on Rails:</p>
<blockquote>
<p>[…] the other inspiration, which was from PHP, where you literally could do a one line thing that said, “Print hello world,” and it would show a web page. It would show Hello World on a web page. You just drop that file into the correct folder, and you were on the web […] I think to this day still unsurpassed ease of Hello World.</p>
</blockquote>
<p>He’s right—this is an unsurpassed ease of Hello World. It is certainly not surpassed by Ruby on Rails, the <a rel="external" href="https://guides.rubyonrails.org/getting_started.html">“Getting Started” guide</a> for which not only requires installing ruby, SQLite, and Rails itself, but also has you run an initialization command (<code>rails new blog</code>) that creates a <a rel="external" href="https://guides.rubyonrails.org/getting_started.html#creating-the-blog-application">genuinely shocking number of files and directories</a>:</p>
<pre class="giallo z-code" data-lang="plain"><code data-lang="plain"><span class="giallo-l"><span>$ rails new blog 2>&1 >/dev/null</span></span>
<span class="giallo-l"><span>$ tree -L 2 blog</span></span>
<span class="giallo-l"><span>blog</span></span>
<span class="giallo-l"><span>├── Dockerfile</span></span>
<span class="giallo-l"><span>├── Gemfile</span></span>
<span class="giallo-l"><span>├── Gemfile.lock</span></span>
<span class="giallo-l"><span>├── README.md</span></span>
<span class="giallo-l"><span>├── Rakefile</span></span>
<span class="giallo-l"><span>├── app</span></span>
<span class="giallo-l"><span>│ ├── assets</span></span>
<span class="giallo-l"><span>│ ├── channels</span></span>
<span class="giallo-l"><span>│ ├── controllers</span></span>
<span class="giallo-l"><span>│ ├── helpers</span></span>
<span class="giallo-l"><span>│ ├── javascript</span></span>
<span class="giallo-l"><span>│ ├── jobs</span></span>
<span class="giallo-l"><span>│ ├── mailers</span></span>
<span class="giallo-l"><span>│ ├── models</span></span>
<span class="giallo-l"><span>│ └── views</span></span>
<span class="giallo-l"><span>├── bin</span></span>
<span class="giallo-l"><span>│ ├── bundle</span></span>
<span class="giallo-l"><span>│ ├── docker-entrypoint</span></span>
<span class="giallo-l"><span>│ ├── importmap</span></span>
<span class="giallo-l"><span>│ ├── rails</span></span>
<span class="giallo-l"><span>│ ├── rake</span></span>
<span class="giallo-l"><span>│ └── setup</span></span>
<span class="giallo-l"><span>├── config</span></span>
<span class="giallo-l"><span>│ ├── application.rb</span></span>
<span class="giallo-l"><span>│ ├── boot.rb</span></span>
<span class="giallo-l"><span>│ ├── cable.yml</span></span>
<span class="giallo-l"><span>│ ├── credentials.yml.enc</span></span>
<span class="giallo-l"><span>│ ├── database.yml</span></span>
<span class="giallo-l"><span>│ ├── environment.rb</span></span>
<span class="giallo-l"><span>│ ├── environments</span></span>
<span class="giallo-l"><span>│ ├── importmap.rb</span></span>
<span class="giallo-l"><span>│ ├── initializers</span></span>
<span class="giallo-l"><span>│ ├── locales</span></span>
<span class="giallo-l"><span>│ ├── master.key</span></span>
<span class="giallo-l"><span>│ ├── puma.rb</span></span>
<span class="giallo-l"><span>│ ├── routes.rb</span></span>
<span class="giallo-l"><span>│ └── storage.yml</span></span>
<span class="giallo-l"><span>├── config.ru</span></span>
<span class="giallo-l"><span>├── db</span></span>
<span class="giallo-l"><span>│ └── seeds.rb</span></span>
<span class="giallo-l"><span>├── lib</span></span>
<span class="giallo-l"><span>│ ├── assets</span></span>
<span class="giallo-l"><span>│ └── tasks</span></span>
<span class="giallo-l"><span>├── log</span></span>
<span class="giallo-l"><span>│ └── development.log</span></span>
<span class="giallo-l"><span>├── public</span></span>
<span class="giallo-l"><span>│ ├── 404.html</span></span>
<span class="giallo-l"><span>│ ├── 422.html</span></span>
<span class="giallo-l"><span>│ ├── 500.html</span></span>
<span class="giallo-l"><span>│ ├── apple-touch-icon-precomposed.png</span></span>
<span class="giallo-l"><span>│ ├── apple-touch-icon.png</span></span>
<span class="giallo-l"><span>│ ├── favicon.ico</span></span>
<span class="giallo-l"><span>│ └── robots.txt</span></span>
<span class="giallo-l"><span>├── storage</span></span>
<span class="giallo-l"><span>├── test</span></span>
<span class="giallo-l"><span>│ ├── application_system_test_case.rb</span></span>
<span class="giallo-l"><span>│ ├── channels</span></span>
<span class="giallo-l"><span>│ ├── controllers</span></span>
<span class="giallo-l"><span>│ ├── fixtures</span></span>
<span class="giallo-l"><span>│ ├── helpers</span></span>
<span class="giallo-l"><span>│ ├── integration</span></span>
<span class="giallo-l"><span>│ ├── mailers</span></span>
<span class="giallo-l"><span>│ ├── models</span></span>
<span class="giallo-l"><span>│ ├── system</span></span>
<span class="giallo-l"><span>│ └── test_helper.rb</span></span>
<span class="giallo-l"><span>├── tmp</span></span>
<span class="giallo-l"><span>│ ├── cache</span></span>
<span class="giallo-l"><span>│ ├── local_secret.txt</span></span>
<span class="giallo-l"><span>│ ├── pids</span></span>
<span class="giallo-l"><span>│ └── storage</span></span>
<span class="giallo-l"><span>└── vendor</span></span>
<span class="giallo-l"><span> └── javascript</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>38 directories, 35 files</span></span></code></pre>
<p>Of course, Rails is doing a lot of stuff for you! It’s setting up a unit test framework, a blog content folder, a database schema, whatever <code>credentials.yml.enc</code> is, and so on. If I wanted all of that, then Rails might be the way to go. But right now I want to make a webpage that says “Hello World” and start adding content to it; I should not have to figure out what a <code>Gemfile</code> is to do that.</p>
<p>As a reminder, here’s the directory structure for our “Hello World” in PHP:</p>
<pre class="giallo z-code" data-lang="plain"><code data-lang="plain"><span class="giallo-l"><span>$ tree -L 2 php-project</span></span>
<span class="giallo-l"><span>php-project</span></span>
<span class="giallo-l"><span>└── index.php</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>1 directory, 1 file</span></span></code></pre>
<p>My goal here isn’t to rip on Ruby on Rails—although, is a Dockerfile really necessary when you’re just “Getting Started”?—but to highlight a problem that is shared by basically every general-purpose programming language: using Ruby for web development requires a discomfiting amount of scaffolding.</p>
<p>Over in the Python ecosystem, one of the first web development frameworks you will encounter is <a rel="external" href="https://flask.palletsprojects.com/en/3.0.x/quickstart/">flask</a>, which is a much lighter-weight framework than Rails. In flask, you can also get the “Hello World” down to one file, sort of:</p>
<pre class="giallo z-code" data-lang="python"><code data-lang="python"><span class="giallo-l"><span class="z-keyword">from</span><span> flask</span><span class="z-keyword"> import</span><span> Flask</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-keyword z-operator">app = Flask(__name__)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-name z-function">@app.route</span><span class="z-punctuation z-definition z-string">("</span><span class="z-string">/</span><span class="z-punctuation z-definition z-string">")</span></span>
<span class="giallo-l"><span class="z-storage">def</span><span class="z-entity z-name z-function"> hello_world</span><span class="z-punctuation z-definition z-parameters">():</span></span>
<span class="giallo-l"><span class="z-keyword"> return</span><span class="z-punctuation z-definition z-string"> "</span><span class="z-string">Hello World</span><span class="z-punctuation z-definition z-string">"</span></span></code></pre>
<p>Even here, there are a ton of concepts to wrap your head around: you have to understand basic coding constructs like “functions” and “imports”, as well as Python’s syntax for describing these things; you have to figure out how to install Python, how to install Python packages like <code>flask</code>, and how to run Python environment management tool like <code>venv</code> (a truly bizarre kludge that Python developers insist isn’t that big of a deal but is absolutely insane if you come from any other modern programming environment); I know we said one file earlier, but if you want this work on a server you’re going to have to document that you installed flask, using a file like <code>requirements.txt</code>; when you start to add more content you’re going to have to figure out how to do multiline strings; and what’s going with the inscrutable <code>app = Flask(__name__)</code>?</p>
<p>If any of these concepts aren’t arranged properly—in your head and in your file—your server will display nothing.</p>
<p>By contrast, you don’t have to know a <em>thing</em> about PHP to start writing PHP code. Hell, you barely have to know the command line. If you can manage to install and run a PHP server this file will simply display in your browser.</p>
<p>And the file itself:</p>
<pre class="giallo z-code" data-lang="php"><code data-lang="php"><span class="giallo-l"><span class="z-constant">Hello World</span></span></code></pre>
<p>You didn’t have to think about dependencies, or routing, or <code>import</code>, or language constructs, or any of that stuff. You’re just running PHP. And you’re on the web.</p>
<h2 id="the-importance-of-time-to-hello-world"><a class="zola-anchor" href="#the-importance-of-time-to-hello-world" aria-label="Anchor link for: the-importance-of-time-to-hello-world">The Importance of Time-To-Hello-World</a></h2>
<p>The Time-To-Hello-World test is about the time between when you have an idea and when you are able to see the seed of its expression. That time is crucial—it’s when your idea is in its most mortal state.</p>
<p>Years before my friend <a rel="external" href="https://wttdotm.com/">Morry</a> really knew how to code, he was able to kludge together enough PHP of make a website that tells you whether your IP address has 69 in it. It basically looks like this:</p>
<pre class="giallo z-code" data-lang="php"><code data-lang="php"><span class="giallo-l"><span class="z-keyword z-operator z-constant"><!DOCTYPE html></span></span>
<span class="giallo-l"><span class="z-keyword z-operator z-constant"><title>Does my IP address have</span><span class="z-constant z-numeric"> 69</span><span class="z-constant z-keyword z-operator"> in it?</title></span></span>
<span class="giallo-l"><span class="z-keyword z-operator z-constant"><h1>Does my IP address have</span><span class="z-constant z-numeric"> 69</span><span class="z-constant z-keyword z-operator"> in it?</h1></span></span>
<span class="giallo-l"><span class="z-keyword z-operator z-constant"><?php</span></span>
<span class="giallo-l"><span class="z-keyword">if</span><span>(</span><span class="z-support z-function">strpos</span><span class="z-punctuation z-definition z-variable z-punctuation z-definition z-string">($_SERVER['</span><span class="z-string">REMOTE_ADDR</span><span class="z-punctuation z-definition z-string">'], '</span><span class="z-string">69</span><span class="z-punctuation z-definition z-string z-keyword z-operator z-constant">') !== false) {</span></span>
<span class="giallo-l"><span class="z-support z-function"> echo</span><span class="z-punctuation z-definition z-string"> "</span><span class="z-string">Nice</span><span class="z-punctuation z-definition z-string">";</span></span>
<span class="giallo-l"><span>}</span><span class="z-keyword"> else</span><span>{</span></span>
<span class="giallo-l"><span class="z-support z-function"> echo</span><span class="z-punctuation z-definition z-string"> "</span><span class="z-string">Not Nice</span><span class="z-punctuation z-definition z-string">";</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"><span class="z-keyword z-operator">?></span></span></code></pre>
<p>You may or may not find that to be a compelling work of art, but it would not exist if spinning up Flask boilerplate were a requirement to do it. And he had taken a CS course in basic Python; the experience of making a <em>website</em> in PHP was just that much better. This turned out to be the first in a long line of internet art projects, some of which <a rel="external" href="https://wemakeinter.net/">we made together</a> and some of which <a rel="external" href="https://github.com/wttdotm?tab=repositories">he did on his own</a>.</p>
<p><a rel="external" href="https://doesmyipaddresshave69init.com/">doesmyipaddresshave69init.com</a> is <a rel="external" href="https://wttdotm.com/69suite.html">a dumb idea</a> for a website. But sometimes dumb ideas evolve into good ideas, or they teach you something that’s later used to make a good idea, or they just make you chuckle. Or none of the above. The best thing about websites is that you don’t have to justify them to anyone—you can just make them. And PHP is still the fastest way to make a <a rel="external" href="https://blog.wesleyac.com/posts/no-static-websites">“dynamic”</a> website.</p>
<p>I recently made a little <a rel="external" href="https://github.com/alexpetros/invoice-generator">invoice generator with a local browser interface</a> for my freelance business. It works great! It’s got a homepage with a list of my generated invoices, a <code>/new.php</code> route for making a new one, and <code>/invoices/generated/{invoice_id}</code> routes to view each invoice in a printable format.</p>
<p>I don’t find the boilerplate required to make a <a rel="external" href="https://intercoolerjs.org/2016/01/18/rescuing-rest.html">RESTful web service</a> in NodeJS especially onerous—I have a pretty good system for it at this point. But PHP brings the time-to-hello-world down tremendously. I just don’t think this would have gotten off the ground if I had to setup ExpressJS, copy my router boilerplate, make 2 files for each route (the template and the javascript that serves it), and do all the other things I do to structure web-apps in Node. Instead, I got all that stuff built-in with vanilla PHP, and that will presumably work for as long as PHP does. I didn’t even have to touch <a rel="external" href="https://getcomposer.org/">the package manager</a>.</p>
<p>A lot of people have the attitude that writing vanilla code (and vanilla PHP especially) is never okay because you need secure-by-default frameworks to ensure that you don’t make any security mistakes. It clearly true that if you are building professional software you should be aware of <a rel="external" href="https://htmx.org/essays/web-security-basics-with-htmx">the web security model</a> and make informed decisions about the security model of your application; not everyone is building professional software.</p>
<p>Relatedly, one route to becoming is a software professional is to have a delightful experience as a software amateur.</p>
<p>I believe that more people should use the internet not just as consumers, but as creators (not of <em>content</em> but of <em>internet</em>). There is a lot of creativity on the web that can be unlocked by making web development more accessible to artists, enthusiasts, hobbyists, and non-web developers of all types. The softer the learning curve of getting online, the more people will build, share, play, and create there.</p>
<p>Softening the learning curve means making the common things easy and not introducing too many concepts until you hit the point where you need them. Beginners and experts alike benefit.</p>
<h1 id="notes"><a class="zola-anchor" href="#notes" aria-label="Anchor link for: notes">Notes</a></h1>
<p><em>Thanks to <a rel="external" href="https://github.com/gnat">Nathaniel Sabanski</a> and <a rel="external" href="https://inventwithpython.com/">Al Sweigart</a> for their feedback on a draft of this blog. I wrote this blog at <a rel="external" href="https://www.recurse.com/scout/click?t=044d120abf1c334d0b2a3132634eb025">Recurse Center</a>, a terrific programming community that you should check out.</em></p>
<ul>
<li>My example invoice generator is not meant to be put online, so it doesn’t escape text to prevent XSS attacks, or do the other <a rel="external" href="https://htmx.org/essays/web-security-basics-with-htmx">web security basics</a>.</li>
<li>Admittedly, some of PHP’s design decisions really lend themselves to insecure code. For starters, they really need <a rel="external" href="https://www.php.net/manual/en/language.basic-syntax.phptags.php">a short echo tag</a> that auto-escapes.</li>
<li>At this time, I don’t think I’m going to start defaulting to PHP for client work. I’m very comfortable in JS for general-purpose dynamic programming, and JS has a bunch of other useful web built-ins that PHP does not.</li>
<li>I am definitely going to do more web art in PHP, though. I especially like how compact and shareable it can be, which has tremendous value for certain types of code.</li>
<li>PHP is also missing a bunch of stuff I consider really important to writing RESTFUL web services, that makes pre-processing your requests close to mandatory. Big ones for me include <a rel="external" href="https://www.w3.org/Provider/Style/URI">removing the <code>.php</code> file extension</a> from the URL, and PUT/DELETE support.</li>
<li>Yes, I’m aware of well-known opinion-haver David Heinemeier Hansson’s other opinions. Some of them are right and some of them are wrong.</li>
<li>More languages should have a “thing” that they are “for.” Maybe I’ll write about how awk rekindled my love for programming next.</li>
</ul>
Custom HTML Has Levels To It2023-12-31T00:00:00+00:002023-12-31T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/blog/custom-html-has-levels/<p>The comment I received most frequently on “<a href="https://unplannedobsolescence.com/blog/behavior-belongs-in-html/">Behavior Belongs in the HTML</a>” was: “don’t Web Components solve this?”</p>Behavior Belongs in the HTML2023-12-11T00:00:00+00:002023-12-11T00:00:00+00:00
Alexander Petros
https://unplannedobsolescence.com/blog/behavior-belongs-in-html/<p>When you click the button below, it’s going to show you a little message.</p>