Analysis of https://buttondown.email/hillelwayne/rss

Feed fetched in 165 ms.
Warning Feed URL redirected to https://buttondown.com/hillelwayne/rss.
Warning Content type is application/rss+xml; charset=utf-8, not text/xml or applicaton/xml.
Feed is 226,386 characters long.
Warning Feed is missing an ETag.
Feed has a last modified date of Wed, 10 Jun 2026 12:22:04 GMT.
Feed is well-formed XML.
Warning Feed has no styling.
This is an RSS feed.
Feed title: Computer Things
Feed self link matches feed URL.
Warning Feed is missing an image.
Feed has 30 items.
First item published on 2026-06-10T12:22:04.000Z
Last item published on 2025-09-04T14:00:00.000Z
All items have published dates.
Newest item was published on 2026-06-10T12:22:04.000Z.
Home page URL: https://buttondown.com/hillelwayne
Error Home page does not have a matching feed discovery link in the <head>.

1 feed links in <head>
  • https://buttondown.com/hillelwayne/rss

  • Error Home page does not have a link to the feed in the <body>.

    Formatted XML
    <?xml version="1.0" encoding="utf-8"?>
    <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
        <channel>
            <title>Computer Things</title>
            <link>https://buttondown.com/hillelwayne</link>
            <description>&lt;!-- buttondown-editor-mode: fancy --&gt;&lt;p&gt;Hi, I'm Hillel. This is the newsletter version of &lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://www.hillelwayne.com"&gt;my website&lt;/a&gt;. I post all website updates here. I also post weekly content just for the newsletter, on topics like&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Formal Methods&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Software History and Culture&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Fringetech and exotic tooling&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The philosophy and theory of software engineering&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;You can see the archive of all public essays &lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://buttondown.email/hillelwayne/archive/"&gt;here&lt;/a&gt;.&lt;/p&gt;</description>
            <atom:link href="https://buttondown.email/hillelwayne/rss" rel="self"/>
            <language>en-us</language>
            <lastBuildDate>Wed, 10 Jun 2026 12:22:04 +0000</lastBuildDate>
            <item>
                <title>Nontrailing separators do not spark joy</title>
                <link>https://buttondown.com/hillelwayne/archive/nontrailing-separators-do-not-spark-joy/</link>
                <description>&lt;p&gt;This is valid JSON:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;This is invalid JSON:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;The difference is the last comma. The &lt;a href="https://www.json.org/json-en.html" target="_blank"&gt;JSON grammar&lt;/a&gt; specifies that a comma can separate two members of an object but not postcede ("trail") a member. I think this was a design mistake. Say we want to add two new keys to the struct, one before the &lt;code&gt;"a"&lt;/code&gt; member and one after the &lt;code&gt;"c"&lt;/code&gt; member. Here's what it would look like if trailing commas were permitted:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{
    &lt;span class="gi"&gt;+   &amp;quot;x&amp;quot;: 0,&lt;/span&gt;
    &lt;span class="w"&gt; &lt;/span&gt;   &amp;quot;a&amp;quot;: 1,
    &lt;span class="w"&gt; &lt;/span&gt;   &amp;quot;b&amp;quot;: 2,
    &lt;span class="w"&gt; &lt;/span&gt;   &amp;quot;c&amp;quot;: 3,
    &lt;span class="gi"&gt;+   &amp;quot;y&amp;quot;: 4,&lt;/span&gt;
    }
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;It's the exact same text transformation regardless of where we add the key. In the current model, we instead have this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{
    &lt;span class="gi"&gt;+   &amp;quot;x&amp;quot;: 0,&lt;/span&gt;
    &lt;span class="w"&gt; &lt;/span&gt;   &amp;quot;a&amp;quot;: 1,
    &lt;span class="w"&gt; &lt;/span&gt;   &amp;quot;b&amp;quot;: 2,
    &lt;span class="gd"&gt;-   &amp;quot;c&amp;quot;: 3&lt;/span&gt;
    &lt;span class="gi"&gt;+   &amp;quot;c&amp;quot;: 3,&lt;/span&gt;
    &lt;span class="gi"&gt;+   &amp;quot;y&amp;quot;: 4&lt;/span&gt;
    }
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Those are different transformations! Similarly if you want to remove an element, you can't just delete the corresponding line&lt;sup id="fnref:objects"&gt;&lt;a class="footnote-ref" href="#fn:objects"&gt;1&lt;/a&gt;&lt;/sup&gt;, you have to delete the line &lt;em&gt;and then&lt;/em&gt; check that the last line doesn't have a trailing comma. Don't even get me started on all the special cases involved in swapping two lines.&lt;/p&gt;
    &lt;p&gt;JSON isn't the only language with this problem. Haskell writes record types like this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;-- from https://play.haskell.org/&lt;/span&gt;
    &lt;span class="kr"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Drone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Drone&lt;/span&gt;
    &lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xPos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;yPos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zPos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;This "partial bullet point" style of putting separators at the beginning of rows makes it easier to change the last row but harder to change the first one. &lt;/p&gt;
    &lt;p&gt;TLA+ has this problem too:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;\* both valid
    VARIABLES a, b, c
    vars == &amp;lt;&amp;lt;a, b, c&amp;gt;&amp;gt;
    
    \* both invalid
    VARIABLES a, b, c,
    vars == &amp;lt;&amp;lt;a, b, c,&amp;gt;&amp;gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;This one's annoying because 1) you're constantly adding new top-level variables while working on a spec and 2) the PlusCal DSL does &lt;em&gt;not&lt;/em&gt; have this problem:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;\* Totally fine!
    (*--algorithm foo {
    variables a; b; c;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;The worst offenders, IMO, are logic languages like Prolog. Not only don't you have trailing separators, you have a &lt;em&gt;special terminating symbol&lt;/em&gt;:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:-&lt;/span&gt;
        &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;% comma&lt;/span&gt;
        &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;% comma&lt;/span&gt;
        &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.&lt;/span&gt; &lt;span class="c1"&gt;% period!&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;I guess you can sort of think of it as funny-lookin' braces:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:-&lt;/span&gt;
        &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; 
    &lt;span class="p"&gt;.&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;But this is not standard syntax and people will look at you weird if you try it. And you still don't get trailing separators.&lt;/p&gt;
    &lt;h2&gt;Something better&lt;/h2&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;Some languages allow trailing separators:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// go&lt;/span&gt;
    &lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# python&lt;/span&gt;
    &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;But I think we can do one better than that. Python and Go commas can trail but not lead, meaning we can't go 100% bullet points:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# python again&lt;/span&gt;
    &lt;span class="n"&gt;invalid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Now I personally think that bullet points are the bee's knees and wish more languages allowed leading separators. TLA+ actually has leading conjunction and disjunction operations:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;// Not TLA+ but the same semantics
    || &amp;amp;&amp;amp; a == 1
       &amp;amp;&amp;amp; b == 2
    
    || &amp;amp;&amp;amp; a == 3
       &amp;amp;&amp;amp; b == 4
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;You can't trail these, though, no writing &lt;code&gt;(a &amp;amp;&amp;amp;)&lt;/code&gt;.&lt;/p&gt;
    &lt;p&gt;The most flexible I've seen is &lt;a href="https://alloytools.org/" target="_blank"&gt;Alloy&lt;/a&gt;, which allows both leading and trailing commas:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Alloy&lt;/span&gt;
    &lt;span class="kd"&gt;sig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Valid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    
    &lt;span class="kd"&gt;sig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AlsoValid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Alloy does go a little power-mad here, because it also allows empty separators.&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;sig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;StillValid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;,,,,,,,,,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;I've heard some people call this &lt;a href="https://www.reddit.com/r/ProgrammingLanguages/comments/1amsalm/does_your_language_support_trailing_commas/" target="_blank"&gt;"stuttering"&lt;/a&gt;. I can't figure out how to &lt;a href="https://www.hillelwayne.com/post/python-abc/" target="_blank"&gt;commit crimes with this&lt;/a&gt; but you never know.&lt;/p&gt;
    &lt;h2&gt;Devil's advocate&lt;/h2&gt;
    &lt;p&gt;One argument against trailing separators is that they make parsing ambiguous. Consider this Prolog: &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:-&lt;/span&gt;
        &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.&lt;/span&gt;
    
    &lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;c&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Here it's pretty clear that &lt;code&gt;foo&lt;/code&gt; and &lt;code&gt;bar&lt;/code&gt; are separate definitions. But if we replace the rule terminator with commas:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:-&lt;/span&gt;
        &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    
    &lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;c&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Now it &lt;em&gt;could&lt;/em&gt; be alternatively parsed that &lt;code&gt;bar(c)&lt;/code&gt; is part of the definition of &lt;code&gt;foo&lt;/code&gt;— &lt;code&gt;foo&lt;/code&gt; is only true when &lt;code&gt;bar(c)&lt;/code&gt; is also true. &lt;/p&gt;
    &lt;p&gt;As another example, &lt;a href="https://docs.ruby-lang.org/en/master/syntax/layout_rdoc.html" target="_blank"&gt;this is valid Ruby&lt;/a&gt;:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# prints 5&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;succ&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;succ&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;If we could "trail method calls", this is ambiguous:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;baz&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    
    &lt;span class="n"&gt;quux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Now it's not clear if &lt;code&gt;quux()&lt;/code&gt; is a top-level function or a method of &lt;code&gt;foo&lt;/code&gt;.&lt;/p&gt;
    &lt;p&gt;Both of those relate to control separators, not data separators. Python has an edge data case with trailing data separators. The language uses parenthesis both for expression grouping like &lt;code&gt;(2+3)&lt;/code&gt; and for tuple definition like &lt;code&gt;(2,3)&lt;/code&gt;. So how do you distinguish an expression evaluation from a single-element tuple? With a trailing comma!&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; x = (2+3)
    &amp;gt;&amp;gt;&amp;gt; type(x)
    &amp;lt;class &amp;#39;int&amp;#39;&amp;gt;
    &amp;gt;&amp;gt;&amp;gt; x = (2+3,)
    &amp;gt;&amp;gt;&amp;gt; type(x)
    &amp;lt;class &amp;#39;tuple&amp;#39;&amp;gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Okay that's all I got. New (and final) preview release of &lt;em&gt;Logic for Programmers&lt;/em&gt; next week.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:objects"&gt;
    &lt;p&gt;Obvs this doesn't work if your object values are themselves multiline arrays or objects, but those make the "no trailing comma" transformations &lt;em&gt;even more&lt;/em&gt; complicated.&amp;#160;&lt;a class="footnote-backref" href="#fnref:objects" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Wed, 10 Jun 2026 12:22:04 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/nontrailing-separators-do-not-spark-joy/</guid>
            </item>
            <item>
                <title>Logic for Programmers extra credits</title>
                <link>https://buttondown.com/hillelwayne/archive/logic-for-programmers-extra-credits/</link>
                <description>&lt;p&gt;So I said there wasn’t a proper newsletter this week, since I’m in Budapest prepping for a conference. But I still got a thing for y’all.&lt;/p&gt;
    &lt;p&gt;There’s a lot of interesting topics I wanted to cover for &lt;em&gt;Logic for Programmers&lt;/em&gt;, but the book is dense enough as it is and many of these were too tangential or technical to fit in well. So I’ve been writing some supplements and uploading them &lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/logicforprogrammers/book-assets/tree/master/supplements"&gt;here&lt;/a&gt;. I’ve got four so far:&lt;/p&gt;
    &lt;ol&gt;&lt;li&gt;&lt;p&gt;How we compute the number of orderings of multiple concurrent processes&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;How first-order logic can quantify over “a set of functions”, what a “set of functions” looks like, and how functions can be defined in terms of sets (plus a bit on currying and type theory)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Barbara Liskov’s “history rule” in subtyping&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Total and partial orders on sets.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;
    &lt;p&gt;Now I’m going to caveat that these were written off the cuff and haven’t gone through the obsessive editing of the book itself, so they may be rough and there might be errors in them. Still, it’s like 2-3000 words of math content, so hopefully covers not having a proper newsletter this time. Seeya next week!&lt;/p&gt;</description>
                <pubDate>Tue, 02 Jun 2026 14:48:48 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/logic-for-programmers-extra-credits/</guid>
            </item>
            <item>
                <title>Knowing about things is cheaper than knowing things</title>
                <link>https://buttondown.com/hillelwayne/archive/knowing-about-things-is-cheaper-than-knowing/</link>
                <description>&lt;p&gt;&lt;em&gt;Short one this week because I'm way behind on book and conference prep.&lt;/em&gt;&lt;/p&gt;
    &lt;p&gt;Last week a LinkedIn Influencer wrote about how math has nothing to do with programming, so I spite-wrote a rejoinder about how math is necessary to program (just try to write software without knowing arithmetic!) and man I forgot how much spite can fuel writing. Maybe I should go back to Twitter (absolutely not).&lt;/p&gt;
    &lt;p&gt;But it got me thinking about the difference between "all programmers &lt;em&gt;can benefit&lt;/em&gt; from learning math" and "all programmers &lt;em&gt;need to&lt;/em&gt; learn math". I simultaneously believe three things:&lt;/p&gt;
    &lt;ol&gt;
    &lt;li&gt;There is some math, like arithmetic (incl. arithmetic of booleans, sets, functions, etc), that is useful to all programmers.&lt;/li&gt;
    &lt;li&gt;The remaining fields aren't useful to most programmers.&lt;/li&gt;
    &lt;li&gt;Every programmer works in a domain where there is at least one branch of math that would benefit them to learn.&lt;/li&gt;
    &lt;/ol&gt;
    &lt;p&gt;(2) means that if get a group of 100 software engineers and teach them something like algebra or calculus, you can't expect it to be applicable for more than 3 or 5. Whereas if you teach something like shell scripting or regular expressions it'd be useful to at least, like, 50. So no field of math has a good RoI for the average programmer. &lt;/p&gt;
    &lt;p&gt;(3) means that each of those 100 developers could, on their own, find a field of math that is useful to them. In order to do that, though, they need to roughly know what the fields are, what the big ideas are, and where they might be useful. It is more useful to teach them &lt;em&gt;about&lt;/em&gt; many fields than to teach them any one specific field in-depth. &lt;/p&gt;
    &lt;p&gt;I think that's generally true with most areas of knowledge! Getting basic exposure to something takes a lot less time and effort than learning it in-depth. If you're specifically trying to learn things that will be useful to your work&lt;sup id="fnref:topic"&gt;&lt;a class="footnote-ref" href="#fn:topic"&gt;1&lt;/a&gt;&lt;/sup&gt;, you only want to go in-depth on topics you know will be helpful. But you won't know the topic is helpful (or even that it exists) unless you know the very basics already. So it makes sense to get broad exposure to lots of topics and only later choose what to go deep on.&lt;/p&gt;
    &lt;h2&gt;How to do this&lt;/h2&gt;
    &lt;p&gt;Uhhhhhhhhhh&lt;/p&gt;
    &lt;p&gt;Honestly the thing that's worked best for me personally is to read stuff meant to give a broad exposure without lots of details. Non-honors undergraduate textbooks are really good for that. It helps to approach it like "learning for the fun of learning" and not "learning to accumulate potentially useful stuff for later".&lt;/p&gt;
    &lt;p&gt;Conference videos can be good, too, though I'm not a video person. I imagine popular-science stuff could be useful but 1) I grew up at a time when "popsci" meant "lying about science to make it sound more impressive" (though I know Quanta's really good now) and 2) I tend not to mesh with the popsci writing style.&lt;/p&gt;
    &lt;hr /&gt;
    &lt;h2&gt;No Proper Newsletter Next Week&lt;/h2&gt;
    &lt;p&gt;I'll be at &lt;a href="https://craft-conf.com/2026" target="_blank"&gt;Craft Conference Budapest&lt;/a&gt;. That said, there I'll be uploading some &lt;em&gt;Logic for Programmers&lt;/em&gt;-related material that's about the length of a newsletter, so keep your eyes peeled!&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:topic"&gt;
    &lt;p&gt;Which to be clear is not the only reason to learn something!&amp;#160;&lt;a class="footnote-backref" href="#fnref:topic" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Thu, 28 May 2026 16:03:01 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/knowing-about-things-is-cheaper-than-knowing/</guid>
            </item>
            <item>
                <title>Assumptions weaken properties</title>
                <link>https://buttondown.com/hillelwayne/archive/assumptions-weaken-properties/</link>
                <description>&lt;p&gt;In &lt;a href="https://buttondown.com/hillelwayne/archive/some-tests-are-stronger-than-others/" target="_blank"&gt;some tests are stronger than others&lt;/a&gt;, I defined &lt;code&gt;STRONG =&amp;gt; WEAK&lt;/code&gt; to mean "any system passing test STRONG is also guaranteed to pass WEAK". This uses the logical implication operator, defined as &lt;code&gt;P =&amp;gt; Q = !P || (P &amp;amp;&amp;amp; Q)&lt;/code&gt;.&lt;/p&gt;
    &lt;p&gt;Implication may be the most overworked operator in logic. Among other things, it's also used in formal specification, where &lt;code&gt;Spec =&amp;gt; Prop&lt;/code&gt; means "any system satisfying &lt;code&gt;Spec&lt;/code&gt; has property &lt;code&gt;Prop&lt;/code&gt;" and &lt;code&gt;ASSUME =&amp;gt; Spec&lt;/code&gt; means "The assumption &lt;code&gt;ASSUME&lt;/code&gt; must hold in order for the system to satisfy &lt;code&gt;Spec&lt;/code&gt;."&lt;/p&gt;
    &lt;p&gt;Now let's mush these all together and do some math. To start, "the system has property &lt;code&gt;Prop&lt;/code&gt;" is the same as "the system passes the test that checks &lt;code&gt;Prop&lt;/code&gt;", so test strength is also property strength. Now let "&lt;code&gt;ASSUME =&amp;gt; Prop&lt;/code&gt;" mean "the system passes &lt;code&gt;Prop&lt;/code&gt; assuming &lt;code&gt;ASSUME&lt;/code&gt; is true." In classic logic, if &lt;code&gt;P&lt;/code&gt; is true, then obviously &lt;code&gt;!Q || P&lt;/code&gt; is true. Further, that is equivalent (just draw the truth table!) to &lt;code&gt;!Q || (P &amp;amp;&amp;amp; Q)&lt;/code&gt;. So for &lt;em&gt;any&lt;/em&gt; propositions &lt;code&gt;P&lt;/code&gt; and &lt;code&gt;Q&lt;/code&gt;, &lt;code&gt;P =&amp;gt; (Q =&amp;gt; P)&lt;/code&gt;.&lt;/p&gt;
    &lt;p&gt;In other words, &lt;code&gt;Prop =&amp;gt; (ASSUME =&amp;gt; Prop)&lt;/code&gt;. In other other words, "the system passes &lt;code&gt;Prop&lt;/code&gt;" is a stronger property than "the system passes &lt;code&gt;Prop&lt;/code&gt; whenever our assumptions hold."&lt;/p&gt;
    &lt;p&gt;In other other other words, any assumption added makes a property weaker.&lt;/p&gt;
    &lt;p&gt;This makes intuitive sense to me. A JSON parser that's only been verified with ASCII strings has the property "input only uses ASCII &amp;amp;&amp;amp; is valid json =&amp;gt; correctly parsed". A better JSON parser that works for all Unicode will have the property "is valid json =&amp;gt; correctly parsed", which has fewer assumptions, meaning it's guaranteed to work in a strict superset of cases. &lt;/p&gt;
    &lt;p&gt;It also matches the intuition that "more assumptions means more likely to go wrong". We have a bug whenever &lt;code&gt;Prop&lt;/code&gt; is false. The only way for &lt;code&gt;Spec =&amp;gt; Prop&lt;/code&gt; to be true and &lt;code&gt;Prop&lt;/code&gt; be false is if &lt;code&gt;Spec&lt;/code&gt; is false, eg our system doesn't satisfy the specification we intended to implement. On the other hand, &lt;code&gt;Spec =&amp;gt; (ASSUME =&amp;gt; Prop) &amp;amp;&amp;amp; !Prop&lt;/code&gt; is true whenever &lt;code&gt;Spec&lt;/code&gt; &lt;em&gt;and/or&lt;/em&gt; &lt;code&gt;ASSUME&lt;/code&gt; is false, meaning a correctly-implemented system could still show a bug if a runtime assumption is false. &lt;/p&gt;
    &lt;p&gt;...Looking back the last two paragraphs have a lot of conceptual leaps. Does that all make sense to you? It all feels natural to me but that might just be my familiarity with the topic talking. &lt;/p&gt;
    &lt;p&gt;Regardless, a couple more notes on assumptions:&lt;/p&gt;
    &lt;h2&gt;Why we have assumptions&lt;/h2&gt;
    &lt;p&gt;Why not just build our systems to satisfy &lt;code&gt;ASSUME =&amp;gt; Prop&lt;/code&gt; when we can "just" build it to satisfy &lt;code&gt;Prop&lt;/code&gt;? At least three reasons.&lt;/p&gt;
    &lt;p&gt;&lt;strong&gt;First&lt;/strong&gt;, sometimes &lt;code&gt;Prop&lt;/code&gt; is simply impossible to satisfy and we need to add assumptions to make this property. We do this a lot in formal methods with &lt;a href="https://www.hillelwayne.com/post/fairness/" target="_blank"&gt;fairness&lt;/a&gt;. The property "&lt;code&gt;mergesort&lt;/code&gt; always returns a sorted list" is unsatisfiable because we can dropkick the computer before it returns. Instead, we have to verify a weaker property like "&lt;code&gt;mergesort&lt;/code&gt; always returns =&amp;gt; it returns a sorted list" or "&lt;code&gt;mergesort&lt;/code&gt; always makes progress =&amp;gt; it will eventually return a sorted list."&lt;/p&gt;
    &lt;p&gt;Another example is Rust. Rust &lt;em&gt;does not&lt;/em&gt; guarantee the property "the program is memory safe". It guarantees the weaker property "all &lt;code&gt;unsafe&lt;/code&gt; blocks are memory safe =&amp;gt; the program is memory safe". Making the language satisfy the stronger property would rule out too many use cases of Rust. Note you also get memory safety if you don't use &lt;code&gt;unsafe&lt;/code&gt;, but that still satisfies the assumption, as all &lt;em&gt;zero&lt;/em&gt; blocks are safe!&lt;/p&gt;
    &lt;p&gt;&lt;strong&gt;Second&lt;/strong&gt;, sometimes the strong property is satisfiable, but it's simply not worth the extra cost. Like it's possible to make our software resistant against cosmic rays, but if your code isn't running in space, why bother? Just say "No cosmic bit flips =&amp;gt; things work". Or if your plugin works Neovim &lt;code&gt;0.12&lt;/code&gt; but not 0.11, you could put in the effort to make it run on older versions, or you could tell everybody that they need to upgrade to use your plugin. "Neovim version is at least 0.12 =&amp;gt; the plugin works".&lt;/p&gt;
    &lt;p&gt;&lt;strong&gt;Third&lt;/strong&gt;, sometimes the strong property is satisfiable in the system but not easily &lt;em&gt;verifiable&lt;/em&gt;. Say your algorithm makes a lot of API calls and you don't want to hit rate limits while testing. If you &lt;a href="https://en.wikipedia.org/wiki/Mock_object" target="_blank"&gt;mock out the API&lt;/a&gt; you're testing the weaker property "The mock is accurate to the API =&amp;gt; the algorithm is correct".&lt;/p&gt;
    &lt;h2&gt;Assumptions are a second level of system effect&lt;/h2&gt;
    &lt;p&gt;I notice that almost all of the examples in the last two sections are exoprogram factors:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;The JSON parser assumptions are about user input&lt;/li&gt;
    &lt;li&gt;Fairness assumptions are about the OS/hardware/operating environment&lt;/li&gt;
    &lt;li&gt;Unsafe assumptions are about things the Rust compiler can't verify&lt;/li&gt;
    &lt;li&gt;Cosmic ray assumptions depend on the physical location of the hardware&lt;/li&gt;
    &lt;li&gt;The plugin assumptions are about the Neovim team's release schedule and social compatibility contract&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;The edge case is replacing a third party call with a mock. The assumption is intraprogram because the program could just hit the API during testing. We still have the assumption because of an exoprogram constraint. Maybe this is why mocks are considered an antipattern in Agile.&lt;/p&gt;
    &lt;p&gt;One consequence of this is that checking whether assumptions hold is a different problem from verifying that your code works given the assumptions. Like to make sure "all unsafe blocks are safe" can't use the rust compiler, you need a second tool like &lt;a href="https://github.com/rust-lang/miri" target="_blank"&gt;Miri&lt;/a&gt;. I wonder if checking assumptions is, in practice, generally more difficult than checking everything else.&lt;/p&gt;</description>
                <pubDate>Wed, 20 May 2026 15:13:16 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/assumptions-weaken-properties/</guid>
            </item>
            <item>
                <title>Points are a weird and inconsistent unit of measure</title>
                <link>https://buttondown.com/hillelwayne/archive/points-are-a-weird-and-inconsistent-unit-of/</link>
                <description>&lt;p&gt;&lt;img alt="Some pictures of point sizes from &amp;quot;The Practice of Typography&amp;quot;" class="newsletter-image" src="https://assets.buttondown.email/images/1e994c5a-8b40-4c4e-8898-81ebfbfa8bcb.png?w=960&amp;amp;fit=max" /&gt;&lt;/p&gt;
    &lt;p&gt;I'm in the middle of redoing the &lt;a href="https://logicforprogrammers.com" target="_blank"&gt;&lt;em&gt;Logic for Programmers&lt;/em&gt;&lt;/a&gt; diagrams and this has surfaced a really annoying problem. The book is formatted in LaTeX using a pseudo-grid of 10.8pt × 7.2pt. The diagrams are done in Inkscape using a 10.8pt × 7.2pt.&lt;/p&gt;
    &lt;p&gt;Last week I found out that these are not the same points. &lt;/p&gt;
    &lt;p&gt;Latex defines a point as 1/72.27 inches (0.3515 millimeters). Inkscape instead uses 1/72 inches (0.3528 mm). It's only a difference of 0.4% but it still floors me that two widespread digital technologies would be different!&lt;/p&gt;
    &lt;p&gt;So, uh, &lt;em&gt;what happened?&lt;/em&gt;&lt;/p&gt;
    &lt;p&gt;A few hours of reading articles later, this is what I found, caveat that I didn't spend all that much time researching this and this is only initial impressions. &lt;/p&gt;
    &lt;h2&gt;what even is a point&lt;/h2&gt;
    &lt;p&gt;A &lt;a href="https://en.wikipedia.org/wiki/Point_(typography)" target="_blank"&gt;point&lt;/a&gt; is a typographic measure, coming from 1517, that is supposedly the smallest interesting size for a printer. This was notably not a standardized measure- different companies in times used different point sizes depending on their equipment. Over time it was standardized, but each country picked a different standard: the German and Japanese point is 0.250 mm, the French point is allegedly 0.399 mm, etc.&lt;/p&gt;
    &lt;p&gt;But early computer history is super Americentric so that's what technology uses. In the US, they standardized the point around the end of the 19th century. To what? I dunno. &lt;a href="https://archive.org/details/practiceoftypogr00devirich/page/150/mode/1up" target="_blank"&gt;This source&lt;/a&gt; from 1900 gives the length of a point as 35/996 cm (72.281 points/in) and then says there are exactly 867.4699 "ems per foot" (72.289 points/in). &lt;a href="https://books.google.com/books?id=yUkHAwAAQBAJ&amp;amp;pg=PA57#v=onepage&amp;amp;q&amp;amp;f=false" target="_blank"&gt;This source&lt;/a&gt; from 1916 says the standard pica (12 points) is 0.16604 inches and that there are 72.272 "pica ems per foot". Which conveniently enough gives us 72.272 points/in (a pica being 12 points). Then on the very next page they say no a pica is actually 0.16604&lt;strong&gt;4&lt;/strong&gt; inches and a point is exactly 0.013837 inches. I found other sources with other definitions, too.&lt;/p&gt;
    &lt;p&gt;I'm going to chalk the differences up to a mix of "the definitions of 'meter' and 'foot' changed over time" and "these are less than a micron apart so who gives a shit". I do, I give a shit. Regardless, the &lt;a href="https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular570.pdf" target="_blank"&gt;official NIST definition&lt;/a&gt; settled on a point being 0.013837 inches, so you'd get 72.27 points/inch. &lt;/p&gt;
    &lt;p&gt;Wrong! You absolute moron. You get 72.270001 points/inch. This annoyed Donald Knuth enough that he rejiggered the ratio in &lt;a href="https://latex.us/systems/knuth/dist/tex/texbook.tex" target="_blank"&gt;TeX&lt;/a&gt; (pg 58):&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;TEX’s “pt” has been made slightly larger than the official printer’s point, which was defined to equal exactly .013837 in by the American Typefounders Association in 1886 [cf. National Bureau of Standards Circular 570 (1956)]. In fact, one classical point is exactly .99999999 pt, so the “error” is essentially one part in 10^8. … The new definition 72.27 pt = 1 in is not only better for calculation, it is also easier to remember.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;(To explain his motivation a little: American printers measure things in inches, and define the point in terms of inches. TeX measures things in points&lt;sup id="fnref:sp"&gt;&lt;a class="footnote-ref" href="#fn:sp"&gt;1&lt;/a&gt;&lt;/sup&gt;, and define the inch in terms of points.)&lt;/p&gt;
    &lt;p&gt;For the record, NIST seems to think "72 points/inch" is a good enough approximation. TeX calls this the &lt;code&gt;bp&lt;/code&gt; (big point).&lt;/p&gt;
    &lt;h2&gt;AKA the Inkscape value&lt;/h2&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;Now what about Inkscape? As far as I can tell, this comes from the Postscript format's definition of the &lt;a href="https://archive.org/details/postscriptlangua0000unse_c7i6/page/60/mode/2up?q=72" target="_blank"&gt;unit size&lt;/a&gt;:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;The length of a unit along the
    x axis and along the y axis is 1/72 of an inch. We call this coordinate system &lt;em&gt;default user space&lt;/em&gt;.&lt;/p&gt;
    &lt;p&gt;These features of default user space are chosen for their mathematical simplicity and convenience. The location and orientation of the axes follow common mathematical practice and cause all points within the current page to have positive x and y coordinate values. The unit size, 1/72 of an inch, is very close to the size of a printer's point (1/72.27 inch), which is a standard measuring unit used in the printing industry.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;Later on &lt;a href="https://archive.org/details/postscriptlangua0000unse_c7i6/page/86/mode/2up?q=72" target="_blank"&gt;page 86&lt;/a&gt; they straight up call 1/72 inch a "point". &lt;a href="https://www.adobe.com/jp/print/postscript/pdfs/PLRM.pdf#page=197" target="_blank"&gt;Later editions&lt;/a&gt; would clarify it's not actually a point and that points are stupid anyway:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;Note: The default unit size (1/72 inch) is approximately the same as a “point,” a unit
    widely used in the printing industry. It is not exactly the same as a point, however;
    there is no universal definition of a point.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;Apple shipped PostScript in their &lt;a href="https://en.wikipedia.org/wiki/LaserWriter" target="_blank"&gt;LaserWriter&lt;/a&gt; laser printer, other companies followed suite, making PostScript the de facto printing language and 72 points/in the standard digital measure. The W3 consortium used the same measure in &lt;a href="https://www.w3.org/TR/css3-values/#absolute-lengths" target="_blank"&gt;CSS&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Guides/Content_type#length" target="_blank"&gt;SVG&lt;/a&gt;&lt;sup id="fnref:svg"&gt;&lt;a class="footnote-ref" href="#fn:svg"&gt;2&lt;/a&gt;&lt;/sup&gt;, Inkscape is an SVG editor, and that's all she wrote.&lt;/p&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;h3&gt;Epilog: Frink&lt;/h3&gt;
    &lt;p&gt;Whenever I need to mess with units of measure, I turn to  &lt;a href="https://frinklang.org/" target="_blank"&gt;Frink&lt;/a&gt;. Frink is a Turing-complete language with &lt;em&gt;really&lt;/em&gt; good unit of measure support:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;oldinch := surveyfoot / 12 // pre 1959 inch 
    35 cm / (996 pts) -&amp;gt; oldinch / pts
    0.013834839357429718876
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;The author does also does incredibly thorough research on the history of units measurements. Here's what the &lt;a href="https://frinklang.org/frinkdata/units.txt" target="_blank"&gt;Frink data file&lt;/a&gt; says about points:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;point :=          0.013837ee0 inch    // exact, NIST Handbook 44, Appendix 3
    printerspoint :=       point
    
    texscaledpoint :=      1/65536 point    // The TeX typesetting system uses
    texsp :=               texscaledpoint   // this for all computations.
    computerpoint :=       1/72 inch        // The American point was rounded 
    computerpica :=        12 computerpoint // to an even 1/72 inch by computer
    postscriptpoint :=     computerpoint    // people at some point. 
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Well, now we know what that "some point" is!&lt;/p&gt;
    &lt;p&gt;Now we also know that the definitions of &lt;code&gt;texscaledpoint&lt;/code&gt; is (&lt;em&gt;gasp&lt;/em&gt;) wrong. &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;realtexpoint := 1/72.27 inch
    realtexsp := 1/65536 realtexpoint
    (realtexsp - texsp)
    5.36285100578e-17 m (length)
    (realtexsp - texsp) / realtexsp
    1.0000000000005691827e-8
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;The Frink definition is off by about 50 attometers, or approximately 3% of the width of a proton.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:sp"&gt;
    &lt;p&gt;Actually "scaled points", where 2^16 sp = 1 pt.&amp;#160;&lt;a class="footnote-backref" href="#fnref:sp" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:svg"&gt;
    &lt;p&gt;Which is why it's super weird that the SVG editor &lt;a href="https://www.drawio.com/" target="_blank"&gt;draw.io&lt;/a&gt; uses a point size of 1/&lt;strong&gt;100&lt;/strong&gt; inch.&amp;#160;&lt;a class="footnote-backref" href="#fnref:svg" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Wed, 13 May 2026 15:56:37 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/points-are-a-weird-and-inconsistent-unit-of/</guid>
            </item>
            <item>
                <title>New Logic for Programmers (and the future of this newsletter)</title>
                <link>https://buttondown.com/hillelwayne/archive/new-logic-for-programmers-and-the-future-of-this/</link>
                <description>&lt;p&gt;So first the immediate news: I just released version 0.14 of &lt;a href="https://logicforprogrammers.com" target="_blank"&gt;Logic for Programmers&lt;/a&gt;! This release is pretty similar to 0.13. There are a few rewrites but the vast majority of the changes are layout, copyediting, and technical editing. Full notes &lt;a href="https://github.com/logicforprogrammers/book-assets/blob/master/CHANGELOG.md" target="_blank"&gt;here&lt;/a&gt;. &lt;/p&gt;
    &lt;p&gt;In related news, I've started doing test prints of the book:&lt;/p&gt;
    &lt;p&gt;&lt;img alt="Two test prints of Logic for Programmers" class="newsletter-image" src="https://assets.buttondown.email/images/958bdc79-fa06-4bce-b0a8-6e5ecfe8f2e0.png?w=960&amp;amp;fit=max" /&gt;&lt;/p&gt;
    &lt;p&gt;There's not a whole lot left to be done. I've gotta fix up some diagrams, do more formatting and proofreading, incorporate some fixes raised by readers, and make a website and back cover. After that, the book should be ready for 1.0. I'm aiming to have print copies purchasable by the end of June!&lt;/p&gt;
    &lt;hr /&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;Now the big news: starting August, I'll be a full-time employee of &lt;a href="https://antithesis.com/" target="_blank"&gt;Antithesis&lt;/a&gt;, a generative testing platform. Officially my role is "developer educator", and I'll be tasked with making "property-based testing, fuzzing, fault injection, &lt;a href="https://hegel.dev/" target="_blank"&gt;Hegel&lt;/a&gt;, &lt;a href="https://github.com/antithesishq/bombadil" target="_blank"&gt;Bombadil&lt;/a&gt;, and the Antithesis platform understandable to everyday engineers". So the same kind of work I do now, except with far more support and a matching 401(k). &lt;/p&gt;
    &lt;p&gt;I already have three pages of topic ideas you have no idea how excited I am about this &lt;/p&gt;
    &lt;p&gt;So how is this going to affect the newsletter? First, I want to make clear that this is &lt;em&gt;not&lt;/em&gt; going to become an Antithesis newsletter. My Antithesis-related work is going to be on their official platforms. I do think one of the best ways to make a topic "understandable" is to write foundational material that's useful to all engineers, whether they're invested in the topic or not. I might share links to things I make along those lines, but they'll be just that, links.&lt;/p&gt;
    &lt;p&gt;At the same time, the content of &lt;em&gt;this&lt;/em&gt; newsletter will change a little. Property testing and fuzzing aren't the same as formal methods, but a lot of the foundations overlap, especially in how we think about properties and correctness. I don't know for sure yet, but I suspect that I'll start biasing this newsletter away from Antithesis related topics. So there will probably be less theoretic things like &lt;a href="https://buttondown.com/hillelwayne/archive/what-does-undecidable-mean-anyway/" target="_blank"&gt;what does undecidable mean&lt;/a&gt; and &lt;a href="https://buttondown.com/hillelwayne/archive/some-tests-are-stronger-than-others/" target="_blank"&gt;Some tests are stronger than others&lt;/a&gt; and more history and software weirdness things like &lt;a href="https://buttondown.com/hillelwayne/archive/why-do-we-call-it-boilerplate-code/" target="_blank"&gt;Why do we call it "boilerplate code"&lt;/a&gt; and &lt;a href="https://buttondown.com/hillelwayne/archive/finding-hard-24-puzzles-with-planner-programming/" target="_blank"&gt;esoteric programming paradigms&lt;/a&gt;. &lt;/p&gt;
    &lt;p&gt;The other change is going to be frequency. For the past six years I've kept updates to (mostly) a weekly schedule. For the past six years I've also been totally self-employed. I don't know how much time I'll have with a full time job! Once I'm settled in I'd like to keep writing newsletters, but it might slow down from weekly to biweekly or monthly. We'll feel it out as we go. &lt;/p&gt;
    &lt;hr /&gt;
    &lt;p&gt;Anyway, this has been a pretty software-light newsletter, so let's close out with a fun thing. &lt;code&gt;f(x) = x+2&lt;/code&gt; is a  &lt;strong&gt;monotonically increasing function&lt;/strong&gt;: increasing &lt;code&gt;x&lt;/code&gt; increases &lt;code&gt;f(x)&lt;/code&gt; and decreasing &lt;code&gt;f(x)&lt;/code&gt; decreases &lt;code&gt;f(x)&lt;/code&gt;.  Similarly, &lt;code&gt;f(x) = -x+1&lt;/code&gt; is monotonically decreasing, and &lt;code&gt;f(x) = x^2&lt;/code&gt; is neither.&lt;/p&gt;
    &lt;p&gt;While working on the book I realized that the &lt;code&gt;all&lt;/code&gt; quantifier is monotonically false with respect to adding elements and true with respect to removing them. Let &lt;code&gt;A(set) = all x in set: P(x)&lt;/code&gt;. Then if &lt;code&gt;A(S)&lt;/code&gt; is false, &lt;code&gt;A(S | {e})&lt;/code&gt; is also false, and if &lt;code&gt;A(S)&lt;/code&gt; is true, &lt;code&gt;A(S - {e})&lt;/code&gt; is also true. &lt;code&gt;some&lt;/code&gt; goes the other way: if it's true, it's true if you add an element, and if it's false it's still false if you take one away.&lt;/p&gt;
    &lt;p&gt;An interesting consequence is that &lt;code&gt;all&lt;/code&gt; &lt;em&gt;must&lt;/em&gt; be true for the empty set, because if it was false it would be false for all values! This is another justification why, in Python, &lt;a href="https://buttondown.com/hillelwayne/archive/why-all-is-true-prod-is-1-etc/" target="_blank"&gt;&lt;code&gt;all([]) == True&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;Similarly, in temporal logic: &lt;code&gt;always A&lt;/code&gt; is monotonically false with respect to system behavior and &lt;code&gt;eventually A&lt;/code&gt; is monotonically true. I realized this when messing with this &lt;a href="https://quickstrom.github.io/ltl-visualizer/" target="_blank"&gt;LTL visualizer&lt;/a&gt; my friend (and soon to be coworker!) Oskar Wickström. I think this is pretty neat!&lt;/p&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;</description>
                <pubDate>Wed, 06 May 2026 17:03:46 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/new-logic-for-programmers-and-the-future-of-this/</guid>
            </item>
            <item>
                <title>Illegal vs Unwanted States</title>
                <link>https://buttondown.com/hillelwayne/archive/illegal-vs-unwanted-states/</link>
                <description>&lt;p&gt;An &lt;strong&gt;illegal state&lt;/strong&gt; is a state we never want our system to be in. An &lt;strong&gt;unwanted state&lt;/strong&gt; is a state we don't want to stay in. Many states that we wish were illegal are actually unwanted.&lt;/p&gt;
    &lt;p&gt;Considering a calendaring software which stores calendar events as &lt;code&gt;{user: {events: [event]}}&lt;/code&gt;, where each event has a start and end time. This allows one person to attend two events at the same time. We might consider this illegal and replace the data type with &lt;code&gt;{user: {time: optional event}}&lt;/code&gt; which makes this impossible. However, a scheduling conflict isn't illegal, only unwanted! It is possible for a person to sign up for two overlapping events. Maybe they're supposed to choose one event, maybe they'll decide which event to go to later, maybe one of the events doesn't actually represent an in-person meeting. &lt;/p&gt;
    &lt;p&gt;In that case it's acceptable, if not ideal, to remain in the unwanted state. Other unwanted states lead to invalid states if not exited quickly. An airline flight is in an unwanted state if there are more passengers booked to fly than seats available. This must be resolved before passengers actually board, as "more passengers physically on the plane than seats available" is an illegal state.&lt;/p&gt;
    &lt;p&gt;In some cases, an unwanted state does not lead to illegal states, but permanently remaining in the unwanted state is still a problem. We might guarantee that a network partition does not ever lead to inconsistent data. Even though the unwanted state of a network partition cannot cause the illegal state of corrupt data, we still have a big problem if we don't ever fix the partition.&lt;/p&gt;
    &lt;h2&gt;Why systems must represent unwanted states&lt;/h2&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;Generally, unwanted states can happen if we don't have complete control over our system's behavior. We can't guarantee our network is perfectly reliable, our servers are always up, our users all put in correct data. If our system gets input from the external world then the world can push us into an unwanted state. We need to be able to detect these states so we can resolve them.&lt;/p&gt;
    &lt;p&gt;Even when we have complete control over the system, we still may want to be able to temporarily dip into unwanted states. If they wanted, airlines could make overbooking flights impossible. But airlines want to be able to overbook because they expect some number of no-shows. We need to allow them their unwanted state and then put systems in place to prevent it evolving into an illegal state.&lt;/p&gt;
    &lt;p&gt;Sometimes we need unwanted states to make certain workflows possible. It may be the case that 95% never, ever want to accepted conflicting events, and preventing that unwanted state would make their lives better. But without that unwanted state, intentionally double booking yourself is impossible. Some people might need that! In these cases we want to make it very clear to users that they're entering an unwanted state, and then let them decide for themselves how to leave it. &lt;/p&gt;
    &lt;p&gt;(The airlines and users want the unwanted state! It's us engineers who consider it "unwanted" because it can lead to problems down the road.) &lt;/p&gt;
    &lt;h2&gt;Formal models of unwanted states&lt;/h2&gt;
    &lt;p&gt;Illegal states correspond to violated invariants. Conventionally speaking we write this as &lt;code&gt;[]!Illegal&lt;/code&gt;: it should be true at all times that &lt;code&gt;Illegal&lt;/code&gt; is not true. If a single state ever satisfies &lt;code&gt;Illegal&lt;/code&gt; then our system has a bug. &lt;/p&gt;
    &lt;p&gt;Unwanted states are trickier, since they can be both &lt;a href="https://www.hillelwayne.com/post/safety-and-liveness/" target="_blank"&gt;safety and liveness properties&lt;/a&gt;. If modeling an airline system, we don't necessarily want to check properties of overbooking, we only need to check that no overflights happen.&lt;sup id="fnref:overflight"&gt;&lt;a class="footnote-ref" href="#fn:overflight"&gt;1&lt;/a&gt;&lt;/sup&gt; We may discover in the process of verifying that that overbooking is the main cause of overflights and/or that if overbooking is not resolved, then we will eventually have an overflight. Further, if "we never overbook" guarantees "we never overflight", we'd say that "no overbooks" is a &lt;a href="https://buttondown.com/hillelwayne/archive/some-tests-are-stronger-than-others/" target="_blank"&gt;stronger&lt;/a&gt; property than "no overflights". &lt;/p&gt;
    &lt;p&gt;"We never remain in a network partition" is &lt;a href="https://buttondown.com/hillelwayne/archive/formalizing-stability-and-resilience-properties/" target="_blank"&gt;formalized&lt;/a&gt; as &lt;code&gt;[]&amp;lt;&amp;gt;!Partition&lt;/code&gt;: we can enter a partition and stay partitioned a long time but must eventually heal the partition. The &lt;a href="https://p-org.github.io/P/" target="_blank"&gt;P specification language&lt;/a&gt; calls these &lt;a href="https://p-org.github.io/P/manual/monitors/#liveness-specification" target="_blank"&gt;hot states&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;(PS: if all goes well, there should be a new &lt;a href="https://logicforprogrammers.com" target="_blank"&gt;Logic for Programmers&lt;/a&gt; release next week!)&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:overflight"&gt;
    &lt;p&gt;Not a real term.&amp;#160;&lt;a class="footnote-backref" href="#fnref:overflight" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Tue, 28 Apr 2026 15:14:09 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/illegal-vs-unwanted-states/</guid>
            </item>
            <item>
                <title>People get confused when language implementations break language guarantees</title>
                <link>https://buttondown.com/hillelwayne/archive/people-get-confused-when-language-implementations/</link>
                <description>&lt;p&gt;Take the following Python program:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# x = 1, y = 2&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;It'll print &lt;code&gt;[0, 0]&lt;/code&gt;. If we swapped the two assignments, it'd instead print &lt;code&gt;[0, 1]&lt;/code&gt;. Each assignment happens in a separate temporal step. Pretty much all imperative languages behave this way.&lt;/p&gt;
    &lt;p&gt;Now take the following TLA+ snippet:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;\* x = 1, y = 2
    /\ x&amp;#39; = 0
    /\ y&amp;#39; = x
    /\ PrintT(&amp;lt;&amp;lt;x&amp;#39;, y&amp;#39;&amp;gt;&amp;gt;)
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;This'll print &lt;code&gt;&amp;lt;&amp;lt;0, 1&amp;gt;&amp;gt;&lt;/code&gt;. Unlike in imperative languages, TLA+ separates the notion of update and temporal step. We read &lt;code&gt;x' = 0&lt;/code&gt; as "in the &lt;em&gt;next&lt;/em&gt; state, &lt;code&gt;x&lt;/code&gt; will be 0, but it still has the same value in the current state". So in every state &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;x'&lt;/code&gt; are essentially separate variables. As a consequence of this, the order of statements don't matter in TLA+ semantics and swapping the two assignments doesn't change the printed output. The language is really clever like that! This means, among other things that there's basically no intrastep race conditions. One function can update a variable without affecting how any other function uses it.&lt;/p&gt;
    &lt;p&gt;Okay so now beginners inevitably run into a problem with this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;\* x = 1, y = 2
    /\ x&amp;#39; = y&amp;#39;
    /\ y&amp;#39; = x
    /\ PrintT(&amp;lt;&amp;lt;x&amp;#39;, y&amp;#39;&amp;gt;&amp;gt;)
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;This'll crash because &lt;code&gt;y'&lt;/code&gt; isn't defined yet. But if we swap the two assignments this works fine and prints &lt;code&gt;&amp;lt;&amp;lt;1, 1&amp;gt;&amp;gt;&lt;/code&gt;. So clearly that whole thing about nonordering is a pack of bunk. &lt;/p&gt;
    &lt;p&gt;Well, not exactly. TLA+ semantics still guarantee nonordering. The problem is that verifiers don't perfectly implement the TLA+ semantics. &lt;code&gt;y' &amp;gt; 0&lt;/code&gt; is a totally reasonable "assignment" but there's an infinite number of possible next states! So the main model checker (TLC) instead requires that &lt;code&gt;y' = some_value&lt;/code&gt; comes before any other use of &lt;code&gt;y'&lt;/code&gt;, which means ordering now matters.&lt;/p&gt;
    &lt;p&gt;The bigger problem is with &lt;code&gt;PrintT&lt;/code&gt;. The TLA+ semantics guarantee nonordering because the semantics don't allow for side effects. The model checker adds in effectful operators like &lt;code&gt;PrintT&lt;/code&gt; and &lt;code&gt;Assert&lt;/code&gt; and &lt;code&gt;IOExec&lt;/code&gt;. This can cause a problem with guard statements. Theoretically speaking these two script blocks are equivalent:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/\ x = 0 \* guard statement
    /\ P()
    /\ x&amp;#39; = x + 1
    
    /\ x&amp;#39; = x + 1
    /\ P()
    /\ x = 0 \* guard statement
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;When &lt;code&gt;x = 1&lt;/code&gt;, these don't lead to a new state due to the guard clause. But the model checker evaluates each line one at a time, meaning in block 2 it will run &lt;code&gt;x' = x + 1&lt;/code&gt; and &lt;code&gt;P()&lt;/code&gt; before getting to &lt;code&gt;x = 0&lt;/code&gt; and discarding the state. If &lt;code&gt;P&lt;/code&gt; is a proper TLA+ operator, this isn't a problem, but if it's &lt;code&gt;PrintT&lt;/code&gt; or &lt;code&gt;Assert&lt;/code&gt; it will take its effect first, leading to weird ghost prints that don't correspond with any next states.&lt;/p&gt;
    &lt;p&gt;This difference between "what TLA+ semantics guarantees" and "the specific ways TLC can break those guarantees" is a huge source of confusion for people! On top of that many of these operators, like &lt;code&gt;IOExec&lt;/code&gt; and &lt;code&gt;TLCSet&lt;/code&gt;, are meant as escape hatches. So if you need them you're already doing something pretty weird, and that makes it even more confusing. &lt;/p&gt;
    &lt;p&gt;And on top of that is that there's no syntactic or visual distinguishing between a guarantee-breaking TLC operator and a regular safe TLA+ operator. In compiled languages you got pragmas and preprocessors, which let the compiler do things the language can't. But those usually have a visually distinct syntax, so you know from looking that here be dragons. &lt;/p&gt;
    &lt;p&gt;I'm reminded of Neel Krishnaswami's incredible post &lt;a href="https://semantic-domain.blogspot.com/2013/07/what-declarative-languages-are.html" target="_blank"&gt;What Declarative Languages Are&lt;/a&gt;&lt;sup id="fnref:laurie"&gt;&lt;a class="footnote-ref" href="#fn:laurie"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;This also lets us make the prediction that the least-loved features of any declarative language will be the ones that expose the operational model, and break the declarative semantics. So we can predict that people will dislike (a) backreferences in regular expressions, (b) ordered choice in grammars, (c) row IDs in query languages, (d) cut in Prolog, (e) constraint priorities in constraint languages. &lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;Prolog cuts have a visually distinct syntax, but a predicate that uses a cut isn't visually distinct from a &lt;a href="https://www.metalevel.at/prolog/purity" target="_blank"&gt;logically pure&lt;/a&gt; predicate. But that's also a little less tricky than what we got in TLA+, since the cut is still part of the language semantics.&lt;/p&gt;
    &lt;p&gt;(Then again, different Prolog dialects have different ways of &lt;a href="https://eu.swi-prolog.org/pldoc/man?section=printmsg" target="_blank"&gt;printing strings&lt;/a&gt;, which add side effects to Prolog, and they're not visually distinct from other predicates. So the same problem!)&lt;/p&gt;
    &lt;p&gt;I don't actually have any fix for this. I just find it a fascinating example of a &lt;a href="https://en.wikipedia.org/wiki/Leaky_abstraction" target="_blank"&gt;leaky abstraction&lt;/a&gt;. Maybe we could write a code highlighter that highlights all functions that transitively use a "weird" function or something.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:laurie"&gt;
    &lt;p&gt;&lt;a href="https://tratt.net/laurie/blog/2013/relative_and_absolute_levels.html" target="_blank"&gt;Obligatory response post&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:laurie" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Tue, 21 Apr 2026 17:40:17 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/people-get-confused-when-language-implementations/</guid>
            </item>
            <item>
                <title>A sufficiently comprehensive spec is not (necessarily) code</title>
                <link>https://buttondown.com/hillelwayne/archive/a-sufficiently-comprehensive-spec-is-not/</link>
                <description>&lt;p&gt;Sorry for missing last week! Was sick and then busy.&lt;/p&gt;
    &lt;p&gt;This week I want to cover a pet peeve of mine, best seen in this comic:&lt;/p&gt;
    &lt;p&gt;&lt;a href="https://www.commitstrip.com/en/2016/08/25/a-very-comprehensive-and-precise-spec/?" target="_blank"&gt;&lt;img alt="A comic about a business person thinking detailed specs will replace coders, and then the coder calls the spec &amp;quot;code&amp;quot;" class="newsletter-image" src="https://www.commitstrip.com/wp-content/uploads/2016/08/Strip-Les-specs-cest-du-code-650-finalenglish.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;A "comprehensive and precise spec" is not necessarily code. A specification corresponds to a &lt;em&gt;set&lt;/em&gt; of possible implementations, and code is a single implementation in that set. As long as the set has more than one element, there is a separation between the spec and the code. &lt;/p&gt;
    &lt;p&gt;Consider a business person (bp) who asks:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;I want a tool to convert miles to kilometers.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;Is this a comprehensive spec? Maybe, you can give it to Claude Code and tell it to make all design decisions and it will give you a program that converts miles to km. At the same time, there is a huge amount of details left out of this. What language? What's the UX? Should it be a command line script or a mobile app or an enterprise SaaS? For this reason, if we gave Claude's output to the bp, they'll probably be unsatisfied. The set of possible implementations includes the programs they want, but also lots of programs they don't want.&lt;/p&gt;
    &lt;p&gt;So they now they say:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;It should be a textbox on a website.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;Okay, this rules out a lot more stuff, but there's still a lot to decide. React or vanillajs or htmx? Should the output be a separate textbox or a popup? Should we use a conversion of &lt;code&gt;1.6&lt;/code&gt;, &lt;code&gt;1.61&lt;/code&gt;, or &lt;code&gt;1.609&lt;/code&gt;? So you could argue that this is still not a "comprehensive and precise spec". But what if the bp is happy with whatever Claude makes? Then their spec was sufficiently comprehensive and precise, since they got a program that solved their problem!&lt;/p&gt;
    &lt;p&gt;Now the comic above makes the more specific claim that a spec "comprehensive and precise enough &lt;em&gt;to generate a program&lt;/em&gt;" is code. That wasn't even true before LLMs. &lt;a href="https://en.wikipedia.org/wiki/Program_synthesis" target="_blank"&gt;Program synthesis&lt;/a&gt;, the automatic generation of conformant programs from specifications, is an active field of research! Last I checked in 2019 they were only generating local functions from type specifications; I don't know how things have changed with LLMs. But still, it shows that code and comprehensive specs are distinct things. &lt;/p&gt;
    &lt;h3&gt;Specs are abstractions&lt;/h3&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;What I'm getting at here is that a specification is an &lt;strong&gt;abstraction&lt;/strong&gt; of code.&lt;sup id="fnref:abstraction"&gt;&lt;a class="footnote-ref" href="#fn:abstraction"&gt;1&lt;/a&gt;&lt;/sup&gt; For every spec, there is a set of possible programs that satisfy that spec. The more comprehensive and precise the spec, the fewer programs in this set. If &lt;code&gt;spec1&lt;/code&gt; corresponds to a superset of &lt;code&gt;spec2&lt;/code&gt;, we further say that &lt;code&gt;spec2&lt;/code&gt; &lt;strong&gt;refines&lt;/strong&gt; &lt;code&gt;spec1&lt;/code&gt;. A specification is &lt;strong&gt;sufficient&lt;/strong&gt; if it does not need to be refined further: no matter what implementation (&lt;em&gt;within reason&lt;/em&gt;&lt;sup id="fnref:reason"&gt;&lt;a class="footnote-ref" href="#fn:reason"&gt;2&lt;/a&gt;&lt;/sup&gt;) is provided, the specifier would be satisfied. A spec does not need to be fully comprehensive to be sufficient.&lt;/p&gt;
    &lt;h3&gt;Programmers are still needed to write specs&lt;/h3&gt;
    &lt;p&gt;The comic makes a further claim: "a sufficiently detailed spec is code" is a reason why programmers won't be out of a job, even with we could automatically generate code from specs. And this is still true.&lt;/p&gt;
    &lt;p&gt;It is often the case that we express the abstraction spec via a formal language. Normally this makes me think of TLA+ or UML or even &lt;a href="https://syque.com/quality_tools/tools/Tools104.htm" target="_blank"&gt;Planguage&lt;/a&gt;, but the most common example of this would be test suites. &lt;a href="https://buttondown.com/hillelwayne/archive/what-is-a-specification" target="_blank"&gt;Tests are specifications, too&lt;/a&gt;! And as a rule, it seems impossible to get nonprogrammers to successfully encode things in formal languages. Cucumber was a failed attempt to make business people write formal specs.&lt;/p&gt;
    &lt;p&gt;But does this make a comprehensive spec "code"? I'd argue no.  It's possible to encode a specification in a programming language (again, test suites), but it is just that, an encoding. The spec still corresponds to a set of possible implementation programs, and the spec is still useful even if we don't encode it. Keeping "code" and "spec" distinct concepts is useful.&lt;/p&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:abstraction"&gt;
    &lt;p&gt;&lt;a href="https://www.pathsensitive.com/2022/03/abstraction-not-what-you-think-it-is.html" target="_blank"&gt;obligatory link&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:abstraction" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:reason"&gt;
    &lt;p&gt;As in the implementation makes a good faith attempt to make a reasonable implementation. IE "this converts miles to kilometers and also mines crypto" is not a good faith interpretation.&amp;#160;&lt;a class="footnote-backref" href="#fnref:reason" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Wed, 15 Apr 2026 16:18:02 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/a-sufficiently-comprehensive-spec-is-not/</guid>
            </item>
            <item>
                <title>April Cools Post: New York vs Chicago Pizza</title>
                <link>https://buttondown.com/hillelwayne/archive/april-cools-post-new-york-vs-chicago-pizza/</link>
                <description>&lt;p&gt;Happy April Cools! My not-tech post this year is &lt;a href="https://www.hillelwayne.com/post/pizza/" target="_blank"&gt;Chicago vs New York Pizza is the Wrong Argument&lt;/a&gt;, which is mostly an excuse for me to talk about Chicago food. See &lt;a href="https://www.aprilcools.club/" target="_blank"&gt;here&lt;/a&gt; for all of the other April Cools submissions. As of this email we have sixteen posts; there's still time to submit something!&lt;sup id="fnref:praveen"&gt;&lt;a class="footnote-ref" href="#fn:praveen"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
    &lt;p&gt;This one came out of a pizza argument with &lt;a href="https://www.bellotti.tech/about" target="_blank"&gt;Marianne Bellotti&lt;/a&gt;, a true New Yorker who doesn't think deep dish is real pizza. I wanted to find a new angle on the old argument and this is what I came up with. Interestingly, there's arguably no real New York analog to deep dish (Khatchapuri is kinda similar but doesn't have the same cultural relevance). If I had to pick something to compare deep dish to, it'd be &lt;a href="https://en.wikipedia.org/wiki/Toasted_ravioli" target="_blank"&gt;toasted ravioli&lt;/a&gt; (despite that being a appetizer and not an entree).&lt;/p&gt;
    &lt;p&gt;Also, I didn't really touch on it in the article, but one of the annoying things about pizza debates is that the most of the Chicago pizza chains &lt;em&gt;don't serve deep dish&lt;/em&gt;. Instead they do "stuffed pizzas". Deep dish is crust, cheese, tomato, while stuffed pizza is crust, cheese, another layer of crust, tomato. It's heavy and more casserole like and they always have way too much cheese. Don't get me wrong, it's still decent, but I think it's a worse kind of pizza overall.&lt;/p&gt;
    &lt;p&gt;Anyway, the real point of the post is that Chicago's a damn good sausage city. Even the Home Depots here have hot dogs.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:praveen"&gt;
    &lt;p&gt;Praveen, if you're reading this, you didn't include a link to your post or any contact details with your submission. Email me and we'll get it on the site&amp;#160;&lt;a class="footnote-backref" href="#fnref:praveen" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Wed, 01 Apr 2026 17:53:29 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/april-cools-post-new-york-vs-chicago-pizza/</guid>
            </item>
            <item>
                <title>Choose Boring Technology and Innovative Practices</title>
                <link>https://buttondown.com/hillelwayne/archive/choose-boring-technology-and-innovative-practices/</link>
                <description>&lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;The famous article &lt;a href="https://mcfunley.com/choose-boring-technology" target="_blank"&gt;Choose Boring Technology&lt;/a&gt; lists two problems with using innovative technology:&lt;/p&gt;
    &lt;ol&gt;
    &lt;li&gt;There are too many "unknown unknowns" in a new technology, whereas in boring technology the pitfalls are already well-known.&lt;/li&gt;
    &lt;li&gt;Shiny tech has a maintenance burden that persist long after everybody has gotten bored with  it. &lt;/li&gt;
    &lt;/ol&gt;
    &lt;p&gt;Both of these tie back to the idea that the main cost of technology is maintenance. Even if something is easy to build with, it might not be as easy to keep running. We cannot "abandon" mission-critical technology. Say my team builds a new service on &lt;a href="https://julialang.org/" target="_blank"&gt;Julia&lt;/a&gt;, and 2 years later decides it was the wrong choice. We're stuck with either the (expensive) process of migrating all our &lt;del&gt;data to Postgres&lt;/del&gt; code to Java or the (expensive) process of keeping it running anyway. Either way, the company needs to spend resources keeping engineers trained on the tech instead of other useful things, like how to mine crypto in their heads. &lt;/p&gt;
    &lt;p&gt;Tech is slow to change. Not as slow to change as, say, a bridge, but still pretty slow.&lt;/p&gt;
    &lt;p&gt;Now say at the same time as Julia, we also decided to start practicing &lt;a href="https://medium.com/@kentbeck_7670/test-commit-revert-870bbd756864" target="_blank"&gt;test &amp;amp;&amp;amp; commit || revert&lt;/a&gt; (TCR). After two years, we get sick of that, too. To deal with this, we can simply... not do TCR anymore. There is no "legacy practice" we need to support, no maintenance burden to dropping a process. It is much easier to adopt and abandon practices than it is to adopt and abandon technology.&lt;/p&gt;
    &lt;p&gt;This means while we should be conservative in the software we use, we can be more freely innovative in how we use it. If we get &lt;a href="https://mcfunley.com/choose-boring-technology#embrace-boredom" target="_blank"&gt;three innovation tokens&lt;/a&gt; for technology, we get like six or seven for practices. And we can trade in our practices to get those tokens back. &lt;/p&gt;
    &lt;p&gt;(The flip side of this is that social processes are less "stable" than technology and take more work to keep running. This is why "engineering controls" are considered &lt;a href="https://hillelwayne.com/post/hoc/" target="_blank"&gt;more effective as reducing accidents&lt;/a&gt; than administrative controls.) &lt;/p&gt;
    &lt;h3&gt;Choose Boring Material and Innovative Tools&lt;/h3&gt;
    &lt;p&gt;Pushing this argument further, we can divide technology into two categories: "material" and "tools".&lt;sup id="fnref:tools"&gt;&lt;a class="footnote-ref" href="#fn:tools"&gt;1&lt;/a&gt;&lt;/sup&gt; Material is anything that needs to run to support the business:  our code, our service architecture, our data &lt;em&gt;and&lt;/em&gt; database engine, etc. The tools are what we use to make material, but that the material doesn't depend on. Editors, personal bash scripts, etc. The categories are fuzzy, but it boils down to "how bad is it for the project to lose this?" &lt;/p&gt;
    &lt;p&gt;In turn, because tools are easier to replace than material, we can afford to be more innovative with it. I suspect we see this in practice, too, that people replace ephemera faster than they replace their databases.&lt;/p&gt;
    &lt;p&gt;(This is a short one because I severely overestimated how much I could write about this.)&lt;/p&gt;
    &lt;hr /&gt;
    &lt;h2&gt;&lt;a href="https://www.aprilcools.club/" target="_blank"&gt;April Cools&lt;/a&gt;&lt;/h2&gt;
    &lt;p&gt;It's in a week! You can submit your April Cools in the &lt;a href="https://docs.google.com/forms/d/e/1FAIpQLSc6Yt_ZCA_S6EOt-VVtla-uObnzPlSg9x_VOLgiNGN_-AY-kQ/viewform" target="_blank"&gt;google form&lt;/a&gt; or, if you want to be all cool and techie, as a &lt;a href="https://github.com/april-cools/april-cools.github.io/blob/main/_data/projects.yml" target="_blank"&gt;github PR&lt;/a&gt;.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:tools"&gt;
    &lt;p&gt;This is different from how we call all software "tools".&amp;#160;&lt;a class="footnote-backref" href="#fnref:tools" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Tue, 24 Mar 2026 14:38:06 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/choose-boring-technology-and-innovative-practices/</guid>
            </item>
            <item>
                <title>LLMs are bad at vibing specifications</title>
                <link>https://buttondown.com/hillelwayne/archive/llms-are-bad-at-vibing-specifications/</link>
                <description>&lt;h3&gt;No newsletter next week&lt;/h3&gt;
    &lt;p&gt;I'll be speaking at &lt;a href="https://qconlondon.com/" target="_blank"&gt;InfoQ London&lt;/a&gt;. But see below for a book giveaway!&lt;/p&gt;
    &lt;hr /&gt;
    &lt;h1&gt;LLMs are bad at vibing specifications&lt;/h1&gt;
    &lt;p&gt;About a year ago I wrote &lt;a href="https://buttondown.com/hillelwayne/archive/ai-is-a-gamechanger-for-tla-users/" target="_blank"&gt;AI is a gamechanger for TLA+ users&lt;/a&gt;, which argued that AI are a "specification force multiplier". That was written from the perspective an TLA+ expert using these tools. A full &lt;a href="https://github.com/search?q=path%3A*.tla+NOT+is%3Afork+claude&amp;amp;type=code" target="_blank"&gt;4% of Github TLA+ specs&lt;/a&gt; now have the word "Claude" somewhere in them. This is interesting to me, because it suggests there was always an interest in formal methods, people just lacked the skills to do it.  &lt;/p&gt;
    &lt;p&gt;It's also interesting because it gives me a sense of what happens when beginners use AI to write formal specs. It's not good.&lt;/p&gt;
    &lt;p&gt;As a case study, we'll use &lt;a href="https://github.com/myProjectsRavi/sentinel-protocol/tree/main/docs/formal/specs" target="_blank"&gt;this project&lt;/a&gt;, which is kind of enough to have vibed out TLA+ and Alloy specs.&lt;/p&gt;
    &lt;h3&gt;Looking at a project&lt;/h3&gt;
    &lt;p&gt;&lt;a href="https://github.com/myProjectsRavi/sentinel-protocol/blob/main/docs/formal/specs/threat-intel-mesh.als" target="_blank"&gt;Starting with the Alloy spec&lt;/a&gt;. Here it is in its entirety:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;module ThreatIntelMesh
    
    sig Node {}
    
    one sig LocalNode extends Node {}
    
    sig Snapshot {
      owner: one Node,
      signed: one Bool,
      signatures: set Signature
    }
    
    sig Signature {}
    
    sig Policy {
      allowUnsignedImport: one Bool
    }
    
    pred canImport[p: Policy, s: Snapshot] {
      (p.allowUnsignedImport = True) or (s.signed = True)
    }
    
    assert UnsignedImportMustBeDenied {
      all p: Policy, s: Snapshot |
        p.allowUnsignedImport = False and s.signed = False implies not canImport[p, s]
    }
    
    assert SignedImportMayBeAccepted {
      all p: Policy, s: Snapshot |
        s.signed = True implies canImport[p, s]
    }
    
    check UnsignedImportMustBeDenied for 5
    check SignedImportMayBeAccepted for 5
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;Couple of things to note here: first of all, this doesn't actually compile. It's using the &lt;a href="https://alloy.readthedocs.io/en/latest/modules/boolean.html" target="_blank"&gt;Boolean&lt;/a&gt; standard module so needs &lt;code&gt;open util/boolean&lt;/code&gt; to function. Second, Boolean is the wrong approach here; you're supposed to use subtyping. &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sig Snapshot {
    &lt;span class="w"&gt; &lt;/span&gt; owner: one Node,
    &lt;span class="gd"&gt;- signed: one Bool,&lt;/span&gt;
    &lt;span class="w"&gt; &lt;/span&gt; signatures: set Signature
    }
    
    &lt;span class="gi"&gt;+ sig SignedSnapshot in Snapshot {}&lt;/span&gt;
    
    
    pred canImport[p: Policy, s: Snapshot] {
    &lt;span class="gd"&gt;- s.signed = True&lt;/span&gt;
    &lt;span class="gi"&gt;+ s in SignedSnapshot&lt;/span&gt;
    }
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;So we know the person did not actually run these specs. This is &lt;em&gt;somewhat&lt;/em&gt; less of a problem in TLA+, which has an official MCP server that lets the agent run model checking. Even so, I regularly see specs that I'm pretty sure won't model check, with things like using &lt;code&gt;Reals&lt;/code&gt; or assuming &lt;code&gt;NULL&lt;/code&gt; is a built-in and not a user-defined constant.&lt;/p&gt;
    &lt;p&gt;The bigger problem with the spec is that &lt;code&gt;UnsignedImportMustBeDenied&lt;/code&gt; and &lt;code&gt;SignedImportMayBeAccepted&lt;/code&gt; &lt;em&gt;don't actually do anything&lt;/em&gt;. &lt;code&gt;canImport&lt;/code&gt; is defined as &lt;code&gt;P || Q&lt;/code&gt;. &lt;code&gt;UnsignedImportMustBeDenied&lt;/code&gt; checks that &lt;code&gt;!P &amp;amp;&amp;amp; !Q =&amp;gt; !canImport&lt;/code&gt;. &lt;code&gt;SignedImportMayBeAccepted&lt;/code&gt; checks that &lt;code&gt;P =&amp;gt; canImport&lt;/code&gt;. These are tautologically true! If they do anything at all, it is only checking that &lt;code&gt;canImport&lt;/code&gt; was defined correctly. &lt;/p&gt;
    &lt;p&gt;You see the same thing in the &lt;a href="https://github.com/myProjectsRavi/sentinel-protocol/blob/main/docs/formal/specs/serialization-firewall.tla" target="_blank"&gt;TLA+ specs&lt;/a&gt;, too:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;GadgetPayload ==
      /\ gadgetDetected&amp;#39; = TRUE
      /\ depth&amp;#39; \in 0..(MaxDepth + 5)
      /\ UNCHANGED allowlistedFormat
      /\ decision&amp;#39; = &amp;quot;block&amp;quot;
    
    NoExploitAllowed == gadgetDetected =&amp;gt; decision = &amp;quot;block&amp;quot;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;The AI is only writing "obvious properties", which fail for reasons like "we missed a guard clause" or "we forgot to update a variable". It does not seem to be good at writing "subtle" properties that fail due to concurrency, nondeterminism, or bad behavior separated by several steps. Obvious properties are useful for orienting yourself and ensuring the system behaves like you expect, but the actual value in using formal methods comes from the subtle properties. &lt;/p&gt;
    &lt;p&gt;(This ties into &lt;a href="https://buttondown.com/hillelwayne/archive/some-tests-are-stronger-than-others/" target="_blank"&gt;Strong and Weak Properties&lt;/a&gt;. LLM properties are weak, intended properties need to be strong.)&lt;/p&gt;
    &lt;p&gt;This is a problem I see in almost every FM spec written by AI. LLMs aren't doing one of the core features of a spec. Articles like &lt;a href="https://martin.kleppmann.com/2025/12/08/ai-formal-verification.html" target="_blank"&gt;Prediction: AI will make formal verification go mainstream&lt;/a&gt; and &lt;a href="https://leodemoura.github.io/blog/2026/02/28/when-ai-writes-the-worlds-software.html" target="_blank"&gt;When AI Writes the World's Software, Who Verifies It?&lt;/a&gt; argue that LLMs will make formal methods go mainstream, but being easily able to write specifications doesn't help with correctness if the specs don't actually verify anything.&lt;/p&gt;
    &lt;h3&gt;Is this a user error?&lt;/h3&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;I first got interested in LLMs and TLA+ from &lt;a href="https://zfhuang99.github.io/github%20copilot/formal%20verification/tla+/2025/05/24/ai-revolution-in-distributed-systems.html" target="_blank"&gt;The Coming AI Revolution in Distributed Systems&lt;/a&gt;. The author of that later &lt;a href="https://github.com/zfhuang99/lamport-agent/blob/main/spec/CRAQ/CRAQ.tla" target="_blank"&gt;vibecoded a spec&lt;/a&gt; with a considerably more complex property:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;NoStaleStrictRead ==
      \A i \in 1..Len(eventLog) :
        LET ev == eventLog[i] IN
          ev.type = &amp;quot;read&amp;quot; =&amp;gt;
            LET c == ev.chunk IN
            LET v == ev.version IN
            /\ \A j \in 1..i :
                 LET evC == eventLog[j] IN
                   evC.type = &amp;quot;commit&amp;quot; /\ evC.chunk = c =&amp;gt; evC.version &amp;lt;= v
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;This is a lot more complicated than the &lt;code&gt;(P =&amp;gt; Q &amp;amp;&amp;amp; P) =&amp;gt; Q&lt;/code&gt; properties I've seen! It could be because &lt;a href="https://github.com/deepseek-ai/3FS/tree/main/specs/DataStorage" target="_blank"&gt;the corresponding system already had a complete spec written in P&lt;/a&gt;. But it could also be that Cheng Huang is already an expert specifier, meaning he can get more out of an LLM than an ordinary developer can. I've also noticed that I can usually coax an LLM to do more interesting things than most of my clients can. Which is good for my current livelihood, but bad for the hope of LLMs making formal methods mainstream. If you need to know formal methods to get the LLM to do formal methods, is that really helping?&lt;/p&gt;
    &lt;p&gt;(Yes, if it lowers the skill threshold-- means you can apply FM with 20 hours of practice instead of 80. But the jury's still out on how &lt;em&gt;much&lt;/em&gt; it lowers the threshold. What if it only lowers it from 80 to 75?) &lt;/p&gt;
    &lt;p&gt;On the other hand, there also seem to be some properties that AI struggles with, even with explicit instructions. Last week a client and I tried to get Claude to generate a good &lt;a href="https://www.hillelwayne.com/post/safety-and-liveness/" target="_blank"&gt;liveness&lt;/a&gt; or &lt;a href="https://www.hillelwayne.com/post/action-properties/" target="_blank"&gt;action&lt;/a&gt; property instead of a standard obvious invariant, and it just couldn't. Training data issue? Something in the innate complexity of liveness? It's not clear yet. These properties are even more "subtle" than most invariants, so maybe that's it.&lt;/p&gt;
    &lt;p&gt;On the other other hand, this is all as of March 2026. Maybe this whole article will be laughably obsolete by June. &lt;/p&gt;
    &lt;hr /&gt;
    &lt;h3&gt;&lt;a href="https://logicforprogrammers.com" target="_blank"&gt;Logic for Programmers&lt;/a&gt; Giveaway&lt;/h3&gt;
    &lt;p&gt;Last week's giveaway raised a few issues. First, the New World copies were all taken before all of the emails went out, so a lot of people did not even get a chance to try for a book. Second, due to a Leanpub bug the Europe coupon scheduled for 10 AM UTC actually activated at 10 AM my time, which was early evening for Europe. Third, everybody in the APAC region got left out.&lt;/p&gt;
    &lt;p&gt;So, since I'm not doing a newsletter next week, let's have another giveaway:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;a href="https://leanpub.com/logic/c/E5A55F7B482C3" target="_blank"&gt;This coupon&lt;/a&gt; will go up 2026-03-16 at 11:00 UTC, which should be noon Central European Time, and be good for ten books (five for this giveaway, five to account for last week's bug).&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://leanpub.com/logic/c/ADC664C95B6D1" target="_blank"&gt;This coupon&lt;/a&gt; will go up 2026-03-17 at 04:00 UTC, which should be noon Beijing Time, and be good for five books.&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://leanpub.com/logic/c/U1250212A9070" target="_blank"&gt;This coupon&lt;/a&gt; will go up 2026-03-17 at 17:00 UTC, which should be noon Central US Time, and also be good for five books.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;I think that gives the best chance of everybody getting at least a chance of a book, while being resilient to timezone shenanigans due to travel / Leanpub dropping bugfixes / daylight savings / whatever. &lt;/p&gt;
    &lt;p&gt;(No guarantees that later "no newsletter" weeks will have giveaways! This is a gimmick)&lt;/p&gt;</description>
                <pubDate>Tue, 10 Mar 2026 17:12:30 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/llms-are-bad-at-vibing-specifications/</guid>
            </item>
            <item>
                <title>Free Books</title>
                <link>https://buttondown.com/hillelwayne/archive/free-books/</link>
                <description>&lt;p&gt;Spinning a &lt;a href="https://www.youtube.com/watch?v=NB4hzg4k7_A" target="_blank"&gt;lot of plates&lt;/a&gt; this week so skipping the newsletter. As an apology, have ten free copies of &lt;em&gt;Logic for Programmers&lt;/em&gt;.&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;a href="https://leanpub.com/logic/c/EBDFA51B15C1" target="_blank"&gt;These five&lt;/a&gt; are available now.&lt;/li&gt;
    &lt;li&gt;&lt;del&gt;&lt;a href="https://leanpub.com/logic/c/5A55F7B482C3" target="_blank"&gt;These five&lt;/a&gt; &lt;em&gt;should&lt;/em&gt; be available at 10:30 AM CEST tomorrow, so people in Europe have a better chance of nabbing one.&lt;/del&gt; Nevermind Leanpub had a bug that made this not work properly&lt;/li&gt;
    &lt;/ul&gt;</description>
                <pubDate>Tue, 03 Mar 2026 16:34:33 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/free-books/</guid>
            </item>
            <item>
                <title>New Blog Post: Some Silly Z3 Scripts I Wrote</title>
                <link>https://buttondown.com/hillelwayne/archive/new-blog-post-some-silly-z3-scripts-i-wrote/</link>
                <description>&lt;p&gt;Now that I'm not spending all my time on Logic for Programmers, I have time to update my website again! So here's the first blog post in five months: &lt;a href="https://www.hillelwayne.com/post/z3-examples/" target="_blank"&gt;Some Silly Z3 Scripts I Wrote&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;Normally I'd also put a link to the Patreon notes but I've decided I don't like publishing gated content and am going to wind that whole thing down. So some quick notes about this post:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;Part of the point is admittedly to hype up the eventual release of LfP. I want to start marketing the book, but don't want the marketing material to be devoid of interest, so tangentially-related-but-independent blog posts are a good place to start.&lt;/li&gt;
    &lt;li&gt;The post discusses the concept of "chaff", the enormous quantity of material (both code samples and prose) that didn't make it into the book. The book is about 50,000 words… and considerably shorter than the total volume of chaff! I don't &lt;em&gt;think&lt;/em&gt; most of it can be turned into useful public posts, but I'm not entirely opposed to the idea. Maybe some of the old chapters could be made into something?&lt;/li&gt;
    &lt;li&gt;Coming up with a conditioned mathematical property to prove was a struggle. I had two candidates: &lt;code&gt;a == b * c =&amp;gt; a / b == c&lt;/code&gt;, which would have required a long tangent on how division must be total in Z3, and  &lt;code&gt;a != 0 =&amp;gt; some b: b * a == 1&lt;/code&gt;, which would have required introducing a quantifier (SMT is real weird about quantifiers). Division by zero has already caused me enough grief so I went with the latter. This did mean I had to reintroduce "operations must be total" when talking about arrays.&lt;/li&gt;
    &lt;li&gt;I have no idea why the array example returns &lt;code&gt;2&lt;/code&gt; for the max profit and not &lt;code&gt;99999999&lt;/code&gt;. I'm guessing there's some short circuiting logic in the optimizer when the problem is ill-defined?&lt;/li&gt;
    &lt;li&gt;One example I could not get working, which is unfortunate, was a demonstration of how SMT solvers are undecidable via encoding Goldbach's conjecture as an SMT problem. Anything with multiple nested quantifiers is a pain.&lt;/li&gt;
    &lt;/ul&gt;</description>
                <pubDate>Mon, 23 Feb 2026 16:49:10 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/new-blog-post-some-silly-z3-scripts-i-wrote/</guid>
            </item>
            <item>
                <title>Stream of Consciousness Driven Development</title>
                <link>https://buttondown.com/hillelwayne/archive/stream-of-consciousness-driven-development/</link>
                <description>&lt;p&gt;This is something I just tried out last week but it seems to have enough potential to be worth showing unpolished. I was pairing with a client on writing a spec. I saw a problem with the spec, a convoluted way of fixing the spec. Instead of trying to verbally explain it, I started by creating a new markdown file:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;NameOfProblem.md
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Then I started typing. First the problem summary, then a detailed description, then the solution and why it worked. When my partner asked questions, I incorporated his question and our discussion of it into the flow. If we hit a dead end with the solution, we marked it out as a dead end. Eventually the file looked something like this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Current state of spec
    Problems caused by this
        Elaboration of problems
        What we tried that didn&amp;#39;t work
    Proposed Solution
        Theory behind proposed solution
        How the solution works
        Expected changes
        Other problems this helps solve
        Problems this does *not* help with
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Only once this was done, my partner fully understood the chain of thought, &lt;em&gt;and&lt;/em&gt; we agreed it represented the right approach, did we start making changes to the spec. &lt;/p&gt;
    &lt;h3&gt;How is this better than just making the change?&lt;/h3&gt;
    &lt;p&gt;The change was &lt;em&gt;conceptually&lt;/em&gt; complex. A rough analogy: imagine pairing with a beginner who wrote an insertion sort, and you want to replace it with quicksort. You need to explain why the insertion sort is too slow, why the quicksort isn't slow, and how quicksort actually correctly sorts a list. This could involve tangents into computational complexity, big-o notation, recursion, etc. These are all concepts you have internalized, so the change is simple to you, but the solution uses concepts the beginner does not know. So it's conceptually complex to them.&lt;/p&gt;
    &lt;p&gt;I wasn't pairing with a beginning programmer or even a beginning specifier. This was a client who could confidently write complex specs on their own. But they don't work on specifications full time like I do. Any time there's a relative gap in experience in a pair, there's solutions that are conceptually simple to one person and complex to the other.&lt;/p&gt;
    &lt;p&gt;I've noticed too often that when one person doesn't fully understand the concepts behind a change, they just go "you're the expert, I trust you." That eventually leads to a totally unmaintainable spec. Hence, writing it all out. &lt;/p&gt;
    &lt;p&gt;As I said before, I've only tried this once (though I've successfully used a similar idea when teaching workshops). It worked pretty well, though! Just be prepared for a lot of typing.&lt;/p&gt;</description>
                <pubDate>Wed, 18 Feb 2026 16:33:08 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/stream-of-consciousness-driven-development/</guid>
            </item>
            <item>
                <title>Proving What's Possible</title>
                <link>https://buttondown.com/hillelwayne/archive/proving-whats-possible/</link>
                <description>&lt;p&gt;As a formal methods consultant I have to mathematically express properties of systems. I generally do this with two "temporal operators": &lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;A(x) means that &lt;code&gt;x&lt;/code&gt; is always true. For example, a database table &lt;em&gt;always&lt;/em&gt; satisfies all record-level constraints, and a state machine &lt;em&gt;always&lt;/em&gt; makes valid transitions between states. If &lt;code&gt;x&lt;/code&gt; is a statement about an individual state (as in the database but not state machine example), we further call it an &lt;strong&gt;invariant&lt;/strong&gt;.&lt;/li&gt;
    &lt;li&gt;E(x) means that &lt;code&gt;x&lt;/code&gt; is "eventually" true, conventionally meaning "guaranteed true at some point in the future". A database transaction &lt;em&gt;eventually&lt;/em&gt; completes or rolls back, a state machine &lt;em&gt;eventually&lt;/em&gt; reaches the "done" state, etc. &lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;These come from linear temporal logic, which is the mainstream notation for expressing system properties. &lt;sup id="fnref:modal"&gt;&lt;a class="footnote-ref" href="#fn:modal"&gt;1&lt;/a&gt;&lt;/sup&gt; We like these operators because they elegantly cover &lt;a href="https://www.hillelwayne.com/post/safety-and-liveness/" target="_blank"&gt;safety and liveness properties&lt;/a&gt;, and because &lt;a href="https://buttondown.com/hillelwayne/archive/formalizing-stability-and-resilience-properties/" target="_blank"&gt;we can combine them&lt;/a&gt;. &lt;code&gt;A(E(x))&lt;/code&gt; means &lt;code&gt;x&lt;/code&gt; is true an infinite number of times, while &lt;code&gt;A(x =&amp;gt; E(y)&lt;/code&gt; means that &lt;code&gt;x&lt;/code&gt; being true guarantees &lt;code&gt;y&lt;/code&gt; true in the future. &lt;/p&gt;
    &lt;p&gt;There's a third class of properties, that I will call &lt;em&gt;possibility&lt;/em&gt; properties: &lt;code&gt;P(x)&lt;/code&gt; is "can x happen in this model"? Is it possible for a table to have more than ten records? Can a state machine transition from "Done" to "Retry", even if it &lt;em&gt;doesn't&lt;/em&gt;? Importantly, &lt;code&gt;P(x)&lt;/code&gt; does not need to be possible &lt;em&gt;immediately&lt;/em&gt;, just at some point in the future. It's possible to lose 100 dollars betting on slot machines, even if you only bet one dollar at a time. If &lt;code&gt;x&lt;/code&gt; is a statement about an individual state, we can further call it a &lt;a href="https://en.wikipedia.org/wiki/Reachability" target="_blank"&gt;&lt;em&gt;reachability&lt;/em&gt; property&lt;/a&gt;. I'm going to use the two interchangeably for flow. &lt;/p&gt;
    &lt;p&gt;&lt;code&gt;A(P(x))&lt;/code&gt; says that &lt;code&gt;x&lt;/code&gt; is &lt;em&gt;always&lt;/em&gt; possible. No matter what we've done in our system, we can make &lt;code&gt;x&lt;/code&gt; happen again. There's no way to do this with just &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;E&lt;/code&gt;. Other meaningful combinations include:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;code&gt;P(A(x))&lt;/code&gt;: there is a reachable state from which &lt;code&gt;x&lt;/code&gt; is always true.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;A(x =&amp;gt; P(y))&lt;/code&gt;: &lt;code&gt;y&lt;/code&gt; is possible from any state where &lt;code&gt;x&lt;/code&gt; is true.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;E(x &amp;amp;&amp;amp; P(y))&lt;/code&gt;: There is always a future state where x is true and y is reachable.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;A(P(x) =&amp;gt; E(x))&lt;/code&gt;: If &lt;code&gt;x&lt;/code&gt; is ever possible, it will eventually happen.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;E(P(x))&lt;/code&gt; and &lt;code&gt;P(E(x))&lt;/code&gt; are the same as &lt;code&gt;P(x)&lt;/code&gt;.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;See the paper &lt;a href="https://dl.acm.org/doi/epdf/10.1145/567446.567463" target="_blank"&gt;"Sometime" is sometimes "not never"&lt;/a&gt; for a deeper discussion of &lt;code&gt;E&lt;/code&gt; and &lt;code&gt;P&lt;/code&gt;.&lt;/p&gt;
    &lt;h3&gt;The use case&lt;/h3&gt;
    &lt;p&gt;Possibility properties are "something good &lt;em&gt;can&lt;/em&gt; happen", which is generally less useful (&lt;em&gt;in specifications&lt;/em&gt;) than "something bad &lt;em&gt;can't&lt;/em&gt; happen" (safety) and "something good &lt;em&gt;will&lt;/em&gt; happen" (liveness). But it still comes up as an important property! My favorite example:&lt;/p&gt;
    &lt;p&gt;&lt;img alt="A guy who can't shut down his computer because system preferences interrupts shutdown" class="newsletter-image" src="https://www.hillelwayne.com/post/safety-and-liveness/img/tweet2.png" /&gt;&lt;/p&gt;
    &lt;p&gt;The big use I've found for the idea is as a sense-check that we wrote the spec properly. Say I take the property "A worker in the 'Retry' state eventually leaves that state":&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;A(state == &amp;#39;Retry&amp;#39; =&amp;gt; E(state != &amp;#39;Retry&amp;#39;))
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;The model checker checks this property and confirms it holds of the spec. Great! Our system is correct! ...Unless the system can never &lt;em&gt;reach&lt;/em&gt; the "Retry" state, in which case the expression is trivially true. I need to verify that 'Retry' is reachable, eg &lt;code&gt;P(state == 'Retry')&lt;/code&gt;. Notice I can't use &lt;code&gt;E&lt;/code&gt; to do this, because I don't want to say "the worker always needs to retry at least once". &lt;/p&gt;
    &lt;h3&gt;It's not supported though&lt;/h3&gt;
    &lt;p&gt;I say "use I've found for &lt;em&gt;the idea&lt;/em&gt;" because the main formalisms I use (Alloy and TLA+) don't natively support &lt;code&gt;P&lt;/code&gt;. &lt;sup id="fnref:tla"&gt;&lt;a class="footnote-ref" href="#fn:tla"&gt;2&lt;/a&gt;&lt;/sup&gt; On top of &lt;code&gt;P&lt;/code&gt; being less useful than &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;E&lt;/code&gt;, simple reachability properties are &lt;a href="https://www.hillelwayne.com/post/software-mimicry/" target="_blank"&gt;mimickable&lt;/a&gt; with A(x). &lt;code&gt;P(x)&lt;/code&gt; &lt;em&gt;passes&lt;/em&gt; whenever &lt;code&gt;A(!x)&lt;/code&gt; &lt;em&gt;fails&lt;/em&gt;, meaning I can verify &lt;code&gt;P(state == 'Retry')&lt;/code&gt; by testing that &lt;code&gt;A(!(state == 'Retry'))&lt;/code&gt; finds a counterexample. We &lt;em&gt;cannot&lt;/em&gt; mimic combined operators this way like &lt;code&gt;A(P(x))&lt;/code&gt; but those are significantly less common than state-reachability. &lt;/p&gt;
    &lt;p&gt;(Also, refinement doesn't preserve possibility properties, but that's a whole other kettle of worms.)&lt;/p&gt;
    &lt;p&gt;The one that's bitten me a little is that we can't mimic "&lt;code&gt;P(x)&lt;/code&gt; from every starting state". "&lt;code&gt;A(!x)&lt;/code&gt;" fails if there's at least one path from one starting state that leads to &lt;code&gt;x&lt;/code&gt;, but other starting states might not make &lt;code&gt;x&lt;/code&gt; possible.&lt;/p&gt;
    &lt;p&gt;I suspect there's also a chicken-and-egg problem here. Since my tools can't verify possibility properties, I'm not used to noticing them in systems. I'd be interested in hearing if anybody works with codebases where possibility properties are important, especially if it's something complex like &lt;code&gt;A(x =&amp;gt; P(y))&lt;/code&gt;.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:modal"&gt;
    &lt;p&gt;Instead of &lt;code&gt;A(x)&lt;/code&gt;, the literature uses &lt;code&gt;[]x&lt;/code&gt; or &lt;code&gt;Gx&lt;/code&gt; ("globally x") and instead of &lt;code&gt;E(x)&lt;/code&gt; it uses &lt;code&gt;&amp;lt;&amp;gt;x&lt;/code&gt; or &lt;code&gt;Fx&lt;/code&gt; ("finally x"). I'm using A and E because this isn't teaching material.&amp;#160;&lt;a class="footnote-backref" href="#fnref:modal" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:tla"&gt;
    &lt;p&gt;There's &lt;a href="https://github.com/tlaplus/tlaplus/issues/860" target="_blank"&gt;some discussion to add it to TLA+, though&lt;/a&gt;.&amp;#160;&lt;a class="footnote-backref" href="#fnref:tla" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Wed, 11 Feb 2026 18:36:53 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/proving-whats-possible/</guid>
            </item>
            <item>
                <title>Logic for Programmers New Release and Next Steps</title>
                <link>https://buttondown.com/hillelwayne/archive/logic-for-programmers-new-release-and-next-steps/</link>
                <description>&lt;p&gt;&lt;img alt="cover.jpg" class="newsletter-image" src="https://assets.buttondown.email/images/f821145f-d310-403c-88f4-327758a66606.jpg?w=480&amp;amp;fit=max" /&gt;&lt;/p&gt;
    &lt;p&gt;It's taken four months, but the next release of &lt;a href="https://logicforprogrammers.com" target="_blank"&gt;Logic for Programmers is now available&lt;/a&gt;! v0.13 is over 50,000 words, making it both 20% larger than v0.12 and officially the longest thing I have ever written.&lt;sup id="fnref:longest"&gt;&lt;a class="footnote-ref" href="#fn:longest"&gt;1&lt;/a&gt;&lt;/sup&gt; Full release notes are &lt;a href="https://github.com/logicforprogrammers/book-assets/blob/master/CHANGELOG.md" target="_blank"&gt;here&lt;/a&gt;, but I'll talk a bit about the biggest changes. &lt;/p&gt;
    &lt;p&gt;For one, every chapter has been rewritten. Every single one. They span from &lt;em&gt;relatively&lt;/em&gt; minor changes to complete chapter rewrites. After some rough git diffing, I think I deleted about 11,000 words?&lt;sup id="fnref:gross-additions"&gt;&lt;a class="footnote-ref" href="#fn:gross-additions"&gt;2&lt;/a&gt;&lt;/sup&gt; The biggest change is probably to the Alloy chapter. After many sleepless nights, I realized the right approach wasn't to teach Alloy as a &lt;em&gt;data modeling&lt;/em&gt; tool but to teach it as a &lt;em&gt;domain modeling&lt;/em&gt; tool. Which technically means the book no longer covers data modeling.&lt;/p&gt;
    &lt;p&gt;There's also a lot more connections between the chapters. The introductory math chapter, for example, foreshadows how each bit of math will be used in the future techniques. I also put more emphasis on the general "themes" like the expressiveness-guarantees tradeoff (working title). One theme I'm really excited about is compatibility (extremely working title). It turns out that the &lt;a href="https://buttondown.com/hillelwayne/archive/the-liskov-substitution-principle-does-more-than/" target="_blank"&gt;Liskov substitution principle&lt;/a&gt;/subtyping in general, &lt;a href="https://buttondown.com/hillelwayne/archive/refinement-without-specification/" target="_blank"&gt;database migrations&lt;/a&gt;, backwards-compatible API changes, and &lt;a href="https://hillelwayne.com/post/refinement/" target="_blank"&gt;specification refinement&lt;/a&gt; all follow &lt;em&gt;basically&lt;/em&gt; the same general principles. I'm calling this "compatibility" for now but prolly need a better name.&lt;/p&gt;
    &lt;p&gt;Finally, there's just a lot more new topics in the various chapters. &lt;code&gt;Testing&lt;/code&gt; properly covers structural and metamorphic properties. &lt;code&gt;Proofs&lt;/code&gt; covers proof by induction and proving recursive functions (in an exercise). &lt;code&gt;Logic Programming&lt;/code&gt; now finally has a section on answer set programming. You get the picture.&lt;/p&gt;
    &lt;h3&gt;Next Steps&lt;/h3&gt;
    &lt;p&gt;There's a lot I still want to add to the book: proper data modeling, data structures, type theory, model-based testing, etc. But I've added new material for two year, and if I keep going it will never get done. So with this release, all the content is in!&lt;/p&gt;
    &lt;p&gt;Just like all the content was in &lt;a href="https://buttondown.com/hillelwayne/archive/five-unusual-raku-features/" target="_blank"&gt;two Novembers ago&lt;/a&gt; and &lt;a href="https://buttondown.com/hillelwayne/archive/logic-for-programmers-project-update/" target="_blank"&gt;two Januaries ago&lt;/a&gt; and &lt;a href="https://buttondown.com/hillelwayne/archive/logic-for-programmers-turns-one/" target="_blank"&gt;last July&lt;/a&gt;. To make it absolutely 100% for sure that I won't be tempted to add anything else, I passed the whole manuscript over to a copy editor. So if I write more, it won't get edits. That's a pretty good incentive to stop.&lt;/p&gt;
    &lt;p&gt;I also need to find a technical reviewer and proofreader. Once all three phases are done then it's "just" a matter of fixing the layout and finding a good printer. I don't know what the timeline looks like but I really want to have something I can hold in my hands before the summer.&lt;/p&gt;
    &lt;p&gt;(I also need to get notable-people testimonials. Hampered a little in this because I'm trying real hard not to quid-pro-quo, so I'd like to avoid anybody who helped me or is mentioned in the book. And given I tapped most of my network to help me... I've got some ideas though!)&lt;/p&gt;
    &lt;p&gt;There's still a lot of work ahead. Even so, for the first time in two years I don't have research to do or sections to write and it feels so crazy. Maybe I'll update my blog again! Maybe I'll run a workshop! Maybe I'll go outside if Chicago ever gets above 6°F! &lt;/p&gt;
    &lt;hr /&gt;
    &lt;h2&gt;Conference Season&lt;/h2&gt;
    &lt;p&gt;After a pretty slow 2025, the 2026 conference season is looking to be pretty busy! Here's where I'm speaking so far:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;a href="https://qconlondon.com/" target="_blank"&gt;QCon London&lt;/a&gt;, March 16-19&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://craft-conf.com/2026" target="_blank"&gt;Craft Conference&lt;/a&gt;, Budapest, June 4-5&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://softwareshould.work/" target="_blank"&gt;Software Should Work&lt;/a&gt;, Missouri, July 16-17&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://hfpug.org/" target="_blank"&gt;Houston Functional Programmers&lt;/a&gt;, Virtual, December 3&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;For the first three I'm giving variations of my talk "How to find bugs in systems that don't exist", which I gave last year at &lt;a href="https://systemsdistributed.com/" target="_blank"&gt;Systems Distributed&lt;/a&gt;. Last one will ideally be a talk based on LfP. &lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:longest"&gt;
    &lt;p&gt;The second longest was my 2003 NaNoWriMo. The third longest was &lt;em&gt;Practical TLA+&lt;/em&gt;.&amp;#160;&lt;a class="footnote-backref" href="#fnref:longest" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:gross-additions"&gt;
    &lt;p&gt;This means I must have written 20,000 words total. For comparison, the v0.1 release was 19,000 words.&amp;#160;&lt;a class="footnote-backref" href="#fnref:gross-additions" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Wed, 04 Feb 2026 14:00:00 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/logic-for-programmers-new-release-and-next-steps/</guid>
            </item>
            <item>
                <title>Refinement without Specification</title>
                <link>https://buttondown.com/hillelwayne/archive/refinement-without-specification/</link>
                <description>&lt;p&gt;Imagine we have a SQL database with a &lt;code&gt;user&lt;/code&gt; table, and users have a non-nullable &lt;code&gt;is_activated&lt;/code&gt; boolean column. Having read &lt;a href="https://ntietz.com/blog/that-boolean-should-probably-be-something-else/" target="_blank"&gt;That Boolean Should Probably Be Something else&lt;/a&gt;, you decide to migrate it to a nullable &lt;code&gt;activated_at&lt;/code&gt; column. You can change any of the SQL queries that read/update the &lt;code&gt;user&lt;/code&gt; table but not any of the code that uses the results of these queries. Can we make this change in a way that preserves all external properties? &lt;/p&gt;
    &lt;p&gt;Yes. If an update would set &lt;code&gt;is_activated&lt;/code&gt; to true, instead set it to the current date. Now define the &lt;strong&gt;refinement mapping&lt;/strong&gt; that takes a &lt;code&gt;new_user&lt;/code&gt; and returns an &lt;code&gt;old_user&lt;/code&gt;. All columns will be unchanged &lt;em&gt;except&lt;/em&gt; &lt;code&gt;is_activated&lt;/code&gt;, which will be&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;f(new_user).is_activated = 
        if new_user.activated_at == NULL 
        then FALSE
        else TRUE
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Now new code can use &lt;code&gt;new_user&lt;/code&gt; directly while legacy code can use &lt;code&gt;f(new_user)&lt;/code&gt; instead, which will behave indistinguishably from the &lt;code&gt;old_user&lt;/code&gt;. &lt;/p&gt;
    &lt;p&gt;A little more time passes and you decide to switch to an &lt;a href="https://martinfowler.com/eaaDev/EventSourcing.html" target="_blank"&gt;event sourcing&lt;/a&gt;-like model. So instead of an &lt;code&gt;activated_at&lt;/code&gt; column, you have a &lt;code&gt;user_events&lt;/code&gt; table, where every record is &lt;code&gt;(user_id, timestamp, event)&lt;/code&gt;. So adding an &lt;code&gt;activate&lt;/code&gt; event will activate the user, adding a &lt;code&gt;deactivate&lt;/code&gt; event will deactivate the user. Once again, we can update the queries but not any of the code that uses the results of these queries. Can we make a change that preserves all external properties?&lt;/p&gt;
    &lt;p&gt;Yes. If an update would change &lt;code&gt;is_activated&lt;/code&gt;, instead have it add an appropriate record to the event table. Now, define the refinement mapping that takes &lt;code&gt;newer_user&lt;/code&gt; and returns &lt;code&gt;new_user&lt;/code&gt;. The &lt;code&gt;activated_at&lt;/code&gt; field will be computed like this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;g(newer_user).activated_at =
            # last_activated_event
        let lae = 
                newer_user.events
                          .filter(event = &amp;quot;activate&amp;quot; | &amp;quot;deactivate&amp;quot;)
                          .last,
        in
            if lae.event == &amp;quot;activate&amp;quot; 
            then lae.timestamp
            else NULL
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;Now new code can use &lt;code&gt;newer_user&lt;/code&gt; directly while old code can use &lt;code&gt;g(newer_user)&lt;/code&gt; and the really old code can use &lt;code&gt;f(g(newer_user))&lt;/code&gt;.&lt;/p&gt;
    &lt;h3&gt;Mutability constraints&lt;/h3&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;I said "these preserve all external properties" and that was a lie. It depends on the properties we explicitly have, and I didn't list any. The real interesting properties for me are mutability constraints on how the system can evolve. So let's go back in time and add a constraint to &lt;code&gt;user&lt;/code&gt;:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;C1(u) = u.is_activated =&amp;gt; u.is_activated&amp;#39;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;This constraint means that if a user is activated, any change will preserve its activated-ness. This means a user can go from deactivated to activated but not the other way. It's not a particular good constraint but it's good enough for teaching purposes. Such a SQL constraint can be enforced with &lt;a href="https://www.postgresql.org/docs/current/sql-createeventtrigger.html" target="_blank"&gt;triggers&lt;/a&gt;. &lt;/p&gt;
    &lt;p&gt;Now we can throw a constraint on &lt;code&gt;new_user&lt;/code&gt;:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;C2(nu) = nu.activated_at != NULL =&amp;gt; nu.activated_at&amp;#39; != NULL
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;If &lt;code&gt;nu&lt;/code&gt; satisfies &lt;code&gt;C2&lt;/code&gt;, then &lt;code&gt;f(nu)&lt;/code&gt; satisfies &lt;code&gt;C1&lt;/code&gt;. So the refinement still holds.&lt;/p&gt;
    &lt;p&gt;With &lt;code&gt;newer_u&lt;/code&gt;, we &lt;em&gt;cannot&lt;/em&gt; guarantee that &lt;code&gt;g(newer_u)&lt;/code&gt; satisfies &lt;code&gt;C2&lt;/code&gt; because we can go from "activated" to "deactivated" just by appending a new event. So it's not a refinement. This is fixable by removing deactivation events, that would work too.&lt;/p&gt;
    &lt;p&gt;So a more interesting case is &lt;code&gt;bad_user&lt;/code&gt;, a refinement of &lt;code&gt;user&lt;/code&gt; that has both &lt;code&gt;activated_at&lt;/code&gt; and &lt;code&gt;activated_until&lt;/code&gt;. We propose the refinement mapping &lt;code&gt;b&lt;/code&gt;:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;b(bad_user).activated =
        if bad_user.activated_at == NULL &amp;amp;&amp;amp; activated_until == NULL
        then FALSE
        else bad_user.activated_at &amp;lt;= now() &amp;lt; bad_user.activated_until
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;But now if enough time passes, &lt;code&gt;b(bad_user).activated' = false&lt;/code&gt;, so this is not a refinement either.&lt;/p&gt;
    &lt;h3&gt;The punchline&lt;/h3&gt;
    &lt;p&gt;Refinement is one of the most powerful techniques in formal specification, but also one of the hardest for people to understand. I'm starting to think that the reason it's so hard is because they learn refinement while they're &lt;em&gt;also&lt;/em&gt; learning formal methods, so are faced with an unfamiliar topic in an unfamiliar context. If that's the case, then maybe it's easier introducing refinement in a more common context like databases.&lt;/p&gt;
    &lt;p&gt;I've written a bit about refinement in the normal context &lt;a href="https://hillelwayne.com/post/refinement/" target="_blank"&gt;here&lt;/a&gt; (showing one specification is an implementation of another). I kinda want to work this explanation into the book but it might be too late for big content additions like this.&lt;/p&gt;
    &lt;p&gt;(Food for thought: how do refinement mappings relate to database views?)&lt;/p&gt;</description>
                <pubDate>Tue, 20 Jan 2026 17:49:07 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/refinement-without-specification/</guid>
            </item>
            <item>
                <title>My Gripes with Prolog</title>
                <link>https://buttondown.com/hillelwayne/archive/my-gripes-with-prolog/</link>
                <description>&lt;p&gt;For the next release of &lt;a href="https://leanpub.com/logic/" target="_blank"&gt;Logic for Programmers&lt;/a&gt;, I'm finally adding the sections on Answer Set Programming and Constraint Logic Programming that I TODOd back in version 0.9. And this is making me re-experience some of my pain points with Prolog, which I will gripe about now.  If you want to know more about why Prolog is cool instead, go &lt;a href="https://buttondown.com/hillelwayne/archive/a48fce5b-8a05-4302-b620-9b26f057f145/" target="_blank"&gt;here&lt;/a&gt; or &lt;a href="https://www.metalevel.at/prolog" target="_blank"&gt;here&lt;/a&gt; or &lt;a href="https://ianthehenry.com/posts/drinking-with-datalog/" target="_blank"&gt;here&lt;/a&gt; or &lt;a href="https://logicprogramming.org/" target="_blank"&gt;here&lt;/a&gt;. &lt;/p&gt;
    &lt;h3&gt;No standardized strings&lt;/h3&gt;
    &lt;p&gt;ISO "strings" are just atoms or lists of single-character atoms (or lists of integer character codes). The various implementations of Prolog add custom string operators but they are not cross compatible, so code written with strings in SWI-Prolog will not work in Scryer Prolog. &lt;/p&gt;
    &lt;h3&gt;No functions&lt;/h3&gt;
    &lt;p&gt;Code logic is expressed entirely in &lt;em&gt;rules&lt;/em&gt;, predicates which return true or false for certain values. For example if you wanted to get the length of a Prolog list, you write this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s s-Atom"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;c&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;Len&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    
       &lt;span class="nv"&gt;Len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;Now this is pretty cool in that it allows bidirectionality, or running predicates "in reverse". To generate lists of length 3, you can write &lt;code&gt;length(L, 3)&lt;/code&gt;. But it also means that if you want to get the length a list &lt;em&gt;plus one&lt;/em&gt;, you can't do that in one expression, you have to write &lt;code&gt;length(List, Out), X is Out+1&lt;/code&gt;.&lt;/p&gt;
    &lt;p&gt;For a while I thought no functions was necessary evil for bidirectionality, but then I discovered &lt;a href="https://picat-lang.org/" target="_blank"&gt;Picat&lt;/a&gt; has functions and works just fine. That by itself is a reason for me to prefer Picat for my LP needs.&lt;/p&gt;
    &lt;p&gt;(Bidirectionality is a killer feature of Prolog, so it's a shame I so rarely run into situations that use it.)&lt;/p&gt;
    &lt;h3&gt;No standardized collection types besides lists&lt;/h3&gt;
    &lt;p&gt;Aside from atoms (&lt;code&gt;abc&lt;/code&gt;) and numbers, there are two data types:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;Linked lists like &lt;code&gt;[a,b,c,d]&lt;/code&gt;.&lt;/li&gt;
    &lt;li&gt;Compound terms like &lt;code&gt;dog(rex, poodle)&lt;/code&gt;, which &lt;em&gt;seem&lt;/em&gt; like record types but are actually tuples. You can even convert compound terms to linked lists with &lt;code&gt;=..&lt;/code&gt;:&lt;/li&gt;
    &lt;/ul&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nv"&gt;L&lt;/span&gt; &lt;span class="s s-Atom"&gt;=..&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s s-Atom"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;c&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;
       &lt;span class="nv"&gt;L&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;c&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;c&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="s s-Atom"&gt;=..&lt;/span&gt; &lt;span class="nv"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
       &lt;span class="nv"&gt;L&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s s-Atom"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;There's no proper key-value maps or even struct types. Again, this is something that individual distributions can fix (without cross compatibility), but these never feel integrated with the rest of the language. &lt;/p&gt;
    &lt;h3&gt;No boolean values&lt;/h3&gt;
    &lt;p&gt;&lt;code&gt;true&lt;/code&gt; and &lt;code&gt;false&lt;/code&gt; aren't values, they're control flow statements. &lt;code&gt;true&lt;/code&gt; is a noop and &lt;code&gt;false&lt;/code&gt; says that the current search path is a dead end, so backtrack and start again. You can't explicitly store true and false as values, you have to implicitly have them in facts (&lt;code&gt;passed(test)&lt;/code&gt; instead of &lt;code&gt;test.passed? == true&lt;/code&gt;).&lt;/p&gt;
    &lt;p&gt;This hasn't made any tasks impossible, and I can usually find a workaround to whatever I want to do. But I do think it makes things more inconvenient! Sometimes I want to do something dumb like "get all atoms that don't pass at least three of these rules", and that'd be a lot easier if I could shove intermediate results into a sack of booleans. &lt;/p&gt;
    &lt;p&gt;(This is called "&lt;a href="https://en.wikipedia.org/wiki/Negation_as_failure" target="_blank"&gt;Negation as Failure&lt;/a&gt;". I think this might be necessary to make Prolog a Turing complete general programming language. Picat fixes a lot of Prolog's gripes and still has negation as failure. ASP has regular negation but it's not Turing complete.) &lt;/p&gt;
    &lt;h3&gt;Cuts are confusing&lt;/h3&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;Prolog finds solutions through depth first search, and a "cut" (&lt;code&gt;!&lt;/code&gt;) symbol prevents backtracking past a certain point. This is necessary for optimization but can lead to invalid programs. &lt;/p&gt;
    &lt;p&gt;You're not supposed to use cuts if you can avoid it, so I pretended cuts didn't exist. Which is why I was surprised to find that &lt;a href="https://eu.swi-prolog.org/pldoc/doc_for?object=(-%3E)/2" target="_blank"&gt;conditionals&lt;/a&gt; are implemented with cuts. Because cuts are spooky dark magic conditionals &lt;em&gt;sometimes&lt;/em&gt; conditionals work as I expect them to and sometimes leave out valid solutions and I have no idea how to tell which it'll be. Usually I find it safer to just avoid conditionals entirely, which means my code gets a lot longer and messier. &lt;/p&gt;
    &lt;h3&gt;Non-cuts are confusing&lt;/h3&gt;
    &lt;p&gt;The original example in the last section was this: &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:-&lt;/span&gt;
        &lt;span class="s s-Atom"&gt;\+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;&lt;code&gt;foo(1, 2)&lt;/code&gt; returns true, so you'd expect &lt;code&gt;f(A, B)&lt;/code&gt; to return &lt;code&gt;A=1, B=2&lt;/code&gt;. But it returns &lt;code&gt;false&lt;/code&gt;.  Whereas this works as expected.&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:-&lt;/span&gt;
        &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s s-Atom"&gt;\+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;I &lt;em&gt;thought&lt;/em&gt; this was because &lt;code&gt;\+&lt;/code&gt; was implemented with cuts, and the &lt;a href="https://www.amazon.com/Programming-Prolog-Using-ISO-Standard/dp/3540006788" target="_blank"&gt;Clocksin book&lt;/a&gt; suggests it's &lt;code&gt;call(P), !, fail&lt;/code&gt;, so this was my prime example about how cuts are confusing. But then I tried this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nf"&gt;member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="s s-Atom"&gt;\+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.&lt;/span&gt;
    &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.&lt;/span&gt; &lt;span class="c1"&gt;% wtf?&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;There's no way to get that behavior with cuts! I don't think &lt;code&gt;\+&lt;/code&gt; uses cuts at all! And now I have to figure out why 
    &lt;code&gt;foo(A, B)&lt;/code&gt; doesn't returns results. Is it &lt;a href="https://github.com/dtonhofer/prolog_notes/blob/master/other_notes/about_negation/floundering.md" target="_blank"&gt;floundering&lt;/a&gt;? Is it because &lt;code&gt;\+ P&lt;/code&gt; only succeeds if &lt;code&gt;P&lt;/code&gt; fails, and &lt;code&gt;A = B&lt;/code&gt; always succeeds? A closed-world assumption? Something else?&lt;sup id="fnref:dif"&gt;&lt;a class="footnote-ref" href="#fn:dif"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
    &lt;h3&gt;Straying outside of default queries is confusing&lt;/h3&gt;
    &lt;p&gt;Say I have a program like this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n11&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n21&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n22&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;n11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n111&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;n11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n112&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    
    &lt;span class="nf"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:-&lt;/span&gt; &lt;span class="c1"&gt;% two children&lt;/span&gt;
        &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;C1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;C2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;C1&lt;/span&gt; &lt;span class="s s-Atom"&gt;@&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;C2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="c1"&gt;% ordering&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;And I want to know all of the nodes that are parents of branches. The normal way to do this is with a query:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Atom"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Atom"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;% show more...&lt;/span&gt;
    &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Atom"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Atom"&gt;n11&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;This is interactively making me query for every result. That's usually not what I want, I know the result of my query is finite and I want all of the results at once, so I can count or farble or whatever them. It took a while to figure out that the proper solution is &lt;a href="https://www.swi-prolog.org/pldoc/man?predicate=bagof/3" target="_blank"&gt;&lt;code&gt;bagof(Template, Goal, Bag)&lt;/code&gt;&lt;/a&gt;, which will "Unify Bag with the alternatives of Template":&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nf"&gt;bagof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nv"&gt;As&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    
    &lt;span class="nv"&gt;As&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s s-Atom"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Atom"&gt;n11&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;As&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s s-Atom"&gt;n&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Atom"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;Wait crap that's still giving one result at a time, because &lt;code&gt;N&lt;/code&gt; is a free variable in &lt;code&gt;bagof&lt;/code&gt; so it backtracks over that. It surprises me but I guess it's good to have as an option. So how do I get all of the results at once?&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nf"&gt;bagof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="s s-Atom"&gt;^&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nv"&gt;As&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    
    &lt;span class="nv"&gt;As&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s s-Atom"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;The only difference is the &lt;code&gt;N^Goal&lt;/code&gt;, which tells &lt;code&gt;bagof&lt;/code&gt; to ignore and group the results of &lt;code&gt;N&lt;/code&gt;. As far as I can tell, this is the &lt;em&gt;only&lt;/em&gt; place the ISO standard uses &lt;code&gt;^&lt;/code&gt; to mean anything besides exponentiation. Supposedly it's the &lt;a href="https://sicstus.sics.se/sicstus/docs/latest4/html/sicstus.html/ref_002dall_002dsum.html" target="_blank"&gt;existential quantifier&lt;/a&gt;? In general whenever I try to stray outside simpler use-cases, especially if I try to do things non-interactively, I run into trouble.&lt;/p&gt;
    &lt;h3&gt;I have mixed feelings about symbol terms&lt;/h3&gt;
    &lt;p&gt;It took me a long time to realize the reason &lt;code&gt;bagof&lt;/code&gt;  "works" is because infix symbols are mapped to prefix compound terms, so that  &lt;code&gt;a^b&lt;/code&gt; is &lt;code&gt;^(a, b)&lt;/code&gt;, and then different predicates can decide to do different things with &lt;code&gt;^(a, b)&lt;/code&gt;.&lt;/p&gt;
    &lt;p&gt;This is also why you can't just write &lt;code&gt;A = B+1&lt;/code&gt;: that unifies &lt;code&gt;A&lt;/code&gt; with the &lt;em&gt;compound term&lt;/em&gt; &lt;code&gt;+(B, 1)&lt;/code&gt;. &lt;code&gt;A+1 = B+2&lt;/code&gt; is &lt;em&gt;false&lt;/em&gt;, as &lt;code&gt;1 \= 2&lt;/code&gt;. You have to write &lt;code&gt;A+1 is B+2&lt;/code&gt;, as &lt;code&gt;is&lt;/code&gt; is the operator that converts &lt;code&gt;+(B, 1)&lt;/code&gt; to a mathematical term.&lt;/p&gt;
    &lt;p&gt;(And &lt;em&gt;that&lt;/em&gt; fails because &lt;code&gt;is&lt;/code&gt; isn't fully bidirectional. The lhs &lt;em&gt;must&lt;/em&gt; be a single variable. You have to import &lt;code&gt;clpfd&lt;/code&gt; and write &lt;code&gt;A + 1 #= B + 2&lt;/code&gt;.)&lt;/p&gt;
    &lt;p&gt;I don't like this, but I'm a hypocrite for saying that because I appreciate the idea and don't mind custom symbols in other languages. I guess what annoys me is there's no official definition of what &lt;code&gt;^(a, b)&lt;/code&gt; is, it's purely a convention. ISO Prolog uses &lt;code&gt;-(a, b)&lt;/code&gt; (aka &lt;code&gt;a-b&lt;/code&gt;) as a convention to mean "pairs", and the only way to realize that is to see that an awful lot of standard modules use that convention. But you can use &lt;code&gt;-(a, b)&lt;/code&gt; to mean something else in your own code and nothing will warn you of the inconsistency.&lt;/p&gt;
    &lt;p&gt;Anyway I griped about pairs so I can gripe about &lt;code&gt;sort&lt;/code&gt;.&lt;/p&gt;
    &lt;h3&gt;go home sort, ur drunk&lt;/h3&gt;
    &lt;p&gt;This one's just a blunder:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
       &lt;span class="nv"&gt;Out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt; &lt;span class="c1"&gt;% wat&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;According to an expert online this is because sort is supposed to return a sorted &lt;em&gt;set&lt;/em&gt;, not a sorted list. If you want to preserve duplicates you're supposed to lift all of the values into &lt;code&gt;-($key, $value)&lt;/code&gt; compound terms, then use &lt;a href="https://eu.swi-prolog.org/pldoc/doc_for?object=keysort/2" target="_blank"&gt;keysort&lt;/a&gt;, then extract the values. And, since there's no functions, this process takes at least three lines. This is also how you're supposed to sort by a custom predicate, like "the second value of a compound term". &lt;/p&gt;
    &lt;p&gt;(Most (but not all) distributions have a duplicate merge like &lt;a href="https://eu.swi-prolog.org/pldoc/doc_for?object=msort/2" target="_blank"&gt;msort&lt;/a&gt;. SWI-Prolog also has a &lt;a href="https://eu.swi-prolog.org/pldoc/doc_for?object=predsort/3" target="_blank"&gt;sort by key&lt;/a&gt; but it removes duplicates.)&lt;/p&gt;
    &lt;h3&gt;Please just let me end rules with a trailing comma instead of a period, I'm begging you&lt;/h3&gt;
    &lt;p&gt;I don't care if it makes fact parsing ambiguous, I just don't want "reorder two lines" to be a syntax error anymore&lt;/p&gt;
    &lt;hr/&gt;
    &lt;p&gt;I expect by this time tomorrow I'll have been Cunningham'd and there will be a 2000 word essay about how all of my gripes are either easily fixable by doing XYZ or how they are the best possible choice that Prolog could have made. I mean, even in writing this I found out some fixes to problems I had. Like I was going to gripe about how I can't run SWI-Prolog queries from the command line but, in doing do diligence finally &lt;em&gt;finally&lt;/em&gt; figured it out:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;swipl&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;halt&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bagof(X, Goal, Xs), print(Xs)"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./file.pl
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;It's pretty clunky but still better than the old process of having to enter an interactive session every time I wanted to validate a script change.&lt;/p&gt;
    &lt;p&gt;(Also, answer set programming is pretty darn cool. Excited to write about it in the book!)&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:dif"&gt;
    &lt;p&gt;A couple of people mentioned using &lt;a href="https://eu.swi-prolog.org/pldoc/doc_for?object=dif/2" target="_blank"&gt;dif/2&lt;/a&gt; instead of &lt;code&gt;\+ A = B&lt;/code&gt;. Dif is great but usually I hit the negation footgun with things like &lt;code&gt;\+ foo(A, B), bar(B, C), baz(A, C)&lt;/code&gt;, where &lt;code&gt;dif/2&lt;/code&gt; isn't applicable. &lt;a class="footnote-backref" href="#fnref:dif" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Wed, 14 Jan 2026 16:48:51 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/my-gripes-with-prolog/</guid>
            </item>
            <item>
                <title>The Liskov Substitution Principle does more than you think</title>
                <link>https://buttondown.com/hillelwayne/archive/the-liskov-substitution-principle-does-more-than/</link>
                <description>&lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;Happy New Year! I'm done with the newsletter hiatus and am going to try updating weekly again. To ease into things a bit, I'll try to keep posts a little more off the cuff and casual for a while, at least until &lt;a href="https://leanpub.com/logic/" target="_blank"&gt;&lt;em&gt;Logic for Programmers&lt;/em&gt;&lt;/a&gt; is done. Speaking of which, v0.13 should be out by the end of this month.&lt;/p&gt;
    &lt;p&gt;So for this newsletter I want to talk about the &lt;a href="https://en.wikipedia.org/wiki/Liskov_substitution_principle" target="_blank"&gt;Liskov Substitution Principle&lt;/a&gt; (LSP). Last week I read &lt;a href="https://loup-vaillant.fr/articles/solid-bull" target="_blank"&gt;A SOLID Load of Bull&lt;/a&gt; by cryptographer Loupe Vaillant, where he argues the &lt;a href="https://en.wikipedia.org/wiki/SOLID" target="_blank"&gt;SOLID&lt;/a&gt; principles of OOP are not worth following. He makes an exception for LSP, but also claims that it's "just subtyping" and further:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;If I were trying really hard to be negative about the Liskov substitution principle, I would stress that &lt;strong&gt;it only applies when inheritance is involved&lt;/strong&gt;, and inheritance is strongly discouraged anyway.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;LSP is more interesting than that! In the original paper, &lt;a href="https://www.cs.cmu.edu/~wing/publications/LiskovWing94.pdf" target="_blank"&gt;A Behavioral Notion of Subtyping&lt;/a&gt;, Barbara Liskov and Jeannette Wing start by defining a "correct" subtyping as follows:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;Subtype Requirement: Let ϕ(x) be a property provable about objects x of type T. Then ϕ(y) should be true for objects y of type S where S is a subtype of T.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;From then on, the paper determine what &lt;em&gt;guarantees&lt;/em&gt; that a subtype is correct.&lt;sup id="fnref:safety"&gt;&lt;a class="footnote-ref" href="#fn:safety"&gt;1&lt;/a&gt;&lt;/sup&gt;  They identify three conditions: &lt;/p&gt;
    &lt;ol&gt;
    &lt;li&gt;Each of the subtype's methods has the same or weaker preconditions and the same or stronger postconditions as the corresponding supertype method.&lt;sup id="fnref:cocontra"&gt;&lt;a class="footnote-ref" href="#fn:cocontra"&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;/li&gt;
    &lt;li&gt;The subtype satisfies all state invariants of the supertype. &lt;/li&gt;
    &lt;li&gt;The subtype satisfies all "history properties" of the supertype. &lt;sup id="fnref:refinement"&gt;&lt;a class="footnote-ref" href="#fn:refinement"&gt;3&lt;/a&gt;&lt;/sup&gt; e.g. if a supertype has an immutable field, the subtype cannot make it mutable. &lt;/li&gt;
    &lt;/ol&gt;
    &lt;p&gt;(Later, Elisa Baniassad and Alexander Summers &lt;a href="https://www.cs.ubc.ca/~alexsumm/papers/BaniassadSummers21.pdf" target="_blank"&gt;would realize&lt;/a&gt; these are equivalent to "the subtype passes all black-box tests designed for the supertype", which I wrote a little bit more about &lt;a href="https://www.hillelwayne.com/post/lsp/" target="_blank"&gt;here&lt;/a&gt;.)&lt;/p&gt;
    &lt;p&gt;I want to focus on the first rule about preconditions and postconditions. This refers to the method's &lt;strong&gt;contract&lt;/strong&gt;.  For a function &lt;code&gt;f&lt;/code&gt;, &lt;code&gt;f.Pre&lt;/code&gt; is what must be true going into the function, and &lt;code&gt;f.Post&lt;/code&gt; is what the function guarantees on execution. A canonical example is square root: &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sqrt.Pre(x) = x &amp;gt;= 0
    sqrt.Post(x, out) = out &amp;gt;= 0 &amp;amp;&amp;amp; out*out == x
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;Mathematically we would write this as &lt;code&gt;all x: f.Pre(x) =&amp;gt; f.Post(x)&lt;/code&gt; (where &lt;code&gt;=&amp;gt;&lt;/code&gt; is the &lt;a href="https://en.wikipedia.org/wiki/Material_conditional" target="_blank"&gt;implication operator&lt;/a&gt;). If that relation holds for all &lt;code&gt;x&lt;/code&gt;, we say the function is "correct". With this definition we can actually formally deduce the first  subtyping requirement. Let &lt;code&gt;caller&lt;/code&gt; be some code that uses a method, which we will call &lt;code&gt;super&lt;/code&gt;, and let both &lt;code&gt;caller&lt;/code&gt; and &lt;code&gt;super&lt;/code&gt; be correct. Then we know the following statements are true:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  1. caller.Pre &amp;amp;&amp;amp; stuff =&amp;gt; super.Pre
      2. super.Pre =&amp;gt; super.Post
      3. super.Post &amp;amp;&amp;amp; more_stuff =&amp;gt; caller.Post
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;Now let's say we substitute &lt;code&gt;super&lt;/code&gt; with &lt;code&gt;sub&lt;/code&gt;, which is also correct. Here is what we now know is true: &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt; 1. caller.Pre =&amp;gt; super.Pre
    &lt;span class="gd"&gt;- 2. super.Pre =&amp;gt; super.Post&lt;/span&gt;
    &lt;span class="gi"&gt;+ 2. sub.Pre =&amp;gt; sub.Post&lt;/span&gt;
    &lt;span class="w"&gt; &lt;/span&gt; 3. super.Post =&amp;gt; caller.Post
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;When is &lt;code&gt;caller&lt;/code&gt; still correct? When we can fill in the "gaps" in the chain, aka if &lt;code&gt;super.Pre =&amp;gt; sub.Pre&lt;/code&gt; and &lt;code&gt;sub.Post =&amp;gt; super.Post&lt;/code&gt;. In other words, if &lt;code&gt;sub&lt;/code&gt;'s preconditions are weaker than (or equivalent to) &lt;code&gt;super&lt;/code&gt;'s preconditions and if &lt;code&gt;sub&lt;/code&gt;'s postconditions are stronger than (or equivalent to) &lt;code&gt;super&lt;/code&gt;'s postconditions.&lt;/p&gt;
    &lt;p&gt;Notice that I never actually said &lt;code&gt;sub&lt;/code&gt; was from a subtype of &lt;code&gt;super&lt;/code&gt;! The LSP conditions (at least, the contract rule of LSP) doesn't just apply to &lt;em&gt;subtypes&lt;/em&gt; but can be applied in any situation where we substitute a function or block of code for another. Subtyping is a common place where this happens, but by no means the only! We can also substitute across time.Any time we modify some code's behavior, we are effectively substituting the new version in for the old version, and so the new version's contract must be compatible with the old version's to guarantee no existing code is broken.&lt;/p&gt;
    &lt;p&gt;For example, say we maintain an API or function with two required inputs, &lt;code&gt;X&lt;/code&gt; and &lt;code&gt;Y&lt;/code&gt;, and one optional input, &lt;code&gt;Z&lt;/code&gt;. Making &lt;code&gt;Z&lt;/code&gt; required strengthens the precondition ("input must have Z" is stronger than "input may have Z"), so potentially breaks existing users of our API. Making &lt;code&gt;Y&lt;/code&gt; optional weakens the precondition ("input may have Y" is weaker than "input must have Y"), so is guaranteed to be compatible.&lt;/p&gt;
    &lt;p&gt;(This also underpins &lt;a href="https://en.wikipedia.org/wiki/Robustness_principle" target="_blank"&gt;The robustness principle&lt;/a&gt;: "be conservative in what you send, be liberal in what you accept".)&lt;/p&gt;
    &lt;p&gt;Now the dark side of all this is &lt;a href="https://www.hyrumslaw.com/" target="_blank"&gt;Hyrum's Law&lt;/a&gt;. In the below code, are &lt;code&gt;new&lt;/code&gt;'s postconditions stronger than &lt;code&gt;old&lt;/code&gt;'s postconditions? &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;old&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"baz"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;On a first appearance, this is a strengthened postcondition: &lt;code&gt;out.contains_keys([a, b, c]) =&amp;gt; out.contains_keys([a, b])&lt;/code&gt;. But now someone does this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;my_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"blat"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="n"&gt;my_dict&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;my_dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"blat"&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;Oh no, their code now breaks! They saw &lt;code&gt;old&lt;/code&gt; had the postcondition "&lt;code&gt;out&lt;/code&gt; does NOT contain "c" as a key", and then wrote their code expecting that postcondition. In a sense, &lt;em&gt;any&lt;/em&gt; change the postcondition can potentially break &lt;em&gt;someone&lt;/em&gt;. "All observable behaviors of your system
    will be depended on by somebody", as &lt;a href="https://www.hyrumslaw.com/" target="_blank"&gt;Hyrum's Law&lt;/a&gt; puts it.&lt;/p&gt;
    &lt;p&gt;So we need to be explicit in what our postconditions actually are, and properties of the output that are not part of our explicit postconditions are subject to be violated on the next version. You'll break people's workflows but you also have grounds to say "I warned you".&lt;/p&gt;
    &lt;p&gt;Overall, Liskov and Wing did their work in the context of subtyping, but the principles are more widely applicable, certainly to more than just the use of inheritance.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:safety"&gt;
    &lt;p&gt;Though they restrict it to just &lt;a href="https://www.hillelwayne.com/post/safety-and-liveness/" target="_blank"&gt;safety properties&lt;/a&gt;. &lt;a class="footnote-backref" href="#fnref:safety" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:cocontra"&gt;
    &lt;p&gt;The paper lists a couple of other authors as introduce the idea of "contra/covariance rules", but part of being "off-the-cuff and casual" means not diving into every referenced paper. So they might have gotten the pre/postconditions thing from an earlier author, dunno for sure! &lt;a class="footnote-backref" href="#fnref:cocontra" title="Jump back to footnote 2 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:refinement"&gt;
    &lt;p&gt;I &lt;em&gt;believe&lt;/em&gt; that this is equivalent to the formal methods notion of a &lt;a href="https://www.hillelwayne.com/post/refinement/" target="_blank"&gt;refinement&lt;/a&gt;. &lt;a class="footnote-backref" href="#fnref:refinement" title="Jump back to footnote 3 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Tue, 06 Jan 2026 16:51:26 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/the-liskov-substitution-principle-does-more-than/</guid>
            </item>
            <item>
                <title>Some Fun Software Facts</title>
                <link>https://buttondown.com/hillelwayne/archive/some-fun-software-facts/</link>
                <description>&lt;p&gt;Last newsletter of the year!&lt;/p&gt;
    &lt;p&gt;First some news on &lt;em&gt;Logic for Programmers&lt;/em&gt;. Thanks to everyone who donated to the &lt;a href="https://buttondown.com/hillelwayne/archive/get-logic-for-programmers-50-off-support-chicago" target="_blank"&gt;feedchicago charity drive&lt;/a&gt;! In total we raised $2250 for Chicago food banks. Proof &lt;a href="https://link.fndrsp.net/CL0/https:%2F%2Fgiving.chicagosfoodbank.org%2Freceipts%2FBMDDDCAF%3FreceiptType=oneTime%26emailLog=YS699MZW/2/0100019ae2b7eb92-7c917ad0-c94e-4fe2-8ee1-1b9dc521c607-000000/brmxoTOvoJN94I9nQH26s7fRrmyFDj_Jir1FySSoxCw=434" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;If you missed buying &lt;em&gt;Logic for Programmers&lt;/em&gt; real cheap in the charity drive, you can still get it for $10 off with the holiday code &lt;a href="https://leanpub.com/logic/c/hannukah-presents" target="_blank"&gt;hannukah-presents&lt;/a&gt;. This will last from now until the end of the year. After that, I'll be raising the price from $25 to $30.&lt;/p&gt;
    &lt;p&gt;Anyway, to make this more than just some record keeping, let's close out with something light. I'm one of those people who loves hearing "fun facts" about stuff. So here's some random fun facts I accumulated about software over the years:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;In 2017, a team of eight+ programmers &lt;a href="https://codegolf.stackexchange.com/questions/11880/build-a-working-game-of-tetris-in-conways-game-of-life" target="_blank"&gt;successfully implemented Tetris&lt;/a&gt; as a &lt;a href="https://en.wikipedia.org/wiki/Conway's_Game_of_Life" target="_blank"&gt;game of life simulation&lt;/a&gt;. The GoL grid had an area of 30 trillion pixels and implemented a full programmable CPU as part of the project.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;Computer systems have to deal with leap seconds in order to keep UTC (where one day is 86,400 seconds) in sync with UT1 (where one day is exactly one full earth rotation). The people in charge recently passed a resolution to abolish the leap second by 2035, letting UTC and UT1 slowly drift out of sync.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;a href="https://buttondown.com/hillelwayne/archive/vim-is-turing-complete/" target="_blank"&gt;Vim is Turing complete&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;ul&gt;
    &lt;li&gt;The backslash character basically didn't exist in writing before 1930, and &lt;a href="http://dump.deadcodersociety.org/ascii.pdf" target="_blank"&gt;was only added to ASCII&lt;/a&gt; so mathematicians (and ALGOLists) could write &lt;code&gt;/\&lt;/code&gt; and &lt;code&gt;\/&lt;/code&gt;. It's popular use in computing stems entirely from being a useless key on the keyboard.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Galactic_algorithm" target="_blank"&gt;Galactic Algorithms&lt;/a&gt; are algorithms that are theoretically faster than algorithms we use, but only at scales that make them impractical. For example, matrix multiplication of NxN is &lt;a href="https://en.wikipedia.org/wiki/Strassen_algorithm" target="_blank"&gt;normally&lt;/a&gt; O(N^2.81). The &lt;a href="https://www-auth.cs.wisc.edu/lists/theory-reading/2009-December/pdfmN6UVeUiJ3.pdf" target="_blank"&gt;Coppersmith Winograd&lt;/a&gt; algorithm is O(N^2.38), but is so complex that it's vastly slower for even &lt;a href="https://mathoverflow.net/questions/1743/what-is-the-constant-of-the-coppersmith-winograd-matrix-multiplication-algorithm" target="_blank"&gt;10,000 x 10,000 matrices&lt;/a&gt;. It's still interesting in advancing our mathematical understanding of algorithms!&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;Cloudflare generates random numbers by, in part, &lt;a href="https://www.cloudflare.com/learning/ssl/lava-lamp-encryption/" target="_blank"&gt;taking pictures of 100 lava lamps&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;Mergesort is older than bubblesort. Quicksort is slightly younger than bubblesort but older than the &lt;em&gt;term&lt;/em&gt; "bubblesort". Bubblesort, btw, &lt;a href="https://buttondown.com/hillelwayne/archive/when-would-you-ever-want-bubblesort/" target="_blank"&gt;does have some uses&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;Speaking of mergesort, most implementations of mergesort pre-2006 &lt;a href="https://research.google/blog/extra-extra-read-all-about-it-nearly-all-binary-searches-and-mergesorts-are-broken/" target="_blank"&gt;were broken&lt;/a&gt;. Basically the problem was that the "find the midpoint of a list" step &lt;em&gt;could&lt;/em&gt; overflow if the list was big enough. For C with 32-bit signed integers, "big enough" meant over a billion elements, which was why the bug went unnoticed for so long.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;a href="https://nibblestew.blogspot.com/2023/09/circles-do-not-exist.html" target="_blank"&gt;PDF's drawing model cannot render perfect circles&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;People make fun of how you have to flip USBs three times to get them into a computer, but there's supposed to be a guide: according to the standard, USBs are supposed to be inserted &lt;em&gt;logo-side up&lt;/em&gt;. Of course, this assumes that the port is right-side up, too, which is why USB-C is just symmetric. &lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;I was gonna write a fun fact about how all spreadsheet software treats 1900 as a leap year, as that was a bug in Lotus 1-2-3 and everybody preserved backwards compatibility. But I checked and Google sheets considers it a normal year. So I guess the fun fact is that things have changed!&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;Speaking of spreadsheet errors, in 2020 &lt;a href="https://www.engadget.com/scientists-rename-genes-due-to-excel-151748790.html" target="_blank"&gt;biologists changed the official nomenclature&lt;/a&gt; of 27 genes because Excel kept parsing their names as dates. F.ex MARCH1 was renamed to MARCHF1 to avoid being parsed as "March 1st". Microsoft rolled out a fix for this... three years later.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;It is possible to encode any valid JavaScript program with just the characters &lt;code&gt;()+[]!&lt;/code&gt;. This encoding is called &lt;a href="https://en.wikipedia.org/wiki/JSFuck" target="_blank"&gt;JSFuck&lt;/a&gt; and was once used to distribute malware on &lt;a href="https://arstechnica.com/information-technology/2016/02/ebay-has-no-plans-to-fix-severe-bug-that-allows-malware-distribution/" target="_blank"&gt;Ebay&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Happy holidays everyone, and see you in 2026!&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:status"&gt;
    &lt;p&gt;Current status update: I'm finally getting line by line structural editing done and it's turning up lots of improvements, so I'm doing more rewrites than I expected to be doing. &lt;a class="footnote-backref" href="#fnref:status" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Wed, 10 Dec 2025 18:45:37 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/some-fun-software-facts/</guid>
            </item>
            <item>
                <title>One more week to the Logic for Programmers Food Drive</title>
                <link>https://buttondown.com/hillelwayne/archive/one-more-week-to-the-logic-for-programmers-food/</link>
                <description>&lt;p&gt;A couple of weeks ago I started a fundraiser for the &lt;a href="https://www.chicagosfoodbank.org/" target="_blank"&gt;Greater Chicago Food Depository&lt;/a&gt;: get &lt;a href="https://leanpub.com/logic/c/feedchicago" target="_blank"&gt;Logic for Programmers 50% off&lt;/a&gt; and all the royalties will go to charity.&lt;sup id="fnref:royalties"&gt;&lt;a class="footnote-ref" href="#fn:royalties"&gt;1&lt;/a&gt;&lt;/sup&gt; Since then, we've raised a bit over $1600. Y'all are great! &lt;/p&gt;
    &lt;p&gt;The fundraiser is going on until the end of November, so you still have one more week to get the book real cheap.&lt;/p&gt;
    &lt;p&gt;I feel a bit weird about doing two newsletter adverts without raw content, so here's a teaser from a old project I really need to get back to. &lt;a href="https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/#what-is-a-goto-statement-anyway" target="_blank"&gt;Notes on structured concurrency&lt;/a&gt; argues that old languages had a "old-testament fire-and-brimstone &lt;code&gt;goto&lt;/code&gt;" that could send control flow anywhere, like from the body of one function into the body of another function. This "wild goto", the article claims, what Dijkstra was railing against in &lt;a href="https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf" target="_blank"&gt;Go To Statement Considered Harmful&lt;/a&gt;, and that modern goto statements are much more limited, "tame" if you will, and wouldn't invoke Dijkstra's ire.&lt;/p&gt;
    &lt;p&gt;I've shared this historical fact about Dijkstra many times, but recently two &lt;a href="https://without.boats/blog/" target="_blank"&gt;separate&lt;/a&gt; &lt;a href="https://matklad.github.io/" target="_blank"&gt;people&lt;/a&gt; have told me it doesn't makes sense: Dijkstra used ALGOL-60, which &lt;em&gt;already had&lt;/em&gt; tame gotos. All of the problems he raises with &lt;code&gt;goto&lt;/code&gt; hold even for tame ones, none are exclusive to wild gotos. So &lt;/p&gt;
    &lt;p&gt;This got me looking to see which languages, if any, ever had the wild goto. I define this as any goto which lets you jump from outside to into a loop or function scope. Turns out, FORTRAN had tame gotos from the start, BASIC has wild gotos, and COBOL is a nonsense language intentionally designed to horrify me. I mean, look at this:&lt;/p&gt;
    &lt;p&gt;&lt;img alt="The COBOL ALTER statement, which redefines a goto target" class="newsletter-image" src="https://assets.buttondown.email/images/e4dfa0fd-fdd5-4fef-b813-4053a183be2f.png?w=960&amp;amp;fit=max"/&gt;&lt;/p&gt;
    &lt;p&gt;The COBOL ALTER statement &lt;em&gt;changes a &lt;code&gt;goto&lt;/code&gt;'s target at runtime&lt;/em&gt;. &lt;/p&gt;
    &lt;p&gt;(Early COBOL has tame gotos but only on a technicality: there are no nested scopes in COBOL so no jumping from outside and into a nested scope.)&lt;/p&gt;
    &lt;p&gt;Anyway I need to write up the full story (and complain about COBOL more) but this is pretty neat! Reminder, &lt;a href="https://leanpub.com/logic/c/feedchicago" target="_blank"&gt;fundraiser here&lt;/a&gt;. Let's get it to 2k.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:royalties"&gt;
    &lt;p&gt;Royalties are 80% so if you already have the book you get a bit more bang for your buck by donating to the GCFD directly &lt;a class="footnote-backref" href="#fnref:royalties" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Mon, 24 Nov 2025 18:21:49 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/one-more-week-to-the-logic-for-programmers-food/</guid>
            </item>
            <item>
                <title>Get Logic for Programmers 50% off &amp; Support Chicago Foodbanks</title>
                <link>https://buttondown.com/hillelwayne/archive/get-logic-for-programmers-50-off-support-chicago/</link>
                <description>&lt;p&gt;From now until the end of the month, you can get &lt;a href="https://leanpub.com/logic/c/feedchicago" target="_blank"&gt;Logic for Programmers at half price&lt;/a&gt; with the coupon &lt;code&gt;feedchicago&lt;/code&gt;. All royalties from that coupon will go to the &lt;a href="https://www.chicagosfoodbank.org/" target="_blank"&gt;Greater Chicago Food Depository&lt;/a&gt;. Thank you!&lt;/p&gt;</description>
                <pubDate>Mon, 10 Nov 2025 16:31:11 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/get-logic-for-programmers-50-off-support-chicago/</guid>
            </item>
            <item>
                <title>I'm taking a break</title>
                <link>https://buttondown.com/hillelwayne/archive/im-taking-a-break/</link>
                <description>&lt;p&gt;Hi everyone,&lt;/p&gt;
    &lt;p&gt;I've been getting burnt out on writing a weekly software essay. It's gone from taking me an afternoon to write a post to taking two or three days, and that's made it really difficult to get other writing done. That, plus some short-term work and life priorities, means now feels like a good time for a break. &lt;/p&gt;
    &lt;p&gt;So I'm taking off from &lt;em&gt;Computer Things&lt;/em&gt; for the rest of the year. There &lt;em&gt;might&lt;/em&gt; be some announcements and/or one or two short newsletters in the meantime but I won't be attempting a weekly cadence until 2026.&lt;/p&gt;
    &lt;p&gt;Thanks again for reading!&lt;/p&gt;
    &lt;p&gt;Hillel&lt;/p&gt;</description>
                <pubDate>Mon, 27 Oct 2025 21:02:37 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/im-taking-a-break/</guid>
            </item>
            <item>
                <title>Modal editing is a weird historical contingency we have through sheer happenstance</title>
                <link>https://buttondown.com/hillelwayne/archive/modal-editing-is-a-weird-historical-contingency/</link>
                <description>&lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;A while back my friend &lt;a href="https://morepablo.com/" target="_blank"&gt;Pablo Meier&lt;/a&gt; was reviewing some 2024 videogames and wrote &lt;a href="https://morepablo.com/2025/03/games-of-2024.html" target="_blank"&gt;this&lt;/a&gt;:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;I feel like some artists, if they didn't exist, would have the resulting void filled in by someone similar (e.g. if Katy Perry didn't exist, someone like her would have). But others don't have successful imitators or comparisons (thinking Jackie Chan, or Weird Al): they are irreplaceable.  &lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;He was using it to describe auteurs but I see this as a property of opportunity, in that "replaceable" artists are those who work in bigger markets. Katy Perry's market is large, visible and obviously (but not &lt;em&gt;easily&lt;/em&gt;) exploitable, so there are a lot of people who'd compete in her niche. Weird Al's market is unclear: while there were successful parody songs in the past, it wasn't clear there was enough opportunity there to support a superstar.&lt;/p&gt;
    &lt;p&gt;I think that modal editing is in the latter category. Vim is now very popular and has spawned numerous successors. But its key feature, &lt;strong&gt;modes&lt;/strong&gt;, is not obviously-beneficial, to the point that if Bill Joy didn't make vi (vim's direct predecessor) fifty years ago I don't think we'd have any modal editors today. &lt;/p&gt;
    &lt;h3&gt;A quick overview of "modal editing"&lt;/h3&gt;
    &lt;p&gt;In a non-modal editor, pressing the "u" key adds a "u" to your text, as you'd expect. In a &lt;strong&gt;modal editor&lt;/strong&gt;, pressing "u" does something different depending on the "mode" you are in. In Vim's default "normal" mode, "u" undoes the last change to the text, while in the "visual" mode it lowercases all selected text. It only inserts the character in "insert" mode. All other keys, as well as chorded shortcuts (&lt;code&gt;ctrl-x&lt;/code&gt;), work the same way. &lt;/p&gt;
    &lt;p&gt;The clearest benefit to this is you can densely pack the keyboard with advanced commands. The standard US keyboard has 48ish keys dedicated to inserting characters. With the ctrl and shift modifiers that becomes at least ~150 extra shortcuts for each other mode. This is also what IMO "spiritually" distinguishes modal editing from contextual shortcuts. Even if a unimodal editor lets you change a keyboard shortcut's behavior based on languages or focused panel, without global user-controlled modes it simply can't achieve that density of shortcuts.&lt;/p&gt;
    &lt;p&gt;Now while modal editing today is widely beloved (the Vim plugin for &lt;a href="https://marketplace.visualstudio.com/items?itemName=vscodevim.vim" target="_blank"&gt;VSCode&lt;/a&gt; has at least eight million downloads), I suspect it was "carried" by the popularity of vi, as opposed to driving vi's popularity.&lt;/p&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;h3&gt;Modal editing is an unusual idea&lt;/h3&gt;
    &lt;p&gt;Pre-vi editors weren't modal. Some, like &lt;a href="https://en.wikipedia.org/wiki/EDT_(Digital)" target="_blank"&gt;EDT/KED&lt;/a&gt;, used chorded commands, while others like &lt;a href="https://en.wikipedia.org/wiki/Ed_(software)" target="_blank"&gt;ed&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/TECO_(text_editor)" target="_blank"&gt;TECO&lt;/a&gt; basically REPLs for text-editing DSLs. Both of these ideas widely reappear in modern editors.&lt;/p&gt;
    &lt;p&gt;As far as I can tell, the first modal editor was Butler Lampson's &lt;a href="https://en.wikipedia.org/wiki/Bravo_(editor)" target="_blank"&gt;Bravo&lt;/a&gt; in 1974. Bill Joy &lt;a href="https://web.archive.org/web/20120210184000/http://web.cecs.pdx.edu/~kirkenda/joy84.html" target="_blank"&gt;admits he used it for inspiration&lt;/a&gt;: &lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;A lot of the ideas for the screen editing mode were stolen from a Bravo manual I surreptitiously looked at and copied. Dot is really the double-escape from Bravo, the redo command. Most of the stuff was stolen. &lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;Bill Joy probably took the idea because he was working on &lt;a href="https://en.wikipedia.org/wiki/ADM-3A" target="_blank"&gt;dumb terminals&lt;/a&gt; that were slow to register keystrokes, which put pressure to minimize the number needed for complex operations.&lt;/p&gt;
    &lt;p&gt;Why did Bravo have modal editing? Looking at the &lt;a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/11/15a-AltoHandbook.pdf" target="_blank"&gt;Alto handbook&lt;/a&gt;, I get the impression that Xerox was trying to figure out the best mouse and GUI workflows. Bravo was an experiment with modes, one hand on the mouse and one issuing commands on the keyboard. Other experiments included context menus (the Markup program) and toolbars (Draw).&lt;/p&gt;
    &lt;p&gt;Xerox very quickly decided &lt;em&gt;against&lt;/em&gt; modes, as the successors &lt;a href="http://www.bitsavers.org/pdf/xerox/alto/memos_1975/Gypsy_The_Ginn_Typescript_System_Apr75.pdf" target="_blank"&gt;Gypsy&lt;/a&gt; and &lt;a href="http://www.bitsavers.org/pdf/xerox/alto/BravoXMan.pdf" target="_blank"&gt;BravoX&lt;/a&gt; were modeless. Commands originally assigned to English letters were moved to graphical menus, special keys, and chords. &lt;/p&gt;
    &lt;p&gt;It seems to me that modes started as an unsuccessful experiment deal with a specific constraint and then later successfully adopted to deal with a different constraint. It was a specialized feature as opposed to a generally useful feature like chords.&lt;/p&gt;
    &lt;h3&gt;Modal editing didn't popularize vi&lt;/h3&gt;
    &lt;p&gt;While vi was popular at Bill Joy's coworkers, he doesn't &lt;a href="https://web.archive.org/web/20120210184000/http://web.cecs.pdx.edu/~kirkenda/joy84.html" target="_blank"&gt;attribute its success to its features&lt;/a&gt;:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;I think the wonderful thing about vi is that it has such a good market share because we gave it away. Everybody has it now. So it actually had a chance to become part of what is perceived as basic UNIX. EMACS is a nice editor too, but because it costs hundreds of dollars, there will always be people who won't buy it. &lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;Vi was distributed for free with the popular &lt;a href="https://en.wikipedia.org/wiki/Berkeley_Software_Distribution" target="_blank"&gt;BSD Unix&lt;/a&gt; and was standardized in &lt;a href="https://pubs.opengroup.org/onlinepubs/9799919799/" target="_blank"&gt;POSIX Issue 2&lt;/a&gt;, meaning all Unix OSes had to have vi. That arguably is what made it popular, and why so many people ended up learning a modal editor. &lt;/p&gt;
    &lt;h3&gt;Modal editing doesn't really spread outside of vim&lt;/h3&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;I think by the 90s, people started believing that modal editing was a Good Idea, if not an obvious one. That's why we see direct descendants of vi, most famously vim. It's also why extensible editors like Emacs and VSCode have vim-mode extensions, but these are but these are always simple emulation layers on top of a unimodal baselines. This was good for getting people used to the vim keybindings (I learned on &lt;a href="https://en.wikipedia.org/wiki/Kile" target="_blank"&gt;Kile&lt;/a&gt;) but it means people weren't really &lt;em&gt;doing&lt;/em&gt; anything with modal editing. It was always "The Vim Gimmick".&lt;/p&gt;
    &lt;p&gt;Modes also didn't take off anywhere else. There's no modal word processor, spreadsheet editor, or email client.&lt;sup id="fnref:gmail"&gt;&lt;a class="footnote-ref" href="#fn:gmail"&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;a href="https://www.visidata.org/" target="_blank"&gt;Visidata&lt;/a&gt; is an extremely cool modal data exploration tool but it's pretty niche. Firefox used to have &lt;a href="https://en.wikipedia.org/wiki/Vimperator" target="_blank"&gt;vimperator&lt;/a&gt; (which was inspired by Vim) but that's defunct now. Modal software means modal editing which means vi.&lt;/p&gt;
    &lt;p&gt;This has been changing a little, though! Nowadays we do see new modal text editors, like &lt;a href="https://kakoune.org/" target="_blank"&gt;kakoune&lt;/a&gt; and &lt;a href="https://helix-editor.com/" target="_blank"&gt;Helix&lt;/a&gt;, that don't just try to emulate vi but do entirely new things. These were made, though, in response to perceived shortcomings in vi's editing model. I think they are still classifiable as descendants. If vi never existed, would the developers of kak and helix have still made modal editors, or would they have explored different ideas? &lt;/p&gt;
    &lt;h3&gt;People aren't clamouring for more experiments&lt;/h3&gt;
    &lt;p&gt;Not too related to the overall picture, but a gripe of mine. Vi and vim have a set of hardcoded modes, and adding an entirely new mode is impossible. Like if a plugin (like vim's default &lt;code&gt;netrw&lt;/code&gt;) adds a file explorer it should be able to add a filesystem mode, right? But it can't, so instead it waits for you to open the filesystem and then &lt;a href="https://github.com/vim/vim/blob/0124320c97b0fbbb44613f42fc1c34fee6181fc8/runtime/pack/dist/opt/netrw/autoload/netrw.vim#L4867" target="_blank"&gt;adds 60 new mappings to normal mode&lt;/a&gt;. There's no way to properly add a "filesystem" mode, a "diff" mode, a "git" mode, etc, so plugin developers have to &lt;a href="https://www.hillelwayne.com/post/software-mimicry/" target="_blank"&gt;mimic&lt;/a&gt; them.&lt;/p&gt;
    &lt;p&gt;I don't think people see this as a problem, though! Neovim, which aims to fix all of the baggage in vim's legacy, didn't consider creating modes an important feature. Kak and Helix, which reimagine modal editing from from the ground up, don't support creating modes either.&lt;sup id="fnref:helix"&gt;&lt;a class="footnote-ref" href="#fn:helix"&gt;2&lt;/a&gt;&lt;/sup&gt; People aren't clamouring for new modes!&lt;/p&gt;
    &lt;h2&gt;Modes are a niche power user feature&lt;/h2&gt;
    &lt;p&gt;So far I've been trying to show that vi is, in Pablo's words, "irreplaceable". Editors weren't doing modal editing before Bravo, and even after vi became incredibly popular, unrelated editors did not adapt modal editing. At most, they got a vi emulation layer. Kak and helix complicate this story but I don't think they refute it; they appear much later and arguably count as descendants (so are related). &lt;/p&gt;
    &lt;p&gt;I think the best explanation is that in a vacuum modal editing sounds like a bad idea. The mode is global state that users always have to know, which makes it dangerous. To use new modes well you have to memorize all of the keybindings, which makes it difficult. Modal editing has a brutal skill floor before it becomes more efficient than a unimodal, chorded editor like VSCode.&lt;/p&gt;
    &lt;p&gt;That's why it originally appears in very specific circumstances, as early experiments in mouse UX and as a way of dealing with modem latencies. The fact we have vim today is a historical accident. &lt;/p&gt;
    &lt;p&gt;And I'm glad for it! You can pry Neovim from my cold dead hands, you monsters.&lt;/p&gt;
    &lt;hr/&gt;
    &lt;h1&gt;&lt;a href="https://www.p99conf.io/" target="_blank"&gt;P99 talk this Thursday&lt;/a&gt;!&lt;/h1&gt;
    &lt;p&gt;My talk, "Designing Low-Latency Systems with TLA+", is happening 10/23 at 11:40 central time. Tickets are free, the conf is online, and the talk's only 16 minutes, so come check it out!&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:gmail"&gt;
    &lt;p&gt;I guess if you squint &lt;a href="https://support.google.com/mail/answer/6594?hl=en&amp;amp;co=GENIE.Platform%3DDesktop" target="_blank"&gt;gmail kinda counts&lt;/a&gt; but it's basically an antifeature &lt;a class="footnote-backref" href="#fnref:gmail" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:helix"&gt;
    &lt;p&gt;It looks like Helix supports &lt;a href="https://docs.helix-editor.com/remapping.html" target="_blank"&gt;creating minor modes&lt;/a&gt;, but these are only active for one keystroke, making them akin to a better, more ergonomic version of vim multikey mappings. &lt;a class="footnote-backref" href="#fnref:helix" title="Jump back to footnote 2 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Tue, 21 Oct 2025 16:46:24 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/modal-editing-is-a-weird-historical-contingency/</guid>
            </item>
            <item>
                <title>The Phase Change</title>
                <link>https://buttondown.com/hillelwayne/archive/the-phase-change/</link>
                <description>&lt;p&gt;Last week I ran my first 10k.&lt;/p&gt;
    &lt;p&gt;It wasn't a race or anything. I left that evening planning to run a 5k, and then three miles later thought "what if I kept going?"&lt;sup id="fnref:distance"&gt;&lt;a class="footnote-ref" href="#fn:distance"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
    &lt;p&gt;I've been running for just over two years now. My goal was to run a mile, then three, then three at a pace faster than a power-walk. I wish I could say that I then found joy in running, but really I was just mad at myself for being so bad at it. Spite has always been my brightest muse.&lt;/p&gt;
    &lt;p&gt;Looking back, the thing I find most fascinating is what progress looked like. I couldn't tell you if I was physically progressing steadily, but for sure mental progress moved in discrete jumps. For a long time a 5k was me pushing myself, then suddenly a "phase change" happens and it becomes something I can just do on a run. Sometime in the future the 10k will feel the same way.&lt;/p&gt;
    &lt;p&gt;I've noticed this in a lot of other places. For every skill I know, my sense of myself follows a phase change. In every programming language I've ever learned, I lurch from "bad" to "okay" to "good". There's no "20% bad / 80% normal" in between. Pedagogical experts say that learning is about steadily building a &lt;a href="https://teachtogether.tech/en/index.html#s:models" target="_blank"&gt;mental model&lt;/a&gt; of the topic. It really feels like knowledge grows continuously, and then it suddenly becomes a model.&lt;/p&gt;
    &lt;p&gt;Now, for all the time I spend writing about software history and software theory and stuff, my actually job boils down to &lt;a href="https://www.hillelwayne.com/consulting/" target="_blank"&gt;teaching formal methods&lt;/a&gt;. So I now have two questions about phase changes.&lt;/p&gt;
    &lt;p&gt;The first is "can we make phase changes happen faster?" I don't know if this is even possible! I've found lots of ways to teach concepts faster, cover more ground in less time, so that people know the material more quickly. But it doesn't seem to speed up that very first phase change from "this is foreign" to "this is normal". Maybe we can't really do that until we've spent enough effort on understanding.&lt;/p&gt;
    &lt;p&gt;So the second may be more productive: "can we motivate people to keep going until the phase change?" This is a lot easier to tackle! For example, removing frustration makes a huge difference. Getting a proper pair of running shoes made running so much less unpleasant, and made me more willing to keep putting in the hours. For teaching tech topics like formal methods, this often takes the form of better tooling and troubleshooting info.&lt;/p&gt;
    &lt;p&gt;We can also reduce the effort of investing time. This is also why I prefer to pair on writing specifications with clients and not just write specs for them. It's more work for them than fobbing it all off on me, but a whole lot &lt;em&gt;less&lt;/em&gt; work than writing the spec by themselves, so they'll put in time and gradually develop skills on their own.&lt;/p&gt;
    &lt;p&gt;Question two seems much more fruitful than question one but also so much less interesting! Speeding up the phase change feels like the kind of dream that empires are built on. I know I'm going to keep obsessing over it, even if that leads nowhere.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:distance"&gt;
    &lt;p&gt;For non-running Americans: 5km is about 3.1 miles, and 10km is 6.2. &lt;a class="footnote-backref" href="#fnref:distance" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Thu, 16 Oct 2025 14:59:25 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/the-phase-change/</guid>
            </item>
            <item>
                <title>Three ways formally verified code can go wrong in practice</title>
                <link>https://buttondown.com/hillelwayne/archive/three-ways-formally-verified-code-can-go-wrong-in/</link>
                <description>&lt;h3&gt;New Logic for Programmers Release!&lt;/h3&gt;
    &lt;p&gt;&lt;a href="https://leanpub.com/logic/" rel="noopener noreferrer nofollow" target="_blank"&gt;v0.12 is now available&lt;/a&gt;! This should be the last major content release. The next few months are going to be technical review, copyediting and polishing, with a hopeful 1.0 release in March. &lt;a href="https://github.com/logicforprogrammers/book-assets/blob/master/CHANGELOG.md" rel="noopener noreferrer nofollow" target="_blank"&gt;Full release notes here&lt;/a&gt;.&lt;/p&gt;
    &lt;figure&gt;&lt;img alt="Cover of the boooooook" draggable="false" src="https://assets.buttondown.email/images/92b4a35d-2bdd-416a-92c7-15ff42b49d8d.jpg?w=960&amp;amp;fit=max"/&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;h1&gt;Three ways formally verified code can go wrong in practice&lt;/h1&gt;
    &lt;p&gt;I run this small project called &lt;a href="https://github.com/hwayne/lets-prove-leftpad" rel="noopener noreferrer nofollow" target="_blank"&gt;Let's Prove Leftpad&lt;/a&gt;, where people submit formally verified proofs of the &lt;a href="https://en.wikipedia.org/wiki/Npm_left-pad_incident" rel="noopener noreferrer nofollow" target="_blank"&gt;eponymous meme&lt;/a&gt;. Recently I read &lt;a href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/" rel="noopener noreferrer nofollow" target="_blank"&gt;Breaking “provably correct” Leftpad&lt;/a&gt;, which argued that most (if not all) of the provably correct leftpads have bugs! The lean proof, for example, &lt;em&gt;should&lt;/em&gt; render &lt;code&gt;leftpad('-', 9, אֳֽ֑)&lt;/code&gt; as &lt;code&gt;---------אֳֽ֑&lt;/code&gt;, but actually does &lt;code&gt;------אֳֽ֑&lt;/code&gt;.&lt;/p&gt;
    &lt;p&gt;You can read the article for a good explanation of why this goes wrong (Unicode). The actual problem is that correct can mean two different things, and this leads to confusion about how much formal methods can actually guarantee us. So I see this as a great opportunity to talk about the nature of proof, correctness, and how "correct" code can still have bugs.&lt;/p&gt;
    &lt;h2&gt;What we talk about when we talk about correctness&lt;/h2&gt;
    &lt;p&gt;In most of the real world, correct means "no bugs". Except "bugs" isn't a very clear category. A bug is anything that causes someone to say "this isn't working right, there's a bug." Being too slow is a bug, a typo is a bug, etc. "correct" is a little fuzzy.&lt;/p&gt;
    &lt;p&gt;In formal methods, "correct" has a very specific and precise meaning: the code conforms to a &lt;strong&gt;specification&lt;/strong&gt; (or "spec"). The spec is a higher-level description of what is supposed the code's properties, usually something we can't just directly implement. Let's look at the most popular kind of proven specification:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Haskell&lt;/span&gt;
    &lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;The type signature &lt;code&gt;Int -&amp;gt; Int&lt;/code&gt; is a specification! It corresponds to the logical statement &lt;code&gt;all x in Int: inc(x) in Int&lt;/code&gt;. The Haskell type checker can automatically verify this for us. It cannot, however, verify properties like &lt;code&gt;all x in Int: inc(x) &amp;gt; x&lt;/code&gt;. Formal verification is concerned with verifying arbitrary properties beyond what is (easily) automatically verifiable. Most often, this takes the form of proof. A human manually writes a proof that the code conforms to its specification, and the prover checks that the proof is correct.&lt;/p&gt;
    &lt;p&gt;Even if we have a proof of "correctness", though, there's a few different ways the code can still have bugs.&lt;/p&gt;
    &lt;h3&gt;1. The proof is invalid&lt;/h3&gt;
    &lt;p&gt;For some reason the proof doesn't actually show the code matches the specification. This is pretty common in pencil-and-paper verification, where the proof is checked by someone saying "yep looks good to me". It's much rarer when doing formal verification but it can still happen in a couple of specific cases:&lt;/p&gt;
    &lt;ol&gt;&lt;li&gt;&lt;p&gt;The theorem prover itself has a bug (in the code or introduced in the compiled binary) that makes it accept an incorrect proof. This is something people are really concerned about but it's so much rarer than every other way verified code goes wrong, so is only included for completeness.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;For convenience, most provers and FM languages have an "just accept this statement is true" feature. This helps you work on the big picture proof and fill in the details later. If you leave in a shortcut, &lt;em&gt;and&lt;/em&gt; the compiler is configured to allow code-with-proof-assumptions to compile, &lt;em&gt;then&lt;/em&gt; you can compile incorrect code that "passes the proof checker". You really should know better, though.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;h3&gt;2. The properties are wrong&lt;/h3&gt;
    &lt;blockquote&gt;&lt;figure&gt;&lt;img alt="The horrible bug you had wasn't covered in the specification/came from some other module/etc" draggable="false" src="https://cdn.prod.website-files.com/673b407e535dbf3b547179ff/681ca0bf4a045f39f785faeb_AD_4nXfFhdn6DGmgLAcmaUNHl9a3Nog8gH8Hluve5Kof7zLk4CyOlD4zCmCqVJaowKqu-pTicwZ393jE7anIrjYZTSuRvGiYhFhAkkX9vifNt9vEWYwZUp65hsbrRTmZzRgb9vgu7n7buA.png"/&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href="https://www.galois.com/articles/what-works-and-doesnt-selling-formal-methods" rel="noopener noreferrer nofollow" target="_blank"&gt;Galois&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
    &lt;p&gt;This code is provably correct:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;The only specification I've given is the type signature &lt;code&gt;Int -&amp;gt; Int&lt;/code&gt;. At no point did I put the property &lt;code&gt;inc(x) &amp;gt; x&lt;/code&gt; in my specification, so it doesn't matter that it doesn't hold, the code is still "correct".&lt;/p&gt;
    &lt;p&gt;This is what "went wrong" with the leftpad proofs. They do &lt;em&gt;not&lt;/em&gt; prove the property "&lt;code&gt;leftpad(c, n, s)&lt;/code&gt; will take up either &lt;code&gt;n&lt;/code&gt; spaces on the screen or however many characters &lt;code&gt;s&lt;/code&gt; takes up (if more than &lt;code&gt;n&lt;/code&gt;)". They prove the weaker property "&lt;code&gt;len(leftpad(c, n, s)) == max(n, len(s))&lt;/code&gt;, for however you want to define &lt;code&gt;len(string)&lt;/code&gt;". The second is a rough proxy for the first that works in most cases, but if someone really needs the former property they are liable to experience a bug.&lt;/p&gt;
    &lt;p&gt;Why don't we prove the stronger property? Sometimes it's because the code is meant to be used one way and people want to use it another way. This can lead to accusations that the developer is "misusing the provably correct code" but this should more often be seen as the verification expert failing to educate devs on was actually "proven".&lt;/p&gt;
    &lt;p&gt;Sometimes it's because the property is too hard to prove. "Outputs are visually aligned" is a proof about Unicode inputs, and the &lt;em&gt;core&lt;/em&gt; Unicode specification is &lt;a href="https://www.unicode.org/versions/Unicode17.0.0/UnicodeStandard-17.0.pdf" rel="noopener noreferrer nofollow" target="_blank"&gt;1,243 pages long&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;Sometimes it's because the property we want is too hard to &lt;em&gt;express&lt;/em&gt;. How do you mathematically represent "people will perceive the output as being visually aligned"? Is it OS and font dependent? These two lines are exactly five characters but not visually aligned:&lt;/p&gt;
    &lt;blockquote&gt;&lt;p&gt;|||||&lt;/p&gt;&lt;p&gt;MMMMM&lt;/p&gt;&lt;/blockquote&gt;
    &lt;p&gt;Or maybe they are aligned for you! I don't know, lots of people read email in a monospace font. "We can't express the property" comes up a lot when dealing with human/business concepts as opposed to mathematical/computational ones.&lt;/p&gt;
    &lt;p&gt;Finally, there's just the possibility of a brain fart. All of the proofs in &lt;a href="https://research.google/blog/extra-extra-read-all-about-it-nearly-all-binary-searches-and-mergesorts-are-broken/" rel="noopener noreferrer nofollow" target="_blank"&gt;Nearly All Binary Searches and Mergesorts are Broken&lt;/a&gt; are like this. They (informally) proved the correctness of binary search with unbound integers, forgetting that many programming languages use &lt;em&gt;machine&lt;/em&gt; integers, where a large enough sum can overflow.&lt;/p&gt;
    &lt;h3&gt;3. The assumptions are wrong&lt;/h3&gt;
    &lt;p&gt;This is arguably the most important and most subtle source of bugs. Most properties we prove aren't "&lt;code&gt;X&lt;/code&gt; is always true". They are "&lt;em&gt;assuming&lt;/em&gt; &lt;code&gt;Y&lt;/code&gt; is true, &lt;code&gt;X&lt;/code&gt; is also true". Then if &lt;code&gt;Y&lt;/code&gt; is not true, the proof no longer guarantees &lt;code&gt;X&lt;/code&gt;. A good example of this is binary &lt;s&gt;sort&lt;/s&gt; &lt;em&gt;search&lt;/em&gt;, which only correctly finds elements &lt;em&gt;assuming&lt;/em&gt; the input list is sorted. If the list is not sorted, it will not work correctly.&lt;/p&gt;
    &lt;p&gt;Formal verification adds two more wrinkles. One: sometimes we need assumptions to make the property valid, but we can also add them to make the proof easier. So the code can be bug-free even if the assumptions used to verify it no longer hold! Even if a leftpad implements visual alignment for all Unicode glyphs, it will be a lot easier to &lt;em&gt;prove&lt;/em&gt; visual alignment for just ASCII strings and padding.&lt;/p&gt;
    &lt;p&gt;Two: we need make a lot of &lt;em&gt;environmental&lt;/em&gt; assumptions that are outside our control. Does the algorithm return output or use the stack? Need to assume that there's sufficient memory to store stuff. Does it use any variables? Need to assume nothing is concurrently modifying them. Does it use an external service? Need to assume the vendor doesn't change the API or response formats. You need to assume the compiler worked correctly, the hardware isn't faulty, and the OS doesn't mess with things, etc. Any of these could change well after the code is proven and deployed, meaning formal verification can't be a one-and-done thing.&lt;/p&gt;
    &lt;p&gt;You don't actually have to assume most of these, but each assumption drop makes the proof harder and the properties you can prove more restricted. Remember, the code might still be bug-free even if the environmental assumptions change, so there's a tradeoff in time spent proving vs doing other useful work.&lt;/p&gt;
    &lt;p&gt;Another common source of "assumptions" is when verified code depends on unverified code. The Rust compiler can prove that safe code doesn't have a memory bug &lt;em&gt;assuming&lt;/em&gt; unsafe code does not have one either, but depends on the human to confirm that assumption. &lt;a href="https://ucsd-progsys.github.io/liquidhaskell/" rel="noopener noreferrer nofollow" target="_blank"&gt;Liquid Haskell&lt;/a&gt; is verifiable but can also call regular Haskell libraries, which are unverified. We need to assume that code is correct (in the "conforms to spec") sense, and if it's not, our proof can be "correct" and still cause bugs.&lt;/p&gt;
    &lt;hr/&gt;&lt;p&gt;These boundaries are fuzzy. I wrote that the "binary search" bug happened because they proved the wrong property, but you can just as well argue that it was a broken assumption (that integers could not overflow). What really matters is having a clear understanding of what "this code is proven correct" actually &lt;em&gt;tells&lt;/em&gt; you. Where can you use it safely? When should you worry? How do you communicate all of this to your teammates?&lt;/p&gt;
    &lt;p&gt;Good lord it's already Friday&lt;/p&gt;</description>
                <pubDate>Fri, 10 Oct 2025 17:06:19 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/three-ways-formally-verified-code-can-go-wrong-in/</guid>
            </item>
            <item>
                <title>New Blog Post: " A Very Early History of Algebraic Data Types"</title>
                <link>https://buttondown.com/hillelwayne/archive/new-blog-post-a-very-early-history-of-algebraic/</link>
                <description>&lt;p&gt;Last week I said that this week's newsletter would be a brief history of algebraic data types.&lt;/p&gt;
    &lt;p&gt;I was wrong.&lt;/p&gt;
    &lt;p&gt;That history is now a &lt;a href="https://www.hillelwayne.com/post/algdt-history/" target="_blank"&gt;3500 word blog post&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;&lt;a href="https://www.patreon.com/posts/blog-notes-very-139696324?utm_medium=clipboard_copy&amp;amp;utm_source=copyLink&amp;amp;utm_campaign=postshare_creator&amp;amp;utm_content=join_link" target="_blank"&gt;Patreon blog notes here&lt;/a&gt;.&lt;/p&gt;
    &lt;hr/&gt;
    &lt;h3&gt;I'm speaking at &lt;a href="https://www.p99conf.io/" target="_blank"&gt;P99 Conf&lt;/a&gt;!&lt;/h3&gt;
    &lt;p&gt;My talk, "Designing Low-Latency Systems with TLA+", is happening 10/23 at 11:30 central time. It's an online conf and the talk's only 16 minutes, so come check it out!&lt;/p&gt;</description>
                <pubDate>Thu, 25 Sep 2025 16:50:58 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/new-blog-post-a-very-early-history-of-algebraic/</guid>
            </item>
            <item>
                <title>Many Hard Leetcode Problems are Easy Constraint Problems</title>
                <link>https://buttondown.com/hillelwayne/archive/many-hard-leetcode-problems-are-easy-constraint/</link>
                <description>&lt;p&gt;In my first interview out of college I was asked the change counter problem:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;Given a set of coin denominations, find the minimum number of coins required to make change for a given number. IE for USA coinage and 37 cents, the minimum number is four (quarter, dime, 2 pennies).&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;I implemented the simple greedy algorithm and immediately fell into the trap of the question: the greedy algorithm only works for "well-behaved" denominations. If the coin values were &lt;code&gt;[10, 9, 1]&lt;/code&gt;, then making 37 cents would take 10 coins in the greedy algorithm but only 4 coins optimally (&lt;code&gt;10+9+9+9&lt;/code&gt;). The "smart" answer is to use a dynamic programming algorithm, which I didn't know how to do. So I failed the interview.&lt;/p&gt;
    &lt;p&gt;But you only need dynamic programming if you're writing your own algorithm. It's really easy if you throw it into a constraint solver like &lt;a href="https://www.minizinc.org/" target="_blank"&gt;MiniZinc&lt;/a&gt; and call it a day. &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;int: total;
    array[int] of int: values = [10, 9, 1];
    array[index_set(values)] of var 0..: coins;
    
    constraint sum (c in index_set(coins)) (coins[c] * values[c]) == total;
    solve minimize sum(coins);
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;You can try this online &lt;a href="https://play.minizinc.dev/" target="_blank"&gt;here&lt;/a&gt;. It'll give you a prompt to put in &lt;code&gt;total&lt;/code&gt; and then give you successively-better solutions:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;coins = [0, 0, 37];
    ----------
    coins = [0, 1, 28];
    ----------
    coins = [0, 2, 19];
    ----------
    coins = [0, 3, 10];
    ----------
    coins = [0, 4, 1];
    ----------
    coins = [1, 3, 0];
    ----------
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;Lots of similar interview questions are this kind of mathematical optimization problem, where we have to find the maximum or minimum of a function corresponding to constraints. They're hard in programming languages because programming languages are too low-level. They are also exactly the problems that constraint solvers were designed to solve. Hard leetcode problems are easy constraint problems.&lt;sup id="fnref:leetcode"&gt;&lt;a class="footnote-ref" href="#fn:leetcode"&gt;1&lt;/a&gt;&lt;/sup&gt; Here I'm using MiniZinc, but you could just as easily use Z3 or OR-Tools or whatever your favorite generalized solver is.&lt;/p&gt;
    &lt;h3&gt;More examples&lt;/h3&gt;
    &lt;p&gt;This was a question in a different interview (which I thankfully passed):&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;Given a list of stock prices through the day, find maximum profit you can get by buying one stock and selling one stock later.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;It's easy to do in O(n^2) time, or if you are clever, you can do it in O(n). Or you could be not clever at all and just write it as a constraint problem:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;array[int] of int: prices = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8];
    var int: buy;
    var int: sell;
    var int: profit = prices[sell] - prices[buy];
    
    constraint sell &amp;gt; buy;
    constraint profit &amp;gt; 0;
    solve maximize profit;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;Reminder, link to trying it online &lt;a href="https://play.minizinc.dev/" target="_blank"&gt;here&lt;/a&gt;. While working at that job, one interview question we tested out was:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;Given a list, determine if three numbers in that list can be added or subtracted to give 0? &lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;This is a satisfaction problem, not a constraint problem: we don't need the "best answer", any answer will do. We eventually decided against it for being too tricky for the engineers we were targeting. But it's not tricky in a solver; &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;include "globals.mzn";
    array[int] of int: numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8];
    array[index_set(numbers)] of var {0, -1, 1}: choices;
    
    constraint sum(n in index_set(numbers)) (numbers[n] * choices[n]) = 0;
    constraint count(choices, -1) + count(choices, 1) = 3;
    solve satisfy;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;Okay, one last one, a problem I saw last year at &lt;a href="https://chicagopython.github.io/algosig/" target="_blank"&gt;Chipy AlgoSIG&lt;/a&gt;. Basically they pick some leetcode problems and we all do them. I failed to solve &lt;a href="https://leetcode.com/problems/largest-rectangle-in-histogram/description/" target="_blank"&gt;this one&lt;/a&gt;:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;Given an array of integers heights representing the histogram's bar height where the width of each bar is 1, return the area of the largest rectangle in the histogram.&lt;/p&gt;
    &lt;p&gt;&lt;img alt="example from leetcode link" class="newsletter-image" src="https://assets.buttondown.email/images/63337f78-7138-4b21-87a0-917c0c5b1706.jpg?w=960&amp;amp;fit=max"/&gt;&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;The "proper" solution is a tricky thing involving tracking lots of bookkeeping states, which you can completely bypass by expressing it as constraints:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;array[int] of int: numbers = [2,1,5,6,2,3];
    
    var 1..length(numbers): x; 
    var 1..length(numbers): dx;
    var 1..: y;
    
    constraint x + dx &amp;lt;= length(numbers);
    constraint forall (i in x..(x+dx)) (y &amp;lt;= numbers[i]);
    
    var int: area = (dx+1)*y;
    solve maximize area;
    
    output ["(\(x)-&amp;gt;\(x+dx))*\(y) = \(area)"]
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;There's even a way to &lt;a href="https://docs.minizinc.dev/en/2.9.3/visualisation.html" target="_blank"&gt;automatically visualize the solution&lt;/a&gt; (using &lt;code&gt;vis_geost_2d&lt;/code&gt;), but I didn't feel like figuring it out in time for the newsletter.&lt;/p&gt;
    &lt;h3&gt;Is this better?&lt;/h3&gt;
    &lt;p&gt;Now if I actually brought these questions to an interview the interviewee could ruin my day by asking "what's the runtime complexity?" Constraint solvers runtimes are unpredictable and almost always slower than an ideal bespoke algorithm because they are more expressive, in what I refer to as the &lt;a href="https://buttondown.com/hillelwayne/archive/the-capability-tractability-tradeoff/" target="_blank"&gt;capability/tractability tradeoff&lt;/a&gt;. But even so, they'll do way better than a &lt;em&gt;bad&lt;/em&gt; bespoke algorithm, and I'm not experienced enough in handwriting algorithms to consistently beat a solver.&lt;/p&gt;
    &lt;p&gt;The real advantage of solvers, though, is how well they handle new constraints. Take the stock picking problem above. I can write an O(n²) algorithm in a few minutes and the O(n) algorithm if you give me some time to think. Now change the problem to&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;Maximize the profit by buying and selling up to &lt;code&gt;max_sales&lt;/code&gt; stocks, but you can only buy or sell one stock at a given time and you can only hold up to &lt;code&gt;max_hold&lt;/code&gt; stocks at a time?&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;That's a way harder problem to write even an inefficient algorithm for! While the constraint problem is only a tiny bit more complicated:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;include "globals.mzn";
    int: max_sales = 3;
    int: max_hold = 2;
    array[int] of int: prices = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8];
    array [1..max_sales] of var int: buy;
    array [1..max_sales] of var int: sell;
    array [index_set(prices)] of var 0..max_hold: stocks_held;
    var int: profit = sum(s in 1..max_sales) (prices[sell[s]] - prices[buy[s]]);
    
    constraint forall (s in 1..max_sales) (sell[s] &amp;gt; buy[s]);
    constraint profit &amp;gt; 0;
    
    constraint forall(i in index_set(prices)) (stocks_held[i] = (count(s in 1..max_sales) (buy[s] &amp;lt;= i) - count(s in 1..max_sales) (sell[s] &amp;lt;= i)));
    constraint alldifferent(buy ++ sell);
    solve maximize profit;
    
    output ["buy at \(buy)\n", "sell at \(sell)\n", "for \(profit)"];
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;Most constraint solving examples online are puzzles, like &lt;a href="https://docs.minizinc.dev/en/stable/modelling2.html#ex-sudoku" target="_blank"&gt;Sudoku&lt;/a&gt; or "&lt;a href="https://docs.minizinc.dev/en/stable/modelling2.html#ex-smm" target="_blank"&gt;SEND + MORE = MONEY&lt;/a&gt;". Solving leetcode problems would be a more interesting demonstration. And you get more interesting opportunities to teach optimizations, like symmetry breaking.&lt;/p&gt;
    &lt;hr/&gt;
    &lt;h3&gt;Update for the Internet&lt;/h3&gt;
    &lt;p&gt;This was sent as a weekly newsletter, which is usually on topics like &lt;a href="https://buttondown.com/hillelwayne/archive/why-do-we-call-it-boilerplate-code" target="_blank"&gt;software history&lt;/a&gt;, &lt;a href="https://buttondown.com/hillelwayne/archive/the-seven-specification-ur-languages/" target="_blank"&gt;formal methods&lt;/a&gt;, &lt;a href="https://buttondown.com/hillelwayne/archive/i-formally-modeled-dreidel-for-no-good-reason/" target="_blank"&gt;unusual technologies&lt;/a&gt;, and the &lt;a href="https://buttondown.com/hillelwayne/archive/be-suspicious-of-success/" target="_blank"&gt;theory of software engineering&lt;/a&gt;. You can subscribe here: &lt;/p&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:leetcode"&gt;
    &lt;p&gt;Because my dad will email me if I don't explain this: "leetcode" is slang for "tricky algorithmic interview questions that have little-to-no relevance in the actual job you're interviewing for." It's from &lt;a href="https://leetcode.com/" target="_blank"&gt;leetcode.com&lt;/a&gt;. &lt;a class="footnote-backref" href="#fnref:leetcode" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Wed, 10 Sep 2025 13:00:00 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/many-hard-leetcode-problems-are-easy-constraint/</guid>
            </item>
            <item>
                <title>The Angels and Demons of Nondeterminism</title>
                <link>https://buttondown.com/hillelwayne/archive/the-angels-and-demons-of-nondeterminism/</link>
                <description>&lt;p&gt;Greetings everyone! You might have noticed that it's September and I don't have the next version of &lt;em&gt;Logic for Programmers&lt;/em&gt; ready. As penance, &lt;a href="https://leanpub.com/logic/c/september-2025-kuBCrhBnUzb7" target="_blank"&gt;here's ten free copies of the book&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;So a few months ago I wrote &lt;a href="https://buttondown.com/hillelwayne/archive/five-kinds-of-nondeterminism/" target="_blank"&gt;a newsletter&lt;/a&gt; about how we use nondeterminism in formal methods.  The overarching idea:&lt;/p&gt;
    &lt;ol&gt;
    &lt;li&gt;Nondeterminism is when multiple paths are possible from a starting state.&lt;/li&gt;
    &lt;li&gt;A system preserves a property if it holds on &lt;em&gt;all&lt;/em&gt; possible paths. If even one path violates the property, then we have a bug.&lt;/li&gt;
    &lt;/ol&gt;
    &lt;p&gt;An intuitive model of this is that for this is that when faced with a nondeterministic choice, the system always makes the &lt;em&gt;worst possible choice&lt;/em&gt;. This is sometimes called &lt;strong&gt;demonic nondeterminism&lt;/strong&gt; and is favored in formal methods because we are paranoid to a fault.&lt;/p&gt;
    &lt;p&gt;The opposite would be &lt;strong&gt;angelic nondeterminism&lt;/strong&gt;, where the system always makes the &lt;em&gt;best possible choice&lt;/em&gt;. A property then holds if &lt;em&gt;any&lt;/em&gt; possible path satisfies that property.&lt;sup id="fnref:duals"&gt;&lt;a class="footnote-ref" href="#fn:duals"&gt;1&lt;/a&gt;&lt;/sup&gt; This is not as common in FM, but it still has its uses! "Players can access the secret level" or "&lt;a href="https://www.hillelwayne.com/post/safety-and-liveness/#other-properties" target="_blank"&gt;We can always shut down the computer&lt;/a&gt;" are &lt;strong&gt;reachability&lt;/strong&gt; properties, that something is possible even if not actually done.&lt;/p&gt;
    &lt;p&gt;In broader computer science research, I'd say that angelic nondeterminism is more popular, due to its widespread use in complexity analysis and programming languages.&lt;/p&gt;
    &lt;h3&gt;Complexity Analysis&lt;/h3&gt;
    &lt;p&gt;P is the set of all "decision problems" (&lt;em&gt;basically&lt;/em&gt;, boolean functions) can be solved in polynomial time: there's an algorithm that's worst-case in &lt;code&gt;O(n)&lt;/code&gt;, &lt;code&gt;O(n²)&lt;/code&gt;, &lt;code&gt;O(n³)&lt;/code&gt;, etc.&lt;sup id="fnref:big-o"&gt;&lt;a class="footnote-ref" href="#fn:big-o"&gt;2&lt;/a&gt;&lt;/sup&gt;  NP is the set of all problems that can be solved in polynomial time by an algorithm with &lt;em&gt;angelic nondeterminism&lt;/em&gt;.&lt;sup id="fnref:TM"&gt;&lt;a class="footnote-ref" href="#fn:TM"&gt;3&lt;/a&gt;&lt;/sup&gt; For example, the question "does list &lt;code&gt;l&lt;/code&gt; contain &lt;code&gt;x&lt;/code&gt;" can be solved in O(1) time by a nondeterministic algorithm:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fun is_member(l: List[T], x: T): bool {
      if l == [] {return false};
    
      guess i in 0..&amp;lt;(len(l)-1);
      return l[i] == x;
    }
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;Say call &lt;code&gt;is_member([a, b, c, d], c)&lt;/code&gt;. The best possible choice would be to guess &lt;code&gt;i = 2&lt;/code&gt;, which would correctly return true. Now call &lt;code&gt;is_member([a, b], d)&lt;/code&gt;. No matter what we guess, the algorithm correctly returns false. and just return false. Ergo, O(1). NP stands for "Nondeterministic Polynomial". &lt;/p&gt;
    &lt;p&gt;(And I just now realized something pretty cool: you can say that P is the set of all problems solvable in polynomial time under &lt;em&gt;demonic nondeterminism&lt;/em&gt;, which is a nice parallel between the two classes.)&lt;/p&gt;
    &lt;p&gt;Computer scientists have proven that angelic nondeterminism doesn't give us any more "power": there are no problems solvable with AN that aren't also solvable deterministically. The big question is whether AN is more &lt;em&gt;efficient&lt;/em&gt;: it is widely believed, but not &lt;em&gt;proven&lt;/em&gt;, that there are problems in NP but not in P. Most famously, "Is there any variable assignment that makes this boolean formula true?" A polynomial AN algorithm is again easy:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fun SAT(f(x1, x2, …: bool): bool): bool {
       N = num_params(f)
       for i in 1..=num_params(f) {
         guess x_i in {true, false}
       }
    
       return f(x_1, x_2, …)
    }
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;The best deterministic algorithms we have to solve the same problem are worst-case exponential with the number of boolean parameters. This a real frustrating problem because real computers don't have angelic nondeterminism, so problems like SAT remain hard. We can solve most "well-behaved" instances of the problem &lt;a href="https://www.hillelwayne.com/post/np-hard/" target="_blank"&gt;in reasonable time&lt;/a&gt;, but the worst-case instances get intractable real fast.&lt;/p&gt;
    &lt;h3&gt;Means of Abstraction&lt;/h3&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;We can directly turn an AN algorithm into a (possibly much slower) deterministic algorithm, such as by &lt;a href="https://en.wikipedia.org/wiki/Backtracking" target="_blank"&gt;backtracking&lt;/a&gt;. This makes AN a pretty good abstraction over what an algorithm is doing. Does the regex &lt;code&gt;(a+b)\1+&lt;/code&gt; match "abaabaabaab"? Yes, if the regex engine nondeterministically guesses that it needs to start at the third letter and make the group &lt;code&gt;aab&lt;/code&gt;. How does my PL's regex implementation find that match? I dunno, backtracking or &lt;a href="https://swtch.com/~rsc/regexp/regexp1.html" target="_blank"&gt;NFA construction&lt;/a&gt; or something, I don't need to know the deterministic specifics in order to use the nondeterministic abstraction.&lt;/p&gt;
    &lt;p&gt;Neel Krishnaswami has &lt;a href="https://semantic-domain.blogspot.com/2013/07/what-declarative-languages-are.html" target="_blank"&gt;a great definition of 'declarative language'&lt;/a&gt;: "any language with a semantics has some nontrivial existential quantifiers in it". I'm not sure if this is &lt;em&gt;identical&lt;/em&gt; to saying "a language with an angelic nondeterministic abstraction", but they must be pretty close, and all of his examples match:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;SQL's selects and joins&lt;/li&gt;
    &lt;li&gt;Parsing DSLs&lt;/li&gt;
    &lt;li&gt;Logic programming's unification&lt;/li&gt;
    &lt;li&gt;Constraint solving&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;On top of that I'd add CSS selectors and &lt;a href="https://www.hillelwayne.com/post/picat/" target="_blank"&gt;planner's actions&lt;/a&gt;; all nondeterministic abstractions over a deterministic implementation. He also says that the things programmers hate most in declarative languages are features that "that expose the operational model": constraint solver search strategies, Prolog cuts, regex backreferences, etc. Which again matches my experiences with angelic nondeterminism: I dread features that force me to understand the deterministic implementation. But they're necessary, since P probably != NP and so we need to worry about operational optimizations.&lt;/p&gt;
    &lt;h3&gt;Eldritch Nondeterminism&lt;/h3&gt;
    &lt;p&gt;If you need to know the &lt;a href="https://en.wikipedia.org/wiki/PP_(complexity)" target="_blank"&gt;ratio of good/bad paths&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/%E2%99%AFP" target="_blank"&gt;the number of good paths&lt;/a&gt;, or probability, or anything more than "there is a good path" or "there is a bad path", you are beyond the reach of heaven or hell.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:duals"&gt;
    &lt;p&gt;Angelic and demonic nondeterminism are &lt;a href="https://buttondown.com/hillelwayne/archive/logical-duals-in-software-engineering/" target="_blank"&gt;duals&lt;/a&gt;: angelic returns "yes" if &lt;code&gt;some choice: correct&lt;/code&gt; and demonic returns "no" if &lt;code&gt;!all choice: correct&lt;/code&gt;, which is the same as &lt;code&gt;some choice: !correct&lt;/code&gt;. &lt;a class="footnote-backref" href="#fnref:duals" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:big-o"&gt;
    &lt;p&gt;Pet peeve about Big-O notation: &lt;code&gt;O(n²)&lt;/code&gt; is the &lt;em&gt;set&lt;/em&gt; of all algorithms that, for sufficiently large problem sizes, grow no faster that quadratically. "Bubblesort has &lt;code&gt;O(n²)&lt;/code&gt; complexity" &lt;em&gt;should&lt;/em&gt; be written &lt;code&gt;Bubblesort in O(n²)&lt;/code&gt;, &lt;em&gt;not&lt;/em&gt; &lt;code&gt;Bubblesort = O(n²)&lt;/code&gt;. &lt;a class="footnote-backref" href="#fnref:big-o" title="Jump back to footnote 2 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:TM"&gt;
    &lt;p&gt;To be precise, solvable in polynomial time by a &lt;em&gt;Nondeterministic Turing Machine&lt;/em&gt;, a very particular model of computation. We can broadly talk about P and NP without framing everything in terms of Turing machines, but some details of complexity classes (like the existence "weak NP-hardness") kinda need Turing machines to make sense. &lt;a class="footnote-backref" href="#fnref:TM" title="Jump back to footnote 3 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;</description>
                <pubDate>Thu, 04 Sep 2025 14:00:00 +0000</pubDate>
                <guid>https://buttondown.com/hillelwayne/archive/the-angels-and-demons-of-nondeterminism/</guid>
            </item>
        </channel>
    </rss>
    Raw text
    <?xml version="1.0" encoding="utf-8"?>
    <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Computer Things</title><link>https://buttondown.com/hillelwayne</link><description>&lt;!-- buttondown-editor-mode: fancy --&gt;&lt;p&gt;Hi, I'm Hillel. This is the newsletter version of &lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://www.hillelwayne.com"&gt;my website&lt;/a&gt;. I post all website updates here. I also post weekly content just for the newsletter, on topics like&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Formal Methods&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Software History and Culture&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Fringetech and exotic tooling&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The philosophy and theory of software engineering&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;You can see the archive of all public essays &lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://buttondown.email/hillelwayne/archive/"&gt;here&lt;/a&gt;.&lt;/p&gt;</description><atom:link href="https://buttondown.email/hillelwayne/rss" rel="self"/><language>en-us</language><lastBuildDate>Wed, 10 Jun 2026 12:22:04 +0000</lastBuildDate><item><title>Nontrailing separators do not spark joy</title><link>https://buttondown.com/hillelwayne/archive/nontrailing-separators-do-not-spark-joy/</link><description>
    &lt;p&gt;This is valid JSON:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;This is invalid JSON:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;The difference is the last comma. The &lt;a href="https://www.json.org/json-en.html" target="_blank"&gt;JSON grammar&lt;/a&gt; specifies that a comma can separate two members of an object but not postcede ("trail") a member. I think this was a design mistake. Say we want to add two new keys to the struct, one before the &lt;code&gt;"a"&lt;/code&gt; member and one after the &lt;code&gt;"c"&lt;/code&gt; member. Here's what it would look like if trailing commas were permitted:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{
    &lt;span class="gi"&gt;+   &amp;quot;x&amp;quot;: 0,&lt;/span&gt;
    &lt;span class="w"&gt; &lt;/span&gt;   &amp;quot;a&amp;quot;: 1,
    &lt;span class="w"&gt; &lt;/span&gt;   &amp;quot;b&amp;quot;: 2,
    &lt;span class="w"&gt; &lt;/span&gt;   &amp;quot;c&amp;quot;: 3,
    &lt;span class="gi"&gt;+   &amp;quot;y&amp;quot;: 4,&lt;/span&gt;
    }
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;It's the exact same text transformation regardless of where we add the key. In the current model, we instead have this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{
    &lt;span class="gi"&gt;+   &amp;quot;x&amp;quot;: 0,&lt;/span&gt;
    &lt;span class="w"&gt; &lt;/span&gt;   &amp;quot;a&amp;quot;: 1,
    &lt;span class="w"&gt; &lt;/span&gt;   &amp;quot;b&amp;quot;: 2,
    &lt;span class="gd"&gt;-   &amp;quot;c&amp;quot;: 3&lt;/span&gt;
    &lt;span class="gi"&gt;+   &amp;quot;c&amp;quot;: 3,&lt;/span&gt;
    &lt;span class="gi"&gt;+   &amp;quot;y&amp;quot;: 4&lt;/span&gt;
    }
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Those are different transformations! Similarly if you want to remove an element, you can't just delete the corresponding line&lt;sup id="fnref:objects"&gt;&lt;a class="footnote-ref" href="#fn:objects"&gt;1&lt;/a&gt;&lt;/sup&gt;, you have to delete the line &lt;em&gt;and then&lt;/em&gt; check that the last line doesn't have a trailing comma. Don't even get me started on all the special cases involved in swapping two lines.&lt;/p&gt;
    &lt;p&gt;JSON isn't the only language with this problem. Haskell writes record types like this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;-- from https://play.haskell.org/&lt;/span&gt;
    &lt;span class="kr"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Drone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Drone&lt;/span&gt;
    &lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xPos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;yPos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zPos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;This "partial bullet point" style of putting separators at the beginning of rows makes it easier to change the last row but harder to change the first one. &lt;/p&gt;
    &lt;p&gt;TLA+ has this problem too:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;\* both valid
    VARIABLES a, b, c
    vars == &amp;lt;&amp;lt;a, b, c&amp;gt;&amp;gt;
    
    \* both invalid
    VARIABLES a, b, c,
    vars == &amp;lt;&amp;lt;a, b, c,&amp;gt;&amp;gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;This one's annoying because 1) you're constantly adding new top-level variables while working on a spec and 2) the PlusCal DSL does &lt;em&gt;not&lt;/em&gt; have this problem:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;\* Totally fine!
    (*--algorithm foo {
    variables a; b; c;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;The worst offenders, IMO, are logic languages like Prolog. Not only don't you have trailing separators, you have a &lt;em&gt;special terminating symbol&lt;/em&gt;:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:-&lt;/span&gt;
        &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;% comma&lt;/span&gt;
        &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;% comma&lt;/span&gt;
        &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.&lt;/span&gt; &lt;span class="c1"&gt;% period!&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;I guess you can sort of think of it as funny-lookin' braces:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:-&lt;/span&gt;
        &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; 
    &lt;span class="p"&gt;.&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;But this is not standard syntax and people will look at you weird if you try it. And you still don't get trailing separators.&lt;/p&gt;
    &lt;h2&gt;Something better&lt;/h2&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;Some languages allow trailing separators:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// go&lt;/span&gt;
    &lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# python&lt;/span&gt;
    &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;But I think we can do one better than that. Python and Go commas can trail but not lead, meaning we can't go 100% bullet points:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# python again&lt;/span&gt;
    &lt;span class="n"&gt;invalid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Now I personally think that bullet points are the bee's knees and wish more languages allowed leading separators. TLA+ actually has leading conjunction and disjunction operations:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;// Not TLA+ but the same semantics
    || &amp;amp;&amp;amp; a == 1
       &amp;amp;&amp;amp; b == 2
    
    || &amp;amp;&amp;amp; a == 3
       &amp;amp;&amp;amp; b == 4
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;You can't trail these, though, no writing &lt;code&gt;(a &amp;amp;&amp;amp;)&lt;/code&gt;.&lt;/p&gt;
    &lt;p&gt;The most flexible I've seen is &lt;a href="https://alloytools.org/" target="_blank"&gt;Alloy&lt;/a&gt;, which allows both leading and trailing commas:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Alloy&lt;/span&gt;
    &lt;span class="kd"&gt;sig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Valid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    
    &lt;span class="kd"&gt;sig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AlsoValid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Alloy does go a little power-mad here, because it also allows empty separators.&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;sig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;StillValid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;,,,,,,,,,&lt;/span&gt;
    &lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;I've heard some people call this &lt;a href="https://www.reddit.com/r/ProgrammingLanguages/comments/1amsalm/does_your_language_support_trailing_commas/" target="_blank"&gt;"stuttering"&lt;/a&gt;. I can't figure out how to &lt;a href="https://www.hillelwayne.com/post/python-abc/" target="_blank"&gt;commit crimes with this&lt;/a&gt; but you never know.&lt;/p&gt;
    &lt;h2&gt;Devil's advocate&lt;/h2&gt;
    &lt;p&gt;One argument against trailing separators is that they make parsing ambiguous. Consider this Prolog: &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:-&lt;/span&gt;
        &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.&lt;/span&gt;
    
    &lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;c&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Here it's pretty clear that &lt;code&gt;foo&lt;/code&gt; and &lt;code&gt;bar&lt;/code&gt; are separate definitions. But if we replace the rule terminator with commas:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:-&lt;/span&gt;
        &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    
    &lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;c&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Now it &lt;em&gt;could&lt;/em&gt; be alternatively parsed that &lt;code&gt;bar(c)&lt;/code&gt; is part of the definition of &lt;code&gt;foo&lt;/code&gt;— &lt;code&gt;foo&lt;/code&gt; is only true when &lt;code&gt;bar(c)&lt;/code&gt; is also true. &lt;/p&gt;
    &lt;p&gt;As another example, &lt;a href="https://docs.ruby-lang.org/en/master/syntax/layout_rdoc.html" target="_blank"&gt;this is valid Ruby&lt;/a&gt;:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# prints 5&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;succ&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;succ&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;If we could "trail method calls", this is ambiguous:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;baz&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    
    &lt;span class="n"&gt;quux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Now it's not clear if &lt;code&gt;quux()&lt;/code&gt; is a top-level function or a method of &lt;code&gt;foo&lt;/code&gt;.&lt;/p&gt;
    &lt;p&gt;Both of those relate to control separators, not data separators. Python has an edge data case with trailing data separators. The language uses parenthesis both for expression grouping like &lt;code&gt;(2+3)&lt;/code&gt; and for tuple definition like &lt;code&gt;(2,3)&lt;/code&gt;. So how do you distinguish an expression evaluation from a single-element tuple? With a trailing comma!&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; x = (2+3)
    &amp;gt;&amp;gt;&amp;gt; type(x)
    &amp;lt;class &amp;#39;int&amp;#39;&amp;gt;
    &amp;gt;&amp;gt;&amp;gt; x = (2+3,)
    &amp;gt;&amp;gt;&amp;gt; type(x)
    &amp;lt;class &amp;#39;tuple&amp;#39;&amp;gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Okay that's all I got. New (and final) preview release of &lt;em&gt;Logic for Programmers&lt;/em&gt; next week.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:objects"&gt;
    &lt;p&gt;Obvs this doesn't work if your object values are themselves multiline arrays or objects, but those make the "no trailing comma" transformations &lt;em&gt;even more&lt;/em&gt; complicated.&amp;#160;&lt;a class="footnote-backref" href="#fnref:objects" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Wed, 10 Jun 2026 12:22:04 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/nontrailing-separators-do-not-spark-joy/</guid></item><item><title>Logic for Programmers extra credits</title><link>https://buttondown.com/hillelwayne/archive/logic-for-programmers-extra-credits/</link><description>
    &lt;p&gt;So I said there wasn’t a proper newsletter this week, since I’m in Budapest prepping for a conference. But I still got a thing for y’all.&lt;/p&gt;
    &lt;p&gt;There’s a lot of interesting topics I wanted to cover for &lt;em&gt;Logic for Programmers&lt;/em&gt;, but the book is dense enough as it is and many of these were too tangential or technical to fit in well. So I’ve been writing some supplements and uploading them &lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/logicforprogrammers/book-assets/tree/master/supplements"&gt;here&lt;/a&gt;. I’ve got four so far:&lt;/p&gt;
    &lt;ol&gt;&lt;li&gt;&lt;p&gt;How we compute the number of orderings of multiple concurrent processes&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;How first-order logic can quantify over “a set of functions”, what a “set of functions” looks like, and how functions can be defined in terms of sets (plus a bit on currying and type theory)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Barbara Liskov’s “history rule” in subtyping&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Total and partial orders on sets.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;
    &lt;p&gt;Now I’m going to caveat that these were written off the cuff and haven’t gone through the obsessive editing of the book itself, so they may be rough and there might be errors in them. Still, it’s like 2-3000 words of math content, so hopefully covers not having a proper newsletter this time. Seeya next week!&lt;/p&gt;
    </description><pubDate>Tue, 02 Jun 2026 14:48:48 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/logic-for-programmers-extra-credits/</guid></item><item><title>Knowing about things is cheaper than knowing things</title><link>https://buttondown.com/hillelwayne/archive/knowing-about-things-is-cheaper-than-knowing/</link><description>
    &lt;p&gt;&lt;em&gt;Short one this week because I'm way behind on book and conference prep.&lt;/em&gt;&lt;/p&gt;
    &lt;p&gt;Last week a LinkedIn Influencer wrote about how math has nothing to do with programming, so I spite-wrote a rejoinder about how math is necessary to program (just try to write software without knowing arithmetic!) and man I forgot how much spite can fuel writing. Maybe I should go back to Twitter (absolutely not).&lt;/p&gt;
    &lt;p&gt;But it got me thinking about the difference between "all programmers &lt;em&gt;can benefit&lt;/em&gt; from learning math" and "all programmers &lt;em&gt;need to&lt;/em&gt; learn math". I simultaneously believe three things:&lt;/p&gt;
    &lt;ol&gt;
    &lt;li&gt;There is some math, like arithmetic (incl. arithmetic of booleans, sets, functions, etc), that is useful to all programmers.&lt;/li&gt;
    &lt;li&gt;The remaining fields aren't useful to most programmers.&lt;/li&gt;
    &lt;li&gt;Every programmer works in a domain where there is at least one branch of math that would benefit them to learn.&lt;/li&gt;
    &lt;/ol&gt;
    &lt;p&gt;(2) means that if get a group of 100 software engineers and teach them something like algebra or calculus, you can't expect it to be applicable for more than 3 or 5. Whereas if you teach something like shell scripting or regular expressions it'd be useful to at least, like, 50. So no field of math has a good RoI for the average programmer. &lt;/p&gt;
    &lt;p&gt;(3) means that each of those 100 developers could, on their own, find a field of math that is useful to them. In order to do that, though, they need to roughly know what the fields are, what the big ideas are, and where they might be useful. It is more useful to teach them &lt;em&gt;about&lt;/em&gt; many fields than to teach them any one specific field in-depth. &lt;/p&gt;
    &lt;p&gt;I think that's generally true with most areas of knowledge! Getting basic exposure to something takes a lot less time and effort than learning it in-depth. If you're specifically trying to learn things that will be useful to your work&lt;sup id="fnref:topic"&gt;&lt;a class="footnote-ref" href="#fn:topic"&gt;1&lt;/a&gt;&lt;/sup&gt;, you only want to go in-depth on topics you know will be helpful. But you won't know the topic is helpful (or even that it exists) unless you know the very basics already. So it makes sense to get broad exposure to lots of topics and only later choose what to go deep on.&lt;/p&gt;
    &lt;h2&gt;How to do this&lt;/h2&gt;
    &lt;p&gt;Uhhhhhhhhhh&lt;/p&gt;
    &lt;p&gt;Honestly the thing that's worked best for me personally is to read stuff meant to give a broad exposure without lots of details. Non-honors undergraduate textbooks are really good for that. It helps to approach it like "learning for the fun of learning" and not "learning to accumulate potentially useful stuff for later".&lt;/p&gt;
    &lt;p&gt;Conference videos can be good, too, though I'm not a video person. I imagine popular-science stuff could be useful but 1) I grew up at a time when "popsci" meant "lying about science to make it sound more impressive" (though I know Quanta's really good now) and 2) I tend not to mesh with the popsci writing style.&lt;/p&gt;
    &lt;hr /&gt;
    &lt;h2&gt;No Proper Newsletter Next Week&lt;/h2&gt;
    &lt;p&gt;I'll be at &lt;a href="https://craft-conf.com/2026" target="_blank"&gt;Craft Conference Budapest&lt;/a&gt;. That said, there I'll be uploading some &lt;em&gt;Logic for Programmers&lt;/em&gt;-related material that's about the length of a newsletter, so keep your eyes peeled!&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:topic"&gt;
    &lt;p&gt;Which to be clear is not the only reason to learn something!&amp;#160;&lt;a class="footnote-backref" href="#fnref:topic" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Thu, 28 May 2026 16:03:01 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/knowing-about-things-is-cheaper-than-knowing/</guid></item><item><title>Assumptions weaken properties</title><link>https://buttondown.com/hillelwayne/archive/assumptions-weaken-properties/</link><description>
    &lt;p&gt;In &lt;a href="https://buttondown.com/hillelwayne/archive/some-tests-are-stronger-than-others/" target="_blank"&gt;some tests are stronger than others&lt;/a&gt;, I defined &lt;code&gt;STRONG =&amp;gt; WEAK&lt;/code&gt; to mean "any system passing test STRONG is also guaranteed to pass WEAK". This uses the logical implication operator, defined as &lt;code&gt;P =&amp;gt; Q = !P || (P &amp;amp;&amp;amp; Q)&lt;/code&gt;.&lt;/p&gt;
    &lt;p&gt;Implication may be the most overworked operator in logic. Among other things, it's also used in formal specification, where &lt;code&gt;Spec =&amp;gt; Prop&lt;/code&gt; means "any system satisfying &lt;code&gt;Spec&lt;/code&gt; has property &lt;code&gt;Prop&lt;/code&gt;" and &lt;code&gt;ASSUME =&amp;gt; Spec&lt;/code&gt; means "The assumption &lt;code&gt;ASSUME&lt;/code&gt; must hold in order for the system to satisfy &lt;code&gt;Spec&lt;/code&gt;."&lt;/p&gt;
    &lt;p&gt;Now let's mush these all together and do some math. To start, "the system has property &lt;code&gt;Prop&lt;/code&gt;" is the same as "the system passes the test that checks &lt;code&gt;Prop&lt;/code&gt;", so test strength is also property strength. Now let "&lt;code&gt;ASSUME =&amp;gt; Prop&lt;/code&gt;" mean "the system passes &lt;code&gt;Prop&lt;/code&gt; assuming &lt;code&gt;ASSUME&lt;/code&gt; is true." In classic logic, if &lt;code&gt;P&lt;/code&gt; is true, then obviously &lt;code&gt;!Q || P&lt;/code&gt; is true. Further, that is equivalent (just draw the truth table!) to &lt;code&gt;!Q || (P &amp;amp;&amp;amp; Q)&lt;/code&gt;. So for &lt;em&gt;any&lt;/em&gt; propositions &lt;code&gt;P&lt;/code&gt; and &lt;code&gt;Q&lt;/code&gt;, &lt;code&gt;P =&amp;gt; (Q =&amp;gt; P)&lt;/code&gt;.&lt;/p&gt;
    &lt;p&gt;In other words, &lt;code&gt;Prop =&amp;gt; (ASSUME =&amp;gt; Prop)&lt;/code&gt;. In other other words, "the system passes &lt;code&gt;Prop&lt;/code&gt;" is a stronger property than "the system passes &lt;code&gt;Prop&lt;/code&gt; whenever our assumptions hold."&lt;/p&gt;
    &lt;p&gt;In other other other words, any assumption added makes a property weaker.&lt;/p&gt;
    &lt;p&gt;This makes intuitive sense to me. A JSON parser that's only been verified with ASCII strings has the property "input only uses ASCII &amp;amp;&amp;amp; is valid json =&amp;gt; correctly parsed". A better JSON parser that works for all Unicode will have the property "is valid json =&amp;gt; correctly parsed", which has fewer assumptions, meaning it's guaranteed to work in a strict superset of cases. &lt;/p&gt;
    &lt;p&gt;It also matches the intuition that "more assumptions means more likely to go wrong". We have a bug whenever &lt;code&gt;Prop&lt;/code&gt; is false. The only way for &lt;code&gt;Spec =&amp;gt; Prop&lt;/code&gt; to be true and &lt;code&gt;Prop&lt;/code&gt; be false is if &lt;code&gt;Spec&lt;/code&gt; is false, eg our system doesn't satisfy the specification we intended to implement. On the other hand, &lt;code&gt;Spec =&amp;gt; (ASSUME =&amp;gt; Prop) &amp;amp;&amp;amp; !Prop&lt;/code&gt; is true whenever &lt;code&gt;Spec&lt;/code&gt; &lt;em&gt;and/or&lt;/em&gt; &lt;code&gt;ASSUME&lt;/code&gt; is false, meaning a correctly-implemented system could still show a bug if a runtime assumption is false. &lt;/p&gt;
    &lt;p&gt;...Looking back the last two paragraphs have a lot of conceptual leaps. Does that all make sense to you? It all feels natural to me but that might just be my familiarity with the topic talking. &lt;/p&gt;
    &lt;p&gt;Regardless, a couple more notes on assumptions:&lt;/p&gt;
    &lt;h2&gt;Why we have assumptions&lt;/h2&gt;
    &lt;p&gt;Why not just build our systems to satisfy &lt;code&gt;ASSUME =&amp;gt; Prop&lt;/code&gt; when we can "just" build it to satisfy &lt;code&gt;Prop&lt;/code&gt;? At least three reasons.&lt;/p&gt;
    &lt;p&gt;&lt;strong&gt;First&lt;/strong&gt;, sometimes &lt;code&gt;Prop&lt;/code&gt; is simply impossible to satisfy and we need to add assumptions to make this property. We do this a lot in formal methods with &lt;a href="https://www.hillelwayne.com/post/fairness/" target="_blank"&gt;fairness&lt;/a&gt;. The property "&lt;code&gt;mergesort&lt;/code&gt; always returns a sorted list" is unsatisfiable because we can dropkick the computer before it returns. Instead, we have to verify a weaker property like "&lt;code&gt;mergesort&lt;/code&gt; always returns =&amp;gt; it returns a sorted list" or "&lt;code&gt;mergesort&lt;/code&gt; always makes progress =&amp;gt; it will eventually return a sorted list."&lt;/p&gt;
    &lt;p&gt;Another example is Rust. Rust &lt;em&gt;does not&lt;/em&gt; guarantee the property "the program is memory safe". It guarantees the weaker property "all &lt;code&gt;unsafe&lt;/code&gt; blocks are memory safe =&amp;gt; the program is memory safe". Making the language satisfy the stronger property would rule out too many use cases of Rust. Note you also get memory safety if you don't use &lt;code&gt;unsafe&lt;/code&gt;, but that still satisfies the assumption, as all &lt;em&gt;zero&lt;/em&gt; blocks are safe!&lt;/p&gt;
    &lt;p&gt;&lt;strong&gt;Second&lt;/strong&gt;, sometimes the strong property is satisfiable, but it's simply not worth the extra cost. Like it's possible to make our software resistant against cosmic rays, but if your code isn't running in space, why bother? Just say "No cosmic bit flips =&amp;gt; things work". Or if your plugin works Neovim &lt;code&gt;0.12&lt;/code&gt; but not 0.11, you could put in the effort to make it run on older versions, or you could tell everybody that they need to upgrade to use your plugin. "Neovim version is at least 0.12 =&amp;gt; the plugin works".&lt;/p&gt;
    &lt;p&gt;&lt;strong&gt;Third&lt;/strong&gt;, sometimes the strong property is satisfiable in the system but not easily &lt;em&gt;verifiable&lt;/em&gt;. Say your algorithm makes a lot of API calls and you don't want to hit rate limits while testing. If you &lt;a href="https://en.wikipedia.org/wiki/Mock_object" target="_blank"&gt;mock out the API&lt;/a&gt; you're testing the weaker property "The mock is accurate to the API =&amp;gt; the algorithm is correct".&lt;/p&gt;
    &lt;h2&gt;Assumptions are a second level of system effect&lt;/h2&gt;
    &lt;p&gt;I notice that almost all of the examples in the last two sections are exoprogram factors:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;The JSON parser assumptions are about user input&lt;/li&gt;
    &lt;li&gt;Fairness assumptions are about the OS/hardware/operating environment&lt;/li&gt;
    &lt;li&gt;Unsafe assumptions are about things the Rust compiler can't verify&lt;/li&gt;
    &lt;li&gt;Cosmic ray assumptions depend on the physical location of the hardware&lt;/li&gt;
    &lt;li&gt;The plugin assumptions are about the Neovim team's release schedule and social compatibility contract&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;The edge case is replacing a third party call with a mock. The assumption is intraprogram because the program could just hit the API during testing. We still have the assumption because of an exoprogram constraint. Maybe this is why mocks are considered an antipattern in Agile.&lt;/p&gt;
    &lt;p&gt;One consequence of this is that checking whether assumptions hold is a different problem from verifying that your code works given the assumptions. Like to make sure "all unsafe blocks are safe" can't use the rust compiler, you need a second tool like &lt;a href="https://github.com/rust-lang/miri" target="_blank"&gt;Miri&lt;/a&gt;. I wonder if checking assumptions is, in practice, generally more difficult than checking everything else.&lt;/p&gt;
    </description><pubDate>Wed, 20 May 2026 15:13:16 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/assumptions-weaken-properties/</guid></item><item><title>Points are a weird and inconsistent unit of measure</title><link>https://buttondown.com/hillelwayne/archive/points-are-a-weird-and-inconsistent-unit-of/</link><description>
    &lt;p&gt;&lt;img alt="Some pictures of point sizes from &amp;quot;The Practice of Typography&amp;quot;" class="newsletter-image" src="https://assets.buttondown.email/images/1e994c5a-8b40-4c4e-8898-81ebfbfa8bcb.png?w=960&amp;amp;fit=max" /&gt;&lt;/p&gt;
    &lt;p&gt;I'm in the middle of redoing the &lt;a href="https://logicforprogrammers.com" target="_blank"&gt;&lt;em&gt;Logic for Programmers&lt;/em&gt;&lt;/a&gt; diagrams and this has surfaced a really annoying problem. The book is formatted in LaTeX using a pseudo-grid of 10.8pt × 7.2pt. The diagrams are done in Inkscape using a 10.8pt × 7.2pt.&lt;/p&gt;
    &lt;p&gt;Last week I found out that these are not the same points. &lt;/p&gt;
    &lt;p&gt;Latex defines a point as 1/72.27 inches (0.3515 millimeters). Inkscape instead uses 1/72 inches (0.3528 mm). It's only a difference of 0.4% but it still floors me that two widespread digital technologies would be different!&lt;/p&gt;
    &lt;p&gt;So, uh, &lt;em&gt;what happened?&lt;/em&gt;&lt;/p&gt;
    &lt;p&gt;A few hours of reading articles later, this is what I found, caveat that I didn't spend all that much time researching this and this is only initial impressions. &lt;/p&gt;
    &lt;h2&gt;what even is a point&lt;/h2&gt;
    &lt;p&gt;A &lt;a href="https://en.wikipedia.org/wiki/Point_(typography)" target="_blank"&gt;point&lt;/a&gt; is a typographic measure, coming from 1517, that is supposedly the smallest interesting size for a printer. This was notably not a standardized measure- different companies in times used different point sizes depending on their equipment. Over time it was standardized, but each country picked a different standard: the German and Japanese point is 0.250 mm, the French point is allegedly 0.399 mm, etc.&lt;/p&gt;
    &lt;p&gt;But early computer history is super Americentric so that's what technology uses. In the US, they standardized the point around the end of the 19th century. To what? I dunno. &lt;a href="https://archive.org/details/practiceoftypogr00devirich/page/150/mode/1up" target="_blank"&gt;This source&lt;/a&gt; from 1900 gives the length of a point as 35/996 cm (72.281 points/in) and then says there are exactly 867.4699 "ems per foot" (72.289 points/in). &lt;a href="https://books.google.com/books?id=yUkHAwAAQBAJ&amp;amp;pg=PA57#v=onepage&amp;amp;q&amp;amp;f=false" target="_blank"&gt;This source&lt;/a&gt; from 1916 says the standard pica (12 points) is 0.16604 inches and that there are 72.272 "pica ems per foot". Which conveniently enough gives us 72.272 points/in (a pica being 12 points). Then on the very next page they say no a pica is actually 0.16604&lt;strong&gt;4&lt;/strong&gt; inches and a point is exactly 0.013837 inches. I found other sources with other definitions, too.&lt;/p&gt;
    &lt;p&gt;I'm going to chalk the differences up to a mix of "the definitions of 'meter' and 'foot' changed over time" and "these are less than a micron apart so who gives a shit". I do, I give a shit. Regardless, the &lt;a href="https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular570.pdf" target="_blank"&gt;official NIST definition&lt;/a&gt; settled on a point being 0.013837 inches, so you'd get 72.27 points/inch. &lt;/p&gt;
    &lt;p&gt;Wrong! You absolute moron. You get 72.270001 points/inch. This annoyed Donald Knuth enough that he rejiggered the ratio in &lt;a href="https://latex.us/systems/knuth/dist/tex/texbook.tex" target="_blank"&gt;TeX&lt;/a&gt; (pg 58):&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;TEX’s “pt” has been made slightly larger than the official printer’s point, which was defined to equal exactly .013837 in by the American Typefounders Association in 1886 [cf. National Bureau of Standards Circular 570 (1956)]. In fact, one classical point is exactly .99999999 pt, so the “error” is essentially one part in 10^8. … The new definition 72.27 pt = 1 in is not only better for calculation, it is also easier to remember.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;(To explain his motivation a little: American printers measure things in inches, and define the point in terms of inches. TeX measures things in points&lt;sup id="fnref:sp"&gt;&lt;a class="footnote-ref" href="#fn:sp"&gt;1&lt;/a&gt;&lt;/sup&gt;, and define the inch in terms of points.)&lt;/p&gt;
    &lt;p&gt;For the record, NIST seems to think "72 points/inch" is a good enough approximation. TeX calls this the &lt;code&gt;bp&lt;/code&gt; (big point).&lt;/p&gt;
    &lt;h2&gt;AKA the Inkscape value&lt;/h2&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;Now what about Inkscape? As far as I can tell, this comes from the Postscript format's definition of the &lt;a href="https://archive.org/details/postscriptlangua0000unse_c7i6/page/60/mode/2up?q=72" target="_blank"&gt;unit size&lt;/a&gt;:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;The length of a unit along the
    x axis and along the y axis is 1/72 of an inch. We call this coordinate system &lt;em&gt;default user space&lt;/em&gt;.&lt;/p&gt;
    &lt;p&gt;These features of default user space are chosen for their mathematical simplicity and convenience. The location and orientation of the axes follow common mathematical practice and cause all points within the current page to have positive x and y coordinate values. The unit size, 1/72 of an inch, is very close to the size of a printer's point (1/72.27 inch), which is a standard measuring unit used in the printing industry.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;Later on &lt;a href="https://archive.org/details/postscriptlangua0000unse_c7i6/page/86/mode/2up?q=72" target="_blank"&gt;page 86&lt;/a&gt; they straight up call 1/72 inch a "point". &lt;a href="https://www.adobe.com/jp/print/postscript/pdfs/PLRM.pdf#page=197" target="_blank"&gt;Later editions&lt;/a&gt; would clarify it's not actually a point and that points are stupid anyway:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;Note: The default unit size (1/72 inch) is approximately the same as a “point,” a unit
    widely used in the printing industry. It is not exactly the same as a point, however;
    there is no universal definition of a point.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;Apple shipped PostScript in their &lt;a href="https://en.wikipedia.org/wiki/LaserWriter" target="_blank"&gt;LaserWriter&lt;/a&gt; laser printer, other companies followed suite, making PostScript the de facto printing language and 72 points/in the standard digital measure. The W3 consortium used the same measure in &lt;a href="https://www.w3.org/TR/css3-values/#absolute-lengths" target="_blank"&gt;CSS&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Guides/Content_type#length" target="_blank"&gt;SVG&lt;/a&gt;&lt;sup id="fnref:svg"&gt;&lt;a class="footnote-ref" href="#fn:svg"&gt;2&lt;/a&gt;&lt;/sup&gt;, Inkscape is an SVG editor, and that's all she wrote.&lt;/p&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;h3&gt;Epilog: Frink&lt;/h3&gt;
    &lt;p&gt;Whenever I need to mess with units of measure, I turn to  &lt;a href="https://frinklang.org/" target="_blank"&gt;Frink&lt;/a&gt;. Frink is a Turing-complete language with &lt;em&gt;really&lt;/em&gt; good unit of measure support:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;oldinch := surveyfoot / 12 // pre 1959 inch 
    35 cm / (996 pts) -&amp;gt; oldinch / pts
    0.013834839357429718876
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;The author does also does incredibly thorough research on the history of units measurements. Here's what the &lt;a href="https://frinklang.org/frinkdata/units.txt" target="_blank"&gt;Frink data file&lt;/a&gt; says about points:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;point :=          0.013837ee0 inch    // exact, NIST Handbook 44, Appendix 3
    printerspoint :=       point
    
    texscaledpoint :=      1/65536 point    // The TeX typesetting system uses
    texsp :=               texscaledpoint   // this for all computations.
    computerpoint :=       1/72 inch        // The American point was rounded 
    computerpica :=        12 computerpoint // to an even 1/72 inch by computer
    postscriptpoint :=     computerpoint    // people at some point. 
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Well, now we know what that "some point" is!&lt;/p&gt;
    &lt;p&gt;Now we also know that the definitions of &lt;code&gt;texscaledpoint&lt;/code&gt; is (&lt;em&gt;gasp&lt;/em&gt;) wrong. &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;realtexpoint := 1/72.27 inch
    realtexsp := 1/65536 realtexpoint
    (realtexsp - texsp)
    5.36285100578e-17 m (length)
    (realtexsp - texsp) / realtexsp
    1.0000000000005691827e-8
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;The Frink definition is off by about 50 attometers, or approximately 3% of the width of a proton.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:sp"&gt;
    &lt;p&gt;Actually "scaled points", where 2^16 sp = 1 pt.&amp;#160;&lt;a class="footnote-backref" href="#fnref:sp" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:svg"&gt;
    &lt;p&gt;Which is why it's super weird that the SVG editor &lt;a href="https://www.drawio.com/" target="_blank"&gt;draw.io&lt;/a&gt; uses a point size of 1/&lt;strong&gt;100&lt;/strong&gt; inch.&amp;#160;&lt;a class="footnote-backref" href="#fnref:svg" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Wed, 13 May 2026 15:56:37 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/points-are-a-weird-and-inconsistent-unit-of/</guid></item><item><title>New Logic for Programmers (and the future of this newsletter)</title><link>https://buttondown.com/hillelwayne/archive/new-logic-for-programmers-and-the-future-of-this/</link><description>
    &lt;p&gt;So first the immediate news: I just released version 0.14 of &lt;a href="https://logicforprogrammers.com" target="_blank"&gt;Logic for Programmers&lt;/a&gt;! This release is pretty similar to 0.13. There are a few rewrites but the vast majority of the changes are layout, copyediting, and technical editing. Full notes &lt;a href="https://github.com/logicforprogrammers/book-assets/blob/master/CHANGELOG.md" target="_blank"&gt;here&lt;/a&gt;. &lt;/p&gt;
    &lt;p&gt;In related news, I've started doing test prints of the book:&lt;/p&gt;
    &lt;p&gt;&lt;img alt="Two test prints of Logic for Programmers" class="newsletter-image" src="https://assets.buttondown.email/images/958bdc79-fa06-4bce-b0a8-6e5ecfe8f2e0.png?w=960&amp;amp;fit=max" /&gt;&lt;/p&gt;
    &lt;p&gt;There's not a whole lot left to be done. I've gotta fix up some diagrams, do more formatting and proofreading, incorporate some fixes raised by readers, and make a website and back cover. After that, the book should be ready for 1.0. I'm aiming to have print copies purchasable by the end of June!&lt;/p&gt;
    &lt;hr /&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;Now the big news: starting August, I'll be a full-time employee of &lt;a href="https://antithesis.com/" target="_blank"&gt;Antithesis&lt;/a&gt;, a generative testing platform. Officially my role is "developer educator", and I'll be tasked with making "property-based testing, fuzzing, fault injection, &lt;a href="https://hegel.dev/" target="_blank"&gt;Hegel&lt;/a&gt;, &lt;a href="https://github.com/antithesishq/bombadil" target="_blank"&gt;Bombadil&lt;/a&gt;, and the Antithesis platform understandable to everyday engineers". So the same kind of work I do now, except with far more support and a matching 401(k). &lt;/p&gt;
    &lt;p&gt;I already have three pages of topic ideas you have no idea how excited I am about this &lt;/p&gt;
    &lt;p&gt;So how is this going to affect the newsletter? First, I want to make clear that this is &lt;em&gt;not&lt;/em&gt; going to become an Antithesis newsletter. My Antithesis-related work is going to be on their official platforms. I do think one of the best ways to make a topic "understandable" is to write foundational material that's useful to all engineers, whether they're invested in the topic or not. I might share links to things I make along those lines, but they'll be just that, links.&lt;/p&gt;
    &lt;p&gt;At the same time, the content of &lt;em&gt;this&lt;/em&gt; newsletter will change a little. Property testing and fuzzing aren't the same as formal methods, but a lot of the foundations overlap, especially in how we think about properties and correctness. I don't know for sure yet, but I suspect that I'll start biasing this newsletter away from Antithesis related topics. So there will probably be less theoretic things like &lt;a href="https://buttondown.com/hillelwayne/archive/what-does-undecidable-mean-anyway/" target="_blank"&gt;what does undecidable mean&lt;/a&gt; and &lt;a href="https://buttondown.com/hillelwayne/archive/some-tests-are-stronger-than-others/" target="_blank"&gt;Some tests are stronger than others&lt;/a&gt; and more history and software weirdness things like &lt;a href="https://buttondown.com/hillelwayne/archive/why-do-we-call-it-boilerplate-code/" target="_blank"&gt;Why do we call it "boilerplate code"&lt;/a&gt; and &lt;a href="https://buttondown.com/hillelwayne/archive/finding-hard-24-puzzles-with-planner-programming/" target="_blank"&gt;esoteric programming paradigms&lt;/a&gt;. &lt;/p&gt;
    &lt;p&gt;The other change is going to be frequency. For the past six years I've kept updates to (mostly) a weekly schedule. For the past six years I've also been totally self-employed. I don't know how much time I'll have with a full time job! Once I'm settled in I'd like to keep writing newsletters, but it might slow down from weekly to biweekly or monthly. We'll feel it out as we go. &lt;/p&gt;
    &lt;hr /&gt;
    &lt;p&gt;Anyway, this has been a pretty software-light newsletter, so let's close out with a fun thing. &lt;code&gt;f(x) = x+2&lt;/code&gt; is a  &lt;strong&gt;monotonically increasing function&lt;/strong&gt;: increasing &lt;code&gt;x&lt;/code&gt; increases &lt;code&gt;f(x)&lt;/code&gt; and decreasing &lt;code&gt;f(x)&lt;/code&gt; decreases &lt;code&gt;f(x)&lt;/code&gt;.  Similarly, &lt;code&gt;f(x) = -x+1&lt;/code&gt; is monotonically decreasing, and &lt;code&gt;f(x) = x^2&lt;/code&gt; is neither.&lt;/p&gt;
    &lt;p&gt;While working on the book I realized that the &lt;code&gt;all&lt;/code&gt; quantifier is monotonically false with respect to adding elements and true with respect to removing them. Let &lt;code&gt;A(set) = all x in set: P(x)&lt;/code&gt;. Then if &lt;code&gt;A(S)&lt;/code&gt; is false, &lt;code&gt;A(S | {e})&lt;/code&gt; is also false, and if &lt;code&gt;A(S)&lt;/code&gt; is true, &lt;code&gt;A(S - {e})&lt;/code&gt; is also true. &lt;code&gt;some&lt;/code&gt; goes the other way: if it's true, it's true if you add an element, and if it's false it's still false if you take one away.&lt;/p&gt;
    &lt;p&gt;An interesting consequence is that &lt;code&gt;all&lt;/code&gt; &lt;em&gt;must&lt;/em&gt; be true for the empty set, because if it was false it would be false for all values! This is another justification why, in Python, &lt;a href="https://buttondown.com/hillelwayne/archive/why-all-is-true-prod-is-1-etc/" target="_blank"&gt;&lt;code&gt;all([]) == True&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;Similarly, in temporal logic: &lt;code&gt;always A&lt;/code&gt; is monotonically false with respect to system behavior and &lt;code&gt;eventually A&lt;/code&gt; is monotonically true. I realized this when messing with this &lt;a href="https://quickstrom.github.io/ltl-visualizer/" target="_blank"&gt;LTL visualizer&lt;/a&gt; my friend (and soon to be coworker!) Oskar Wickström. I think this is pretty neat!&lt;/p&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    </description><pubDate>Wed, 06 May 2026 17:03:46 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/new-logic-for-programmers-and-the-future-of-this/</guid></item><item><title>Illegal vs Unwanted States</title><link>https://buttondown.com/hillelwayne/archive/illegal-vs-unwanted-states/</link><description>
    &lt;p&gt;An &lt;strong&gt;illegal state&lt;/strong&gt; is a state we never want our system to be in. An &lt;strong&gt;unwanted state&lt;/strong&gt; is a state we don't want to stay in. Many states that we wish were illegal are actually unwanted.&lt;/p&gt;
    &lt;p&gt;Considering a calendaring software which stores calendar events as &lt;code&gt;{user: {events: [event]}}&lt;/code&gt;, where each event has a start and end time. This allows one person to attend two events at the same time. We might consider this illegal and replace the data type with &lt;code&gt;{user: {time: optional event}}&lt;/code&gt; which makes this impossible. However, a scheduling conflict isn't illegal, only unwanted! It is possible for a person to sign up for two overlapping events. Maybe they're supposed to choose one event, maybe they'll decide which event to go to later, maybe one of the events doesn't actually represent an in-person meeting. &lt;/p&gt;
    &lt;p&gt;In that case it's acceptable, if not ideal, to remain in the unwanted state. Other unwanted states lead to invalid states if not exited quickly. An airline flight is in an unwanted state if there are more passengers booked to fly than seats available. This must be resolved before passengers actually board, as "more passengers physically on the plane than seats available" is an illegal state.&lt;/p&gt;
    &lt;p&gt;In some cases, an unwanted state does not lead to illegal states, but permanently remaining in the unwanted state is still a problem. We might guarantee that a network partition does not ever lead to inconsistent data. Even though the unwanted state of a network partition cannot cause the illegal state of corrupt data, we still have a big problem if we don't ever fix the partition.&lt;/p&gt;
    &lt;h2&gt;Why systems must represent unwanted states&lt;/h2&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;Generally, unwanted states can happen if we don't have complete control over our system's behavior. We can't guarantee our network is perfectly reliable, our servers are always up, our users all put in correct data. If our system gets input from the external world then the world can push us into an unwanted state. We need to be able to detect these states so we can resolve them.&lt;/p&gt;
    &lt;p&gt;Even when we have complete control over the system, we still may want to be able to temporarily dip into unwanted states. If they wanted, airlines could make overbooking flights impossible. But airlines want to be able to overbook because they expect some number of no-shows. We need to allow them their unwanted state and then put systems in place to prevent it evolving into an illegal state.&lt;/p&gt;
    &lt;p&gt;Sometimes we need unwanted states to make certain workflows possible. It may be the case that 95% never, ever want to accepted conflicting events, and preventing that unwanted state would make their lives better. But without that unwanted state, intentionally double booking yourself is impossible. Some people might need that! In these cases we want to make it very clear to users that they're entering an unwanted state, and then let them decide for themselves how to leave it. &lt;/p&gt;
    &lt;p&gt;(The airlines and users want the unwanted state! It's us engineers who consider it "unwanted" because it can lead to problems down the road.) &lt;/p&gt;
    &lt;h2&gt;Formal models of unwanted states&lt;/h2&gt;
    &lt;p&gt;Illegal states correspond to violated invariants. Conventionally speaking we write this as &lt;code&gt;[]!Illegal&lt;/code&gt;: it should be true at all times that &lt;code&gt;Illegal&lt;/code&gt; is not true. If a single state ever satisfies &lt;code&gt;Illegal&lt;/code&gt; then our system has a bug. &lt;/p&gt;
    &lt;p&gt;Unwanted states are trickier, since they can be both &lt;a href="https://www.hillelwayne.com/post/safety-and-liveness/" target="_blank"&gt;safety and liveness properties&lt;/a&gt;. If modeling an airline system, we don't necessarily want to check properties of overbooking, we only need to check that no overflights happen.&lt;sup id="fnref:overflight"&gt;&lt;a class="footnote-ref" href="#fn:overflight"&gt;1&lt;/a&gt;&lt;/sup&gt; We may discover in the process of verifying that that overbooking is the main cause of overflights and/or that if overbooking is not resolved, then we will eventually have an overflight. Further, if "we never overbook" guarantees "we never overflight", we'd say that "no overbooks" is a &lt;a href="https://buttondown.com/hillelwayne/archive/some-tests-are-stronger-than-others/" target="_blank"&gt;stronger&lt;/a&gt; property than "no overflights". &lt;/p&gt;
    &lt;p&gt;"We never remain in a network partition" is &lt;a href="https://buttondown.com/hillelwayne/archive/formalizing-stability-and-resilience-properties/" target="_blank"&gt;formalized&lt;/a&gt; as &lt;code&gt;[]&amp;lt;&amp;gt;!Partition&lt;/code&gt;: we can enter a partition and stay partitioned a long time but must eventually heal the partition. The &lt;a href="https://p-org.github.io/P/" target="_blank"&gt;P specification language&lt;/a&gt; calls these &lt;a href="https://p-org.github.io/P/manual/monitors/#liveness-specification" target="_blank"&gt;hot states&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;(PS: if all goes well, there should be a new &lt;a href="https://logicforprogrammers.com" target="_blank"&gt;Logic for Programmers&lt;/a&gt; release next week!)&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:overflight"&gt;
    &lt;p&gt;Not a real term.&amp;#160;&lt;a class="footnote-backref" href="#fnref:overflight" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Tue, 28 Apr 2026 15:14:09 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/illegal-vs-unwanted-states/</guid></item><item><title>People get confused when language implementations break language guarantees</title><link>https://buttondown.com/hillelwayne/archive/people-get-confused-when-language-implementations/</link><description>
    &lt;p&gt;Take the following Python program:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# x = 1, y = 2&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;It'll print &lt;code&gt;[0, 0]&lt;/code&gt;. If we swapped the two assignments, it'd instead print &lt;code&gt;[0, 1]&lt;/code&gt;. Each assignment happens in a separate temporal step. Pretty much all imperative languages behave this way.&lt;/p&gt;
    &lt;p&gt;Now take the following TLA+ snippet:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;\* x = 1, y = 2
    /\ x&amp;#39; = 0
    /\ y&amp;#39; = x
    /\ PrintT(&amp;lt;&amp;lt;x&amp;#39;, y&amp;#39;&amp;gt;&amp;gt;)
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;This'll print &lt;code&gt;&amp;lt;&amp;lt;0, 1&amp;gt;&amp;gt;&lt;/code&gt;. Unlike in imperative languages, TLA+ separates the notion of update and temporal step. We read &lt;code&gt;x' = 0&lt;/code&gt; as "in the &lt;em&gt;next&lt;/em&gt; state, &lt;code&gt;x&lt;/code&gt; will be 0, but it still has the same value in the current state". So in every state &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;x'&lt;/code&gt; are essentially separate variables. As a consequence of this, the order of statements don't matter in TLA+ semantics and swapping the two assignments doesn't change the printed output. The language is really clever like that! This means, among other things that there's basically no intrastep race conditions. One function can update a variable without affecting how any other function uses it.&lt;/p&gt;
    &lt;p&gt;Okay so now beginners inevitably run into a problem with this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;\* x = 1, y = 2
    /\ x&amp;#39; = y&amp;#39;
    /\ y&amp;#39; = x
    /\ PrintT(&amp;lt;&amp;lt;x&amp;#39;, y&amp;#39;&amp;gt;&amp;gt;)
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;This'll crash because &lt;code&gt;y'&lt;/code&gt; isn't defined yet. But if we swap the two assignments this works fine and prints &lt;code&gt;&amp;lt;&amp;lt;1, 1&amp;gt;&amp;gt;&lt;/code&gt;. So clearly that whole thing about nonordering is a pack of bunk. &lt;/p&gt;
    &lt;p&gt;Well, not exactly. TLA+ semantics still guarantee nonordering. The problem is that verifiers don't perfectly implement the TLA+ semantics. &lt;code&gt;y' &amp;gt; 0&lt;/code&gt; is a totally reasonable "assignment" but there's an infinite number of possible next states! So the main model checker (TLC) instead requires that &lt;code&gt;y' = some_value&lt;/code&gt; comes before any other use of &lt;code&gt;y'&lt;/code&gt;, which means ordering now matters.&lt;/p&gt;
    &lt;p&gt;The bigger problem is with &lt;code&gt;PrintT&lt;/code&gt;. The TLA+ semantics guarantee nonordering because the semantics don't allow for side effects. The model checker adds in effectful operators like &lt;code&gt;PrintT&lt;/code&gt; and &lt;code&gt;Assert&lt;/code&gt; and &lt;code&gt;IOExec&lt;/code&gt;. This can cause a problem with guard statements. Theoretically speaking these two script blocks are equivalent:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/\ x = 0 \* guard statement
    /\ P()
    /\ x&amp;#39; = x + 1
    
    /\ x&amp;#39; = x + 1
    /\ P()
    /\ x = 0 \* guard statement
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;When &lt;code&gt;x = 1&lt;/code&gt;, these don't lead to a new state due to the guard clause. But the model checker evaluates each line one at a time, meaning in block 2 it will run &lt;code&gt;x' = x + 1&lt;/code&gt; and &lt;code&gt;P()&lt;/code&gt; before getting to &lt;code&gt;x = 0&lt;/code&gt; and discarding the state. If &lt;code&gt;P&lt;/code&gt; is a proper TLA+ operator, this isn't a problem, but if it's &lt;code&gt;PrintT&lt;/code&gt; or &lt;code&gt;Assert&lt;/code&gt; it will take its effect first, leading to weird ghost prints that don't correspond with any next states.&lt;/p&gt;
    &lt;p&gt;This difference between "what TLA+ semantics guarantees" and "the specific ways TLC can break those guarantees" is a huge source of confusion for people! On top of that many of these operators, like &lt;code&gt;IOExec&lt;/code&gt; and &lt;code&gt;TLCSet&lt;/code&gt;, are meant as escape hatches. So if you need them you're already doing something pretty weird, and that makes it even more confusing. &lt;/p&gt;
    &lt;p&gt;And on top of that is that there's no syntactic or visual distinguishing between a guarantee-breaking TLC operator and a regular safe TLA+ operator. In compiled languages you got pragmas and preprocessors, which let the compiler do things the language can't. But those usually have a visually distinct syntax, so you know from looking that here be dragons. &lt;/p&gt;
    &lt;p&gt;I'm reminded of Neel Krishnaswami's incredible post &lt;a href="https://semantic-domain.blogspot.com/2013/07/what-declarative-languages-are.html" target="_blank"&gt;What Declarative Languages Are&lt;/a&gt;&lt;sup id="fnref:laurie"&gt;&lt;a class="footnote-ref" href="#fn:laurie"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;This also lets us make the prediction that the least-loved features of any declarative language will be the ones that expose the operational model, and break the declarative semantics. So we can predict that people will dislike (a) backreferences in regular expressions, (b) ordered choice in grammars, (c) row IDs in query languages, (d) cut in Prolog, (e) constraint priorities in constraint languages. &lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;Prolog cuts have a visually distinct syntax, but a predicate that uses a cut isn't visually distinct from a &lt;a href="https://www.metalevel.at/prolog/purity" target="_blank"&gt;logically pure&lt;/a&gt; predicate. But that's also a little less tricky than what we got in TLA+, since the cut is still part of the language semantics.&lt;/p&gt;
    &lt;p&gt;(Then again, different Prolog dialects have different ways of &lt;a href="https://eu.swi-prolog.org/pldoc/man?section=printmsg" target="_blank"&gt;printing strings&lt;/a&gt;, which add side effects to Prolog, and they're not visually distinct from other predicates. So the same problem!)&lt;/p&gt;
    &lt;p&gt;I don't actually have any fix for this. I just find it a fascinating example of a &lt;a href="https://en.wikipedia.org/wiki/Leaky_abstraction" target="_blank"&gt;leaky abstraction&lt;/a&gt;. Maybe we could write a code highlighter that highlights all functions that transitively use a "weird" function or something.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:laurie"&gt;
    &lt;p&gt;&lt;a href="https://tratt.net/laurie/blog/2013/relative_and_absolute_levels.html" target="_blank"&gt;Obligatory response post&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:laurie" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Tue, 21 Apr 2026 17:40:17 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/people-get-confused-when-language-implementations/</guid></item><item><title>A sufficiently comprehensive spec is not (necessarily) code</title><link>https://buttondown.com/hillelwayne/archive/a-sufficiently-comprehensive-spec-is-not/</link><description>
    &lt;p&gt;Sorry for missing last week! Was sick and then busy.&lt;/p&gt;
    &lt;p&gt;This week I want to cover a pet peeve of mine, best seen in this comic:&lt;/p&gt;
    &lt;p&gt;&lt;a href="https://www.commitstrip.com/en/2016/08/25/a-very-comprehensive-and-precise-spec/?" target="_blank"&gt;&lt;img alt="A comic about a business person thinking detailed specs will replace coders, and then the coder calls the spec &amp;quot;code&amp;quot;" class="newsletter-image" src="https://www.commitstrip.com/wp-content/uploads/2016/08/Strip-Les-specs-cest-du-code-650-finalenglish.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;A "comprehensive and precise spec" is not necessarily code. A specification corresponds to a &lt;em&gt;set&lt;/em&gt; of possible implementations, and code is a single implementation in that set. As long as the set has more than one element, there is a separation between the spec and the code. &lt;/p&gt;
    &lt;p&gt;Consider a business person (bp) who asks:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;I want a tool to convert miles to kilometers.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;Is this a comprehensive spec? Maybe, you can give it to Claude Code and tell it to make all design decisions and it will give you a program that converts miles to km. At the same time, there is a huge amount of details left out of this. What language? What's the UX? Should it be a command line script or a mobile app or an enterprise SaaS? For this reason, if we gave Claude's output to the bp, they'll probably be unsatisfied. The set of possible implementations includes the programs they want, but also lots of programs they don't want.&lt;/p&gt;
    &lt;p&gt;So they now they say:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;It should be a textbox on a website.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;Okay, this rules out a lot more stuff, but there's still a lot to decide. React or vanillajs or htmx? Should the output be a separate textbox or a popup? Should we use a conversion of &lt;code&gt;1.6&lt;/code&gt;, &lt;code&gt;1.61&lt;/code&gt;, or &lt;code&gt;1.609&lt;/code&gt;? So you could argue that this is still not a "comprehensive and precise spec". But what if the bp is happy with whatever Claude makes? Then their spec was sufficiently comprehensive and precise, since they got a program that solved their problem!&lt;/p&gt;
    &lt;p&gt;Now the comic above makes the more specific claim that a spec "comprehensive and precise enough &lt;em&gt;to generate a program&lt;/em&gt;" is code. That wasn't even true before LLMs. &lt;a href="https://en.wikipedia.org/wiki/Program_synthesis" target="_blank"&gt;Program synthesis&lt;/a&gt;, the automatic generation of conformant programs from specifications, is an active field of research! Last I checked in 2019 they were only generating local functions from type specifications; I don't know how things have changed with LLMs. But still, it shows that code and comprehensive specs are distinct things. &lt;/p&gt;
    &lt;h3&gt;Specs are abstractions&lt;/h3&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;What I'm getting at here is that a specification is an &lt;strong&gt;abstraction&lt;/strong&gt; of code.&lt;sup id="fnref:abstraction"&gt;&lt;a class="footnote-ref" href="#fn:abstraction"&gt;1&lt;/a&gt;&lt;/sup&gt; For every spec, there is a set of possible programs that satisfy that spec. The more comprehensive and precise the spec, the fewer programs in this set. If &lt;code&gt;spec1&lt;/code&gt; corresponds to a superset of &lt;code&gt;spec2&lt;/code&gt;, we further say that &lt;code&gt;spec2&lt;/code&gt; &lt;strong&gt;refines&lt;/strong&gt; &lt;code&gt;spec1&lt;/code&gt;. A specification is &lt;strong&gt;sufficient&lt;/strong&gt; if it does not need to be refined further: no matter what implementation (&lt;em&gt;within reason&lt;/em&gt;&lt;sup id="fnref:reason"&gt;&lt;a class="footnote-ref" href="#fn:reason"&gt;2&lt;/a&gt;&lt;/sup&gt;) is provided, the specifier would be satisfied. A spec does not need to be fully comprehensive to be sufficient.&lt;/p&gt;
    &lt;h3&gt;Programmers are still needed to write specs&lt;/h3&gt;
    &lt;p&gt;The comic makes a further claim: "a sufficiently detailed spec is code" is a reason why programmers won't be out of a job, even with we could automatically generate code from specs. And this is still true.&lt;/p&gt;
    &lt;p&gt;It is often the case that we express the abstraction spec via a formal language. Normally this makes me think of TLA+ or UML or even &lt;a href="https://syque.com/quality_tools/tools/Tools104.htm" target="_blank"&gt;Planguage&lt;/a&gt;, but the most common example of this would be test suites. &lt;a href="https://buttondown.com/hillelwayne/archive/what-is-a-specification" target="_blank"&gt;Tests are specifications, too&lt;/a&gt;! And as a rule, it seems impossible to get nonprogrammers to successfully encode things in formal languages. Cucumber was a failed attempt to make business people write formal specs.&lt;/p&gt;
    &lt;p&gt;But does this make a comprehensive spec "code"? I'd argue no.  It's possible to encode a specification in a programming language (again, test suites), but it is just that, an encoding. The spec still corresponds to a set of possible implementation programs, and the spec is still useful even if we don't encode it. Keeping "code" and "spec" distinct concepts is useful.&lt;/p&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:abstraction"&gt;
    &lt;p&gt;&lt;a href="https://www.pathsensitive.com/2022/03/abstraction-not-what-you-think-it-is.html" target="_blank"&gt;obligatory link&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:abstraction" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:reason"&gt;
    &lt;p&gt;As in the implementation makes a good faith attempt to make a reasonable implementation. IE "this converts miles to kilometers and also mines crypto" is not a good faith interpretation.&amp;#160;&lt;a class="footnote-backref" href="#fnref:reason" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Wed, 15 Apr 2026 16:18:02 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/a-sufficiently-comprehensive-spec-is-not/</guid></item><item><title>April Cools Post: New York vs Chicago Pizza</title><link>https://buttondown.com/hillelwayne/archive/april-cools-post-new-york-vs-chicago-pizza/</link><description>
    &lt;p&gt;Happy April Cools! My not-tech post this year is &lt;a href="https://www.hillelwayne.com/post/pizza/" target="_blank"&gt;Chicago vs New York Pizza is the Wrong Argument&lt;/a&gt;, which is mostly an excuse for me to talk about Chicago food. See &lt;a href="https://www.aprilcools.club/" target="_blank"&gt;here&lt;/a&gt; for all of the other April Cools submissions. As of this email we have sixteen posts; there's still time to submit something!&lt;sup id="fnref:praveen"&gt;&lt;a class="footnote-ref" href="#fn:praveen"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
    &lt;p&gt;This one came out of a pizza argument with &lt;a href="https://www.bellotti.tech/about" target="_blank"&gt;Marianne Bellotti&lt;/a&gt;, a true New Yorker who doesn't think deep dish is real pizza. I wanted to find a new angle on the old argument and this is what I came up with. Interestingly, there's arguably no real New York analog to deep dish (Khatchapuri is kinda similar but doesn't have the same cultural relevance). If I had to pick something to compare deep dish to, it'd be &lt;a href="https://en.wikipedia.org/wiki/Toasted_ravioli" target="_blank"&gt;toasted ravioli&lt;/a&gt; (despite that being a appetizer and not an entree).&lt;/p&gt;
    &lt;p&gt;Also, I didn't really touch on it in the article, but one of the annoying things about pizza debates is that the most of the Chicago pizza chains &lt;em&gt;don't serve deep dish&lt;/em&gt;. Instead they do "stuffed pizzas". Deep dish is crust, cheese, tomato, while stuffed pizza is crust, cheese, another layer of crust, tomato. It's heavy and more casserole like and they always have way too much cheese. Don't get me wrong, it's still decent, but I think it's a worse kind of pizza overall.&lt;/p&gt;
    &lt;p&gt;Anyway, the real point of the post is that Chicago's a damn good sausage city. Even the Home Depots here have hot dogs.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:praveen"&gt;
    &lt;p&gt;Praveen, if you're reading this, you didn't include a link to your post or any contact details with your submission. Email me and we'll get it on the site&amp;#160;&lt;a class="footnote-backref" href="#fnref:praveen" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Wed, 01 Apr 2026 17:53:29 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/april-cools-post-new-york-vs-chicago-pizza/</guid></item><item><title>Choose Boring Technology and Innovative Practices</title><link>https://buttondown.com/hillelwayne/archive/choose-boring-technology-and-innovative-practices/</link><description>
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;The famous article &lt;a href="https://mcfunley.com/choose-boring-technology" target="_blank"&gt;Choose Boring Technology&lt;/a&gt; lists two problems with using innovative technology:&lt;/p&gt;
    &lt;ol&gt;
    &lt;li&gt;There are too many "unknown unknowns" in a new technology, whereas in boring technology the pitfalls are already well-known.&lt;/li&gt;
    &lt;li&gt;Shiny tech has a maintenance burden that persist long after everybody has gotten bored with  it. &lt;/li&gt;
    &lt;/ol&gt;
    &lt;p&gt;Both of these tie back to the idea that the main cost of technology is maintenance. Even if something is easy to build with, it might not be as easy to keep running. We cannot "abandon" mission-critical technology. Say my team builds a new service on &lt;a href="https://julialang.org/" target="_blank"&gt;Julia&lt;/a&gt;, and 2 years later decides it was the wrong choice. We're stuck with either the (expensive) process of migrating all our &lt;del&gt;data to Postgres&lt;/del&gt; code to Java or the (expensive) process of keeping it running anyway. Either way, the company needs to spend resources keeping engineers trained on the tech instead of other useful things, like how to mine crypto in their heads. &lt;/p&gt;
    &lt;p&gt;Tech is slow to change. Not as slow to change as, say, a bridge, but still pretty slow.&lt;/p&gt;
    &lt;p&gt;Now say at the same time as Julia, we also decided to start practicing &lt;a href="https://medium.com/@kentbeck_7670/test-commit-revert-870bbd756864" target="_blank"&gt;test &amp;amp;&amp;amp; commit || revert&lt;/a&gt; (TCR). After two years, we get sick of that, too. To deal with this, we can simply... not do TCR anymore. There is no "legacy practice" we need to support, no maintenance burden to dropping a process. It is much easier to adopt and abandon practices than it is to adopt and abandon technology.&lt;/p&gt;
    &lt;p&gt;This means while we should be conservative in the software we use, we can be more freely innovative in how we use it. If we get &lt;a href="https://mcfunley.com/choose-boring-technology#embrace-boredom" target="_blank"&gt;three innovation tokens&lt;/a&gt; for technology, we get like six or seven for practices. And we can trade in our practices to get those tokens back. &lt;/p&gt;
    &lt;p&gt;(The flip side of this is that social processes are less "stable" than technology and take more work to keep running. This is why "engineering controls" are considered &lt;a href="https://hillelwayne.com/post/hoc/" target="_blank"&gt;more effective as reducing accidents&lt;/a&gt; than administrative controls.) &lt;/p&gt;
    &lt;h3&gt;Choose Boring Material and Innovative Tools&lt;/h3&gt;
    &lt;p&gt;Pushing this argument further, we can divide technology into two categories: "material" and "tools".&lt;sup id="fnref:tools"&gt;&lt;a class="footnote-ref" href="#fn:tools"&gt;1&lt;/a&gt;&lt;/sup&gt; Material is anything that needs to run to support the business:  our code, our service architecture, our data &lt;em&gt;and&lt;/em&gt; database engine, etc. The tools are what we use to make material, but that the material doesn't depend on. Editors, personal bash scripts, etc. The categories are fuzzy, but it boils down to "how bad is it for the project to lose this?" &lt;/p&gt;
    &lt;p&gt;In turn, because tools are easier to replace than material, we can afford to be more innovative with it. I suspect we see this in practice, too, that people replace ephemera faster than they replace their databases.&lt;/p&gt;
    &lt;p&gt;(This is a short one because I severely overestimated how much I could write about this.)&lt;/p&gt;
    &lt;hr /&gt;
    &lt;h2&gt;&lt;a href="https://www.aprilcools.club/" target="_blank"&gt;April Cools&lt;/a&gt;&lt;/h2&gt;
    &lt;p&gt;It's in a week! You can submit your April Cools in the &lt;a href="https://docs.google.com/forms/d/e/1FAIpQLSc6Yt_ZCA_S6EOt-VVtla-uObnzPlSg9x_VOLgiNGN_-AY-kQ/viewform" target="_blank"&gt;google form&lt;/a&gt; or, if you want to be all cool and techie, as a &lt;a href="https://github.com/april-cools/april-cools.github.io/blob/main/_data/projects.yml" target="_blank"&gt;github PR&lt;/a&gt;.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:tools"&gt;
    &lt;p&gt;This is different from how we call all software "tools".&amp;#160;&lt;a class="footnote-backref" href="#fnref:tools" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Tue, 24 Mar 2026 14:38:06 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/choose-boring-technology-and-innovative-practices/</guid></item><item><title>LLMs are bad at vibing specifications</title><link>https://buttondown.com/hillelwayne/archive/llms-are-bad-at-vibing-specifications/</link><description>
    &lt;h3&gt;No newsletter next week&lt;/h3&gt;
    &lt;p&gt;I'll be speaking at &lt;a href="https://qconlondon.com/" target="_blank"&gt;InfoQ London&lt;/a&gt;. But see below for a book giveaway!&lt;/p&gt;
    &lt;hr /&gt;
    &lt;h1&gt;LLMs are bad at vibing specifications&lt;/h1&gt;
    &lt;p&gt;About a year ago I wrote &lt;a href="https://buttondown.com/hillelwayne/archive/ai-is-a-gamechanger-for-tla-users/" target="_blank"&gt;AI is a gamechanger for TLA+ users&lt;/a&gt;, which argued that AI are a "specification force multiplier". That was written from the perspective an TLA+ expert using these tools. A full &lt;a href="https://github.com/search?q=path%3A*.tla+NOT+is%3Afork+claude&amp;amp;type=code" target="_blank"&gt;4% of Github TLA+ specs&lt;/a&gt; now have the word "Claude" somewhere in them. This is interesting to me, because it suggests there was always an interest in formal methods, people just lacked the skills to do it.  &lt;/p&gt;
    &lt;p&gt;It's also interesting because it gives me a sense of what happens when beginners use AI to write formal specs. It's not good.&lt;/p&gt;
    &lt;p&gt;As a case study, we'll use &lt;a href="https://github.com/myProjectsRavi/sentinel-protocol/tree/main/docs/formal/specs" target="_blank"&gt;this project&lt;/a&gt;, which is kind of enough to have vibed out TLA+ and Alloy specs.&lt;/p&gt;
    &lt;h3&gt;Looking at a project&lt;/h3&gt;
    &lt;p&gt;&lt;a href="https://github.com/myProjectsRavi/sentinel-protocol/blob/main/docs/formal/specs/threat-intel-mesh.als" target="_blank"&gt;Starting with the Alloy spec&lt;/a&gt;. Here it is in its entirety:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;module ThreatIntelMesh
    
    sig Node {}
    
    one sig LocalNode extends Node {}
    
    sig Snapshot {
      owner: one Node,
      signed: one Bool,
      signatures: set Signature
    }
    
    sig Signature {}
    
    sig Policy {
      allowUnsignedImport: one Bool
    }
    
    pred canImport[p: Policy, s: Snapshot] {
      (p.allowUnsignedImport = True) or (s.signed = True)
    }
    
    assert UnsignedImportMustBeDenied {
      all p: Policy, s: Snapshot |
        p.allowUnsignedImport = False and s.signed = False implies not canImport[p, s]
    }
    
    assert SignedImportMayBeAccepted {
      all p: Policy, s: Snapshot |
        s.signed = True implies canImport[p, s]
    }
    
    check UnsignedImportMustBeDenied for 5
    check SignedImportMayBeAccepted for 5
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;Couple of things to note here: first of all, this doesn't actually compile. It's using the &lt;a href="https://alloy.readthedocs.io/en/latest/modules/boolean.html" target="_blank"&gt;Boolean&lt;/a&gt; standard module so needs &lt;code&gt;open util/boolean&lt;/code&gt; to function. Second, Boolean is the wrong approach here; you're supposed to use subtyping. &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sig Snapshot {
    &lt;span class="w"&gt; &lt;/span&gt; owner: one Node,
    &lt;span class="gd"&gt;- signed: one Bool,&lt;/span&gt;
    &lt;span class="w"&gt; &lt;/span&gt; signatures: set Signature
    }
    
    &lt;span class="gi"&gt;+ sig SignedSnapshot in Snapshot {}&lt;/span&gt;
    
    
    pred canImport[p: Policy, s: Snapshot] {
    &lt;span class="gd"&gt;- s.signed = True&lt;/span&gt;
    &lt;span class="gi"&gt;+ s in SignedSnapshot&lt;/span&gt;
    }
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;So we know the person did not actually run these specs. This is &lt;em&gt;somewhat&lt;/em&gt; less of a problem in TLA+, which has an official MCP server that lets the agent run model checking. Even so, I regularly see specs that I'm pretty sure won't model check, with things like using &lt;code&gt;Reals&lt;/code&gt; or assuming &lt;code&gt;NULL&lt;/code&gt; is a built-in and not a user-defined constant.&lt;/p&gt;
    &lt;p&gt;The bigger problem with the spec is that &lt;code&gt;UnsignedImportMustBeDenied&lt;/code&gt; and &lt;code&gt;SignedImportMayBeAccepted&lt;/code&gt; &lt;em&gt;don't actually do anything&lt;/em&gt;. &lt;code&gt;canImport&lt;/code&gt; is defined as &lt;code&gt;P || Q&lt;/code&gt;. &lt;code&gt;UnsignedImportMustBeDenied&lt;/code&gt; checks that &lt;code&gt;!P &amp;amp;&amp;amp; !Q =&amp;gt; !canImport&lt;/code&gt;. &lt;code&gt;SignedImportMayBeAccepted&lt;/code&gt; checks that &lt;code&gt;P =&amp;gt; canImport&lt;/code&gt;. These are tautologically true! If they do anything at all, it is only checking that &lt;code&gt;canImport&lt;/code&gt; was defined correctly. &lt;/p&gt;
    &lt;p&gt;You see the same thing in the &lt;a href="https://github.com/myProjectsRavi/sentinel-protocol/blob/main/docs/formal/specs/serialization-firewall.tla" target="_blank"&gt;TLA+ specs&lt;/a&gt;, too:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;GadgetPayload ==
      /\ gadgetDetected&amp;#39; = TRUE
      /\ depth&amp;#39; \in 0..(MaxDepth + 5)
      /\ UNCHANGED allowlistedFormat
      /\ decision&amp;#39; = &amp;quot;block&amp;quot;
    
    NoExploitAllowed == gadgetDetected =&amp;gt; decision = &amp;quot;block&amp;quot;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;The AI is only writing "obvious properties", which fail for reasons like "we missed a guard clause" or "we forgot to update a variable". It does not seem to be good at writing "subtle" properties that fail due to concurrency, nondeterminism, or bad behavior separated by several steps. Obvious properties are useful for orienting yourself and ensuring the system behaves like you expect, but the actual value in using formal methods comes from the subtle properties. &lt;/p&gt;
    &lt;p&gt;(This ties into &lt;a href="https://buttondown.com/hillelwayne/archive/some-tests-are-stronger-than-others/" target="_blank"&gt;Strong and Weak Properties&lt;/a&gt;. LLM properties are weak, intended properties need to be strong.)&lt;/p&gt;
    &lt;p&gt;This is a problem I see in almost every FM spec written by AI. LLMs aren't doing one of the core features of a spec. Articles like &lt;a href="https://martin.kleppmann.com/2025/12/08/ai-formal-verification.html" target="_blank"&gt;Prediction: AI will make formal verification go mainstream&lt;/a&gt; and &lt;a href="https://leodemoura.github.io/blog/2026/02/28/when-ai-writes-the-worlds-software.html" target="_blank"&gt;When AI Writes the World's Software, Who Verifies It?&lt;/a&gt; argue that LLMs will make formal methods go mainstream, but being easily able to write specifications doesn't help with correctness if the specs don't actually verify anything.&lt;/p&gt;
    &lt;h3&gt;Is this a user error?&lt;/h3&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;I first got interested in LLMs and TLA+ from &lt;a href="https://zfhuang99.github.io/github%20copilot/formal%20verification/tla+/2025/05/24/ai-revolution-in-distributed-systems.html" target="_blank"&gt;The Coming AI Revolution in Distributed Systems&lt;/a&gt;. The author of that later &lt;a href="https://github.com/zfhuang99/lamport-agent/blob/main/spec/CRAQ/CRAQ.tla" target="_blank"&gt;vibecoded a spec&lt;/a&gt; with a considerably more complex property:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;NoStaleStrictRead ==
      \A i \in 1..Len(eventLog) :
        LET ev == eventLog[i] IN
          ev.type = &amp;quot;read&amp;quot; =&amp;gt;
            LET c == ev.chunk IN
            LET v == ev.version IN
            /\ \A j \in 1..i :
                 LET evC == eventLog[j] IN
                   evC.type = &amp;quot;commit&amp;quot; /\ evC.chunk = c =&amp;gt; evC.version &amp;lt;= v
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;This is a lot more complicated than the &lt;code&gt;(P =&amp;gt; Q &amp;amp;&amp;amp; P) =&amp;gt; Q&lt;/code&gt; properties I've seen! It could be because &lt;a href="https://github.com/deepseek-ai/3FS/tree/main/specs/DataStorage" target="_blank"&gt;the corresponding system already had a complete spec written in P&lt;/a&gt;. But it could also be that Cheng Huang is already an expert specifier, meaning he can get more out of an LLM than an ordinary developer can. I've also noticed that I can usually coax an LLM to do more interesting things than most of my clients can. Which is good for my current livelihood, but bad for the hope of LLMs making formal methods mainstream. If you need to know formal methods to get the LLM to do formal methods, is that really helping?&lt;/p&gt;
    &lt;p&gt;(Yes, if it lowers the skill threshold-- means you can apply FM with 20 hours of practice instead of 80. But the jury's still out on how &lt;em&gt;much&lt;/em&gt; it lowers the threshold. What if it only lowers it from 80 to 75?) &lt;/p&gt;
    &lt;p&gt;On the other hand, there also seem to be some properties that AI struggles with, even with explicit instructions. Last week a client and I tried to get Claude to generate a good &lt;a href="https://www.hillelwayne.com/post/safety-and-liveness/" target="_blank"&gt;liveness&lt;/a&gt; or &lt;a href="https://www.hillelwayne.com/post/action-properties/" target="_blank"&gt;action&lt;/a&gt; property instead of a standard obvious invariant, and it just couldn't. Training data issue? Something in the innate complexity of liveness? It's not clear yet. These properties are even more "subtle" than most invariants, so maybe that's it.&lt;/p&gt;
    &lt;p&gt;On the other other hand, this is all as of March 2026. Maybe this whole article will be laughably obsolete by June. &lt;/p&gt;
    &lt;hr /&gt;
    &lt;h3&gt;&lt;a href="https://logicforprogrammers.com" target="_blank"&gt;Logic for Programmers&lt;/a&gt; Giveaway&lt;/h3&gt;
    &lt;p&gt;Last week's giveaway raised a few issues. First, the New World copies were all taken before all of the emails went out, so a lot of people did not even get a chance to try for a book. Second, due to a Leanpub bug the Europe coupon scheduled for 10 AM UTC actually activated at 10 AM my time, which was early evening for Europe. Third, everybody in the APAC region got left out.&lt;/p&gt;
    &lt;p&gt;So, since I'm not doing a newsletter next week, let's have another giveaway:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;a href="https://leanpub.com/logic/c/E5A55F7B482C3" target="_blank"&gt;This coupon&lt;/a&gt; will go up 2026-03-16 at 11:00 UTC, which should be noon Central European Time, and be good for ten books (five for this giveaway, five to account for last week's bug).&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://leanpub.com/logic/c/ADC664C95B6D1" target="_blank"&gt;This coupon&lt;/a&gt; will go up 2026-03-17 at 04:00 UTC, which should be noon Beijing Time, and be good for five books.&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://leanpub.com/logic/c/U1250212A9070" target="_blank"&gt;This coupon&lt;/a&gt; will go up 2026-03-17 at 17:00 UTC, which should be noon Central US Time, and also be good for five books.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;I think that gives the best chance of everybody getting at least a chance of a book, while being resilient to timezone shenanigans due to travel / Leanpub dropping bugfixes / daylight savings / whatever. &lt;/p&gt;
    &lt;p&gt;(No guarantees that later "no newsletter" weeks will have giveaways! This is a gimmick)&lt;/p&gt;
    </description><pubDate>Tue, 10 Mar 2026 17:12:30 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/llms-are-bad-at-vibing-specifications/</guid></item><item><title>Free Books</title><link>https://buttondown.com/hillelwayne/archive/free-books/</link><description>
    &lt;p&gt;Spinning a &lt;a href="https://www.youtube.com/watch?v=NB4hzg4k7_A" target="_blank"&gt;lot of plates&lt;/a&gt; this week so skipping the newsletter. As an apology, have ten free copies of &lt;em&gt;Logic for Programmers&lt;/em&gt;.&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;a href="https://leanpub.com/logic/c/EBDFA51B15C1" target="_blank"&gt;These five&lt;/a&gt; are available now.&lt;/li&gt;
    &lt;li&gt;&lt;del&gt;&lt;a href="https://leanpub.com/logic/c/5A55F7B482C3" target="_blank"&gt;These five&lt;/a&gt; &lt;em&gt;should&lt;/em&gt; be available at 10:30 AM CEST tomorrow, so people in Europe have a better chance of nabbing one.&lt;/del&gt; Nevermind Leanpub had a bug that made this not work properly&lt;/li&gt;
    &lt;/ul&gt;
    </description><pubDate>Tue, 03 Mar 2026 16:34:33 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/free-books/</guid></item><item><title>New Blog Post: Some Silly Z3 Scripts I Wrote</title><link>https://buttondown.com/hillelwayne/archive/new-blog-post-some-silly-z3-scripts-i-wrote/</link><description>
    &lt;p&gt;Now that I'm not spending all my time on Logic for Programmers, I have time to update my website again! So here's the first blog post in five months: &lt;a href="https://www.hillelwayne.com/post/z3-examples/" target="_blank"&gt;Some Silly Z3 Scripts I Wrote&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;Normally I'd also put a link to the Patreon notes but I've decided I don't like publishing gated content and am going to wind that whole thing down. So some quick notes about this post:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;Part of the point is admittedly to hype up the eventual release of LfP. I want to start marketing the book, but don't want the marketing material to be devoid of interest, so tangentially-related-but-independent blog posts are a good place to start.&lt;/li&gt;
    &lt;li&gt;The post discusses the concept of "chaff", the enormous quantity of material (both code samples and prose) that didn't make it into the book. The book is about 50,000 words… and considerably shorter than the total volume of chaff! I don't &lt;em&gt;think&lt;/em&gt; most of it can be turned into useful public posts, but I'm not entirely opposed to the idea. Maybe some of the old chapters could be made into something?&lt;/li&gt;
    &lt;li&gt;Coming up with a conditioned mathematical property to prove was a struggle. I had two candidates: &lt;code&gt;a == b * c =&amp;gt; a / b == c&lt;/code&gt;, which would have required a long tangent on how division must be total in Z3, and  &lt;code&gt;a != 0 =&amp;gt; some b: b * a == 1&lt;/code&gt;, which would have required introducing a quantifier (SMT is real weird about quantifiers). Division by zero has already caused me enough grief so I went with the latter. This did mean I had to reintroduce "operations must be total" when talking about arrays.&lt;/li&gt;
    &lt;li&gt;I have no idea why the array example returns &lt;code&gt;2&lt;/code&gt; for the max profit and not &lt;code&gt;99999999&lt;/code&gt;. I'm guessing there's some short circuiting logic in the optimizer when the problem is ill-defined?&lt;/li&gt;
    &lt;li&gt;One example I could not get working, which is unfortunate, was a demonstration of how SMT solvers are undecidable via encoding Goldbach's conjecture as an SMT problem. Anything with multiple nested quantifiers is a pain.&lt;/li&gt;
    &lt;/ul&gt;
    </description><pubDate>Mon, 23 Feb 2026 16:49:10 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/new-blog-post-some-silly-z3-scripts-i-wrote/</guid></item><item><title>Stream of Consciousness Driven Development</title><link>https://buttondown.com/hillelwayne/archive/stream-of-consciousness-driven-development/</link><description>
    &lt;p&gt;This is something I just tried out last week but it seems to have enough potential to be worth showing unpolished. I was pairing with a client on writing a spec. I saw a problem with the spec, a convoluted way of fixing the spec. Instead of trying to verbally explain it, I started by creating a new markdown file:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;NameOfProblem.md
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Then I started typing. First the problem summary, then a detailed description, then the solution and why it worked. When my partner asked questions, I incorporated his question and our discussion of it into the flow. If we hit a dead end with the solution, we marked it out as a dead end. Eventually the file looked something like this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Current state of spec
    Problems caused by this
        Elaboration of problems
        What we tried that didn&amp;#39;t work
    Proposed Solution
        Theory behind proposed solution
        How the solution works
        Expected changes
        Other problems this helps solve
        Problems this does *not* help with
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Only once this was done, my partner fully understood the chain of thought, &lt;em&gt;and&lt;/em&gt; we agreed it represented the right approach, did we start making changes to the spec. &lt;/p&gt;
    &lt;h3&gt;How is this better than just making the change?&lt;/h3&gt;
    &lt;p&gt;The change was &lt;em&gt;conceptually&lt;/em&gt; complex. A rough analogy: imagine pairing with a beginner who wrote an insertion sort, and you want to replace it with quicksort. You need to explain why the insertion sort is too slow, why the quicksort isn't slow, and how quicksort actually correctly sorts a list. This could involve tangents into computational complexity, big-o notation, recursion, etc. These are all concepts you have internalized, so the change is simple to you, but the solution uses concepts the beginner does not know. So it's conceptually complex to them.&lt;/p&gt;
    &lt;p&gt;I wasn't pairing with a beginning programmer or even a beginning specifier. This was a client who could confidently write complex specs on their own. But they don't work on specifications full time like I do. Any time there's a relative gap in experience in a pair, there's solutions that are conceptually simple to one person and complex to the other.&lt;/p&gt;
    &lt;p&gt;I've noticed too often that when one person doesn't fully understand the concepts behind a change, they just go "you're the expert, I trust you." That eventually leads to a totally unmaintainable spec. Hence, writing it all out. &lt;/p&gt;
    &lt;p&gt;As I said before, I've only tried this once (though I've successfully used a similar idea when teaching workshops). It worked pretty well, though! Just be prepared for a lot of typing.&lt;/p&gt;
    </description><pubDate>Wed, 18 Feb 2026 16:33:08 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/stream-of-consciousness-driven-development/</guid></item><item><title>Proving What's Possible</title><link>https://buttondown.com/hillelwayne/archive/proving-whats-possible/</link><description>
    &lt;p&gt;As a formal methods consultant I have to mathematically express properties of systems. I generally do this with two "temporal operators": &lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;A(x) means that &lt;code&gt;x&lt;/code&gt; is always true. For example, a database table &lt;em&gt;always&lt;/em&gt; satisfies all record-level constraints, and a state machine &lt;em&gt;always&lt;/em&gt; makes valid transitions between states. If &lt;code&gt;x&lt;/code&gt; is a statement about an individual state (as in the database but not state machine example), we further call it an &lt;strong&gt;invariant&lt;/strong&gt;.&lt;/li&gt;
    &lt;li&gt;E(x) means that &lt;code&gt;x&lt;/code&gt; is "eventually" true, conventionally meaning "guaranteed true at some point in the future". A database transaction &lt;em&gt;eventually&lt;/em&gt; completes or rolls back, a state machine &lt;em&gt;eventually&lt;/em&gt; reaches the "done" state, etc. &lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;These come from linear temporal logic, which is the mainstream notation for expressing system properties. &lt;sup id="fnref:modal"&gt;&lt;a class="footnote-ref" href="#fn:modal"&gt;1&lt;/a&gt;&lt;/sup&gt; We like these operators because they elegantly cover &lt;a href="https://www.hillelwayne.com/post/safety-and-liveness/" target="_blank"&gt;safety and liveness properties&lt;/a&gt;, and because &lt;a href="https://buttondown.com/hillelwayne/archive/formalizing-stability-and-resilience-properties/" target="_blank"&gt;we can combine them&lt;/a&gt;. &lt;code&gt;A(E(x))&lt;/code&gt; means &lt;code&gt;x&lt;/code&gt; is true an infinite number of times, while &lt;code&gt;A(x =&amp;gt; E(y)&lt;/code&gt; means that &lt;code&gt;x&lt;/code&gt; being true guarantees &lt;code&gt;y&lt;/code&gt; true in the future. &lt;/p&gt;
    &lt;p&gt;There's a third class of properties, that I will call &lt;em&gt;possibility&lt;/em&gt; properties: &lt;code&gt;P(x)&lt;/code&gt; is "can x happen in this model"? Is it possible for a table to have more than ten records? Can a state machine transition from "Done" to "Retry", even if it &lt;em&gt;doesn't&lt;/em&gt;? Importantly, &lt;code&gt;P(x)&lt;/code&gt; does not need to be possible &lt;em&gt;immediately&lt;/em&gt;, just at some point in the future. It's possible to lose 100 dollars betting on slot machines, even if you only bet one dollar at a time. If &lt;code&gt;x&lt;/code&gt; is a statement about an individual state, we can further call it a &lt;a href="https://en.wikipedia.org/wiki/Reachability" target="_blank"&gt;&lt;em&gt;reachability&lt;/em&gt; property&lt;/a&gt;. I'm going to use the two interchangeably for flow. &lt;/p&gt;
    &lt;p&gt;&lt;code&gt;A(P(x))&lt;/code&gt; says that &lt;code&gt;x&lt;/code&gt; is &lt;em&gt;always&lt;/em&gt; possible. No matter what we've done in our system, we can make &lt;code&gt;x&lt;/code&gt; happen again. There's no way to do this with just &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;E&lt;/code&gt;. Other meaningful combinations include:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;code&gt;P(A(x))&lt;/code&gt;: there is a reachable state from which &lt;code&gt;x&lt;/code&gt; is always true.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;A(x =&amp;gt; P(y))&lt;/code&gt;: &lt;code&gt;y&lt;/code&gt; is possible from any state where &lt;code&gt;x&lt;/code&gt; is true.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;E(x &amp;amp;&amp;amp; P(y))&lt;/code&gt;: There is always a future state where x is true and y is reachable.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;A(P(x) =&amp;gt; E(x))&lt;/code&gt;: If &lt;code&gt;x&lt;/code&gt; is ever possible, it will eventually happen.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;E(P(x))&lt;/code&gt; and &lt;code&gt;P(E(x))&lt;/code&gt; are the same as &lt;code&gt;P(x)&lt;/code&gt;.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;See the paper &lt;a href="https://dl.acm.org/doi/epdf/10.1145/567446.567463" target="_blank"&gt;"Sometime" is sometimes "not never"&lt;/a&gt; for a deeper discussion of &lt;code&gt;E&lt;/code&gt; and &lt;code&gt;P&lt;/code&gt;.&lt;/p&gt;
    &lt;h3&gt;The use case&lt;/h3&gt;
    &lt;p&gt;Possibility properties are "something good &lt;em&gt;can&lt;/em&gt; happen", which is generally less useful (&lt;em&gt;in specifications&lt;/em&gt;) than "something bad &lt;em&gt;can't&lt;/em&gt; happen" (safety) and "something good &lt;em&gt;will&lt;/em&gt; happen" (liveness). But it still comes up as an important property! My favorite example:&lt;/p&gt;
    &lt;p&gt;&lt;img alt="A guy who can't shut down his computer because system preferences interrupts shutdown" class="newsletter-image" src="https://www.hillelwayne.com/post/safety-and-liveness/img/tweet2.png" /&gt;&lt;/p&gt;
    &lt;p&gt;The big use I've found for the idea is as a sense-check that we wrote the spec properly. Say I take the property "A worker in the 'Retry' state eventually leaves that state":&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;A(state == &amp;#39;Retry&amp;#39; =&amp;gt; E(state != &amp;#39;Retry&amp;#39;))
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;The model checker checks this property and confirms it holds of the spec. Great! Our system is correct! ...Unless the system can never &lt;em&gt;reach&lt;/em&gt; the "Retry" state, in which case the expression is trivially true. I need to verify that 'Retry' is reachable, eg &lt;code&gt;P(state == 'Retry')&lt;/code&gt;. Notice I can't use &lt;code&gt;E&lt;/code&gt; to do this, because I don't want to say "the worker always needs to retry at least once". &lt;/p&gt;
    &lt;h3&gt;It's not supported though&lt;/h3&gt;
    &lt;p&gt;I say "use I've found for &lt;em&gt;the idea&lt;/em&gt;" because the main formalisms I use (Alloy and TLA+) don't natively support &lt;code&gt;P&lt;/code&gt;. &lt;sup id="fnref:tla"&gt;&lt;a class="footnote-ref" href="#fn:tla"&gt;2&lt;/a&gt;&lt;/sup&gt; On top of &lt;code&gt;P&lt;/code&gt; being less useful than &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;E&lt;/code&gt;, simple reachability properties are &lt;a href="https://www.hillelwayne.com/post/software-mimicry/" target="_blank"&gt;mimickable&lt;/a&gt; with A(x). &lt;code&gt;P(x)&lt;/code&gt; &lt;em&gt;passes&lt;/em&gt; whenever &lt;code&gt;A(!x)&lt;/code&gt; &lt;em&gt;fails&lt;/em&gt;, meaning I can verify &lt;code&gt;P(state == 'Retry')&lt;/code&gt; by testing that &lt;code&gt;A(!(state == 'Retry'))&lt;/code&gt; finds a counterexample. We &lt;em&gt;cannot&lt;/em&gt; mimic combined operators this way like &lt;code&gt;A(P(x))&lt;/code&gt; but those are significantly less common than state-reachability. &lt;/p&gt;
    &lt;p&gt;(Also, refinement doesn't preserve possibility properties, but that's a whole other kettle of worms.)&lt;/p&gt;
    &lt;p&gt;The one that's bitten me a little is that we can't mimic "&lt;code&gt;P(x)&lt;/code&gt; from every starting state". "&lt;code&gt;A(!x)&lt;/code&gt;" fails if there's at least one path from one starting state that leads to &lt;code&gt;x&lt;/code&gt;, but other starting states might not make &lt;code&gt;x&lt;/code&gt; possible.&lt;/p&gt;
    &lt;p&gt;I suspect there's also a chicken-and-egg problem here. Since my tools can't verify possibility properties, I'm not used to noticing them in systems. I'd be interested in hearing if anybody works with codebases where possibility properties are important, especially if it's something complex like &lt;code&gt;A(x =&amp;gt; P(y))&lt;/code&gt;.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:modal"&gt;
    &lt;p&gt;Instead of &lt;code&gt;A(x)&lt;/code&gt;, the literature uses &lt;code&gt;[]x&lt;/code&gt; or &lt;code&gt;Gx&lt;/code&gt; ("globally x") and instead of &lt;code&gt;E(x)&lt;/code&gt; it uses &lt;code&gt;&amp;lt;&amp;gt;x&lt;/code&gt; or &lt;code&gt;Fx&lt;/code&gt; ("finally x"). I'm using A and E because this isn't teaching material.&amp;#160;&lt;a class="footnote-backref" href="#fnref:modal" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:tla"&gt;
    &lt;p&gt;There's &lt;a href="https://github.com/tlaplus/tlaplus/issues/860" target="_blank"&gt;some discussion to add it to TLA+, though&lt;/a&gt;.&amp;#160;&lt;a class="footnote-backref" href="#fnref:tla" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Wed, 11 Feb 2026 18:36:53 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/proving-whats-possible/</guid></item><item><title>Logic for Programmers New Release and Next Steps</title><link>https://buttondown.com/hillelwayne/archive/logic-for-programmers-new-release-and-next-steps/</link><description>
    &lt;p&gt;&lt;img alt="cover.jpg" class="newsletter-image" src="https://assets.buttondown.email/images/f821145f-d310-403c-88f4-327758a66606.jpg?w=480&amp;amp;fit=max" /&gt;&lt;/p&gt;
    &lt;p&gt;It's taken four months, but the next release of &lt;a href="https://logicforprogrammers.com" target="_blank"&gt;Logic for Programmers is now available&lt;/a&gt;! v0.13 is over 50,000 words, making it both 20% larger than v0.12 and officially the longest thing I have ever written.&lt;sup id="fnref:longest"&gt;&lt;a class="footnote-ref" href="#fn:longest"&gt;1&lt;/a&gt;&lt;/sup&gt; Full release notes are &lt;a href="https://github.com/logicforprogrammers/book-assets/blob/master/CHANGELOG.md" target="_blank"&gt;here&lt;/a&gt;, but I'll talk a bit about the biggest changes. &lt;/p&gt;
    &lt;p&gt;For one, every chapter has been rewritten. Every single one. They span from &lt;em&gt;relatively&lt;/em&gt; minor changes to complete chapter rewrites. After some rough git diffing, I think I deleted about 11,000 words?&lt;sup id="fnref:gross-additions"&gt;&lt;a class="footnote-ref" href="#fn:gross-additions"&gt;2&lt;/a&gt;&lt;/sup&gt; The biggest change is probably to the Alloy chapter. After many sleepless nights, I realized the right approach wasn't to teach Alloy as a &lt;em&gt;data modeling&lt;/em&gt; tool but to teach it as a &lt;em&gt;domain modeling&lt;/em&gt; tool. Which technically means the book no longer covers data modeling.&lt;/p&gt;
    &lt;p&gt;There's also a lot more connections between the chapters. The introductory math chapter, for example, foreshadows how each bit of math will be used in the future techniques. I also put more emphasis on the general "themes" like the expressiveness-guarantees tradeoff (working title). One theme I'm really excited about is compatibility (extremely working title). It turns out that the &lt;a href="https://buttondown.com/hillelwayne/archive/the-liskov-substitution-principle-does-more-than/" target="_blank"&gt;Liskov substitution principle&lt;/a&gt;/subtyping in general, &lt;a href="https://buttondown.com/hillelwayne/archive/refinement-without-specification/" target="_blank"&gt;database migrations&lt;/a&gt;, backwards-compatible API changes, and &lt;a href="https://hillelwayne.com/post/refinement/" target="_blank"&gt;specification refinement&lt;/a&gt; all follow &lt;em&gt;basically&lt;/em&gt; the same general principles. I'm calling this "compatibility" for now but prolly need a better name.&lt;/p&gt;
    &lt;p&gt;Finally, there's just a lot more new topics in the various chapters. &lt;code&gt;Testing&lt;/code&gt; properly covers structural and metamorphic properties. &lt;code&gt;Proofs&lt;/code&gt; covers proof by induction and proving recursive functions (in an exercise). &lt;code&gt;Logic Programming&lt;/code&gt; now finally has a section on answer set programming. You get the picture.&lt;/p&gt;
    &lt;h3&gt;Next Steps&lt;/h3&gt;
    &lt;p&gt;There's a lot I still want to add to the book: proper data modeling, data structures, type theory, model-based testing, etc. But I've added new material for two year, and if I keep going it will never get done. So with this release, all the content is in!&lt;/p&gt;
    &lt;p&gt;Just like all the content was in &lt;a href="https://buttondown.com/hillelwayne/archive/five-unusual-raku-features/" target="_blank"&gt;two Novembers ago&lt;/a&gt; and &lt;a href="https://buttondown.com/hillelwayne/archive/logic-for-programmers-project-update/" target="_blank"&gt;two Januaries ago&lt;/a&gt; and &lt;a href="https://buttondown.com/hillelwayne/archive/logic-for-programmers-turns-one/" target="_blank"&gt;last July&lt;/a&gt;. To make it absolutely 100% for sure that I won't be tempted to add anything else, I passed the whole manuscript over to a copy editor. So if I write more, it won't get edits. That's a pretty good incentive to stop.&lt;/p&gt;
    &lt;p&gt;I also need to find a technical reviewer and proofreader. Once all three phases are done then it's "just" a matter of fixing the layout and finding a good printer. I don't know what the timeline looks like but I really want to have something I can hold in my hands before the summer.&lt;/p&gt;
    &lt;p&gt;(I also need to get notable-people testimonials. Hampered a little in this because I'm trying real hard not to quid-pro-quo, so I'd like to avoid anybody who helped me or is mentioned in the book. And given I tapped most of my network to help me... I've got some ideas though!)&lt;/p&gt;
    &lt;p&gt;There's still a lot of work ahead. Even so, for the first time in two years I don't have research to do or sections to write and it feels so crazy. Maybe I'll update my blog again! Maybe I'll run a workshop! Maybe I'll go outside if Chicago ever gets above 6°F! &lt;/p&gt;
    &lt;hr /&gt;
    &lt;h2&gt;Conference Season&lt;/h2&gt;
    &lt;p&gt;After a pretty slow 2025, the 2026 conference season is looking to be pretty busy! Here's where I'm speaking so far:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;a href="https://qconlondon.com/" target="_blank"&gt;QCon London&lt;/a&gt;, March 16-19&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://craft-conf.com/2026" target="_blank"&gt;Craft Conference&lt;/a&gt;, Budapest, June 4-5&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://softwareshould.work/" target="_blank"&gt;Software Should Work&lt;/a&gt;, Missouri, July 16-17&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://hfpug.org/" target="_blank"&gt;Houston Functional Programmers&lt;/a&gt;, Virtual, December 3&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;For the first three I'm giving variations of my talk "How to find bugs in systems that don't exist", which I gave last year at &lt;a href="https://systemsdistributed.com/" target="_blank"&gt;Systems Distributed&lt;/a&gt;. Last one will ideally be a talk based on LfP. &lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr /&gt;
    &lt;ol&gt;
    &lt;li id="fn:longest"&gt;
    &lt;p&gt;The second longest was my 2003 NaNoWriMo. The third longest was &lt;em&gt;Practical TLA+&lt;/em&gt;.&amp;#160;&lt;a class="footnote-backref" href="#fnref:longest" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:gross-additions"&gt;
    &lt;p&gt;This means I must have written 20,000 words total. For comparison, the v0.1 release was 19,000 words.&amp;#160;&lt;a class="footnote-backref" href="#fnref:gross-additions" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Wed, 04 Feb 2026 14:00:00 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/logic-for-programmers-new-release-and-next-steps/</guid></item><item><title>Refinement without Specification</title><link>https://buttondown.com/hillelwayne/archive/refinement-without-specification/</link><description>
    &lt;p&gt;Imagine we have a SQL database with a &lt;code&gt;user&lt;/code&gt; table, and users have a non-nullable &lt;code&gt;is_activated&lt;/code&gt; boolean column. Having read &lt;a href="https://ntietz.com/blog/that-boolean-should-probably-be-something-else/" target="_blank"&gt;That Boolean Should Probably Be Something else&lt;/a&gt;, you decide to migrate it to a nullable &lt;code&gt;activated_at&lt;/code&gt; column. You can change any of the SQL queries that read/update the &lt;code&gt;user&lt;/code&gt; table but not any of the code that uses the results of these queries. Can we make this change in a way that preserves all external properties? &lt;/p&gt;
    &lt;p&gt;Yes. If an update would set &lt;code&gt;is_activated&lt;/code&gt; to true, instead set it to the current date. Now define the &lt;strong&gt;refinement mapping&lt;/strong&gt; that takes a &lt;code&gt;new_user&lt;/code&gt; and returns an &lt;code&gt;old_user&lt;/code&gt;. All columns will be unchanged &lt;em&gt;except&lt;/em&gt; &lt;code&gt;is_activated&lt;/code&gt;, which will be&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;f(new_user).is_activated = 
        if new_user.activated_at == NULL 
        then FALSE
        else TRUE
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;Now new code can use &lt;code&gt;new_user&lt;/code&gt; directly while legacy code can use &lt;code&gt;f(new_user)&lt;/code&gt; instead, which will behave indistinguishably from the &lt;code&gt;old_user&lt;/code&gt;. &lt;/p&gt;
    &lt;p&gt;A little more time passes and you decide to switch to an &lt;a href="https://martinfowler.com/eaaDev/EventSourcing.html" target="_blank"&gt;event sourcing&lt;/a&gt;-like model. So instead of an &lt;code&gt;activated_at&lt;/code&gt; column, you have a &lt;code&gt;user_events&lt;/code&gt; table, where every record is &lt;code&gt;(user_id, timestamp, event)&lt;/code&gt;. So adding an &lt;code&gt;activate&lt;/code&gt; event will activate the user, adding a &lt;code&gt;deactivate&lt;/code&gt; event will deactivate the user. Once again, we can update the queries but not any of the code that uses the results of these queries. Can we make a change that preserves all external properties?&lt;/p&gt;
    &lt;p&gt;Yes. If an update would change &lt;code&gt;is_activated&lt;/code&gt;, instead have it add an appropriate record to the event table. Now, define the refinement mapping that takes &lt;code&gt;newer_user&lt;/code&gt; and returns &lt;code&gt;new_user&lt;/code&gt;. The &lt;code&gt;activated_at&lt;/code&gt; field will be computed like this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;g(newer_user).activated_at =
            # last_activated_event
        let lae = 
                newer_user.events
                          .filter(event = &amp;quot;activate&amp;quot; | &amp;quot;deactivate&amp;quot;)
                          .last,
        in
            if lae.event == &amp;quot;activate&amp;quot; 
            then lae.timestamp
            else NULL
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;Now new code can use &lt;code&gt;newer_user&lt;/code&gt; directly while old code can use &lt;code&gt;g(newer_user)&lt;/code&gt; and the really old code can use &lt;code&gt;f(g(newer_user))&lt;/code&gt;.&lt;/p&gt;
    &lt;h3&gt;Mutability constraints&lt;/h3&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;I said "these preserve all external properties" and that was a lie. It depends on the properties we explicitly have, and I didn't list any. The real interesting properties for me are mutability constraints on how the system can evolve. So let's go back in time and add a constraint to &lt;code&gt;user&lt;/code&gt;:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;C1(u) = u.is_activated =&amp;gt; u.is_activated&amp;#39;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;This constraint means that if a user is activated, any change will preserve its activated-ness. This means a user can go from deactivated to activated but not the other way. It's not a particular good constraint but it's good enough for teaching purposes. Such a SQL constraint can be enforced with &lt;a href="https://www.postgresql.org/docs/current/sql-createeventtrigger.html" target="_blank"&gt;triggers&lt;/a&gt;. &lt;/p&gt;
    &lt;p&gt;Now we can throw a constraint on &lt;code&gt;new_user&lt;/code&gt;:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;C2(nu) = nu.activated_at != NULL =&amp;gt; nu.activated_at&amp;#39; != NULL
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;If &lt;code&gt;nu&lt;/code&gt; satisfies &lt;code&gt;C2&lt;/code&gt;, then &lt;code&gt;f(nu)&lt;/code&gt; satisfies &lt;code&gt;C1&lt;/code&gt;. So the refinement still holds.&lt;/p&gt;
    &lt;p&gt;With &lt;code&gt;newer_u&lt;/code&gt;, we &lt;em&gt;cannot&lt;/em&gt; guarantee that &lt;code&gt;g(newer_u)&lt;/code&gt; satisfies &lt;code&gt;C2&lt;/code&gt; because we can go from "activated" to "deactivated" just by appending a new event. So it's not a refinement. This is fixable by removing deactivation events, that would work too.&lt;/p&gt;
    &lt;p&gt;So a more interesting case is &lt;code&gt;bad_user&lt;/code&gt;, a refinement of &lt;code&gt;user&lt;/code&gt; that has both &lt;code&gt;activated_at&lt;/code&gt; and &lt;code&gt;activated_until&lt;/code&gt;. We propose the refinement mapping &lt;code&gt;b&lt;/code&gt;:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;b(bad_user).activated =
        if bad_user.activated_at == NULL &amp;amp;&amp;amp; activated_until == NULL
        then FALSE
        else bad_user.activated_at &amp;lt;= now() &amp;lt; bad_user.activated_until
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;p&gt;But now if enough time passes, &lt;code&gt;b(bad_user).activated' = false&lt;/code&gt;, so this is not a refinement either.&lt;/p&gt;
    &lt;h3&gt;The punchline&lt;/h3&gt;
    &lt;p&gt;Refinement is one of the most powerful techniques in formal specification, but also one of the hardest for people to understand. I'm starting to think that the reason it's so hard is because they learn refinement while they're &lt;em&gt;also&lt;/em&gt; learning formal methods, so are faced with an unfamiliar topic in an unfamiliar context. If that's the case, then maybe it's easier introducing refinement in a more common context like databases.&lt;/p&gt;
    &lt;p&gt;I've written a bit about refinement in the normal context &lt;a href="https://hillelwayne.com/post/refinement/" target="_blank"&gt;here&lt;/a&gt; (showing one specification is an implementation of another). I kinda want to work this explanation into the book but it might be too late for big content additions like this.&lt;/p&gt;
    &lt;p&gt;(Food for thought: how do refinement mappings relate to database views?)&lt;/p&gt;
    </description><pubDate>Tue, 20 Jan 2026 17:49:07 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/refinement-without-specification/</guid></item><item><title>My Gripes with Prolog</title><link>https://buttondown.com/hillelwayne/archive/my-gripes-with-prolog/</link><description>
    &lt;p&gt;For the next release of &lt;a href="https://leanpub.com/logic/" target="_blank"&gt;Logic for Programmers&lt;/a&gt;, I'm finally adding the sections on Answer Set Programming and Constraint Logic Programming that I TODOd back in version 0.9. And this is making me re-experience some of my pain points with Prolog, which I will gripe about now.  If you want to know more about why Prolog is cool instead, go &lt;a href="https://buttondown.com/hillelwayne/archive/a48fce5b-8a05-4302-b620-9b26f057f145/" target="_blank"&gt;here&lt;/a&gt; or &lt;a href="https://www.metalevel.at/prolog" target="_blank"&gt;here&lt;/a&gt; or &lt;a href="https://ianthehenry.com/posts/drinking-with-datalog/" target="_blank"&gt;here&lt;/a&gt; or &lt;a href="https://logicprogramming.org/" target="_blank"&gt;here&lt;/a&gt;. &lt;/p&gt;
    &lt;h3&gt;No standardized strings&lt;/h3&gt;
    &lt;p&gt;ISO "strings" are just atoms or lists of single-character atoms (or lists of integer character codes). The various implementations of Prolog add custom string operators but they are not cross compatible, so code written with strings in SWI-Prolog will not work in Scryer Prolog. &lt;/p&gt;
    &lt;h3&gt;No functions&lt;/h3&gt;
    &lt;p&gt;Code logic is expressed entirely in &lt;em&gt;rules&lt;/em&gt;, predicates which return true or false for certain values. For example if you wanted to get the length of a Prolog list, you write this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s s-Atom"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;c&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;Len&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    
       &lt;span class="nv"&gt;Len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;Now this is pretty cool in that it allows bidirectionality, or running predicates "in reverse". To generate lists of length 3, you can write &lt;code&gt;length(L, 3)&lt;/code&gt;. But it also means that if you want to get the length a list &lt;em&gt;plus one&lt;/em&gt;, you can't do that in one expression, you have to write &lt;code&gt;length(List, Out), X is Out+1&lt;/code&gt;.&lt;/p&gt;
    &lt;p&gt;For a while I thought no functions was necessary evil for bidirectionality, but then I discovered &lt;a href="https://picat-lang.org/" target="_blank"&gt;Picat&lt;/a&gt; has functions and works just fine. That by itself is a reason for me to prefer Picat for my LP needs.&lt;/p&gt;
    &lt;p&gt;(Bidirectionality is a killer feature of Prolog, so it's a shame I so rarely run into situations that use it.)&lt;/p&gt;
    &lt;h3&gt;No standardized collection types besides lists&lt;/h3&gt;
    &lt;p&gt;Aside from atoms (&lt;code&gt;abc&lt;/code&gt;) and numbers, there are two data types:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;Linked lists like &lt;code&gt;[a,b,c,d]&lt;/code&gt;.&lt;/li&gt;
    &lt;li&gt;Compound terms like &lt;code&gt;dog(rex, poodle)&lt;/code&gt;, which &lt;em&gt;seem&lt;/em&gt; like record types but are actually tuples. You can even convert compound terms to linked lists with &lt;code&gt;=..&lt;/code&gt;:&lt;/li&gt;
    &lt;/ul&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nv"&gt;L&lt;/span&gt; &lt;span class="s s-Atom"&gt;=..&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s s-Atom"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;c&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;
       &lt;span class="nv"&gt;L&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;c&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;c&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="s s-Atom"&gt;=..&lt;/span&gt; &lt;span class="nv"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
       &lt;span class="nv"&gt;L&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s s-Atom"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;There's no proper key-value maps or even struct types. Again, this is something that individual distributions can fix (without cross compatibility), but these never feel integrated with the rest of the language. &lt;/p&gt;
    &lt;h3&gt;No boolean values&lt;/h3&gt;
    &lt;p&gt;&lt;code&gt;true&lt;/code&gt; and &lt;code&gt;false&lt;/code&gt; aren't values, they're control flow statements. &lt;code&gt;true&lt;/code&gt; is a noop and &lt;code&gt;false&lt;/code&gt; says that the current search path is a dead end, so backtrack and start again. You can't explicitly store true and false as values, you have to implicitly have them in facts (&lt;code&gt;passed(test)&lt;/code&gt; instead of &lt;code&gt;test.passed? == true&lt;/code&gt;).&lt;/p&gt;
    &lt;p&gt;This hasn't made any tasks impossible, and I can usually find a workaround to whatever I want to do. But I do think it makes things more inconvenient! Sometimes I want to do something dumb like "get all atoms that don't pass at least three of these rules", and that'd be a lot easier if I could shove intermediate results into a sack of booleans. &lt;/p&gt;
    &lt;p&gt;(This is called "&lt;a href="https://en.wikipedia.org/wiki/Negation_as_failure" target="_blank"&gt;Negation as Failure&lt;/a&gt;". I think this might be necessary to make Prolog a Turing complete general programming language. Picat fixes a lot of Prolog's gripes and still has negation as failure. ASP has regular negation but it's not Turing complete.) &lt;/p&gt;
    &lt;h3&gt;Cuts are confusing&lt;/h3&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;Prolog finds solutions through depth first search, and a "cut" (&lt;code&gt;!&lt;/code&gt;) symbol prevents backtracking past a certain point. This is necessary for optimization but can lead to invalid programs. &lt;/p&gt;
    &lt;p&gt;You're not supposed to use cuts if you can avoid it, so I pretended cuts didn't exist. Which is why I was surprised to find that &lt;a href="https://eu.swi-prolog.org/pldoc/doc_for?object=(-%3E)/2" target="_blank"&gt;conditionals&lt;/a&gt; are implemented with cuts. Because cuts are spooky dark magic conditionals &lt;em&gt;sometimes&lt;/em&gt; conditionals work as I expect them to and sometimes leave out valid solutions and I have no idea how to tell which it'll be. Usually I find it safer to just avoid conditionals entirely, which means my code gets a lot longer and messier. &lt;/p&gt;
    &lt;h3&gt;Non-cuts are confusing&lt;/h3&gt;
    &lt;p&gt;The original example in the last section was this: &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:-&lt;/span&gt;
        &lt;span class="s s-Atom"&gt;\+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;&lt;code&gt;foo(1, 2)&lt;/code&gt; returns true, so you'd expect &lt;code&gt;f(A, B)&lt;/code&gt; to return &lt;code&gt;A=1, B=2&lt;/code&gt;. But it returns &lt;code&gt;false&lt;/code&gt;.  Whereas this works as expected.&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:-&lt;/span&gt;
        &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s s-Atom"&gt;\+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;I &lt;em&gt;thought&lt;/em&gt; this was because &lt;code&gt;\+&lt;/code&gt; was implemented with cuts, and the &lt;a href="https://www.amazon.com/Programming-Prolog-Using-ISO-Standard/dp/3540006788" target="_blank"&gt;Clocksin book&lt;/a&gt; suggests it's &lt;code&gt;call(P), !, fail&lt;/code&gt;, so this was my prime example about how cuts are confusing. But then I tried this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nf"&gt;member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="s s-Atom"&gt;\+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.&lt;/span&gt;
    &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.&lt;/span&gt; &lt;span class="c1"&gt;% wtf?&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;There's no way to get that behavior with cuts! I don't think &lt;code&gt;\+&lt;/code&gt; uses cuts at all! And now I have to figure out why 
    &lt;code&gt;foo(A, B)&lt;/code&gt; doesn't returns results. Is it &lt;a href="https://github.com/dtonhofer/prolog_notes/blob/master/other_notes/about_negation/floundering.md" target="_blank"&gt;floundering&lt;/a&gt;? Is it because &lt;code&gt;\+ P&lt;/code&gt; only succeeds if &lt;code&gt;P&lt;/code&gt; fails, and &lt;code&gt;A = B&lt;/code&gt; always succeeds? A closed-world assumption? Something else?&lt;sup id="fnref:dif"&gt;&lt;a class="footnote-ref" href="#fn:dif"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
    &lt;h3&gt;Straying outside of default queries is confusing&lt;/h3&gt;
    &lt;p&gt;Say I have a program like this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n11&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n21&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n22&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;n11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n111&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;n11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n112&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    
    &lt;span class="nf"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:-&lt;/span&gt; &lt;span class="c1"&gt;% two children&lt;/span&gt;
        &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;C1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;C2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;C1&lt;/span&gt; &lt;span class="s s-Atom"&gt;@&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;C2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="c1"&gt;% ordering&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;And I want to know all of the nodes that are parents of branches. The normal way to do this is with a query:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Atom"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Atom"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;% show more...&lt;/span&gt;
    &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Atom"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Atom"&gt;n11&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;This is interactively making me query for every result. That's usually not what I want, I know the result of my query is finite and I want all of the results at once, so I can count or farble or whatever them. It took a while to figure out that the proper solution is &lt;a href="https://www.swi-prolog.org/pldoc/man?predicate=bagof/3" target="_blank"&gt;&lt;code&gt;bagof(Template, Goal, Bag)&lt;/code&gt;&lt;/a&gt;, which will "Unify Bag with the alternatives of Template":&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nf"&gt;bagof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nv"&gt;As&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    
    &lt;span class="nv"&gt;As&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s s-Atom"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Atom"&gt;n11&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;As&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s s-Atom"&gt;n&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Atom"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;Wait crap that's still giving one result at a time, because &lt;code&gt;N&lt;/code&gt; is a free variable in &lt;code&gt;bagof&lt;/code&gt; so it backtracks over that. It surprises me but I guess it's good to have as an option. So how do I get all of the results at once?&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nf"&gt;bagof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="s s-Atom"&gt;^&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nv"&gt;As&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    
    &lt;span class="nv"&gt;As&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s s-Atom"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;The only difference is the &lt;code&gt;N^Goal&lt;/code&gt;, which tells &lt;code&gt;bagof&lt;/code&gt; to ignore and group the results of &lt;code&gt;N&lt;/code&gt;. As far as I can tell, this is the &lt;em&gt;only&lt;/em&gt; place the ISO standard uses &lt;code&gt;^&lt;/code&gt; to mean anything besides exponentiation. Supposedly it's the &lt;a href="https://sicstus.sics.se/sicstus/docs/latest4/html/sicstus.html/ref_002dall_002dsum.html" target="_blank"&gt;existential quantifier&lt;/a&gt;? In general whenever I try to stray outside simpler use-cases, especially if I try to do things non-interactively, I run into trouble.&lt;/p&gt;
    &lt;h3&gt;I have mixed feelings about symbol terms&lt;/h3&gt;
    &lt;p&gt;It took me a long time to realize the reason &lt;code&gt;bagof&lt;/code&gt;  "works" is because infix symbols are mapped to prefix compound terms, so that  &lt;code&gt;a^b&lt;/code&gt; is &lt;code&gt;^(a, b)&lt;/code&gt;, and then different predicates can decide to do different things with &lt;code&gt;^(a, b)&lt;/code&gt;.&lt;/p&gt;
    &lt;p&gt;This is also why you can't just write &lt;code&gt;A = B+1&lt;/code&gt;: that unifies &lt;code&gt;A&lt;/code&gt; with the &lt;em&gt;compound term&lt;/em&gt; &lt;code&gt;+(B, 1)&lt;/code&gt;. &lt;code&gt;A+1 = B+2&lt;/code&gt; is &lt;em&gt;false&lt;/em&gt;, as &lt;code&gt;1 \= 2&lt;/code&gt;. You have to write &lt;code&gt;A+1 is B+2&lt;/code&gt;, as &lt;code&gt;is&lt;/code&gt; is the operator that converts &lt;code&gt;+(B, 1)&lt;/code&gt; to a mathematical term.&lt;/p&gt;
    &lt;p&gt;(And &lt;em&gt;that&lt;/em&gt; fails because &lt;code&gt;is&lt;/code&gt; isn't fully bidirectional. The lhs &lt;em&gt;must&lt;/em&gt; be a single variable. You have to import &lt;code&gt;clpfd&lt;/code&gt; and write &lt;code&gt;A + 1 #= B + 2&lt;/code&gt;.)&lt;/p&gt;
    &lt;p&gt;I don't like this, but I'm a hypocrite for saying that because I appreciate the idea and don't mind custom symbols in other languages. I guess what annoys me is there's no official definition of what &lt;code&gt;^(a, b)&lt;/code&gt; is, it's purely a convention. ISO Prolog uses &lt;code&gt;-(a, b)&lt;/code&gt; (aka &lt;code&gt;a-b&lt;/code&gt;) as a convention to mean "pairs", and the only way to realize that is to see that an awful lot of standard modules use that convention. But you can use &lt;code&gt;-(a, b)&lt;/code&gt; to mean something else in your own code and nothing will warn you of the inconsistency.&lt;/p&gt;
    &lt;p&gt;Anyway I griped about pairs so I can gripe about &lt;code&gt;sort&lt;/code&gt;.&lt;/p&gt;
    &lt;h3&gt;go home sort, ur drunk&lt;/h3&gt;
    &lt;p&gt;This one's just a blunder:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;?-&lt;/span&gt; &lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
       &lt;span class="nv"&gt;Out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt; &lt;span class="c1"&gt;% wat&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;According to an expert online this is because sort is supposed to return a sorted &lt;em&gt;set&lt;/em&gt;, not a sorted list. If you want to preserve duplicates you're supposed to lift all of the values into &lt;code&gt;-($key, $value)&lt;/code&gt; compound terms, then use &lt;a href="https://eu.swi-prolog.org/pldoc/doc_for?object=keysort/2" target="_blank"&gt;keysort&lt;/a&gt;, then extract the values. And, since there's no functions, this process takes at least three lines. This is also how you're supposed to sort by a custom predicate, like "the second value of a compound term". &lt;/p&gt;
    &lt;p&gt;(Most (but not all) distributions have a duplicate merge like &lt;a href="https://eu.swi-prolog.org/pldoc/doc_for?object=msort/2" target="_blank"&gt;msort&lt;/a&gt;. SWI-Prolog also has a &lt;a href="https://eu.swi-prolog.org/pldoc/doc_for?object=predsort/3" target="_blank"&gt;sort by key&lt;/a&gt; but it removes duplicates.)&lt;/p&gt;
    &lt;h3&gt;Please just let me end rules with a trailing comma instead of a period, I'm begging you&lt;/h3&gt;
    &lt;p&gt;I don't care if it makes fact parsing ambiguous, I just don't want "reorder two lines" to be a syntax error anymore&lt;/p&gt;
    &lt;hr/&gt;
    &lt;p&gt;I expect by this time tomorrow I'll have been Cunningham'd and there will be a 2000 word essay about how all of my gripes are either easily fixable by doing XYZ or how they are the best possible choice that Prolog could have made. I mean, even in writing this I found out some fixes to problems I had. Like I was going to gripe about how I can't run SWI-Prolog queries from the command line but, in doing do diligence finally &lt;em&gt;finally&lt;/em&gt; figured it out:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;swipl&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;halt&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bagof(X, Goal, Xs), print(Xs)"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./file.pl
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;It's pretty clunky but still better than the old process of having to enter an interactive session every time I wanted to validate a script change.&lt;/p&gt;
    &lt;p&gt;(Also, answer set programming is pretty darn cool. Excited to write about it in the book!)&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:dif"&gt;
    &lt;p&gt;A couple of people mentioned using &lt;a href="https://eu.swi-prolog.org/pldoc/doc_for?object=dif/2" target="_blank"&gt;dif/2&lt;/a&gt; instead of &lt;code&gt;\+ A = B&lt;/code&gt;. Dif is great but usually I hit the negation footgun with things like &lt;code&gt;\+ foo(A, B), bar(B, C), baz(A, C)&lt;/code&gt;, where &lt;code&gt;dif/2&lt;/code&gt; isn't applicable. &lt;a class="footnote-backref" href="#fnref:dif" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Wed, 14 Jan 2026 16:48:51 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/my-gripes-with-prolog/</guid></item><item><title>The Liskov Substitution Principle does more than you think</title><link>https://buttondown.com/hillelwayne/archive/the-liskov-substitution-principle-does-more-than/</link><description>
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;Happy New Year! I'm done with the newsletter hiatus and am going to try updating weekly again. To ease into things a bit, I'll try to keep posts a little more off the cuff and casual for a while, at least until &lt;a href="https://leanpub.com/logic/" target="_blank"&gt;&lt;em&gt;Logic for Programmers&lt;/em&gt;&lt;/a&gt; is done. Speaking of which, v0.13 should be out by the end of this month.&lt;/p&gt;
    &lt;p&gt;So for this newsletter I want to talk about the &lt;a href="https://en.wikipedia.org/wiki/Liskov_substitution_principle" target="_blank"&gt;Liskov Substitution Principle&lt;/a&gt; (LSP). Last week I read &lt;a href="https://loup-vaillant.fr/articles/solid-bull" target="_blank"&gt;A SOLID Load of Bull&lt;/a&gt; by cryptographer Loupe Vaillant, where he argues the &lt;a href="https://en.wikipedia.org/wiki/SOLID" target="_blank"&gt;SOLID&lt;/a&gt; principles of OOP are not worth following. He makes an exception for LSP, but also claims that it's "just subtyping" and further:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;If I were trying really hard to be negative about the Liskov substitution principle, I would stress that &lt;strong&gt;it only applies when inheritance is involved&lt;/strong&gt;, and inheritance is strongly discouraged anyway.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;LSP is more interesting than that! In the original paper, &lt;a href="https://www.cs.cmu.edu/~wing/publications/LiskovWing94.pdf" target="_blank"&gt;A Behavioral Notion of Subtyping&lt;/a&gt;, Barbara Liskov and Jeannette Wing start by defining a "correct" subtyping as follows:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;Subtype Requirement: Let ϕ(x) be a property provable about objects x of type T. Then ϕ(y) should be true for objects y of type S where S is a subtype of T.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;From then on, the paper determine what &lt;em&gt;guarantees&lt;/em&gt; that a subtype is correct.&lt;sup id="fnref:safety"&gt;&lt;a class="footnote-ref" href="#fn:safety"&gt;1&lt;/a&gt;&lt;/sup&gt;  They identify three conditions: &lt;/p&gt;
    &lt;ol&gt;
    &lt;li&gt;Each of the subtype's methods has the same or weaker preconditions and the same or stronger postconditions as the corresponding supertype method.&lt;sup id="fnref:cocontra"&gt;&lt;a class="footnote-ref" href="#fn:cocontra"&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;/li&gt;
    &lt;li&gt;The subtype satisfies all state invariants of the supertype. &lt;/li&gt;
    &lt;li&gt;The subtype satisfies all "history properties" of the supertype. &lt;sup id="fnref:refinement"&gt;&lt;a class="footnote-ref" href="#fn:refinement"&gt;3&lt;/a&gt;&lt;/sup&gt; e.g. if a supertype has an immutable field, the subtype cannot make it mutable. &lt;/li&gt;
    &lt;/ol&gt;
    &lt;p&gt;(Later, Elisa Baniassad and Alexander Summers &lt;a href="https://www.cs.ubc.ca/~alexsumm/papers/BaniassadSummers21.pdf" target="_blank"&gt;would realize&lt;/a&gt; these are equivalent to "the subtype passes all black-box tests designed for the supertype", which I wrote a little bit more about &lt;a href="https://www.hillelwayne.com/post/lsp/" target="_blank"&gt;here&lt;/a&gt;.)&lt;/p&gt;
    &lt;p&gt;I want to focus on the first rule about preconditions and postconditions. This refers to the method's &lt;strong&gt;contract&lt;/strong&gt;.  For a function &lt;code&gt;f&lt;/code&gt;, &lt;code&gt;f.Pre&lt;/code&gt; is what must be true going into the function, and &lt;code&gt;f.Post&lt;/code&gt; is what the function guarantees on execution. A canonical example is square root: &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sqrt.Pre(x) = x &amp;gt;= 0
    sqrt.Post(x, out) = out &amp;gt;= 0 &amp;amp;&amp;amp; out*out == x
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;Mathematically we would write this as &lt;code&gt;all x: f.Pre(x) =&amp;gt; f.Post(x)&lt;/code&gt; (where &lt;code&gt;=&amp;gt;&lt;/code&gt; is the &lt;a href="https://en.wikipedia.org/wiki/Material_conditional" target="_blank"&gt;implication operator&lt;/a&gt;). If that relation holds for all &lt;code&gt;x&lt;/code&gt;, we say the function is "correct". With this definition we can actually formally deduce the first  subtyping requirement. Let &lt;code&gt;caller&lt;/code&gt; be some code that uses a method, which we will call &lt;code&gt;super&lt;/code&gt;, and let both &lt;code&gt;caller&lt;/code&gt; and &lt;code&gt;super&lt;/code&gt; be correct. Then we know the following statements are true:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  1. caller.Pre &amp;amp;&amp;amp; stuff =&amp;gt; super.Pre
      2. super.Pre =&amp;gt; super.Post
      3. super.Post &amp;amp;&amp;amp; more_stuff =&amp;gt; caller.Post
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;Now let's say we substitute &lt;code&gt;super&lt;/code&gt; with &lt;code&gt;sub&lt;/code&gt;, which is also correct. Here is what we now know is true: &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt; 1. caller.Pre =&amp;gt; super.Pre
    &lt;span class="gd"&gt;- 2. super.Pre =&amp;gt; super.Post&lt;/span&gt;
    &lt;span class="gi"&gt;+ 2. sub.Pre =&amp;gt; sub.Post&lt;/span&gt;
    &lt;span class="w"&gt; &lt;/span&gt; 3. super.Post =&amp;gt; caller.Post
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;When is &lt;code&gt;caller&lt;/code&gt; still correct? When we can fill in the "gaps" in the chain, aka if &lt;code&gt;super.Pre =&amp;gt; sub.Pre&lt;/code&gt; and &lt;code&gt;sub.Post =&amp;gt; super.Post&lt;/code&gt;. In other words, if &lt;code&gt;sub&lt;/code&gt;'s preconditions are weaker than (or equivalent to) &lt;code&gt;super&lt;/code&gt;'s preconditions and if &lt;code&gt;sub&lt;/code&gt;'s postconditions are stronger than (or equivalent to) &lt;code&gt;super&lt;/code&gt;'s postconditions.&lt;/p&gt;
    &lt;p&gt;Notice that I never actually said &lt;code&gt;sub&lt;/code&gt; was from a subtype of &lt;code&gt;super&lt;/code&gt;! The LSP conditions (at least, the contract rule of LSP) doesn't just apply to &lt;em&gt;subtypes&lt;/em&gt; but can be applied in any situation where we substitute a function or block of code for another. Subtyping is a common place where this happens, but by no means the only! We can also substitute across time.Any time we modify some code's behavior, we are effectively substituting the new version in for the old version, and so the new version's contract must be compatible with the old version's to guarantee no existing code is broken.&lt;/p&gt;
    &lt;p&gt;For example, say we maintain an API or function with two required inputs, &lt;code&gt;X&lt;/code&gt; and &lt;code&gt;Y&lt;/code&gt;, and one optional input, &lt;code&gt;Z&lt;/code&gt;. Making &lt;code&gt;Z&lt;/code&gt; required strengthens the precondition ("input must have Z" is stronger than "input may have Z"), so potentially breaks existing users of our API. Making &lt;code&gt;Y&lt;/code&gt; optional weakens the precondition ("input may have Y" is weaker than "input must have Y"), so is guaranteed to be compatible.&lt;/p&gt;
    &lt;p&gt;(This also underpins &lt;a href="https://en.wikipedia.org/wiki/Robustness_principle" target="_blank"&gt;The robustness principle&lt;/a&gt;: "be conservative in what you send, be liberal in what you accept".)&lt;/p&gt;
    &lt;p&gt;Now the dark side of all this is &lt;a href="https://www.hyrumslaw.com/" target="_blank"&gt;Hyrum's Law&lt;/a&gt;. In the below code, are &lt;code&gt;new&lt;/code&gt;'s postconditions stronger than &lt;code&gt;old&lt;/code&gt;'s postconditions? &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;old&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"baz"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;On a first appearance, this is a strengthened postcondition: &lt;code&gt;out.contains_keys([a, b, c]) =&amp;gt; out.contains_keys([a, b])&lt;/code&gt;. But now someone does this:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;my_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"blat"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="n"&gt;my_dict&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;my_dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"blat"&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;Oh no, their code now breaks! They saw &lt;code&gt;old&lt;/code&gt; had the postcondition "&lt;code&gt;out&lt;/code&gt; does NOT contain "c" as a key", and then wrote their code expecting that postcondition. In a sense, &lt;em&gt;any&lt;/em&gt; change the postcondition can potentially break &lt;em&gt;someone&lt;/em&gt;. "All observable behaviors of your system
    will be depended on by somebody", as &lt;a href="https://www.hyrumslaw.com/" target="_blank"&gt;Hyrum's Law&lt;/a&gt; puts it.&lt;/p&gt;
    &lt;p&gt;So we need to be explicit in what our postconditions actually are, and properties of the output that are not part of our explicit postconditions are subject to be violated on the next version. You'll break people's workflows but you also have grounds to say "I warned you".&lt;/p&gt;
    &lt;p&gt;Overall, Liskov and Wing did their work in the context of subtyping, but the principles are more widely applicable, certainly to more than just the use of inheritance.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:safety"&gt;
    &lt;p&gt;Though they restrict it to just &lt;a href="https://www.hillelwayne.com/post/safety-and-liveness/" target="_blank"&gt;safety properties&lt;/a&gt;. &lt;a class="footnote-backref" href="#fnref:safety" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:cocontra"&gt;
    &lt;p&gt;The paper lists a couple of other authors as introduce the idea of "contra/covariance rules", but part of being "off-the-cuff and casual" means not diving into every referenced paper. So they might have gotten the pre/postconditions thing from an earlier author, dunno for sure! &lt;a class="footnote-backref" href="#fnref:cocontra" title="Jump back to footnote 2 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:refinement"&gt;
    &lt;p&gt;I &lt;em&gt;believe&lt;/em&gt; that this is equivalent to the formal methods notion of a &lt;a href="https://www.hillelwayne.com/post/refinement/" target="_blank"&gt;refinement&lt;/a&gt;. &lt;a class="footnote-backref" href="#fnref:refinement" title="Jump back to footnote 3 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Tue, 06 Jan 2026 16:51:26 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/the-liskov-substitution-principle-does-more-than/</guid></item><item><title>Some Fun Software Facts</title><link>https://buttondown.com/hillelwayne/archive/some-fun-software-facts/</link><description>
    &lt;p&gt;Last newsletter of the year!&lt;/p&gt;
    &lt;p&gt;First some news on &lt;em&gt;Logic for Programmers&lt;/em&gt;. Thanks to everyone who donated to the &lt;a href="https://buttondown.com/hillelwayne/archive/get-logic-for-programmers-50-off-support-chicago" target="_blank"&gt;feedchicago charity drive&lt;/a&gt;! In total we raised $2250 for Chicago food banks. Proof &lt;a href="https://link.fndrsp.net/CL0/https:%2F%2Fgiving.chicagosfoodbank.org%2Freceipts%2FBMDDDCAF%3FreceiptType=oneTime%26emailLog=YS699MZW/2/0100019ae2b7eb92-7c917ad0-c94e-4fe2-8ee1-1b9dc521c607-000000/brmxoTOvoJN94I9nQH26s7fRrmyFDj_Jir1FySSoxCw=434" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;If you missed buying &lt;em&gt;Logic for Programmers&lt;/em&gt; real cheap in the charity drive, you can still get it for $10 off with the holiday code &lt;a href="https://leanpub.com/logic/c/hannukah-presents" target="_blank"&gt;hannukah-presents&lt;/a&gt;. This will last from now until the end of the year. After that, I'll be raising the price from $25 to $30.&lt;/p&gt;
    &lt;p&gt;Anyway, to make this more than just some record keeping, let's close out with something light. I'm one of those people who loves hearing "fun facts" about stuff. So here's some random fun facts I accumulated about software over the years:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;In 2017, a team of eight+ programmers &lt;a href="https://codegolf.stackexchange.com/questions/11880/build-a-working-game-of-tetris-in-conways-game-of-life" target="_blank"&gt;successfully implemented Tetris&lt;/a&gt; as a &lt;a href="https://en.wikipedia.org/wiki/Conway's_Game_of_Life" target="_blank"&gt;game of life simulation&lt;/a&gt;. The GoL grid had an area of 30 trillion pixels and implemented a full programmable CPU as part of the project.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;Computer systems have to deal with leap seconds in order to keep UTC (where one day is 86,400 seconds) in sync with UT1 (where one day is exactly one full earth rotation). The people in charge recently passed a resolution to abolish the leap second by 2035, letting UTC and UT1 slowly drift out of sync.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;a href="https://buttondown.com/hillelwayne/archive/vim-is-turing-complete/" target="_blank"&gt;Vim is Turing complete&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;ul&gt;
    &lt;li&gt;The backslash character basically didn't exist in writing before 1930, and &lt;a href="http://dump.deadcodersociety.org/ascii.pdf" target="_blank"&gt;was only added to ASCII&lt;/a&gt; so mathematicians (and ALGOLists) could write &lt;code&gt;/\&lt;/code&gt; and &lt;code&gt;\/&lt;/code&gt;. It's popular use in computing stems entirely from being a useless key on the keyboard.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Galactic_algorithm" target="_blank"&gt;Galactic Algorithms&lt;/a&gt; are algorithms that are theoretically faster than algorithms we use, but only at scales that make them impractical. For example, matrix multiplication of NxN is &lt;a href="https://en.wikipedia.org/wiki/Strassen_algorithm" target="_blank"&gt;normally&lt;/a&gt; O(N^2.81). The &lt;a href="https://www-auth.cs.wisc.edu/lists/theory-reading/2009-December/pdfmN6UVeUiJ3.pdf" target="_blank"&gt;Coppersmith Winograd&lt;/a&gt; algorithm is O(N^2.38), but is so complex that it's vastly slower for even &lt;a href="https://mathoverflow.net/questions/1743/what-is-the-constant-of-the-coppersmith-winograd-matrix-multiplication-algorithm" target="_blank"&gt;10,000 x 10,000 matrices&lt;/a&gt;. It's still interesting in advancing our mathematical understanding of algorithms!&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;Cloudflare generates random numbers by, in part, &lt;a href="https://www.cloudflare.com/learning/ssl/lava-lamp-encryption/" target="_blank"&gt;taking pictures of 100 lava lamps&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;Mergesort is older than bubblesort. Quicksort is slightly younger than bubblesort but older than the &lt;em&gt;term&lt;/em&gt; "bubblesort". Bubblesort, btw, &lt;a href="https://buttondown.com/hillelwayne/archive/when-would-you-ever-want-bubblesort/" target="_blank"&gt;does have some uses&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;Speaking of mergesort, most implementations of mergesort pre-2006 &lt;a href="https://research.google/blog/extra-extra-read-all-about-it-nearly-all-binary-searches-and-mergesorts-are-broken/" target="_blank"&gt;were broken&lt;/a&gt;. Basically the problem was that the "find the midpoint of a list" step &lt;em&gt;could&lt;/em&gt; overflow if the list was big enough. For C with 32-bit signed integers, "big enough" meant over a billion elements, which was why the bug went unnoticed for so long.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;a href="https://nibblestew.blogspot.com/2023/09/circles-do-not-exist.html" target="_blank"&gt;PDF's drawing model cannot render perfect circles&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;People make fun of how you have to flip USBs three times to get them into a computer, but there's supposed to be a guide: according to the standard, USBs are supposed to be inserted &lt;em&gt;logo-side up&lt;/em&gt;. Of course, this assumes that the port is right-side up, too, which is why USB-C is just symmetric. &lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;I was gonna write a fun fact about how all spreadsheet software treats 1900 as a leap year, as that was a bug in Lotus 1-2-3 and everybody preserved backwards compatibility. But I checked and Google sheets considers it a normal year. So I guess the fun fact is that things have changed!&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;Speaking of spreadsheet errors, in 2020 &lt;a href="https://www.engadget.com/scientists-rename-genes-due-to-excel-151748790.html" target="_blank"&gt;biologists changed the official nomenclature&lt;/a&gt; of 27 genes because Excel kept parsing their names as dates. F.ex MARCH1 was renamed to MARCHF1 to avoid being parsed as "March 1st". Microsoft rolled out a fix for this... three years later.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;ul&gt;
    &lt;li&gt;It is possible to encode any valid JavaScript program with just the characters &lt;code&gt;()+[]!&lt;/code&gt;. This encoding is called &lt;a href="https://en.wikipedia.org/wiki/JSFuck" target="_blank"&gt;JSFuck&lt;/a&gt; and was once used to distribute malware on &lt;a href="https://arstechnica.com/information-technology/2016/02/ebay-has-no-plans-to-fix-severe-bug-that-allows-malware-distribution/" target="_blank"&gt;Ebay&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Happy holidays everyone, and see you in 2026!&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:status"&gt;
    &lt;p&gt;Current status update: I'm finally getting line by line structural editing done and it's turning up lots of improvements, so I'm doing more rewrites than I expected to be doing. &lt;a class="footnote-backref" href="#fnref:status" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Wed, 10 Dec 2025 18:45:37 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/some-fun-software-facts/</guid></item><item><title>One more week to the Logic for Programmers Food Drive</title><link>https://buttondown.com/hillelwayne/archive/one-more-week-to-the-logic-for-programmers-food/</link><description>
    &lt;p&gt;A couple of weeks ago I started a fundraiser for the &lt;a href="https://www.chicagosfoodbank.org/" target="_blank"&gt;Greater Chicago Food Depository&lt;/a&gt;: get &lt;a href="https://leanpub.com/logic/c/feedchicago" target="_blank"&gt;Logic for Programmers 50% off&lt;/a&gt; and all the royalties will go to charity.&lt;sup id="fnref:royalties"&gt;&lt;a class="footnote-ref" href="#fn:royalties"&gt;1&lt;/a&gt;&lt;/sup&gt; Since then, we've raised a bit over $1600. Y'all are great! &lt;/p&gt;
    &lt;p&gt;The fundraiser is going on until the end of November, so you still have one more week to get the book real cheap.&lt;/p&gt;
    &lt;p&gt;I feel a bit weird about doing two newsletter adverts without raw content, so here's a teaser from a old project I really need to get back to. &lt;a href="https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/#what-is-a-goto-statement-anyway" target="_blank"&gt;Notes on structured concurrency&lt;/a&gt; argues that old languages had a "old-testament fire-and-brimstone &lt;code&gt;goto&lt;/code&gt;" that could send control flow anywhere, like from the body of one function into the body of another function. This "wild goto", the article claims, what Dijkstra was railing against in &lt;a href="https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf" target="_blank"&gt;Go To Statement Considered Harmful&lt;/a&gt;, and that modern goto statements are much more limited, "tame" if you will, and wouldn't invoke Dijkstra's ire.&lt;/p&gt;
    &lt;p&gt;I've shared this historical fact about Dijkstra many times, but recently two &lt;a href="https://without.boats/blog/" target="_blank"&gt;separate&lt;/a&gt; &lt;a href="https://matklad.github.io/" target="_blank"&gt;people&lt;/a&gt; have told me it doesn't makes sense: Dijkstra used ALGOL-60, which &lt;em&gt;already had&lt;/em&gt; tame gotos. All of the problems he raises with &lt;code&gt;goto&lt;/code&gt; hold even for tame ones, none are exclusive to wild gotos. So &lt;/p&gt;
    &lt;p&gt;This got me looking to see which languages, if any, ever had the wild goto. I define this as any goto which lets you jump from outside to into a loop or function scope. Turns out, FORTRAN had tame gotos from the start, BASIC has wild gotos, and COBOL is a nonsense language intentionally designed to horrify me. I mean, look at this:&lt;/p&gt;
    &lt;p&gt;&lt;img alt="The COBOL ALTER statement, which redefines a goto target" class="newsletter-image" src="https://assets.buttondown.email/images/e4dfa0fd-fdd5-4fef-b813-4053a183be2f.png?w=960&amp;amp;fit=max"/&gt;&lt;/p&gt;
    &lt;p&gt;The COBOL ALTER statement &lt;em&gt;changes a &lt;code&gt;goto&lt;/code&gt;'s target at runtime&lt;/em&gt;. &lt;/p&gt;
    &lt;p&gt;(Early COBOL has tame gotos but only on a technicality: there are no nested scopes in COBOL so no jumping from outside and into a nested scope.)&lt;/p&gt;
    &lt;p&gt;Anyway I need to write up the full story (and complain about COBOL more) but this is pretty neat! Reminder, &lt;a href="https://leanpub.com/logic/c/feedchicago" target="_blank"&gt;fundraiser here&lt;/a&gt;. Let's get it to 2k.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:royalties"&gt;
    &lt;p&gt;Royalties are 80% so if you already have the book you get a bit more bang for your buck by donating to the GCFD directly &lt;a class="footnote-backref" href="#fnref:royalties" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Mon, 24 Nov 2025 18:21:49 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/one-more-week-to-the-logic-for-programmers-food/</guid></item><item><title>Get Logic for Programmers 50% off &amp; Support Chicago Foodbanks</title><link>https://buttondown.com/hillelwayne/archive/get-logic-for-programmers-50-off-support-chicago/</link><description>
    &lt;p&gt;From now until the end of the month, you can get &lt;a href="https://leanpub.com/logic/c/feedchicago" target="_blank"&gt;Logic for Programmers at half price&lt;/a&gt; with the coupon &lt;code&gt;feedchicago&lt;/code&gt;. All royalties from that coupon will go to the &lt;a href="https://www.chicagosfoodbank.org/" target="_blank"&gt;Greater Chicago Food Depository&lt;/a&gt;. Thank you!&lt;/p&gt;
    </description><pubDate>Mon, 10 Nov 2025 16:31:11 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/get-logic-for-programmers-50-off-support-chicago/</guid></item><item><title>I'm taking a break</title><link>https://buttondown.com/hillelwayne/archive/im-taking-a-break/</link><description>
    &lt;p&gt;Hi everyone,&lt;/p&gt;
    &lt;p&gt;I've been getting burnt out on writing a weekly software essay. It's gone from taking me an afternoon to write a post to taking two or three days, and that's made it really difficult to get other writing done. That, plus some short-term work and life priorities, means now feels like a good time for a break. &lt;/p&gt;
    &lt;p&gt;So I'm taking off from &lt;em&gt;Computer Things&lt;/em&gt; for the rest of the year. There &lt;em&gt;might&lt;/em&gt; be some announcements and/or one or two short newsletters in the meantime but I won't be attempting a weekly cadence until 2026.&lt;/p&gt;
    &lt;p&gt;Thanks again for reading!&lt;/p&gt;
    &lt;p&gt;Hillel&lt;/p&gt;
    </description><pubDate>Mon, 27 Oct 2025 21:02:37 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/im-taking-a-break/</guid></item><item><title>Modal editing is a weird historical contingency we have through sheer happenstance</title><link>https://buttondown.com/hillelwayne/archive/modal-editing-is-a-weird-historical-contingency/</link><description>
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;A while back my friend &lt;a href="https://morepablo.com/" target="_blank"&gt;Pablo Meier&lt;/a&gt; was reviewing some 2024 videogames and wrote &lt;a href="https://morepablo.com/2025/03/games-of-2024.html" target="_blank"&gt;this&lt;/a&gt;:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;I feel like some artists, if they didn't exist, would have the resulting void filled in by someone similar (e.g. if Katy Perry didn't exist, someone like her would have). But others don't have successful imitators or comparisons (thinking Jackie Chan, or Weird Al): they are irreplaceable.  &lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;He was using it to describe auteurs but I see this as a property of opportunity, in that "replaceable" artists are those who work in bigger markets. Katy Perry's market is large, visible and obviously (but not &lt;em&gt;easily&lt;/em&gt;) exploitable, so there are a lot of people who'd compete in her niche. Weird Al's market is unclear: while there were successful parody songs in the past, it wasn't clear there was enough opportunity there to support a superstar.&lt;/p&gt;
    &lt;p&gt;I think that modal editing is in the latter category. Vim is now very popular and has spawned numerous successors. But its key feature, &lt;strong&gt;modes&lt;/strong&gt;, is not obviously-beneficial, to the point that if Bill Joy didn't make vi (vim's direct predecessor) fifty years ago I don't think we'd have any modal editors today. &lt;/p&gt;
    &lt;h3&gt;A quick overview of "modal editing"&lt;/h3&gt;
    &lt;p&gt;In a non-modal editor, pressing the "u" key adds a "u" to your text, as you'd expect. In a &lt;strong&gt;modal editor&lt;/strong&gt;, pressing "u" does something different depending on the "mode" you are in. In Vim's default "normal" mode, "u" undoes the last change to the text, while in the "visual" mode it lowercases all selected text. It only inserts the character in "insert" mode. All other keys, as well as chorded shortcuts (&lt;code&gt;ctrl-x&lt;/code&gt;), work the same way. &lt;/p&gt;
    &lt;p&gt;The clearest benefit to this is you can densely pack the keyboard with advanced commands. The standard US keyboard has 48ish keys dedicated to inserting characters. With the ctrl and shift modifiers that becomes at least ~150 extra shortcuts for each other mode. This is also what IMO "spiritually" distinguishes modal editing from contextual shortcuts. Even if a unimodal editor lets you change a keyboard shortcut's behavior based on languages or focused panel, without global user-controlled modes it simply can't achieve that density of shortcuts.&lt;/p&gt;
    &lt;p&gt;Now while modal editing today is widely beloved (the Vim plugin for &lt;a href="https://marketplace.visualstudio.com/items?itemName=vscodevim.vim" target="_blank"&gt;VSCode&lt;/a&gt; has at least eight million downloads), I suspect it was "carried" by the popularity of vi, as opposed to driving vi's popularity.&lt;/p&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;h3&gt;Modal editing is an unusual idea&lt;/h3&gt;
    &lt;p&gt;Pre-vi editors weren't modal. Some, like &lt;a href="https://en.wikipedia.org/wiki/EDT_(Digital)" target="_blank"&gt;EDT/KED&lt;/a&gt;, used chorded commands, while others like &lt;a href="https://en.wikipedia.org/wiki/Ed_(software)" target="_blank"&gt;ed&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/TECO_(text_editor)" target="_blank"&gt;TECO&lt;/a&gt; basically REPLs for text-editing DSLs. Both of these ideas widely reappear in modern editors.&lt;/p&gt;
    &lt;p&gt;As far as I can tell, the first modal editor was Butler Lampson's &lt;a href="https://en.wikipedia.org/wiki/Bravo_(editor)" target="_blank"&gt;Bravo&lt;/a&gt; in 1974. Bill Joy &lt;a href="https://web.archive.org/web/20120210184000/http://web.cecs.pdx.edu/~kirkenda/joy84.html" target="_blank"&gt;admits he used it for inspiration&lt;/a&gt;: &lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;A lot of the ideas for the screen editing mode were stolen from a Bravo manual I surreptitiously looked at and copied. Dot is really the double-escape from Bravo, the redo command. Most of the stuff was stolen. &lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;Bill Joy probably took the idea because he was working on &lt;a href="https://en.wikipedia.org/wiki/ADM-3A" target="_blank"&gt;dumb terminals&lt;/a&gt; that were slow to register keystrokes, which put pressure to minimize the number needed for complex operations.&lt;/p&gt;
    &lt;p&gt;Why did Bravo have modal editing? Looking at the &lt;a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/11/15a-AltoHandbook.pdf" target="_blank"&gt;Alto handbook&lt;/a&gt;, I get the impression that Xerox was trying to figure out the best mouse and GUI workflows. Bravo was an experiment with modes, one hand on the mouse and one issuing commands on the keyboard. Other experiments included context menus (the Markup program) and toolbars (Draw).&lt;/p&gt;
    &lt;p&gt;Xerox very quickly decided &lt;em&gt;against&lt;/em&gt; modes, as the successors &lt;a href="http://www.bitsavers.org/pdf/xerox/alto/memos_1975/Gypsy_The_Ginn_Typescript_System_Apr75.pdf" target="_blank"&gt;Gypsy&lt;/a&gt; and &lt;a href="http://www.bitsavers.org/pdf/xerox/alto/BravoXMan.pdf" target="_blank"&gt;BravoX&lt;/a&gt; were modeless. Commands originally assigned to English letters were moved to graphical menus, special keys, and chords. &lt;/p&gt;
    &lt;p&gt;It seems to me that modes started as an unsuccessful experiment deal with a specific constraint and then later successfully adopted to deal with a different constraint. It was a specialized feature as opposed to a generally useful feature like chords.&lt;/p&gt;
    &lt;h3&gt;Modal editing didn't popularize vi&lt;/h3&gt;
    &lt;p&gt;While vi was popular at Bill Joy's coworkers, he doesn't &lt;a href="https://web.archive.org/web/20120210184000/http://web.cecs.pdx.edu/~kirkenda/joy84.html" target="_blank"&gt;attribute its success to its features&lt;/a&gt;:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;I think the wonderful thing about vi is that it has such a good market share because we gave it away. Everybody has it now. So it actually had a chance to become part of what is perceived as basic UNIX. EMACS is a nice editor too, but because it costs hundreds of dollars, there will always be people who won't buy it. &lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;Vi was distributed for free with the popular &lt;a href="https://en.wikipedia.org/wiki/Berkeley_Software_Distribution" target="_blank"&gt;BSD Unix&lt;/a&gt; and was standardized in &lt;a href="https://pubs.opengroup.org/onlinepubs/9799919799/" target="_blank"&gt;POSIX Issue 2&lt;/a&gt;, meaning all Unix OSes had to have vi. That arguably is what made it popular, and why so many people ended up learning a modal editor. &lt;/p&gt;
    &lt;h3&gt;Modal editing doesn't really spread outside of vim&lt;/h3&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;I think by the 90s, people started believing that modal editing was a Good Idea, if not an obvious one. That's why we see direct descendants of vi, most famously vim. It's also why extensible editors like Emacs and VSCode have vim-mode extensions, but these are but these are always simple emulation layers on top of a unimodal baselines. This was good for getting people used to the vim keybindings (I learned on &lt;a href="https://en.wikipedia.org/wiki/Kile" target="_blank"&gt;Kile&lt;/a&gt;) but it means people weren't really &lt;em&gt;doing&lt;/em&gt; anything with modal editing. It was always "The Vim Gimmick".&lt;/p&gt;
    &lt;p&gt;Modes also didn't take off anywhere else. There's no modal word processor, spreadsheet editor, or email client.&lt;sup id="fnref:gmail"&gt;&lt;a class="footnote-ref" href="#fn:gmail"&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;a href="https://www.visidata.org/" target="_blank"&gt;Visidata&lt;/a&gt; is an extremely cool modal data exploration tool but it's pretty niche. Firefox used to have &lt;a href="https://en.wikipedia.org/wiki/Vimperator" target="_blank"&gt;vimperator&lt;/a&gt; (which was inspired by Vim) but that's defunct now. Modal software means modal editing which means vi.&lt;/p&gt;
    &lt;p&gt;This has been changing a little, though! Nowadays we do see new modal text editors, like &lt;a href="https://kakoune.org/" target="_blank"&gt;kakoune&lt;/a&gt; and &lt;a href="https://helix-editor.com/" target="_blank"&gt;Helix&lt;/a&gt;, that don't just try to emulate vi but do entirely new things. These were made, though, in response to perceived shortcomings in vi's editing model. I think they are still classifiable as descendants. If vi never existed, would the developers of kak and helix have still made modal editors, or would they have explored different ideas? &lt;/p&gt;
    &lt;h3&gt;People aren't clamouring for more experiments&lt;/h3&gt;
    &lt;p&gt;Not too related to the overall picture, but a gripe of mine. Vi and vim have a set of hardcoded modes, and adding an entirely new mode is impossible. Like if a plugin (like vim's default &lt;code&gt;netrw&lt;/code&gt;) adds a file explorer it should be able to add a filesystem mode, right? But it can't, so instead it waits for you to open the filesystem and then &lt;a href="https://github.com/vim/vim/blob/0124320c97b0fbbb44613f42fc1c34fee6181fc8/runtime/pack/dist/opt/netrw/autoload/netrw.vim#L4867" target="_blank"&gt;adds 60 new mappings to normal mode&lt;/a&gt;. There's no way to properly add a "filesystem" mode, a "diff" mode, a "git" mode, etc, so plugin developers have to &lt;a href="https://www.hillelwayne.com/post/software-mimicry/" target="_blank"&gt;mimic&lt;/a&gt; them.&lt;/p&gt;
    &lt;p&gt;I don't think people see this as a problem, though! Neovim, which aims to fix all of the baggage in vim's legacy, didn't consider creating modes an important feature. Kak and Helix, which reimagine modal editing from from the ground up, don't support creating modes either.&lt;sup id="fnref:helix"&gt;&lt;a class="footnote-ref" href="#fn:helix"&gt;2&lt;/a&gt;&lt;/sup&gt; People aren't clamouring for new modes!&lt;/p&gt;
    &lt;h2&gt;Modes are a niche power user feature&lt;/h2&gt;
    &lt;p&gt;So far I've been trying to show that vi is, in Pablo's words, "irreplaceable". Editors weren't doing modal editing before Bravo, and even after vi became incredibly popular, unrelated editors did not adapt modal editing. At most, they got a vi emulation layer. Kak and helix complicate this story but I don't think they refute it; they appear much later and arguably count as descendants (so are related). &lt;/p&gt;
    &lt;p&gt;I think the best explanation is that in a vacuum modal editing sounds like a bad idea. The mode is global state that users always have to know, which makes it dangerous. To use new modes well you have to memorize all of the keybindings, which makes it difficult. Modal editing has a brutal skill floor before it becomes more efficient than a unimodal, chorded editor like VSCode.&lt;/p&gt;
    &lt;p&gt;That's why it originally appears in very specific circumstances, as early experiments in mouse UX and as a way of dealing with modem latencies. The fact we have vim today is a historical accident. &lt;/p&gt;
    &lt;p&gt;And I'm glad for it! You can pry Neovim from my cold dead hands, you monsters.&lt;/p&gt;
    &lt;hr/&gt;
    &lt;h1&gt;&lt;a href="https://www.p99conf.io/" target="_blank"&gt;P99 talk this Thursday&lt;/a&gt;!&lt;/h1&gt;
    &lt;p&gt;My talk, "Designing Low-Latency Systems with TLA+", is happening 10/23 at 11:40 central time. Tickets are free, the conf is online, and the talk's only 16 minutes, so come check it out!&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:gmail"&gt;
    &lt;p&gt;I guess if you squint &lt;a href="https://support.google.com/mail/answer/6594?hl=en&amp;amp;co=GENIE.Platform%3DDesktop" target="_blank"&gt;gmail kinda counts&lt;/a&gt; but it's basically an antifeature &lt;a class="footnote-backref" href="#fnref:gmail" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:helix"&gt;
    &lt;p&gt;It looks like Helix supports &lt;a href="https://docs.helix-editor.com/remapping.html" target="_blank"&gt;creating minor modes&lt;/a&gt;, but these are only active for one keystroke, making them akin to a better, more ergonomic version of vim multikey mappings. &lt;a class="footnote-backref" href="#fnref:helix" title="Jump back to footnote 2 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Tue, 21 Oct 2025 16:46:24 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/modal-editing-is-a-weird-historical-contingency/</guid></item><item><title>The Phase Change</title><link>https://buttondown.com/hillelwayne/archive/the-phase-change/</link><description>
    &lt;p&gt;Last week I ran my first 10k.&lt;/p&gt;
    &lt;p&gt;It wasn't a race or anything. I left that evening planning to run a 5k, and then three miles later thought "what if I kept going?"&lt;sup id="fnref:distance"&gt;&lt;a class="footnote-ref" href="#fn:distance"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
    &lt;p&gt;I've been running for just over two years now. My goal was to run a mile, then three, then three at a pace faster than a power-walk. I wish I could say that I then found joy in running, but really I was just mad at myself for being so bad at it. Spite has always been my brightest muse.&lt;/p&gt;
    &lt;p&gt;Looking back, the thing I find most fascinating is what progress looked like. I couldn't tell you if I was physically progressing steadily, but for sure mental progress moved in discrete jumps. For a long time a 5k was me pushing myself, then suddenly a "phase change" happens and it becomes something I can just do on a run. Sometime in the future the 10k will feel the same way.&lt;/p&gt;
    &lt;p&gt;I've noticed this in a lot of other places. For every skill I know, my sense of myself follows a phase change. In every programming language I've ever learned, I lurch from "bad" to "okay" to "good". There's no "20% bad / 80% normal" in between. Pedagogical experts say that learning is about steadily building a &lt;a href="https://teachtogether.tech/en/index.html#s:models" target="_blank"&gt;mental model&lt;/a&gt; of the topic. It really feels like knowledge grows continuously, and then it suddenly becomes a model.&lt;/p&gt;
    &lt;p&gt;Now, for all the time I spend writing about software history and software theory and stuff, my actually job boils down to &lt;a href="https://www.hillelwayne.com/consulting/" target="_blank"&gt;teaching formal methods&lt;/a&gt;. So I now have two questions about phase changes.&lt;/p&gt;
    &lt;p&gt;The first is "can we make phase changes happen faster?" I don't know if this is even possible! I've found lots of ways to teach concepts faster, cover more ground in less time, so that people know the material more quickly. But it doesn't seem to speed up that very first phase change from "this is foreign" to "this is normal". Maybe we can't really do that until we've spent enough effort on understanding.&lt;/p&gt;
    &lt;p&gt;So the second may be more productive: "can we motivate people to keep going until the phase change?" This is a lot easier to tackle! For example, removing frustration makes a huge difference. Getting a proper pair of running shoes made running so much less unpleasant, and made me more willing to keep putting in the hours. For teaching tech topics like formal methods, this often takes the form of better tooling and troubleshooting info.&lt;/p&gt;
    &lt;p&gt;We can also reduce the effort of investing time. This is also why I prefer to pair on writing specifications with clients and not just write specs for them. It's more work for them than fobbing it all off on me, but a whole lot &lt;em&gt;less&lt;/em&gt; work than writing the spec by themselves, so they'll put in time and gradually develop skills on their own.&lt;/p&gt;
    &lt;p&gt;Question two seems much more fruitful than question one but also so much less interesting! Speeding up the phase change feels like the kind of dream that empires are built on. I know I'm going to keep obsessing over it, even if that leads nowhere.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:distance"&gt;
    &lt;p&gt;For non-running Americans: 5km is about 3.1 miles, and 10km is 6.2. &lt;a class="footnote-backref" href="#fnref:distance" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Thu, 16 Oct 2025 14:59:25 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/the-phase-change/</guid></item><item><title>Three ways formally verified code can go wrong in practice</title><link>https://buttondown.com/hillelwayne/archive/three-ways-formally-verified-code-can-go-wrong-in/</link><description>
    &lt;h3&gt;New Logic for Programmers Release!&lt;/h3&gt;
    &lt;p&gt;&lt;a href="https://leanpub.com/logic/" rel="noopener noreferrer nofollow" target="_blank"&gt;v0.12 is now available&lt;/a&gt;! This should be the last major content release. The next few months are going to be technical review, copyediting and polishing, with a hopeful 1.0 release in March. &lt;a href="https://github.com/logicforprogrammers/book-assets/blob/master/CHANGELOG.md" rel="noopener noreferrer nofollow" target="_blank"&gt;Full release notes here&lt;/a&gt;.&lt;/p&gt;
    &lt;figure&gt;&lt;img alt="Cover of the boooooook" draggable="false" src="https://assets.buttondown.email/images/92b4a35d-2bdd-416a-92c7-15ff42b49d8d.jpg?w=960&amp;amp;fit=max"/&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;h1&gt;Three ways formally verified code can go wrong in practice&lt;/h1&gt;
    &lt;p&gt;I run this small project called &lt;a href="https://github.com/hwayne/lets-prove-leftpad" rel="noopener noreferrer nofollow" target="_blank"&gt;Let's Prove Leftpad&lt;/a&gt;, where people submit formally verified proofs of the &lt;a href="https://en.wikipedia.org/wiki/Npm_left-pad_incident" rel="noopener noreferrer nofollow" target="_blank"&gt;eponymous meme&lt;/a&gt;. Recently I read &lt;a href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/" rel="noopener noreferrer nofollow" target="_blank"&gt;Breaking “provably correct” Leftpad&lt;/a&gt;, which argued that most (if not all) of the provably correct leftpads have bugs! The lean proof, for example, &lt;em&gt;should&lt;/em&gt; render &lt;code&gt;leftpad('-', 9, אֳֽ֑)&lt;/code&gt; as &lt;code&gt;---------אֳֽ֑&lt;/code&gt;, but actually does &lt;code&gt;------אֳֽ֑&lt;/code&gt;.&lt;/p&gt;
    &lt;p&gt;You can read the article for a good explanation of why this goes wrong (Unicode). The actual problem is that correct can mean two different things, and this leads to confusion about how much formal methods can actually guarantee us. So I see this as a great opportunity to talk about the nature of proof, correctness, and how "correct" code can still have bugs.&lt;/p&gt;
    &lt;h2&gt;What we talk about when we talk about correctness&lt;/h2&gt;
    &lt;p&gt;In most of the real world, correct means "no bugs". Except "bugs" isn't a very clear category. A bug is anything that causes someone to say "this isn't working right, there's a bug." Being too slow is a bug, a typo is a bug, etc. "correct" is a little fuzzy.&lt;/p&gt;
    &lt;p&gt;In formal methods, "correct" has a very specific and precise meaning: the code conforms to a &lt;strong&gt;specification&lt;/strong&gt; (or "spec"). The spec is a higher-level description of what is supposed the code's properties, usually something we can't just directly implement. Let's look at the most popular kind of proven specification:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Haskell&lt;/span&gt;
    &lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;The type signature &lt;code&gt;Int -&amp;gt; Int&lt;/code&gt; is a specification! It corresponds to the logical statement &lt;code&gt;all x in Int: inc(x) in Int&lt;/code&gt;. The Haskell type checker can automatically verify this for us. It cannot, however, verify properties like &lt;code&gt;all x in Int: inc(x) &amp;gt; x&lt;/code&gt;. Formal verification is concerned with verifying arbitrary properties beyond what is (easily) automatically verifiable. Most often, this takes the form of proof. A human manually writes a proof that the code conforms to its specification, and the prover checks that the proof is correct.&lt;/p&gt;
    &lt;p&gt;Even if we have a proof of "correctness", though, there's a few different ways the code can still have bugs.&lt;/p&gt;
    &lt;h3&gt;1. The proof is invalid&lt;/h3&gt;
    &lt;p&gt;For some reason the proof doesn't actually show the code matches the specification. This is pretty common in pencil-and-paper verification, where the proof is checked by someone saying "yep looks good to me". It's much rarer when doing formal verification but it can still happen in a couple of specific cases:&lt;/p&gt;
    &lt;ol&gt;&lt;li&gt;&lt;p&gt;The theorem prover itself has a bug (in the code or introduced in the compiled binary) that makes it accept an incorrect proof. This is something people are really concerned about but it's so much rarer than every other way verified code goes wrong, so is only included for completeness.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;For convenience, most provers and FM languages have an "just accept this statement is true" feature. This helps you work on the big picture proof and fill in the details later. If you leave in a shortcut, &lt;em&gt;and&lt;/em&gt; the compiler is configured to allow code-with-proof-assumptions to compile, &lt;em&gt;then&lt;/em&gt; you can compile incorrect code that "passes the proof checker". You really should know better, though.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;h3&gt;2. The properties are wrong&lt;/h3&gt;
    &lt;blockquote&gt;&lt;figure&gt;&lt;img alt="The horrible bug you had wasn't covered in the specification/came from some other module/etc" draggable="false" src="https://cdn.prod.website-files.com/673b407e535dbf3b547179ff/681ca0bf4a045f39f785faeb_AD_4nXfFhdn6DGmgLAcmaUNHl9a3Nog8gH8Hluve5Kof7zLk4CyOlD4zCmCqVJaowKqu-pTicwZ393jE7anIrjYZTSuRvGiYhFhAkkX9vifNt9vEWYwZUp65hsbrRTmZzRgb9vgu7n7buA.png"/&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href="https://www.galois.com/articles/what-works-and-doesnt-selling-formal-methods" rel="noopener noreferrer nofollow" target="_blank"&gt;Galois&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
    &lt;p&gt;This code is provably correct:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;The only specification I've given is the type signature &lt;code&gt;Int -&amp;gt; Int&lt;/code&gt;. At no point did I put the property &lt;code&gt;inc(x) &amp;gt; x&lt;/code&gt; in my specification, so it doesn't matter that it doesn't hold, the code is still "correct".&lt;/p&gt;
    &lt;p&gt;This is what "went wrong" with the leftpad proofs. They do &lt;em&gt;not&lt;/em&gt; prove the property "&lt;code&gt;leftpad(c, n, s)&lt;/code&gt; will take up either &lt;code&gt;n&lt;/code&gt; spaces on the screen or however many characters &lt;code&gt;s&lt;/code&gt; takes up (if more than &lt;code&gt;n&lt;/code&gt;)". They prove the weaker property "&lt;code&gt;len(leftpad(c, n, s)) == max(n, len(s))&lt;/code&gt;, for however you want to define &lt;code&gt;len(string)&lt;/code&gt;". The second is a rough proxy for the first that works in most cases, but if someone really needs the former property they are liable to experience a bug.&lt;/p&gt;
    &lt;p&gt;Why don't we prove the stronger property? Sometimes it's because the code is meant to be used one way and people want to use it another way. This can lead to accusations that the developer is "misusing the provably correct code" but this should more often be seen as the verification expert failing to educate devs on was actually "proven".&lt;/p&gt;
    &lt;p&gt;Sometimes it's because the property is too hard to prove. "Outputs are visually aligned" is a proof about Unicode inputs, and the &lt;em&gt;core&lt;/em&gt; Unicode specification is &lt;a href="https://www.unicode.org/versions/Unicode17.0.0/UnicodeStandard-17.0.pdf" rel="noopener noreferrer nofollow" target="_blank"&gt;1,243 pages long&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;Sometimes it's because the property we want is too hard to &lt;em&gt;express&lt;/em&gt;. How do you mathematically represent "people will perceive the output as being visually aligned"? Is it OS and font dependent? These two lines are exactly five characters but not visually aligned:&lt;/p&gt;
    &lt;blockquote&gt;&lt;p&gt;|||||&lt;/p&gt;&lt;p&gt;MMMMM&lt;/p&gt;&lt;/blockquote&gt;
    &lt;p&gt;Or maybe they are aligned for you! I don't know, lots of people read email in a monospace font. "We can't express the property" comes up a lot when dealing with human/business concepts as opposed to mathematical/computational ones.&lt;/p&gt;
    &lt;p&gt;Finally, there's just the possibility of a brain fart. All of the proofs in &lt;a href="https://research.google/blog/extra-extra-read-all-about-it-nearly-all-binary-searches-and-mergesorts-are-broken/" rel="noopener noreferrer nofollow" target="_blank"&gt;Nearly All Binary Searches and Mergesorts are Broken&lt;/a&gt; are like this. They (informally) proved the correctness of binary search with unbound integers, forgetting that many programming languages use &lt;em&gt;machine&lt;/em&gt; integers, where a large enough sum can overflow.&lt;/p&gt;
    &lt;h3&gt;3. The assumptions are wrong&lt;/h3&gt;
    &lt;p&gt;This is arguably the most important and most subtle source of bugs. Most properties we prove aren't "&lt;code&gt;X&lt;/code&gt; is always true". They are "&lt;em&gt;assuming&lt;/em&gt; &lt;code&gt;Y&lt;/code&gt; is true, &lt;code&gt;X&lt;/code&gt; is also true". Then if &lt;code&gt;Y&lt;/code&gt; is not true, the proof no longer guarantees &lt;code&gt;X&lt;/code&gt;. A good example of this is binary &lt;s&gt;sort&lt;/s&gt; &lt;em&gt;search&lt;/em&gt;, which only correctly finds elements &lt;em&gt;assuming&lt;/em&gt; the input list is sorted. If the list is not sorted, it will not work correctly.&lt;/p&gt;
    &lt;p&gt;Formal verification adds two more wrinkles. One: sometimes we need assumptions to make the property valid, but we can also add them to make the proof easier. So the code can be bug-free even if the assumptions used to verify it no longer hold! Even if a leftpad implements visual alignment for all Unicode glyphs, it will be a lot easier to &lt;em&gt;prove&lt;/em&gt; visual alignment for just ASCII strings and padding.&lt;/p&gt;
    &lt;p&gt;Two: we need make a lot of &lt;em&gt;environmental&lt;/em&gt; assumptions that are outside our control. Does the algorithm return output or use the stack? Need to assume that there's sufficient memory to store stuff. Does it use any variables? Need to assume nothing is concurrently modifying them. Does it use an external service? Need to assume the vendor doesn't change the API or response formats. You need to assume the compiler worked correctly, the hardware isn't faulty, and the OS doesn't mess with things, etc. Any of these could change well after the code is proven and deployed, meaning formal verification can't be a one-and-done thing.&lt;/p&gt;
    &lt;p&gt;You don't actually have to assume most of these, but each assumption drop makes the proof harder and the properties you can prove more restricted. Remember, the code might still be bug-free even if the environmental assumptions change, so there's a tradeoff in time spent proving vs doing other useful work.&lt;/p&gt;
    &lt;p&gt;Another common source of "assumptions" is when verified code depends on unverified code. The Rust compiler can prove that safe code doesn't have a memory bug &lt;em&gt;assuming&lt;/em&gt; unsafe code does not have one either, but depends on the human to confirm that assumption. &lt;a href="https://ucsd-progsys.github.io/liquidhaskell/" rel="noopener noreferrer nofollow" target="_blank"&gt;Liquid Haskell&lt;/a&gt; is verifiable but can also call regular Haskell libraries, which are unverified. We need to assume that code is correct (in the "conforms to spec") sense, and if it's not, our proof can be "correct" and still cause bugs.&lt;/p&gt;
    &lt;hr/&gt;&lt;p&gt;These boundaries are fuzzy. I wrote that the "binary search" bug happened because they proved the wrong property, but you can just as well argue that it was a broken assumption (that integers could not overflow). What really matters is having a clear understanding of what "this code is proven correct" actually &lt;em&gt;tells&lt;/em&gt; you. Where can you use it safely? When should you worry? How do you communicate all of this to your teammates?&lt;/p&gt;
    &lt;p&gt;Good lord it's already Friday&lt;/p&gt;
    </description><pubDate>Fri, 10 Oct 2025 17:06:19 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/three-ways-formally-verified-code-can-go-wrong-in/</guid></item><item><title>New Blog Post: " A Very Early History of Algebraic Data Types"</title><link>https://buttondown.com/hillelwayne/archive/new-blog-post-a-very-early-history-of-algebraic/</link><description>
    &lt;p&gt;Last week I said that this week's newsletter would be a brief history of algebraic data types.&lt;/p&gt;
    &lt;p&gt;I was wrong.&lt;/p&gt;
    &lt;p&gt;That history is now a &lt;a href="https://www.hillelwayne.com/post/algdt-history/" target="_blank"&gt;3500 word blog post&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;&lt;a href="https://www.patreon.com/posts/blog-notes-very-139696324?utm_medium=clipboard_copy&amp;amp;utm_source=copyLink&amp;amp;utm_campaign=postshare_creator&amp;amp;utm_content=join_link" target="_blank"&gt;Patreon blog notes here&lt;/a&gt;.&lt;/p&gt;
    &lt;hr/&gt;
    &lt;h3&gt;I'm speaking at &lt;a href="https://www.p99conf.io/" target="_blank"&gt;P99 Conf&lt;/a&gt;!&lt;/h3&gt;
    &lt;p&gt;My talk, "Designing Low-Latency Systems with TLA+", is happening 10/23 at 11:30 central time. It's an online conf and the talk's only 16 minutes, so come check it out!&lt;/p&gt;
    </description><pubDate>Thu, 25 Sep 2025 16:50:58 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/new-blog-post-a-very-early-history-of-algebraic/</guid></item><item><title>Many Hard Leetcode Problems are Easy Constraint Problems</title><link>https://buttondown.com/hillelwayne/archive/many-hard-leetcode-problems-are-easy-constraint/</link><description>
    &lt;p&gt;In my first interview out of college I was asked the change counter problem:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;Given a set of coin denominations, find the minimum number of coins required to make change for a given number. IE for USA coinage and 37 cents, the minimum number is four (quarter, dime, 2 pennies).&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;I implemented the simple greedy algorithm and immediately fell into the trap of the question: the greedy algorithm only works for "well-behaved" denominations. If the coin values were &lt;code&gt;[10, 9, 1]&lt;/code&gt;, then making 37 cents would take 10 coins in the greedy algorithm but only 4 coins optimally (&lt;code&gt;10+9+9+9&lt;/code&gt;). The "smart" answer is to use a dynamic programming algorithm, which I didn't know how to do. So I failed the interview.&lt;/p&gt;
    &lt;p&gt;But you only need dynamic programming if you're writing your own algorithm. It's really easy if you throw it into a constraint solver like &lt;a href="https://www.minizinc.org/" target="_blank"&gt;MiniZinc&lt;/a&gt; and call it a day. &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;int: total;
    array[int] of int: values = [10, 9, 1];
    array[index_set(values)] of var 0..: coins;
    
    constraint sum (c in index_set(coins)) (coins[c] * values[c]) == total;
    solve minimize sum(coins);
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;You can try this online &lt;a href="https://play.minizinc.dev/" target="_blank"&gt;here&lt;/a&gt;. It'll give you a prompt to put in &lt;code&gt;total&lt;/code&gt; and then give you successively-better solutions:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;coins = [0, 0, 37];
    ----------
    coins = [0, 1, 28];
    ----------
    coins = [0, 2, 19];
    ----------
    coins = [0, 3, 10];
    ----------
    coins = [0, 4, 1];
    ----------
    coins = [1, 3, 0];
    ----------
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;Lots of similar interview questions are this kind of mathematical optimization problem, where we have to find the maximum or minimum of a function corresponding to constraints. They're hard in programming languages because programming languages are too low-level. They are also exactly the problems that constraint solvers were designed to solve. Hard leetcode problems are easy constraint problems.&lt;sup id="fnref:leetcode"&gt;&lt;a class="footnote-ref" href="#fn:leetcode"&gt;1&lt;/a&gt;&lt;/sup&gt; Here I'm using MiniZinc, but you could just as easily use Z3 or OR-Tools or whatever your favorite generalized solver is.&lt;/p&gt;
    &lt;h3&gt;More examples&lt;/h3&gt;
    &lt;p&gt;This was a question in a different interview (which I thankfully passed):&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;Given a list of stock prices through the day, find maximum profit you can get by buying one stock and selling one stock later.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;It's easy to do in O(n^2) time, or if you are clever, you can do it in O(n). Or you could be not clever at all and just write it as a constraint problem:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;array[int] of int: prices = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8];
    var int: buy;
    var int: sell;
    var int: profit = prices[sell] - prices[buy];
    
    constraint sell &amp;gt; buy;
    constraint profit &amp;gt; 0;
    solve maximize profit;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;Reminder, link to trying it online &lt;a href="https://play.minizinc.dev/" target="_blank"&gt;here&lt;/a&gt;. While working at that job, one interview question we tested out was:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;Given a list, determine if three numbers in that list can be added or subtracted to give 0? &lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;This is a satisfaction problem, not a constraint problem: we don't need the "best answer", any answer will do. We eventually decided against it for being too tricky for the engineers we were targeting. But it's not tricky in a solver; &lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;include "globals.mzn";
    array[int] of int: numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8];
    array[index_set(numbers)] of var {0, -1, 1}: choices;
    
    constraint sum(n in index_set(numbers)) (numbers[n] * choices[n]) = 0;
    constraint count(choices, -1) + count(choices, 1) = 3;
    solve satisfy;
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;Okay, one last one, a problem I saw last year at &lt;a href="https://chicagopython.github.io/algosig/" target="_blank"&gt;Chipy AlgoSIG&lt;/a&gt;. Basically they pick some leetcode problems and we all do them. I failed to solve &lt;a href="https://leetcode.com/problems/largest-rectangle-in-histogram/description/" target="_blank"&gt;this one&lt;/a&gt;:&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;Given an array of integers heights representing the histogram's bar height where the width of each bar is 1, return the area of the largest rectangle in the histogram.&lt;/p&gt;
    &lt;p&gt;&lt;img alt="example from leetcode link" class="newsletter-image" src="https://assets.buttondown.email/images/63337f78-7138-4b21-87a0-917c0c5b1706.jpg?w=960&amp;amp;fit=max"/&gt;&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;The "proper" solution is a tricky thing involving tracking lots of bookkeeping states, which you can completely bypass by expressing it as constraints:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;array[int] of int: numbers = [2,1,5,6,2,3];
    
    var 1..length(numbers): x; 
    var 1..length(numbers): dx;
    var 1..: y;
    
    constraint x + dx &amp;lt;= length(numbers);
    constraint forall (i in x..(x+dx)) (y &amp;lt;= numbers[i]);
    
    var int: area = (dx+1)*y;
    solve maximize area;
    
    output ["(\(x)-&amp;gt;\(x+dx))*\(y) = \(area)"]
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;There's even a way to &lt;a href="https://docs.minizinc.dev/en/2.9.3/visualisation.html" target="_blank"&gt;automatically visualize the solution&lt;/a&gt; (using &lt;code&gt;vis_geost_2d&lt;/code&gt;), but I didn't feel like figuring it out in time for the newsletter.&lt;/p&gt;
    &lt;h3&gt;Is this better?&lt;/h3&gt;
    &lt;p&gt;Now if I actually brought these questions to an interview the interviewee could ruin my day by asking "what's the runtime complexity?" Constraint solvers runtimes are unpredictable and almost always slower than an ideal bespoke algorithm because they are more expressive, in what I refer to as the &lt;a href="https://buttondown.com/hillelwayne/archive/the-capability-tractability-tradeoff/" target="_blank"&gt;capability/tractability tradeoff&lt;/a&gt;. But even so, they'll do way better than a &lt;em&gt;bad&lt;/em&gt; bespoke algorithm, and I'm not experienced enough in handwriting algorithms to consistently beat a solver.&lt;/p&gt;
    &lt;p&gt;The real advantage of solvers, though, is how well they handle new constraints. Take the stock picking problem above. I can write an O(n²) algorithm in a few minutes and the O(n) algorithm if you give me some time to think. Now change the problem to&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;Maximize the profit by buying and selling up to &lt;code&gt;max_sales&lt;/code&gt; stocks, but you can only buy or sell one stock at a given time and you can only hold up to &lt;code&gt;max_hold&lt;/code&gt; stocks at a time?&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;That's a way harder problem to write even an inefficient algorithm for! While the constraint problem is only a tiny bit more complicated:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;include "globals.mzn";
    int: max_sales = 3;
    int: max_hold = 2;
    array[int] of int: prices = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8];
    array [1..max_sales] of var int: buy;
    array [1..max_sales] of var int: sell;
    array [index_set(prices)] of var 0..max_hold: stocks_held;
    var int: profit = sum(s in 1..max_sales) (prices[sell[s]] - prices[buy[s]]);
    
    constraint forall (s in 1..max_sales) (sell[s] &amp;gt; buy[s]);
    constraint profit &amp;gt; 0;
    
    constraint forall(i in index_set(prices)) (stocks_held[i] = (count(s in 1..max_sales) (buy[s] &amp;lt;= i) - count(s in 1..max_sales) (sell[s] &amp;lt;= i)));
    constraint alldifferent(buy ++ sell);
    solve maximize profit;
    
    output ["buy at \(buy)\n", "sell at \(sell)\n", "for \(profit)"];
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p class="empty-line" style="height:16px; margin:0px !important;"&gt;&lt;/p&gt;
    &lt;p&gt;Most constraint solving examples online are puzzles, like &lt;a href="https://docs.minizinc.dev/en/stable/modelling2.html#ex-sudoku" target="_blank"&gt;Sudoku&lt;/a&gt; or "&lt;a href="https://docs.minizinc.dev/en/stable/modelling2.html#ex-smm" target="_blank"&gt;SEND + MORE = MONEY&lt;/a&gt;". Solving leetcode problems would be a more interesting demonstration. And you get more interesting opportunities to teach optimizations, like symmetry breaking.&lt;/p&gt;
    &lt;hr/&gt;
    &lt;h3&gt;Update for the Internet&lt;/h3&gt;
    &lt;p&gt;This was sent as a weekly newsletter, which is usually on topics like &lt;a href="https://buttondown.com/hillelwayne/archive/why-do-we-call-it-boilerplate-code" target="_blank"&gt;software history&lt;/a&gt;, &lt;a href="https://buttondown.com/hillelwayne/archive/the-seven-specification-ur-languages/" target="_blank"&gt;formal methods&lt;/a&gt;, &lt;a href="https://buttondown.com/hillelwayne/archive/i-formally-modeled-dreidel-for-no-good-reason/" target="_blank"&gt;unusual technologies&lt;/a&gt;, and the &lt;a href="https://buttondown.com/hillelwayne/archive/be-suspicious-of-success/" target="_blank"&gt;theory of software engineering&lt;/a&gt;. You can subscribe here: &lt;/p&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:leetcode"&gt;
    &lt;p&gt;Because my dad will email me if I don't explain this: "leetcode" is slang for "tricky algorithmic interview questions that have little-to-no relevance in the actual job you're interviewing for." It's from &lt;a href="https://leetcode.com/" target="_blank"&gt;leetcode.com&lt;/a&gt;. &lt;a class="footnote-backref" href="#fnref:leetcode" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Wed, 10 Sep 2025 13:00:00 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/many-hard-leetcode-problems-are-easy-constraint/</guid></item><item><title>The Angels and Demons of Nondeterminism</title><link>https://buttondown.com/hillelwayne/archive/the-angels-and-demons-of-nondeterminism/</link><description>
    &lt;p&gt;Greetings everyone! You might have noticed that it's September and I don't have the next version of &lt;em&gt;Logic for Programmers&lt;/em&gt; ready. As penance, &lt;a href="https://leanpub.com/logic/c/september-2025-kuBCrhBnUzb7" target="_blank"&gt;here's ten free copies of the book&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;So a few months ago I wrote &lt;a href="https://buttondown.com/hillelwayne/archive/five-kinds-of-nondeterminism/" target="_blank"&gt;a newsletter&lt;/a&gt; about how we use nondeterminism in formal methods.  The overarching idea:&lt;/p&gt;
    &lt;ol&gt;
    &lt;li&gt;Nondeterminism is when multiple paths are possible from a starting state.&lt;/li&gt;
    &lt;li&gt;A system preserves a property if it holds on &lt;em&gt;all&lt;/em&gt; possible paths. If even one path violates the property, then we have a bug.&lt;/li&gt;
    &lt;/ol&gt;
    &lt;p&gt;An intuitive model of this is that for this is that when faced with a nondeterministic choice, the system always makes the &lt;em&gt;worst possible choice&lt;/em&gt;. This is sometimes called &lt;strong&gt;demonic nondeterminism&lt;/strong&gt; and is favored in formal methods because we are paranoid to a fault.&lt;/p&gt;
    &lt;p&gt;The opposite would be &lt;strong&gt;angelic nondeterminism&lt;/strong&gt;, where the system always makes the &lt;em&gt;best possible choice&lt;/em&gt;. A property then holds if &lt;em&gt;any&lt;/em&gt; possible path satisfies that property.&lt;sup id="fnref:duals"&gt;&lt;a class="footnote-ref" href="#fn:duals"&gt;1&lt;/a&gt;&lt;/sup&gt; This is not as common in FM, but it still has its uses! "Players can access the secret level" or "&lt;a href="https://www.hillelwayne.com/post/safety-and-liveness/#other-properties" target="_blank"&gt;We can always shut down the computer&lt;/a&gt;" are &lt;strong&gt;reachability&lt;/strong&gt; properties, that something is possible even if not actually done.&lt;/p&gt;
    &lt;p&gt;In broader computer science research, I'd say that angelic nondeterminism is more popular, due to its widespread use in complexity analysis and programming languages.&lt;/p&gt;
    &lt;h3&gt;Complexity Analysis&lt;/h3&gt;
    &lt;p&gt;P is the set of all "decision problems" (&lt;em&gt;basically&lt;/em&gt;, boolean functions) can be solved in polynomial time: there's an algorithm that's worst-case in &lt;code&gt;O(n)&lt;/code&gt;, &lt;code&gt;O(n²)&lt;/code&gt;, &lt;code&gt;O(n³)&lt;/code&gt;, etc.&lt;sup id="fnref:big-o"&gt;&lt;a class="footnote-ref" href="#fn:big-o"&gt;2&lt;/a&gt;&lt;/sup&gt;  NP is the set of all problems that can be solved in polynomial time by an algorithm with &lt;em&gt;angelic nondeterminism&lt;/em&gt;.&lt;sup id="fnref:TM"&gt;&lt;a class="footnote-ref" href="#fn:TM"&gt;3&lt;/a&gt;&lt;/sup&gt; For example, the question "does list &lt;code&gt;l&lt;/code&gt; contain &lt;code&gt;x&lt;/code&gt;" can be solved in O(1) time by a nondeterministic algorithm:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fun is_member(l: List[T], x: T): bool {
      if l == [] {return false};
    
      guess i in 0..&amp;lt;(len(l)-1);
      return l[i] == x;
    }
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;Say call &lt;code&gt;is_member([a, b, c, d], c)&lt;/code&gt;. The best possible choice would be to guess &lt;code&gt;i = 2&lt;/code&gt;, which would correctly return true. Now call &lt;code&gt;is_member([a, b], d)&lt;/code&gt;. No matter what we guess, the algorithm correctly returns false. and just return false. Ergo, O(1). NP stands for "Nondeterministic Polynomial". &lt;/p&gt;
    &lt;p&gt;(And I just now realized something pretty cool: you can say that P is the set of all problems solvable in polynomial time under &lt;em&gt;demonic nondeterminism&lt;/em&gt;, which is a nice parallel between the two classes.)&lt;/p&gt;
    &lt;p&gt;Computer scientists have proven that angelic nondeterminism doesn't give us any more "power": there are no problems solvable with AN that aren't also solvable deterministically. The big question is whether AN is more &lt;em&gt;efficient&lt;/em&gt;: it is widely believed, but not &lt;em&gt;proven&lt;/em&gt;, that there are problems in NP but not in P. Most famously, "Is there any variable assignment that makes this boolean formula true?" A polynomial AN algorithm is again easy:&lt;/p&gt;
    &lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fun SAT(f(x1, x2, …: bool): bool): bool {
       N = num_params(f)
       for i in 1..=num_params(f) {
         guess x_i in {true, false}
       }
    
       return f(x_1, x_2, …)
    }
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p&gt;The best deterministic algorithms we have to solve the same problem are worst-case exponential with the number of boolean parameters. This a real frustrating problem because real computers don't have angelic nondeterminism, so problems like SAT remain hard. We can solve most "well-behaved" instances of the problem &lt;a href="https://www.hillelwayne.com/post/np-hard/" target="_blank"&gt;in reasonable time&lt;/a&gt;, but the worst-case instances get intractable real fast.&lt;/p&gt;
    &lt;h3&gt;Means of Abstraction&lt;/h3&gt;
    &lt;div class="subscribe-form"&gt;&lt;/div&gt;
    &lt;p&gt;We can directly turn an AN algorithm into a (possibly much slower) deterministic algorithm, such as by &lt;a href="https://en.wikipedia.org/wiki/Backtracking" target="_blank"&gt;backtracking&lt;/a&gt;. This makes AN a pretty good abstraction over what an algorithm is doing. Does the regex &lt;code&gt;(a+b)\1+&lt;/code&gt; match "abaabaabaab"? Yes, if the regex engine nondeterministically guesses that it needs to start at the third letter and make the group &lt;code&gt;aab&lt;/code&gt;. How does my PL's regex implementation find that match? I dunno, backtracking or &lt;a href="https://swtch.com/~rsc/regexp/regexp1.html" target="_blank"&gt;NFA construction&lt;/a&gt; or something, I don't need to know the deterministic specifics in order to use the nondeterministic abstraction.&lt;/p&gt;
    &lt;p&gt;Neel Krishnaswami has &lt;a href="https://semantic-domain.blogspot.com/2013/07/what-declarative-languages-are.html" target="_blank"&gt;a great definition of 'declarative language'&lt;/a&gt;: "any language with a semantics has some nontrivial existential quantifiers in it". I'm not sure if this is &lt;em&gt;identical&lt;/em&gt; to saying "a language with an angelic nondeterministic abstraction", but they must be pretty close, and all of his examples match:&lt;/p&gt;
    &lt;ul&gt;
    &lt;li&gt;SQL's selects and joins&lt;/li&gt;
    &lt;li&gt;Parsing DSLs&lt;/li&gt;
    &lt;li&gt;Logic programming's unification&lt;/li&gt;
    &lt;li&gt;Constraint solving&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;On top of that I'd add CSS selectors and &lt;a href="https://www.hillelwayne.com/post/picat/" target="_blank"&gt;planner's actions&lt;/a&gt;; all nondeterministic abstractions over a deterministic implementation. He also says that the things programmers hate most in declarative languages are features that "that expose the operational model": constraint solver search strategies, Prolog cuts, regex backreferences, etc. Which again matches my experiences with angelic nondeterminism: I dread features that force me to understand the deterministic implementation. But they're necessary, since P probably != NP and so we need to worry about operational optimizations.&lt;/p&gt;
    &lt;h3&gt;Eldritch Nondeterminism&lt;/h3&gt;
    &lt;p&gt;If you need to know the &lt;a href="https://en.wikipedia.org/wiki/PP_(complexity)" target="_blank"&gt;ratio of good/bad paths&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/%E2%99%AFP" target="_blank"&gt;the number of good paths&lt;/a&gt;, or probability, or anything more than "there is a good path" or "there is a bad path", you are beyond the reach of heaven or hell.&lt;/p&gt;
    &lt;div class="footnote"&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
    &lt;li id="fn:duals"&gt;
    &lt;p&gt;Angelic and demonic nondeterminism are &lt;a href="https://buttondown.com/hillelwayne/archive/logical-duals-in-software-engineering/" target="_blank"&gt;duals&lt;/a&gt;: angelic returns "yes" if &lt;code&gt;some choice: correct&lt;/code&gt; and demonic returns "no" if &lt;code&gt;!all choice: correct&lt;/code&gt;, which is the same as &lt;code&gt;some choice: !correct&lt;/code&gt;. &lt;a class="footnote-backref" href="#fnref:duals" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:big-o"&gt;
    &lt;p&gt;Pet peeve about Big-O notation: &lt;code&gt;O(n²)&lt;/code&gt; is the &lt;em&gt;set&lt;/em&gt; of all algorithms that, for sufficiently large problem sizes, grow no faster that quadratically. "Bubblesort has &lt;code&gt;O(n²)&lt;/code&gt; complexity" &lt;em&gt;should&lt;/em&gt; be written &lt;code&gt;Bubblesort in O(n²)&lt;/code&gt;, &lt;em&gt;not&lt;/em&gt; &lt;code&gt;Bubblesort = O(n²)&lt;/code&gt;. &lt;a class="footnote-backref" href="#fnref:big-o" title="Jump back to footnote 2 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:TM"&gt;
    &lt;p&gt;To be precise, solvable in polynomial time by a &lt;em&gt;Nondeterministic Turing Machine&lt;/em&gt;, a very particular model of computation. We can broadly talk about P and NP without framing everything in terms of Turing machines, but some details of complexity classes (like the existence "weak NP-hardness") kinda need Turing machines to make sense. &lt;a class="footnote-backref" href="#fnref:TM" title="Jump back to footnote 3 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;/ol&gt;
    &lt;/div&gt;
    </description><pubDate>Thu, 04 Sep 2025 14:00:00 +0000</pubDate><guid>https://buttondown.com/hillelwayne/archive/the-angels-and-demons-of-nondeterminism/</guid></item></channel></rss>
    Raw headers
    {
      "cf-cache-status": "DYNAMIC",
      "cf-ray": "a0b1b8fe4dcfc424-CMH",
      "connection": "close",
      "content-security-policy": "default-src 'self'; script-src 'self' 'nonce-c-vY5nykLY6pxEVLBSyQnQ' 'sha256-fmq7SB5YRtx2c358LZJiPdOUdgbDngasL//1pZSOw7Q=' 'sha256-Zsbh78gbtd2zyd9vW5HmEiB7ZYWa/Und7TqJ7cDC1PM=' 'sha256-8TGjKztrL0R7wxViRmWe0k/Je4l3hMmzBhO8FBQdYf0=' https://static.addtoany.com https://embed.bsky.app https://bookshop.org https://challenges.cloudflare.com https://static.cloudflareinsights.com https://connect.facebook.net https://cdn.usefathom.com https://embedr.flickr.com https://www.googletagmanager.com https://www.instagram.com https://plausible.io https://cdn.seline.com https://scripts.simpleanalyticscdn.com https://sniperl.ink https://js.stripe.com https://js.stripe.com https://cdn.tailwindcss.com https://www.tiktok.com https://tinylytics.app https://sf16-website-login.neutral.ttwstatic.com https://platform.twitter.com https://cloud.umami.is https://pay.google.com https://static-assets.buttondown.com https://tiktokcdn-us.com; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https: http: blob:; media-src 'self' data: https: http: blob:; font-src 'self' data: https:; frame-src https: blob:; connect-src 'self' https:; manifest-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self' https://buttondown.com",
      "content-type": "application/rss+xml; charset=utf-8",
      "cross-origin-opener-policy": "same-origin",
      "date": "Sat, 13 Jun 2026 14:18:06 GMT",
      "last-modified": "Wed, 10 Jun 2026 12:22:04 GMT",
      "nel": "{\"report_to\":\"heroku-nel\",\"response_headers\":[\"Via\"],\"max_age\":3600,\"success_fraction\":0.01,\"failure_fraction\":0.1}, {\"report_to\":\"heroku-nel\",\"response_headers\":[\"Via\"],\"max_age\":3600,\"success_fraction\":0.01,\"failure_fraction\":0.1}",
      "referrer-policy": "strict-origin-when-cross-origin",
      "report-to": "{\"group\":\"heroku-nel\",\"endpoints\":[{\"url\":\"https://nel.heroku.com/reports?s=HzM4JY4GxIfhMKs%2BXgW%2BcTqXAyNEEpQ442BKaAyMgT8%3D\\u0026sid=929419e7-33ea-4e2f-85f0-7d8b7cd5cbd6\\u0026ts=1781360286\"}],\"max_age\":3600}, {\"group\":\"heroku-nel\",\"endpoints\":[{\"url\":\"https://nel.heroku.com/reports?s=ympvCNgALoe69l0f7DYMfXxXS4yx5%2FoklrFAflJjQY4%3D\\u0026sid=e11707d5-02a7-43ef-b45e-2cf4d2036f7d\\u0026ts=1781360286\"}],\"max_age\":3600}",
      "reporting-endpoints": "heroku-nel=\"https://nel.heroku.com/reports?s=HzM4JY4GxIfhMKs%2BXgW%2BcTqXAyNEEpQ442BKaAyMgT8%3D&sid=929419e7-33ea-4e2f-85f0-7d8b7cd5cbd6&ts=1781360286\", heroku-nel=\"https://nel.heroku.com/reports?s=ympvCNgALoe69l0f7DYMfXxXS4yx5%2FoklrFAflJjQY4%3D&sid=e11707d5-02a7-43ef-b45e-2cf4d2036f7d&ts=1781360286\"",
      "server": "cloudflare",
      "set-cookie": "initial_path=\"/hillelwayne/rss\"; expires=Mon, 13 Jul 2026 14:18:06 GMT; Max-Age=2592000; Path=/",
      "transfer-encoding": "chunked",
      "vary": "Cookie, Host, origin, Accept-Encoding",
      "via": "1.1 heroku-router, 2.0 heroku-router",
      "x-content-type-options": "nosniff",
      "x-frame-options": "DENY"
    }
    Parsed with @rowanmanning/feed-parser
    {
      "meta": {
        "type": "rss",
        "version": "2.0"
      },
      "language": "en-us",
      "title": "Computer Things",
      "description": "<!-- buttondown-editor-mode: fancy --><p>Hi, I'm Hillel. This is the newsletter version of <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://www.hillelwayne.com\">my website</a>. I post all website updates here. I also post weekly content just for the newsletter, on topics like</p><ul><li><p>Formal Methods</p></li><li><p>Software History and Culture</p></li><li><p>Fringetech and exotic tooling</p></li><li><p>The philosophy and theory of software engineering</p></li></ul><p>You can see the archive of all public essays <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://buttondown.email/hillelwayne/archive/\">here</a>.</p>",
      "copyright": null,
      "url": "https://buttondown.com/hillelwayne",
      "self": "https://buttondown.email/hillelwayne/rss",
      "published": null,
      "updated": "2026-06-10T12:22:04.000Z",
      "generator": null,
      "image": null,
      "authors": [],
      "categories": [],
      "items": [
        {
          "id": "https://buttondown.com/hillelwayne/archive/nontrailing-separators-do-not-spark-joy/",
          "title": "Nontrailing separators do not spark joy",
          "description": "<p>This is valid JSON:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"p\">{</span>\n<span class=\"w\">    </span><span class=\"nt\">\"a\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">1</span><span class=\"p\">,</span>\n<span class=\"w\">    </span><span class=\"nt\">\"b\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">2</span><span class=\"p\">,</span>\n<span class=\"w\">    </span><span class=\"nt\">\"c\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">3</span>\n<span class=\"p\">}</span>\n</code></pre></div>\n\n<p>This is invalid JSON:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"p\">{</span>\n<span class=\"w\">    </span><span class=\"nt\">\"a\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">1</span><span class=\"p\">,</span>\n<span class=\"w\">    </span><span class=\"nt\">\"b\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">2</span><span class=\"p\">,</span>\n<span class=\"w\">    </span><span class=\"nt\">\"c\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">3</span><span class=\"p\">,</span>\n<span class=\"p\">}</span>\n</code></pre></div>\n\n<p>The difference is the last comma. The <a href=\"https://www.json.org/json-en.html\" target=\"_blank\">JSON grammar</a> specifies that a comma can separate two members of an object but not postcede (\"trail\") a member. I think this was a design mistake. Say we want to add two new keys to the struct, one before the <code>\"a\"</code> member and one after the <code>\"c\"</code> member. Here's what it would look like if trailing commas were permitted:</p>\n<div class=\"codehilite\"><pre><span></span><code>{\n<span class=\"gi\">+   \"x\": 0,</span>\n<span class=\"w\"> </span>   \"a\": 1,\n<span class=\"w\"> </span>   \"b\": 2,\n<span class=\"w\"> </span>   \"c\": 3,\n<span class=\"gi\">+   \"y\": 4,</span>\n}\n</code></pre></div>\n\n<p>It's the exact same text transformation regardless of where we add the key. In the current model, we instead have this:</p>\n<div class=\"codehilite\"><pre><span></span><code>{\n<span class=\"gi\">+   \"x\": 0,</span>\n<span class=\"w\"> </span>   \"a\": 1,\n<span class=\"w\"> </span>   \"b\": 2,\n<span class=\"gd\">-   \"c\": 3</span>\n<span class=\"gi\">+   \"c\": 3,</span>\n<span class=\"gi\">+   \"y\": 4</span>\n}\n</code></pre></div>\n\n<p>Those are different transformations! Similarly if you want to remove an element, you can't just delete the corresponding line<sup id=\"fnref:objects\"><a class=\"footnote-ref\" href=\"#fn:objects\">1</a></sup>, you have to delete the line <em>and then</em> check that the last line doesn't have a trailing comma. Don't even get me started on all the special cases involved in swapping two lines.</p>\n<p>JSON isn't the only language with this problem. Haskell writes record types like this:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"c1\">-- from https://play.haskell.org/</span>\n<span class=\"kr\">data</span><span class=\"w\"> </span><span class=\"kt\">Drone</span><span class=\"w\"> </span><span class=\"ow\">=</span><span class=\"w\"> </span><span class=\"kt\">Drone</span>\n<span class=\"w\">  </span><span class=\"p\">{</span><span class=\"w\"> </span><span class=\"n\">xPos</span><span class=\"w\"> </span><span class=\"ow\">::</span><span class=\"w\"> </span><span class=\"kt\">Int</span>\n<span class=\"w\">  </span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"n\">yPos</span><span class=\"w\"> </span><span class=\"ow\">::</span><span class=\"w\"> </span><span class=\"kt\">Int</span>\n<span class=\"w\">  </span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"n\">zPos</span><span class=\"w\"> </span><span class=\"ow\">::</span><span class=\"w\"> </span><span class=\"kt\">Int</span>\n<span class=\"w\">  </span><span class=\"p\">}</span>\n</code></pre></div>\n\n<p>This \"partial bullet point\" style of putting separators at the beginning of rows makes it easier to change the last row but harder to change the first one. </p>\n<p>TLA+ has this problem too:</p>\n<div class=\"codehilite\"><pre><span></span><code>\\* both valid\nVARIABLES a, b, c\nvars == <<a, b, c>>\n\n\\* both invalid\nVARIABLES a, b, c,\nvars == <<a, b, c,>>\n</code></pre></div>\n\n<p>This one's annoying because 1) you're constantly adding new top-level variables while working on a spec and 2) the PlusCal DSL does <em>not</em> have this problem:</p>\n<div class=\"codehilite\"><pre><span></span><code>\\* Totally fine!\n(*--algorithm foo {\nvariables a; b; c;\n</code></pre></div>\n\n<p>The worst offenders, IMO, are logic languages like Prolog. Not only don't you have trailing separators, you have a <em>special terminating symbol</em>:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"nf\">foo</span><span class=\"p\">(</span><span class=\"nv\">A</span><span class=\"p\">,</span> <span class=\"nv\">B</span><span class=\"p\">,</span> <span class=\"nv\">C</span><span class=\"p\">)</span> <span class=\"p\">:-</span>\n    <span class=\"nv\">A</span> <span class=\"o\">=</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"c1\">% comma</span>\n    <span class=\"nv\">B</span> <span class=\"o\">=</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"c1\">% comma</span>\n    <span class=\"nv\">C</span> <span class=\"o\">=</span> <span class=\"mf\">3.</span> <span class=\"c1\">% period!</span>\n</code></pre></div>\n\n<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<p>I guess you can sort of think of it as funny-lookin' braces:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"nf\">foo</span><span class=\"p\">(</span><span class=\"nv\">A</span><span class=\"p\">,</span> <span class=\"nv\">B</span><span class=\"p\">,</span> <span class=\"nv\">C</span><span class=\"p\">)</span> <span class=\"p\">:-</span>\n    <span class=\"nv\">A</span> <span class=\"o\">=</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n    <span class=\"nv\">B</span> <span class=\"o\">=</span> <span class=\"mi\">2</span><span class=\"p\">,</span>\n    <span class=\"nv\">C</span> <span class=\"o\">=</span> <span class=\"mi\">3</span> \n<span class=\"p\">.</span>\n</code></pre></div>\n\n<p>But this is not standard syntax and people will look at you weird if you try it. And you still don't get trailing separators.</p>\n<h2>Something better</h2>\n<div class=\"subscribe-form\"></div>\n<p>Some languages allow trailing separators:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"c1\">// go</span>\n<span class=\"nx\">valid</span><span class=\"w\"> </span><span class=\"o\">:=</span><span class=\"w\"> </span><span class=\"kd\">map</span><span class=\"p\">[</span><span class=\"kt\">string</span><span class=\"p\">]</span><span class=\"kt\">int</span><span class=\"p\">{</span>\n<span class=\"w\">        </span><span class=\"s\">\"a\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">1</span><span class=\"p\">,</span>\n<span class=\"w\">        </span><span class=\"s\">\"b\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">2</span><span class=\"p\">,</span>\n<span class=\"w\">        </span><span class=\"s\">\"c\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">3</span><span class=\"p\">,</span>\n<span class=\"w\">    </span><span class=\"p\">}</span>\n</code></pre></div>\n\n<div class=\"codehilite\"><pre><span></span><code><span class=\"c1\"># python</span>\n<span class=\"n\">valid</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n  <span class=\"s2\">\"a\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n  <span class=\"s2\">\"b\"</span><span class=\"p\">:</span> <span class=\"mi\">2</span><span class=\"p\">,</span>\n  <span class=\"s2\">\"c\"</span><span class=\"p\">:</span> <span class=\"mi\">3</span><span class=\"p\">,</span>\n<span class=\"p\">}</span>\n</code></pre></div>\n\n<p>But I think we can do one better than that. Python and Go commas can trail but not lead, meaning we can't go 100% bullet points:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"c1\"># python again</span>\n<span class=\"n\">invalid</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n    <span class=\"p\">,</span> <span class=\"s2\">\"a\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span>\n    <span class=\"p\">,</span> <span class=\"s2\">\"b\"</span><span class=\"p\">:</span> <span class=\"mi\">2</span>\n    <span class=\"p\">,</span> <span class=\"s2\">\"c\"</span><span class=\"p\">:</span> <span class=\"mi\">3</span>\n<span class=\"p\">}</span>\n</code></pre></div>\n\n<p>Now I personally think that bullet points are the bee's knees and wish more languages allowed leading separators. TLA+ actually has leading conjunction and disjunction operations:</p>\n<div class=\"codehilite\"><pre><span></span><code>// Not TLA+ but the same semantics\n|| && a == 1\n   && b == 2\n\n|| && a == 3\n   && b == 4\n</code></pre></div>\n\n<p>You can't trail these, though, no writing <code>(a &&)</code>.</p>\n<p>The most flexible I've seen is <a href=\"https://alloytools.org/\" target=\"_blank\">Alloy</a>, which allows both leading and trailing commas:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"c1\">// Alloy</span>\n<span class=\"kd\">sig</span><span class=\"w\"> </span><span class=\"n\">Valid</span><span class=\"w\"> </span><span class=\"o\">{</span>\n<span class=\"w\">    </span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"n\">a</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">1</span>\n<span class=\"w\">    </span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"n\">b</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">2</span>\n<span class=\"o\">}</span>\n\n<span class=\"kd\">sig</span><span class=\"w\"> </span><span class=\"n\">AlsoValid</span><span class=\"w\"> </span><span class=\"o\">{</span>\n<span class=\"w\">    </span><span class=\"n\">a</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">1</span><span class=\"p\">,</span>\n<span class=\"w\">    </span><span class=\"n\">b</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">2</span><span class=\"p\">,</span>\n<span class=\"o\">}</span>\n</code></pre></div>\n\n<p>Alloy does go a little power-mad here, because it also allows empty separators.</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"kd\">sig</span><span class=\"w\"> </span><span class=\"n\">StillValid</span><span class=\"w\"> </span><span class=\"o\">{</span>\n<span class=\"w\">    </span><span class=\"p\">,,</span><span class=\"w\"> </span><span class=\"n\">a</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">1</span><span class=\"p\">,,</span>\n<span class=\"w\">    </span><span class=\"p\">,,,,,,,,,</span>\n<span class=\"w\">    </span><span class=\"p\">,,</span><span class=\"w\"> </span><span class=\"n\">b</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">2</span><span class=\"p\">,,</span>\n<span class=\"o\">}</span>\n</code></pre></div>\n\n<p>I've heard some people call this <a href=\"https://www.reddit.com/r/ProgrammingLanguages/comments/1amsalm/does_your_language_support_trailing_commas/\" target=\"_blank\">\"stuttering\"</a>. I can't figure out how to <a href=\"https://www.hillelwayne.com/post/python-abc/\" target=\"_blank\">commit crimes with this</a> but you never know.</p>\n<h2>Devil's advocate</h2>\n<p>One argument against trailing separators is that they make parsing ambiguous. Consider this Prolog: </p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"nf\">foo</span><span class=\"p\">(</span><span class=\"nv\">A</span><span class=\"p\">,</span> <span class=\"nv\">B</span><span class=\"p\">)</span> <span class=\"p\">:-</span>\n    <span class=\"nv\">A</span> <span class=\"o\">=</span> <span class=\"mi\">1</span><span class=\"p\">,</span> \n    <span class=\"nv\">B</span> <span class=\"o\">=</span> <span class=\"mf\">2.</span>\n\n<span class=\"nf\">bar</span><span class=\"p\">(</span><span class=\"s s-Atom\">c</span><span class=\"p\">).</span>\n</code></pre></div>\n\n<p>Here it's pretty clear that <code>foo</code> and <code>bar</code> are separate definitions. But if we replace the rule terminator with commas:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"nf\">foo</span><span class=\"p\">(</span><span class=\"nv\">A</span><span class=\"p\">,</span> <span class=\"nv\">B</span><span class=\"p\">)</span> <span class=\"p\">:-</span>\n    <span class=\"nv\">A</span> <span class=\"o\">=</span> <span class=\"mi\">1</span><span class=\"p\">,</span> \n    <span class=\"nv\">B</span> <span class=\"o\">=</span> <span class=\"mi\">2</span><span class=\"p\">,</span>\n\n<span class=\"nf\">bar</span><span class=\"p\">(</span><span class=\"s s-Atom\">c</span><span class=\"p\">),</span>\n</code></pre></div>\n\n<p>Now it <em>could</em> be alternatively parsed that <code>bar(c)</code> is part of the definition of <code>foo</code>— <code>foo</code> is only true when <code>bar(c)</code> is also true. </p>\n<p>As another example, <a href=\"https://docs.ruby-lang.org/en/master/syntax/layout_rdoc.html\" target=\"_blank\">this is valid Ruby</a>:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"c1\"># prints 5</span>\n<span class=\"nb\">puts</span><span class=\"w\"> </span><span class=\"mi\">3</span><span class=\"o\">.</span>\n<span class=\"w\">     </span><span class=\"n\">succ</span><span class=\"p\">()</span><span class=\"o\">.</span>\n<span class=\"w\">     </span><span class=\"n\">succ</span><span class=\"p\">()</span>\n</code></pre></div>\n\n<p>If we could \"trail method calls\", this is ambiguous:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"n\">foo</span><span class=\"o\">.</span>\n<span class=\"w\">  </span><span class=\"n\">bar</span><span class=\"p\">()</span><span class=\"o\">.</span>\n<span class=\"w\">  </span><span class=\"n\">baz</span><span class=\"p\">()</span><span class=\"o\">.</span>\n\n<span class=\"n\">quux</span><span class=\"p\">()</span>\n</code></pre></div>\n\n<p>Now it's not clear if <code>quux()</code> is a top-level function or a method of <code>foo</code>.</p>\n<p>Both of those relate to control separators, not data separators. Python has an edge data case with trailing data separators. The language uses parenthesis both for expression grouping like <code>(2+3)</code> and for tuple definition like <code>(2,3)</code>. So how do you distinguish an expression evaluation from a single-element tuple? With a trailing comma!</p>\n<div class=\"codehilite\"><pre><span></span><code>>>> x = (2+3)\n>>> type(x)\n<class 'int'>\n>>> x = (2+3,)\n>>> type(x)\n<class 'tuple'>\n</code></pre></div>\n\n<p>Okay that's all I got. New (and final) preview release of <em>Logic for Programmers</em> next week.</p>\n<div class=\"footnote\">\n<hr />\n<ol>\n<li id=\"fn:objects\">\n<p>Obvs this doesn't work if your object values are themselves multiline arrays or objects, but those make the \"no trailing comma\" transformations <em>even more</em> complicated. <a class=\"footnote-backref\" href=\"#fnref:objects\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/nontrailing-separators-do-not-spark-joy/",
          "published": "2026-06-10T12:22:04.000Z",
          "updated": "2026-06-10T12:22:04.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/logic-for-programmers-extra-credits/",
          "title": "Logic for Programmers extra credits",
          "description": "<p>So I said there wasn’t a proper newsletter this week, since I’m in Budapest prepping for a conference. But I still got a thing for y’all.</p>\n<p>There’s a lot of interesting topics I wanted to cover for <em>Logic for Programmers</em>, but the book is dense enough as it is and many of these were too tangential or technical to fit in well. So I’ve been writing some supplements and uploading them <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://github.com/logicforprogrammers/book-assets/tree/master/supplements\">here</a>. I’ve got four so far:</p>\n<ol><li><p>How we compute the number of orderings of multiple concurrent processes</p></li><li><p>How first-order logic can quantify over “a set of functions”, what a “set of functions” looks like, and how functions can be defined in terms of sets (plus a bit on currying and type theory)</p></li><li><p>Barbara Liskov’s “history rule” in subtyping</p></li><li><p>Total and partial orders on sets.</p></li></ol>\n<p>Now I’m going to caveat that these were written off the cuff and haven’t gone through the obsessive editing of the book itself, so they may be rough and there might be errors in them. Still, it’s like 2-3000 words of math content, so hopefully covers not having a proper newsletter this time. Seeya next week!</p>",
          "url": "https://buttondown.com/hillelwayne/archive/logic-for-programmers-extra-credits/",
          "published": "2026-06-02T14:48:48.000Z",
          "updated": "2026-06-02T14:48:48.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/knowing-about-things-is-cheaper-than-knowing/",
          "title": "Knowing about things is cheaper than knowing things",
          "description": "<p><em>Short one this week because I'm way behind on book and conference prep.</em></p>\n<p>Last week a LinkedIn Influencer wrote about how math has nothing to do with programming, so I spite-wrote a rejoinder about how math is necessary to program (just try to write software without knowing arithmetic!) and man I forgot how much spite can fuel writing. Maybe I should go back to Twitter (absolutely not).</p>\n<p>But it got me thinking about the difference between \"all programmers <em>can benefit</em> from learning math\" and \"all programmers <em>need to</em> learn math\". I simultaneously believe three things:</p>\n<ol>\n<li>There is some math, like arithmetic (incl. arithmetic of booleans, sets, functions, etc), that is useful to all programmers.</li>\n<li>The remaining fields aren't useful to most programmers.</li>\n<li>Every programmer works in a domain where there is at least one branch of math that would benefit them to learn.</li>\n</ol>\n<p>(2) means that if get a group of 100 software engineers and teach them something like algebra or calculus, you can't expect it to be applicable for more than 3 or 5. Whereas if you teach something like shell scripting or regular expressions it'd be useful to at least, like, 50. So no field of math has a good RoI for the average programmer. </p>\n<p>(3) means that each of those 100 developers could, on their own, find a field of math that is useful to them. In order to do that, though, they need to roughly know what the fields are, what the big ideas are, and where they might be useful. It is more useful to teach them <em>about</em> many fields than to teach them any one specific field in-depth. </p>\n<p>I think that's generally true with most areas of knowledge! Getting basic exposure to something takes a lot less time and effort than learning it in-depth. If you're specifically trying to learn things that will be useful to your work<sup id=\"fnref:topic\"><a class=\"footnote-ref\" href=\"#fn:topic\">1</a></sup>, you only want to go in-depth on topics you know will be helpful. But you won't know the topic is helpful (or even that it exists) unless you know the very basics already. So it makes sense to get broad exposure to lots of topics and only later choose what to go deep on.</p>\n<h2>How to do this</h2>\n<p>Uhhhhhhhhhh</p>\n<p>Honestly the thing that's worked best for me personally is to read stuff meant to give a broad exposure without lots of details. Non-honors undergraduate textbooks are really good for that. It helps to approach it like \"learning for the fun of learning\" and not \"learning to accumulate potentially useful stuff for later\".</p>\n<p>Conference videos can be good, too, though I'm not a video person. I imagine popular-science stuff could be useful but 1) I grew up at a time when \"popsci\" meant \"lying about science to make it sound more impressive\" (though I know Quanta's really good now) and 2) I tend not to mesh with the popsci writing style.</p>\n<hr />\n<h2>No Proper Newsletter Next Week</h2>\n<p>I'll be at <a href=\"https://craft-conf.com/2026\" target=\"_blank\">Craft Conference Budapest</a>. That said, there I'll be uploading some <em>Logic for Programmers</em>-related material that's about the length of a newsletter, so keep your eyes peeled!</p>\n<div class=\"footnote\">\n<hr />\n<ol>\n<li id=\"fn:topic\">\n<p>Which to be clear is not the only reason to learn something! <a class=\"footnote-backref\" href=\"#fnref:topic\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/knowing-about-things-is-cheaper-than-knowing/",
          "published": "2026-05-28T16:03:01.000Z",
          "updated": "2026-05-28T16:03:01.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/assumptions-weaken-properties/",
          "title": "Assumptions weaken properties",
          "description": "<p>In <a href=\"https://buttondown.com/hillelwayne/archive/some-tests-are-stronger-than-others/\" target=\"_blank\">some tests are stronger than others</a>, I defined <code>STRONG => WEAK</code> to mean \"any system passing test STRONG is also guaranteed to pass WEAK\". This uses the logical implication operator, defined as <code>P => Q = !P || (P && Q)</code>.</p>\n<p>Implication may be the most overworked operator in logic. Among other things, it's also used in formal specification, where <code>Spec => Prop</code> means \"any system satisfying <code>Spec</code> has property <code>Prop</code>\" and <code>ASSUME => Spec</code> means \"The assumption <code>ASSUME</code> must hold in order for the system to satisfy <code>Spec</code>.\"</p>\n<p>Now let's mush these all together and do some math. To start, \"the system has property <code>Prop</code>\" is the same as \"the system passes the test that checks <code>Prop</code>\", so test strength is also property strength. Now let \"<code>ASSUME => Prop</code>\" mean \"the system passes <code>Prop</code> assuming <code>ASSUME</code> is true.\" In classic logic, if <code>P</code> is true, then obviously <code>!Q || P</code> is true. Further, that is equivalent (just draw the truth table!) to <code>!Q || (P && Q)</code>. So for <em>any</em> propositions <code>P</code> and <code>Q</code>, <code>P => (Q => P)</code>.</p>\n<p>In other words, <code>Prop => (ASSUME => Prop)</code>. In other other words, \"the system passes <code>Prop</code>\" is a stronger property than \"the system passes <code>Prop</code> whenever our assumptions hold.\"</p>\n<p>In other other other words, any assumption added makes a property weaker.</p>\n<p>This makes intuitive sense to me. A JSON parser that's only been verified with ASCII strings has the property \"input only uses ASCII && is valid json => correctly parsed\". A better JSON parser that works for all Unicode will have the property \"is valid json => correctly parsed\", which has fewer assumptions, meaning it's guaranteed to work in a strict superset of cases. </p>\n<p>It also matches the intuition that \"more assumptions means more likely to go wrong\". We have a bug whenever <code>Prop</code> is false. The only way for <code>Spec => Prop</code> to be true and <code>Prop</code> be false is if <code>Spec</code> is false, eg our system doesn't satisfy the specification we intended to implement. On the other hand, <code>Spec => (ASSUME => Prop) && !Prop</code> is true whenever <code>Spec</code> <em>and/or</em> <code>ASSUME</code> is false, meaning a correctly-implemented system could still show a bug if a runtime assumption is false. </p>\n<p>...Looking back the last two paragraphs have a lot of conceptual leaps. Does that all make sense to you? It all feels natural to me but that might just be my familiarity with the topic talking. </p>\n<p>Regardless, a couple more notes on assumptions:</p>\n<h2>Why we have assumptions</h2>\n<p>Why not just build our systems to satisfy <code>ASSUME => Prop</code> when we can \"just\" build it to satisfy <code>Prop</code>? At least three reasons.</p>\n<p><strong>First</strong>, sometimes <code>Prop</code> is simply impossible to satisfy and we need to add assumptions to make this property. We do this a lot in formal methods with <a href=\"https://www.hillelwayne.com/post/fairness/\" target=\"_blank\">fairness</a>. The property \"<code>mergesort</code> always returns a sorted list\" is unsatisfiable because we can dropkick the computer before it returns. Instead, we have to verify a weaker property like \"<code>mergesort</code> always returns => it returns a sorted list\" or \"<code>mergesort</code> always makes progress => it will eventually return a sorted list.\"</p>\n<p>Another example is Rust. Rust <em>does not</em> guarantee the property \"the program is memory safe\". It guarantees the weaker property \"all <code>unsafe</code> blocks are memory safe => the program is memory safe\". Making the language satisfy the stronger property would rule out too many use cases of Rust. Note you also get memory safety if you don't use <code>unsafe</code>, but that still satisfies the assumption, as all <em>zero</em> blocks are safe!</p>\n<p><strong>Second</strong>, sometimes the strong property is satisfiable, but it's simply not worth the extra cost. Like it's possible to make our software resistant against cosmic rays, but if your code isn't running in space, why bother? Just say \"No cosmic bit flips => things work\". Or if your plugin works Neovim <code>0.12</code> but not 0.11, you could put in the effort to make it run on older versions, or you could tell everybody that they need to upgrade to use your plugin. \"Neovim version is at least 0.12 => the plugin works\".</p>\n<p><strong>Third</strong>, sometimes the strong property is satisfiable in the system but not easily <em>verifiable</em>. Say your algorithm makes a lot of API calls and you don't want to hit rate limits while testing. If you <a href=\"https://en.wikipedia.org/wiki/Mock_object\" target=\"_blank\">mock out the API</a> you're testing the weaker property \"The mock is accurate to the API => the algorithm is correct\".</p>\n<h2>Assumptions are a second level of system effect</h2>\n<p>I notice that almost all of the examples in the last two sections are exoprogram factors:</p>\n<ul>\n<li>The JSON parser assumptions are about user input</li>\n<li>Fairness assumptions are about the OS/hardware/operating environment</li>\n<li>Unsafe assumptions are about things the Rust compiler can't verify</li>\n<li>Cosmic ray assumptions depend on the physical location of the hardware</li>\n<li>The plugin assumptions are about the Neovim team's release schedule and social compatibility contract</li>\n</ul>\n<p>The edge case is replacing a third party call with a mock. The assumption is intraprogram because the program could just hit the API during testing. We still have the assumption because of an exoprogram constraint. Maybe this is why mocks are considered an antipattern in Agile.</p>\n<p>One consequence of this is that checking whether assumptions hold is a different problem from verifying that your code works given the assumptions. Like to make sure \"all unsafe blocks are safe\" can't use the rust compiler, you need a second tool like <a href=\"https://github.com/rust-lang/miri\" target=\"_blank\">Miri</a>. I wonder if checking assumptions is, in practice, generally more difficult than checking everything else.</p>",
          "url": "https://buttondown.com/hillelwayne/archive/assumptions-weaken-properties/",
          "published": "2026-05-20T15:13:16.000Z",
          "updated": "2026-05-20T15:13:16.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/points-are-a-weird-and-inconsistent-unit-of/",
          "title": "Points are a weird and inconsistent unit of measure",
          "description": "<p><img alt=\"Some pictures of point sizes from \"The Practice of Typography\"\" class=\"newsletter-image\" src=\"https://assets.buttondown.email/images/1e994c5a-8b40-4c4e-8898-81ebfbfa8bcb.png?w=960&fit=max\" /></p>\n<p>I'm in the middle of redoing the <a href=\"https://logicforprogrammers.com\" target=\"_blank\"><em>Logic for Programmers</em></a> diagrams and this has surfaced a really annoying problem. The book is formatted in LaTeX using a pseudo-grid of 10.8pt × 7.2pt. The diagrams are done in Inkscape using a 10.8pt × 7.2pt.</p>\n<p>Last week I found out that these are not the same points. </p>\n<p>Latex defines a point as 1/72.27 inches (0.3515 millimeters). Inkscape instead uses 1/72 inches (0.3528 mm). It's only a difference of 0.4% but it still floors me that two widespread digital technologies would be different!</p>\n<p>So, uh, <em>what happened?</em></p>\n<p>A few hours of reading articles later, this is what I found, caveat that I didn't spend all that much time researching this and this is only initial impressions. </p>\n<h2>what even is a point</h2>\n<p>A <a href=\"https://en.wikipedia.org/wiki/Point_(typography)\" target=\"_blank\">point</a> is a typographic measure, coming from 1517, that is supposedly the smallest interesting size for a printer. This was notably not a standardized measure- different companies in times used different point sizes depending on their equipment. Over time it was standardized, but each country picked a different standard: the German and Japanese point is 0.250 mm, the French point is allegedly 0.399 mm, etc.</p>\n<p>But early computer history is super Americentric so that's what technology uses. In the US, they standardized the point around the end of the 19th century. To what? I dunno. <a href=\"https://archive.org/details/practiceoftypogr00devirich/page/150/mode/1up\" target=\"_blank\">This source</a> from 1900 gives the length of a point as 35/996 cm (72.281 points/in) and then says there are exactly 867.4699 \"ems per foot\" (72.289 points/in). <a href=\"https://books.google.com/books?id=yUkHAwAAQBAJ&pg=PA57#v=onepage&q&f=false\" target=\"_blank\">This source</a> from 1916 says the standard pica (12 points) is 0.16604 inches and that there are 72.272 \"pica ems per foot\". Which conveniently enough gives us 72.272 points/in (a pica being 12 points). Then on the very next page they say no a pica is actually 0.16604<strong>4</strong> inches and a point is exactly 0.013837 inches. I found other sources with other definitions, too.</p>\n<p>I'm going to chalk the differences up to a mix of \"the definitions of 'meter' and 'foot' changed over time\" and \"these are less than a micron apart so who gives a shit\". I do, I give a shit. Regardless, the <a href=\"https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular570.pdf\" target=\"_blank\">official NIST definition</a> settled on a point being 0.013837 inches, so you'd get 72.27 points/inch. </p>\n<p>Wrong! You absolute moron. You get 72.270001 points/inch. This annoyed Donald Knuth enough that he rejiggered the ratio in <a href=\"https://latex.us/systems/knuth/dist/tex/texbook.tex\" target=\"_blank\">TeX</a> (pg 58):</p>\n<blockquote>\n<p>TEX’s “pt” has been made slightly larger than the official printer’s point, which was defined to equal exactly .013837 in by the American Typefounders Association in 1886 [cf. National Bureau of Standards Circular 570 (1956)]. In fact, one classical point is exactly .99999999 pt, so the “error” is essentially one part in 10^8. … The new definition 72.27 pt = 1 in is not only better for calculation, it is also easier to remember.</p>\n</blockquote>\n<p>(To explain his motivation a little: American printers measure things in inches, and define the point in terms of inches. TeX measures things in points<sup id=\"fnref:sp\"><a class=\"footnote-ref\" href=\"#fn:sp\">1</a></sup>, and define the inch in terms of points.)</p>\n<p>For the record, NIST seems to think \"72 points/inch\" is a good enough approximation. TeX calls this the <code>bp</code> (big point).</p>\n<h2>AKA the Inkscape value</h2>\n<div class=\"subscribe-form\"></div>\n<p>Now what about Inkscape? As far as I can tell, this comes from the Postscript format's definition of the <a href=\"https://archive.org/details/postscriptlangua0000unse_c7i6/page/60/mode/2up?q=72\" target=\"_blank\">unit size</a>:</p>\n<blockquote>\n<p>The length of a unit along the\nx axis and along the y axis is 1/72 of an inch. We call this coordinate system <em>default user space</em>.</p>\n<p>These features of default user space are chosen for their mathematical simplicity and convenience. The location and orientation of the axes follow common mathematical practice and cause all points within the current page to have positive x and y coordinate values. The unit size, 1/72 of an inch, is very close to the size of a printer's point (1/72.27 inch), which is a standard measuring unit used in the printing industry.</p>\n</blockquote>\n<p>Later on <a href=\"https://archive.org/details/postscriptlangua0000unse_c7i6/page/86/mode/2up?q=72\" target=\"_blank\">page 86</a> they straight up call 1/72 inch a \"point\". <a href=\"https://www.adobe.com/jp/print/postscript/pdfs/PLRM.pdf#page=197\" target=\"_blank\">Later editions</a> would clarify it's not actually a point and that points are stupid anyway:</p>\n<blockquote>\n<p>Note: The default unit size (1/72 inch) is approximately the same as a “point,” a unit\nwidely used in the printing industry. It is not exactly the same as a point, however;\nthere is no universal definition of a point.</p>\n</blockquote>\n<p>Apple shipped PostScript in their <a href=\"https://en.wikipedia.org/wiki/LaserWriter\" target=\"_blank\">LaserWriter</a> laser printer, other companies followed suite, making PostScript the de facto printing language and 72 points/in the standard digital measure. The W3 consortium used the same measure in <a href=\"https://www.w3.org/TR/css3-values/#absolute-lengths\" target=\"_blank\">CSS</a> and <a href=\"https://developer.mozilla.org/en-US/docs/Web/SVG/Guides/Content_type#length\" target=\"_blank\">SVG</a><sup id=\"fnref:svg\"><a class=\"footnote-ref\" href=\"#fn:svg\">2</a></sup>, Inkscape is an SVG editor, and that's all she wrote.</p>\n<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<h3>Epilog: Frink</h3>\n<p>Whenever I need to mess with units of measure, I turn to  <a href=\"https://frinklang.org/\" target=\"_blank\">Frink</a>. Frink is a Turing-complete language with <em>really</em> good unit of measure support:</p>\n<div class=\"codehilite\"><pre><span></span><code>oldinch := surveyfoot / 12 // pre 1959 inch \n35 cm / (996 pts) -> oldinch / pts\n0.013834839357429718876\n</code></pre></div>\n\n<p>The author does also does incredibly thorough research on the history of units measurements. Here's what the <a href=\"https://frinklang.org/frinkdata/units.txt\" target=\"_blank\">Frink data file</a> says about points:</p>\n<div class=\"codehilite\"><pre><span></span><code>point :=          0.013837ee0 inch    // exact, NIST Handbook 44, Appendix 3\nprinterspoint :=       point\n\ntexscaledpoint :=      1/65536 point    // The TeX typesetting system uses\ntexsp :=               texscaledpoint   // this for all computations.\ncomputerpoint :=       1/72 inch        // The American point was rounded \ncomputerpica :=        12 computerpoint // to an even 1/72 inch by computer\npostscriptpoint :=     computerpoint    // people at some point. \n</code></pre></div>\n\n<p>Well, now we know what that \"some point\" is!</p>\n<p>Now we also know that the definitions of <code>texscaledpoint</code> is (<em>gasp</em>) wrong. </p>\n<div class=\"codehilite\"><pre><span></span><code>realtexpoint := 1/72.27 inch\nrealtexsp := 1/65536 realtexpoint\n(realtexsp - texsp)\n5.36285100578e-17 m (length)\n(realtexsp - texsp) / realtexsp\n1.0000000000005691827e-8\n</code></pre></div>\n\n<p>The Frink definition is off by about 50 attometers, or approximately 3% of the width of a proton.</p>\n<div class=\"footnote\">\n<hr />\n<ol>\n<li id=\"fn:sp\">\n<p>Actually \"scaled points\", where 2^16 sp = 1 pt. <a class=\"footnote-backref\" href=\"#fnref:sp\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n<li id=\"fn:svg\">\n<p>Which is why it's super weird that the SVG editor <a href=\"https://www.drawio.com/\" target=\"_blank\">draw.io</a> uses a point size of 1/<strong>100</strong> inch. <a class=\"footnote-backref\" href=\"#fnref:svg\" title=\"Jump back to footnote 2 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/points-are-a-weird-and-inconsistent-unit-of/",
          "published": "2026-05-13T15:56:37.000Z",
          "updated": "2026-05-13T15:56:37.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/new-logic-for-programmers-and-the-future-of-this/",
          "title": "New Logic for Programmers (and the future of this newsletter)",
          "description": "<p>So first the immediate news: I just released version 0.14 of <a href=\"https://logicforprogrammers.com\" target=\"_blank\">Logic for Programmers</a>! This release is pretty similar to 0.13. There are a few rewrites but the vast majority of the changes are layout, copyediting, and technical editing. Full notes <a href=\"https://github.com/logicforprogrammers/book-assets/blob/master/CHANGELOG.md\" target=\"_blank\">here</a>. </p>\n<p>In related news, I've started doing test prints of the book:</p>\n<p><img alt=\"Two test prints of Logic for Programmers\" class=\"newsletter-image\" src=\"https://assets.buttondown.email/images/958bdc79-fa06-4bce-b0a8-6e5ecfe8f2e0.png?w=960&fit=max\" /></p>\n<p>There's not a whole lot left to be done. I've gotta fix up some diagrams, do more formatting and proofreading, incorporate some fixes raised by readers, and make a website and back cover. After that, the book should be ready for 1.0. I'm aiming to have print copies purchasable by the end of June!</p>\n<hr />\n<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<p>Now the big news: starting August, I'll be a full-time employee of <a href=\"https://antithesis.com/\" target=\"_blank\">Antithesis</a>, a generative testing platform. Officially my role is \"developer educator\", and I'll be tasked with making \"property-based testing, fuzzing, fault injection, <a href=\"https://hegel.dev/\" target=\"_blank\">Hegel</a>, <a href=\"https://github.com/antithesishq/bombadil\" target=\"_blank\">Bombadil</a>, and the Antithesis platform understandable to everyday engineers\". So the same kind of work I do now, except with far more support and a matching 401(k). </p>\n<p>I already have three pages of topic ideas you have no idea how excited I am about this </p>\n<p>So how is this going to affect the newsletter? First, I want to make clear that this is <em>not</em> going to become an Antithesis newsletter. My Antithesis-related work is going to be on their official platforms. I do think one of the best ways to make a topic \"understandable\" is to write foundational material that's useful to all engineers, whether they're invested in the topic or not. I might share links to things I make along those lines, but they'll be just that, links.</p>\n<p>At the same time, the content of <em>this</em> newsletter will change a little. Property testing and fuzzing aren't the same as formal methods, but a lot of the foundations overlap, especially in how we think about properties and correctness. I don't know for sure yet, but I suspect that I'll start biasing this newsletter away from Antithesis related topics. So there will probably be less theoretic things like <a href=\"https://buttondown.com/hillelwayne/archive/what-does-undecidable-mean-anyway/\" target=\"_blank\">what does undecidable mean</a> and <a href=\"https://buttondown.com/hillelwayne/archive/some-tests-are-stronger-than-others/\" target=\"_blank\">Some tests are stronger than others</a> and more history and software weirdness things like <a href=\"https://buttondown.com/hillelwayne/archive/why-do-we-call-it-boilerplate-code/\" target=\"_blank\">Why do we call it \"boilerplate code\"</a> and <a href=\"https://buttondown.com/hillelwayne/archive/finding-hard-24-puzzles-with-planner-programming/\" target=\"_blank\">esoteric programming paradigms</a>. </p>\n<p>The other change is going to be frequency. For the past six years I've kept updates to (mostly) a weekly schedule. For the past six years I've also been totally self-employed. I don't know how much time I'll have with a full time job! Once I'm settled in I'd like to keep writing newsletters, but it might slow down from weekly to biweekly or monthly. We'll feel it out as we go. </p>\n<hr />\n<p>Anyway, this has been a pretty software-light newsletter, so let's close out with a fun thing. <code>f(x) = x+2</code> is a  <strong>monotonically increasing function</strong>: increasing <code>x</code> increases <code>f(x)</code> and decreasing <code>f(x)</code> decreases <code>f(x)</code>.  Similarly, <code>f(x) = -x+1</code> is monotonically decreasing, and <code>f(x) = x^2</code> is neither.</p>\n<p>While working on the book I realized that the <code>all</code> quantifier is monotonically false with respect to adding elements and true with respect to removing them. Let <code>A(set) = all x in set: P(x)</code>. Then if <code>A(S)</code> is false, <code>A(S | {e})</code> is also false, and if <code>A(S)</code> is true, <code>A(S - {e})</code> is also true. <code>some</code> goes the other way: if it's true, it's true if you add an element, and if it's false it's still false if you take one away.</p>\n<p>An interesting consequence is that <code>all</code> <em>must</em> be true for the empty set, because if it was false it would be false for all values! This is another justification why, in Python, <a href=\"https://buttondown.com/hillelwayne/archive/why-all-is-true-prod-is-1-etc/\" target=\"_blank\"><code>all([]) == True</code></a>.</p>\n<p>Similarly, in temporal logic: <code>always A</code> is monotonically false with respect to system behavior and <code>eventually A</code> is monotonically true. I realized this when messing with this <a href=\"https://quickstrom.github.io/ltl-visualizer/\" target=\"_blank\">LTL visualizer</a> my friend (and soon to be coworker!) Oskar Wickström. I think this is pretty neat!</p>\n<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>",
          "url": "https://buttondown.com/hillelwayne/archive/new-logic-for-programmers-and-the-future-of-this/",
          "published": "2026-05-06T17:03:46.000Z",
          "updated": "2026-05-06T17:03:46.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/illegal-vs-unwanted-states/",
          "title": "Illegal vs Unwanted States",
          "description": "<p>An <strong>illegal state</strong> is a state we never want our system to be in. An <strong>unwanted state</strong> is a state we don't want to stay in. Many states that we wish were illegal are actually unwanted.</p>\n<p>Considering a calendaring software which stores calendar events as <code>{user: {events: [event]}}</code>, where each event has a start and end time. This allows one person to attend two events at the same time. We might consider this illegal and replace the data type with <code>{user: {time: optional event}}</code> which makes this impossible. However, a scheduling conflict isn't illegal, only unwanted! It is possible for a person to sign up for two overlapping events. Maybe they're supposed to choose one event, maybe they'll decide which event to go to later, maybe one of the events doesn't actually represent an in-person meeting. </p>\n<p>In that case it's acceptable, if not ideal, to remain in the unwanted state. Other unwanted states lead to invalid states if not exited quickly. An airline flight is in an unwanted state if there are more passengers booked to fly than seats available. This must be resolved before passengers actually board, as \"more passengers physically on the plane than seats available\" is an illegal state.</p>\n<p>In some cases, an unwanted state does not lead to illegal states, but permanently remaining in the unwanted state is still a problem. We might guarantee that a network partition does not ever lead to inconsistent data. Even though the unwanted state of a network partition cannot cause the illegal state of corrupt data, we still have a big problem if we don't ever fix the partition.</p>\n<h2>Why systems must represent unwanted states</h2>\n<div class=\"subscribe-form\"></div>\n<p>Generally, unwanted states can happen if we don't have complete control over our system's behavior. We can't guarantee our network is perfectly reliable, our servers are always up, our users all put in correct data. If our system gets input from the external world then the world can push us into an unwanted state. We need to be able to detect these states so we can resolve them.</p>\n<p>Even when we have complete control over the system, we still may want to be able to temporarily dip into unwanted states. If they wanted, airlines could make overbooking flights impossible. But airlines want to be able to overbook because they expect some number of no-shows. We need to allow them their unwanted state and then put systems in place to prevent it evolving into an illegal state.</p>\n<p>Sometimes we need unwanted states to make certain workflows possible. It may be the case that 95% never, ever want to accepted conflicting events, and preventing that unwanted state would make their lives better. But without that unwanted state, intentionally double booking yourself is impossible. Some people might need that! In these cases we want to make it very clear to users that they're entering an unwanted state, and then let them decide for themselves how to leave it. </p>\n<p>(The airlines and users want the unwanted state! It's us engineers who consider it \"unwanted\" because it can lead to problems down the road.) </p>\n<h2>Formal models of unwanted states</h2>\n<p>Illegal states correspond to violated invariants. Conventionally speaking we write this as <code>[]!Illegal</code>: it should be true at all times that <code>Illegal</code> is not true. If a single state ever satisfies <code>Illegal</code> then our system has a bug. </p>\n<p>Unwanted states are trickier, since they can be both <a href=\"https://www.hillelwayne.com/post/safety-and-liveness/\" target=\"_blank\">safety and liveness properties</a>. If modeling an airline system, we don't necessarily want to check properties of overbooking, we only need to check that no overflights happen.<sup id=\"fnref:overflight\"><a class=\"footnote-ref\" href=\"#fn:overflight\">1</a></sup> We may discover in the process of verifying that that overbooking is the main cause of overflights and/or that if overbooking is not resolved, then we will eventually have an overflight. Further, if \"we never overbook\" guarantees \"we never overflight\", we'd say that \"no overbooks\" is a <a href=\"https://buttondown.com/hillelwayne/archive/some-tests-are-stronger-than-others/\" target=\"_blank\">stronger</a> property than \"no overflights\". </p>\n<p>\"We never remain in a network partition\" is <a href=\"https://buttondown.com/hillelwayne/archive/formalizing-stability-and-resilience-properties/\" target=\"_blank\">formalized</a> as <code>[]<>!Partition</code>: we can enter a partition and stay partitioned a long time but must eventually heal the partition. The <a href=\"https://p-org.github.io/P/\" target=\"_blank\">P specification language</a> calls these <a href=\"https://p-org.github.io/P/manual/monitors/#liveness-specification\" target=\"_blank\">hot states</a>.</p>\n<p>(PS: if all goes well, there should be a new <a href=\"https://logicforprogrammers.com\" target=\"_blank\">Logic for Programmers</a> release next week!)</p>\n<div class=\"footnote\">\n<hr />\n<ol>\n<li id=\"fn:overflight\">\n<p>Not a real term. <a class=\"footnote-backref\" href=\"#fnref:overflight\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/illegal-vs-unwanted-states/",
          "published": "2026-04-28T15:14:09.000Z",
          "updated": "2026-04-28T15:14:09.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/people-get-confused-when-language-implementations/",
          "title": "People get confused when language implementations break language guarantees",
          "description": "<p>Take the following Python program:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"c1\"># x = 1, y = 2</span>\n<span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"mi\">0</span>\n<span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">x</span>\n<span class=\"nb\">print</span><span class=\"p\">([</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">])</span>\n</code></pre></div>\n\n<p>It'll print <code>[0, 0]</code>. If we swapped the two assignments, it'd instead print <code>[0, 1]</code>. Each assignment happens in a separate temporal step. Pretty much all imperative languages behave this way.</p>\n<p>Now take the following TLA+ snippet:</p>\n<div class=\"codehilite\"><pre><span></span><code>\\* x = 1, y = 2\n/\\ x' = 0\n/\\ y' = x\n/\\ PrintT(<<x', y'>>)\n</code></pre></div>\n\n<p>This'll print <code><<0, 1>></code>. Unlike in imperative languages, TLA+ separates the notion of update and temporal step. We read <code>x' = 0</code> as \"in the <em>next</em> state, <code>x</code> will be 0, but it still has the same value in the current state\". So in every state <code>x</code> and <code>x'</code> are essentially separate variables. As a consequence of this, the order of statements don't matter in TLA+ semantics and swapping the two assignments doesn't change the printed output. The language is really clever like that! This means, among other things that there's basically no intrastep race conditions. One function can update a variable without affecting how any other function uses it.</p>\n<p>Okay so now beginners inevitably run into a problem with this:</p>\n<div class=\"codehilite\"><pre><span></span><code>\\* x = 1, y = 2\n/\\ x' = y'\n/\\ y' = x\n/\\ PrintT(<<x', y'>>)\n</code></pre></div>\n\n<p>This'll crash because <code>y'</code> isn't defined yet. But if we swap the two assignments this works fine and prints <code><<1, 1>></code>. So clearly that whole thing about nonordering is a pack of bunk. </p>\n<p>Well, not exactly. TLA+ semantics still guarantee nonordering. The problem is that verifiers don't perfectly implement the TLA+ semantics. <code>y' > 0</code> is a totally reasonable \"assignment\" but there's an infinite number of possible next states! So the main model checker (TLC) instead requires that <code>y' = some_value</code> comes before any other use of <code>y'</code>, which means ordering now matters.</p>\n<p>The bigger problem is with <code>PrintT</code>. The TLA+ semantics guarantee nonordering because the semantics don't allow for side effects. The model checker adds in effectful operators like <code>PrintT</code> and <code>Assert</code> and <code>IOExec</code>. This can cause a problem with guard statements. Theoretically speaking these two script blocks are equivalent:</p>\n<div class=\"codehilite\"><pre><span></span><code>/\\ x = 0 \\* guard statement\n/\\ P()\n/\\ x' = x + 1\n\n/\\ x' = x + 1\n/\\ P()\n/\\ x = 0 \\* guard statement\n</code></pre></div>\n\n<p>When <code>x = 1</code>, these don't lead to a new state due to the guard clause. But the model checker evaluates each line one at a time, meaning in block 2 it will run <code>x' = x + 1</code> and <code>P()</code> before getting to <code>x = 0</code> and discarding the state. If <code>P</code> is a proper TLA+ operator, this isn't a problem, but if it's <code>PrintT</code> or <code>Assert</code> it will take its effect first, leading to weird ghost prints that don't correspond with any next states.</p>\n<p>This difference between \"what TLA+ semantics guarantees\" and \"the specific ways TLC can break those guarantees\" is a huge source of confusion for people! On top of that many of these operators, like <code>IOExec</code> and <code>TLCSet</code>, are meant as escape hatches. So if you need them you're already doing something pretty weird, and that makes it even more confusing. </p>\n<p>And on top of that is that there's no syntactic or visual distinguishing between a guarantee-breaking TLC operator and a regular safe TLA+ operator. In compiled languages you got pragmas and preprocessors, which let the compiler do things the language can't. But those usually have a visually distinct syntax, so you know from looking that here be dragons. </p>\n<p>I'm reminded of Neel Krishnaswami's incredible post <a href=\"https://semantic-domain.blogspot.com/2013/07/what-declarative-languages-are.html\" target=\"_blank\">What Declarative Languages Are</a><sup id=\"fnref:laurie\"><a class=\"footnote-ref\" href=\"#fn:laurie\">1</a></sup>:</p>\n<blockquote>\n<p>This also lets us make the prediction that the least-loved features of any declarative language will be the ones that expose the operational model, and break the declarative semantics. So we can predict that people will dislike (a) backreferences in regular expressions, (b) ordered choice in grammars, (c) row IDs in query languages, (d) cut in Prolog, (e) constraint priorities in constraint languages. </p>\n</blockquote>\n<p>Prolog cuts have a visually distinct syntax, but a predicate that uses a cut isn't visually distinct from a <a href=\"https://www.metalevel.at/prolog/purity\" target=\"_blank\">logically pure</a> predicate. But that's also a little less tricky than what we got in TLA+, since the cut is still part of the language semantics.</p>\n<p>(Then again, different Prolog dialects have different ways of <a href=\"https://eu.swi-prolog.org/pldoc/man?section=printmsg\" target=\"_blank\">printing strings</a>, which add side effects to Prolog, and they're not visually distinct from other predicates. So the same problem!)</p>\n<p>I don't actually have any fix for this. I just find it a fascinating example of a <a href=\"https://en.wikipedia.org/wiki/Leaky_abstraction\" target=\"_blank\">leaky abstraction</a>. Maybe we could write a code highlighter that highlights all functions that transitively use a \"weird\" function or something.</p>\n<div class=\"footnote\">\n<hr />\n<ol>\n<li id=\"fn:laurie\">\n<p><a href=\"https://tratt.net/laurie/blog/2013/relative_and_absolute_levels.html\" target=\"_blank\">Obligatory response post</a> <a class=\"footnote-backref\" href=\"#fnref:laurie\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/people-get-confused-when-language-implementations/",
          "published": "2026-04-21T17:40:17.000Z",
          "updated": "2026-04-21T17:40:17.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/a-sufficiently-comprehensive-spec-is-not/",
          "title": "A sufficiently comprehensive spec is not (necessarily) code",
          "description": "<p>Sorry for missing last week! Was sick and then busy.</p>\n<p>This week I want to cover a pet peeve of mine, best seen in this comic:</p>\n<p><a href=\"https://www.commitstrip.com/en/2016/08/25/a-very-comprehensive-and-precise-spec/?\" target=\"_blank\"><img alt=\"A comic about a business person thinking detailed specs will replace coders, and then the coder calls the spec \"code\"\" class=\"newsletter-image\" src=\"https://www.commitstrip.com/wp-content/uploads/2016/08/Strip-Les-specs-cest-du-code-650-finalenglish.jpg\" /></a></p>\n<p>A \"comprehensive and precise spec\" is not necessarily code. A specification corresponds to a <em>set</em> of possible implementations, and code is a single implementation in that set. As long as the set has more than one element, there is a separation between the spec and the code. </p>\n<p>Consider a business person (bp) who asks:</p>\n<blockquote>\n<p>I want a tool to convert miles to kilometers.</p>\n</blockquote>\n<p>Is this a comprehensive spec? Maybe, you can give it to Claude Code and tell it to make all design decisions and it will give you a program that converts miles to km. At the same time, there is a huge amount of details left out of this. What language? What's the UX? Should it be a command line script or a mobile app or an enterprise SaaS? For this reason, if we gave Claude's output to the bp, they'll probably be unsatisfied. The set of possible implementations includes the programs they want, but also lots of programs they don't want.</p>\n<p>So they now they say:</p>\n<blockquote>\n<p>It should be a textbox on a website.</p>\n</blockquote>\n<p>Okay, this rules out a lot more stuff, but there's still a lot to decide. React or vanillajs or htmx? Should the output be a separate textbox or a popup? Should we use a conversion of <code>1.6</code>, <code>1.61</code>, or <code>1.609</code>? So you could argue that this is still not a \"comprehensive and precise spec\". But what if the bp is happy with whatever Claude makes? Then their spec was sufficiently comprehensive and precise, since they got a program that solved their problem!</p>\n<p>Now the comic above makes the more specific claim that a spec \"comprehensive and precise enough <em>to generate a program</em>\" is code. That wasn't even true before LLMs. <a href=\"https://en.wikipedia.org/wiki/Program_synthesis\" target=\"_blank\">Program synthesis</a>, the automatic generation of conformant programs from specifications, is an active field of research! Last I checked in 2019 they were only generating local functions from type specifications; I don't know how things have changed with LLMs. But still, it shows that code and comprehensive specs are distinct things. </p>\n<h3>Specs are abstractions</h3>\n<div class=\"subscribe-form\"></div>\n<p>What I'm getting at here is that a specification is an <strong>abstraction</strong> of code.<sup id=\"fnref:abstraction\"><a class=\"footnote-ref\" href=\"#fn:abstraction\">1</a></sup> For every spec, there is a set of possible programs that satisfy that spec. The more comprehensive and precise the spec, the fewer programs in this set. If <code>spec1</code> corresponds to a superset of <code>spec2</code>, we further say that <code>spec2</code> <strong>refines</strong> <code>spec1</code>. A specification is <strong>sufficient</strong> if it does not need to be refined further: no matter what implementation (<em>within reason</em><sup id=\"fnref:reason\"><a class=\"footnote-ref\" href=\"#fn:reason\">2</a></sup>) is provided, the specifier would be satisfied. A spec does not need to be fully comprehensive to be sufficient.</p>\n<h3>Programmers are still needed to write specs</h3>\n<p>The comic makes a further claim: \"a sufficiently detailed spec is code\" is a reason why programmers won't be out of a job, even with we could automatically generate code from specs. And this is still true.</p>\n<p>It is often the case that we express the abstraction spec via a formal language. Normally this makes me think of TLA+ or UML or even <a href=\"https://syque.com/quality_tools/tools/Tools104.htm\" target=\"_blank\">Planguage</a>, but the most common example of this would be test suites. <a href=\"https://buttondown.com/hillelwayne/archive/what-is-a-specification\" target=\"_blank\">Tests are specifications, too</a>! And as a rule, it seems impossible to get nonprogrammers to successfully encode things in formal languages. Cucumber was a failed attempt to make business people write formal specs.</p>\n<p>But does this make a comprehensive spec \"code\"? I'd argue no.  It's possible to encode a specification in a programming language (again, test suites), but it is just that, an encoding. The spec still corresponds to a set of possible implementation programs, and the spec is still useful even if we don't encode it. Keeping \"code\" and \"spec\" distinct concepts is useful.</p>\n<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<div class=\"footnote\">\n<hr />\n<ol>\n<li id=\"fn:abstraction\">\n<p><a href=\"https://www.pathsensitive.com/2022/03/abstraction-not-what-you-think-it-is.html\" target=\"_blank\">obligatory link</a> <a class=\"footnote-backref\" href=\"#fnref:abstraction\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n<li id=\"fn:reason\">\n<p>As in the implementation makes a good faith attempt to make a reasonable implementation. IE \"this converts miles to kilometers and also mines crypto\" is not a good faith interpretation. <a class=\"footnote-backref\" href=\"#fnref:reason\" title=\"Jump back to footnote 2 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/a-sufficiently-comprehensive-spec-is-not/",
          "published": "2026-04-15T16:18:02.000Z",
          "updated": "2026-04-15T16:18:02.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/april-cools-post-new-york-vs-chicago-pizza/",
          "title": "April Cools Post: New York vs Chicago Pizza",
          "description": "<p>Happy April Cools! My not-tech post this year is <a href=\"https://www.hillelwayne.com/post/pizza/\" target=\"_blank\">Chicago vs New York Pizza is the Wrong Argument</a>, which is mostly an excuse for me to talk about Chicago food. See <a href=\"https://www.aprilcools.club/\" target=\"_blank\">here</a> for all of the other April Cools submissions. As of this email we have sixteen posts; there's still time to submit something!<sup id=\"fnref:praveen\"><a class=\"footnote-ref\" href=\"#fn:praveen\">1</a></sup></p>\n<p>This one came out of a pizza argument with <a href=\"https://www.bellotti.tech/about\" target=\"_blank\">Marianne Bellotti</a>, a true New Yorker who doesn't think deep dish is real pizza. I wanted to find a new angle on the old argument and this is what I came up with. Interestingly, there's arguably no real New York analog to deep dish (Khatchapuri is kinda similar but doesn't have the same cultural relevance). If I had to pick something to compare deep dish to, it'd be <a href=\"https://en.wikipedia.org/wiki/Toasted_ravioli\" target=\"_blank\">toasted ravioli</a> (despite that being a appetizer and not an entree).</p>\n<p>Also, I didn't really touch on it in the article, but one of the annoying things about pizza debates is that the most of the Chicago pizza chains <em>don't serve deep dish</em>. Instead they do \"stuffed pizzas\". Deep dish is crust, cheese, tomato, while stuffed pizza is crust, cheese, another layer of crust, tomato. It's heavy and more casserole like and they always have way too much cheese. Don't get me wrong, it's still decent, but I think it's a worse kind of pizza overall.</p>\n<p>Anyway, the real point of the post is that Chicago's a damn good sausage city. Even the Home Depots here have hot dogs.</p>\n<div class=\"footnote\">\n<hr />\n<ol>\n<li id=\"fn:praveen\">\n<p>Praveen, if you're reading this, you didn't include a link to your post or any contact details with your submission. Email me and we'll get it on the site <a class=\"footnote-backref\" href=\"#fnref:praveen\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/april-cools-post-new-york-vs-chicago-pizza/",
          "published": "2026-04-01T17:53:29.000Z",
          "updated": "2026-04-01T17:53:29.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/choose-boring-technology-and-innovative-practices/",
          "title": "Choose Boring Technology and Innovative Practices",
          "description": "<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<p>The famous article <a href=\"https://mcfunley.com/choose-boring-technology\" target=\"_blank\">Choose Boring Technology</a> lists two problems with using innovative technology:</p>\n<ol>\n<li>There are too many \"unknown unknowns\" in a new technology, whereas in boring technology the pitfalls are already well-known.</li>\n<li>Shiny tech has a maintenance burden that persist long after everybody has gotten bored with  it. </li>\n</ol>\n<p>Both of these tie back to the idea that the main cost of technology is maintenance. Even if something is easy to build with, it might not be as easy to keep running. We cannot \"abandon\" mission-critical technology. Say my team builds a new service on <a href=\"https://julialang.org/\" target=\"_blank\">Julia</a>, and 2 years later decides it was the wrong choice. We're stuck with either the (expensive) process of migrating all our <del>data to Postgres</del> code to Java or the (expensive) process of keeping it running anyway. Either way, the company needs to spend resources keeping engineers trained on the tech instead of other useful things, like how to mine crypto in their heads. </p>\n<p>Tech is slow to change. Not as slow to change as, say, a bridge, but still pretty slow.</p>\n<p>Now say at the same time as Julia, we also decided to start practicing <a href=\"https://medium.com/@kentbeck_7670/test-commit-revert-870bbd756864\" target=\"_blank\">test && commit || revert</a> (TCR). After two years, we get sick of that, too. To deal with this, we can simply... not do TCR anymore. There is no \"legacy practice\" we need to support, no maintenance burden to dropping a process. It is much easier to adopt and abandon practices than it is to adopt and abandon technology.</p>\n<p>This means while we should be conservative in the software we use, we can be more freely innovative in how we use it. If we get <a href=\"https://mcfunley.com/choose-boring-technology#embrace-boredom\" target=\"_blank\">three innovation tokens</a> for technology, we get like six or seven for practices. And we can trade in our practices to get those tokens back. </p>\n<p>(The flip side of this is that social processes are less \"stable\" than technology and take more work to keep running. This is why \"engineering controls\" are considered <a href=\"https://hillelwayne.com/post/hoc/\" target=\"_blank\">more effective as reducing accidents</a> than administrative controls.) </p>\n<h3>Choose Boring Material and Innovative Tools</h3>\n<p>Pushing this argument further, we can divide technology into two categories: \"material\" and \"tools\".<sup id=\"fnref:tools\"><a class=\"footnote-ref\" href=\"#fn:tools\">1</a></sup> Material is anything that needs to run to support the business:  our code, our service architecture, our data <em>and</em> database engine, etc. The tools are what we use to make material, but that the material doesn't depend on. Editors, personal bash scripts, etc. The categories are fuzzy, but it boils down to \"how bad is it for the project to lose this?\" </p>\n<p>In turn, because tools are easier to replace than material, we can afford to be more innovative with it. I suspect we see this in practice, too, that people replace ephemera faster than they replace their databases.</p>\n<p>(This is a short one because I severely overestimated how much I could write about this.)</p>\n<hr />\n<h2><a href=\"https://www.aprilcools.club/\" target=\"_blank\">April Cools</a></h2>\n<p>It's in a week! You can submit your April Cools in the <a href=\"https://docs.google.com/forms/d/e/1FAIpQLSc6Yt_ZCA_S6EOt-VVtla-uObnzPlSg9x_VOLgiNGN_-AY-kQ/viewform\" target=\"_blank\">google form</a> or, if you want to be all cool and techie, as a <a href=\"https://github.com/april-cools/april-cools.github.io/blob/main/_data/projects.yml\" target=\"_blank\">github PR</a>.</p>\n<div class=\"footnote\">\n<hr />\n<ol>\n<li id=\"fn:tools\">\n<p>This is different from how we call all software \"tools\". <a class=\"footnote-backref\" href=\"#fnref:tools\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/choose-boring-technology-and-innovative-practices/",
          "published": "2026-03-24T14:38:06.000Z",
          "updated": "2026-03-24T14:38:06.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/llms-are-bad-at-vibing-specifications/",
          "title": "LLMs are bad at vibing specifications",
          "description": "<h3>No newsletter next week</h3>\n<p>I'll be speaking at <a href=\"https://qconlondon.com/\" target=\"_blank\">InfoQ London</a>. But see below for a book giveaway!</p>\n<hr />\n<h1>LLMs are bad at vibing specifications</h1>\n<p>About a year ago I wrote <a href=\"https://buttondown.com/hillelwayne/archive/ai-is-a-gamechanger-for-tla-users/\" target=\"_blank\">AI is a gamechanger for TLA+ users</a>, which argued that AI are a \"specification force multiplier\". That was written from the perspective an TLA+ expert using these tools. A full <a href=\"https://github.com/search?q=path%3A*.tla+NOT+is%3Afork+claude&type=code\" target=\"_blank\">4% of Github TLA+ specs</a> now have the word \"Claude\" somewhere in them. This is interesting to me, because it suggests there was always an interest in formal methods, people just lacked the skills to do it.  </p>\n<p>It's also interesting because it gives me a sense of what happens when beginners use AI to write formal specs. It's not good.</p>\n<p>As a case study, we'll use <a href=\"https://github.com/myProjectsRavi/sentinel-protocol/tree/main/docs/formal/specs\" target=\"_blank\">this project</a>, which is kind of enough to have vibed out TLA+ and Alloy specs.</p>\n<h3>Looking at a project</h3>\n<p><a href=\"https://github.com/myProjectsRavi/sentinel-protocol/blob/main/docs/formal/specs/threat-intel-mesh.als\" target=\"_blank\">Starting with the Alloy spec</a>. Here it is in its entirety:</p>\n<div class=\"codehilite\"><pre><span></span><code>module ThreatIntelMesh\n\nsig Node {}\n\none sig LocalNode extends Node {}\n\nsig Snapshot {\n  owner: one Node,\n  signed: one Bool,\n  signatures: set Signature\n}\n\nsig Signature {}\n\nsig Policy {\n  allowUnsignedImport: one Bool\n}\n\npred canImport[p: Policy, s: Snapshot] {\n  (p.allowUnsignedImport = True) or (s.signed = True)\n}\n\nassert UnsignedImportMustBeDenied {\n  all p: Policy, s: Snapshot |\n    p.allowUnsignedImport = False and s.signed = False implies not canImport[p, s]\n}\n\nassert SignedImportMayBeAccepted {\n  all p: Policy, s: Snapshot |\n    s.signed = True implies canImport[p, s]\n}\n\ncheck UnsignedImportMustBeDenied for 5\ncheck SignedImportMayBeAccepted for 5\n</code></pre></div>\n\n<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<p>Couple of things to note here: first of all, this doesn't actually compile. It's using the <a href=\"https://alloy.readthedocs.io/en/latest/modules/boolean.html\" target=\"_blank\">Boolean</a> standard module so needs <code>open util/boolean</code> to function. Second, Boolean is the wrong approach here; you're supposed to use subtyping. </p>\n<div class=\"codehilite\"><pre><span></span><code>sig Snapshot {\n<span class=\"w\"> </span> owner: one Node,\n<span class=\"gd\">- signed: one Bool,</span>\n<span class=\"w\"> </span> signatures: set Signature\n}\n\n<span class=\"gi\">+ sig SignedSnapshot in Snapshot {}</span>\n\n\npred canImport[p: Policy, s: Snapshot] {\n<span class=\"gd\">- s.signed = True</span>\n<span class=\"gi\">+ s in SignedSnapshot</span>\n}\n</code></pre></div>\n\n<p>So we know the person did not actually run these specs. This is <em>somewhat</em> less of a problem in TLA+, which has an official MCP server that lets the agent run model checking. Even so, I regularly see specs that I'm pretty sure won't model check, with things like using <code>Reals</code> or assuming <code>NULL</code> is a built-in and not a user-defined constant.</p>\n<p>The bigger problem with the spec is that <code>UnsignedImportMustBeDenied</code> and <code>SignedImportMayBeAccepted</code> <em>don't actually do anything</em>. <code>canImport</code> is defined as <code>P || Q</code>. <code>UnsignedImportMustBeDenied</code> checks that <code>!P && !Q => !canImport</code>. <code>SignedImportMayBeAccepted</code> checks that <code>P => canImport</code>. These are tautologically true! If they do anything at all, it is only checking that <code>canImport</code> was defined correctly. </p>\n<p>You see the same thing in the <a href=\"https://github.com/myProjectsRavi/sentinel-protocol/blob/main/docs/formal/specs/serialization-firewall.tla\" target=\"_blank\">TLA+ specs</a>, too:</p>\n<div class=\"codehilite\"><pre><span></span><code>GadgetPayload ==\n  /\\ gadgetDetected' = TRUE\n  /\\ depth' \\in 0..(MaxDepth + 5)\n  /\\ UNCHANGED allowlistedFormat\n  /\\ decision' = \"block\"\n\nNoExploitAllowed == gadgetDetected => decision = \"block\"\n</code></pre></div>\n\n<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<p>The AI is only writing \"obvious properties\", which fail for reasons like \"we missed a guard clause\" or \"we forgot to update a variable\". It does not seem to be good at writing \"subtle\" properties that fail due to concurrency, nondeterminism, or bad behavior separated by several steps. Obvious properties are useful for orienting yourself and ensuring the system behaves like you expect, but the actual value in using formal methods comes from the subtle properties. </p>\n<p>(This ties into <a href=\"https://buttondown.com/hillelwayne/archive/some-tests-are-stronger-than-others/\" target=\"_blank\">Strong and Weak Properties</a>. LLM properties are weak, intended properties need to be strong.)</p>\n<p>This is a problem I see in almost every FM spec written by AI. LLMs aren't doing one of the core features of a spec. Articles like <a href=\"https://martin.kleppmann.com/2025/12/08/ai-formal-verification.html\" target=\"_blank\">Prediction: AI will make formal verification go mainstream</a> and <a href=\"https://leodemoura.github.io/blog/2026/02/28/when-ai-writes-the-worlds-software.html\" target=\"_blank\">When AI Writes the World's Software, Who Verifies It?</a> argue that LLMs will make formal methods go mainstream, but being easily able to write specifications doesn't help with correctness if the specs don't actually verify anything.</p>\n<h3>Is this a user error?</h3>\n<div class=\"subscribe-form\"></div>\n<p>I first got interested in LLMs and TLA+ from <a href=\"https://zfhuang99.github.io/github%20copilot/formal%20verification/tla+/2025/05/24/ai-revolution-in-distributed-systems.html\" target=\"_blank\">The Coming AI Revolution in Distributed Systems</a>. The author of that later <a href=\"https://github.com/zfhuang99/lamport-agent/blob/main/spec/CRAQ/CRAQ.tla\" target=\"_blank\">vibecoded a spec</a> with a considerably more complex property:</p>\n<div class=\"codehilite\"><pre><span></span><code>NoStaleStrictRead ==\n  \\A i \\in 1..Len(eventLog) :\n    LET ev == eventLog[i] IN\n      ev.type = \"read\" =>\n        LET c == ev.chunk IN\n        LET v == ev.version IN\n        /\\ \\A j \\in 1..i :\n             LET evC == eventLog[j] IN\n               evC.type = \"commit\" /\\ evC.chunk = c => evC.version <= v\n</code></pre></div>\n\n<p>This is a lot more complicated than the <code>(P => Q && P) => Q</code> properties I've seen! It could be because <a href=\"https://github.com/deepseek-ai/3FS/tree/main/specs/DataStorage\" target=\"_blank\">the corresponding system already had a complete spec written in P</a>. But it could also be that Cheng Huang is already an expert specifier, meaning he can get more out of an LLM than an ordinary developer can. I've also noticed that I can usually coax an LLM to do more interesting things than most of my clients can. Which is good for my current livelihood, but bad for the hope of LLMs making formal methods mainstream. If you need to know formal methods to get the LLM to do formal methods, is that really helping?</p>\n<p>(Yes, if it lowers the skill threshold-- means you can apply FM with 20 hours of practice instead of 80. But the jury's still out on how <em>much</em> it lowers the threshold. What if it only lowers it from 80 to 75?) </p>\n<p>On the other hand, there also seem to be some properties that AI struggles with, even with explicit instructions. Last week a client and I tried to get Claude to generate a good <a href=\"https://www.hillelwayne.com/post/safety-and-liveness/\" target=\"_blank\">liveness</a> or <a href=\"https://www.hillelwayne.com/post/action-properties/\" target=\"_blank\">action</a> property instead of a standard obvious invariant, and it just couldn't. Training data issue? Something in the innate complexity of liveness? It's not clear yet. These properties are even more \"subtle\" than most invariants, so maybe that's it.</p>\n<p>On the other other hand, this is all as of March 2026. Maybe this whole article will be laughably obsolete by June. </p>\n<hr />\n<h3><a href=\"https://logicforprogrammers.com\" target=\"_blank\">Logic for Programmers</a> Giveaway</h3>\n<p>Last week's giveaway raised a few issues. First, the New World copies were all taken before all of the emails went out, so a lot of people did not even get a chance to try for a book. Second, due to a Leanpub bug the Europe coupon scheduled for 10 AM UTC actually activated at 10 AM my time, which was early evening for Europe. Third, everybody in the APAC region got left out.</p>\n<p>So, since I'm not doing a newsletter next week, let's have another giveaway:</p>\n<ul>\n<li><a href=\"https://leanpub.com/logic/c/E5A55F7B482C3\" target=\"_blank\">This coupon</a> will go up 2026-03-16 at 11:00 UTC, which should be noon Central European Time, and be good for ten books (five for this giveaway, five to account for last week's bug).</li>\n<li><a href=\"https://leanpub.com/logic/c/ADC664C95B6D1\" target=\"_blank\">This coupon</a> will go up 2026-03-17 at 04:00 UTC, which should be noon Beijing Time, and be good for five books.</li>\n<li><a href=\"https://leanpub.com/logic/c/U1250212A9070\" target=\"_blank\">This coupon</a> will go up 2026-03-17 at 17:00 UTC, which should be noon Central US Time, and also be good for five books.</li>\n</ul>\n<p>I think that gives the best chance of everybody getting at least a chance of a book, while being resilient to timezone shenanigans due to travel / Leanpub dropping bugfixes / daylight savings / whatever. </p>\n<p>(No guarantees that later \"no newsletter\" weeks will have giveaways! This is a gimmick)</p>",
          "url": "https://buttondown.com/hillelwayne/archive/llms-are-bad-at-vibing-specifications/",
          "published": "2026-03-10T17:12:30.000Z",
          "updated": "2026-03-10T17:12:30.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/free-books/",
          "title": "Free Books",
          "description": "<p>Spinning a <a href=\"https://www.youtube.com/watch?v=NB4hzg4k7_A\" target=\"_blank\">lot of plates</a> this week so skipping the newsletter. As an apology, have ten free copies of <em>Logic for Programmers</em>.</p>\n<ul>\n<li><a href=\"https://leanpub.com/logic/c/EBDFA51B15C1\" target=\"_blank\">These five</a> are available now.</li>\n<li><del><a href=\"https://leanpub.com/logic/c/5A55F7B482C3\" target=\"_blank\">These five</a> <em>should</em> be available at 10:30 AM CEST tomorrow, so people in Europe have a better chance of nabbing one.</del> Nevermind Leanpub had a bug that made this not work properly</li>\n</ul>",
          "url": "https://buttondown.com/hillelwayne/archive/free-books/",
          "published": "2026-03-03T16:34:33.000Z",
          "updated": "2026-03-03T16:34:33.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/new-blog-post-some-silly-z3-scripts-i-wrote/",
          "title": "New Blog Post: Some Silly Z3 Scripts I Wrote",
          "description": "<p>Now that I'm not spending all my time on Logic for Programmers, I have time to update my website again! So here's the first blog post in five months: <a href=\"https://www.hillelwayne.com/post/z3-examples/\" target=\"_blank\">Some Silly Z3 Scripts I Wrote</a>.</p>\n<p>Normally I'd also put a link to the Patreon notes but I've decided I don't like publishing gated content and am going to wind that whole thing down. So some quick notes about this post:</p>\n<ul>\n<li>Part of the point is admittedly to hype up the eventual release of LfP. I want to start marketing the book, but don't want the marketing material to be devoid of interest, so tangentially-related-but-independent blog posts are a good place to start.</li>\n<li>The post discusses the concept of \"chaff\", the enormous quantity of material (both code samples and prose) that didn't make it into the book. The book is about 50,000 words… and considerably shorter than the total volume of chaff! I don't <em>think</em> most of it can be turned into useful public posts, but I'm not entirely opposed to the idea. Maybe some of the old chapters could be made into something?</li>\n<li>Coming up with a conditioned mathematical property to prove was a struggle. I had two candidates: <code>a == b * c => a / b == c</code>, which would have required a long tangent on how division must be total in Z3, and  <code>a != 0 => some b: b * a == 1</code>, which would have required introducing a quantifier (SMT is real weird about quantifiers). Division by zero has already caused me enough grief so I went with the latter. This did mean I had to reintroduce \"operations must be total\" when talking about arrays.</li>\n<li>I have no idea why the array example returns <code>2</code> for the max profit and not <code>99999999</code>. I'm guessing there's some short circuiting logic in the optimizer when the problem is ill-defined?</li>\n<li>One example I could not get working, which is unfortunate, was a demonstration of how SMT solvers are undecidable via encoding Goldbach's conjecture as an SMT problem. Anything with multiple nested quantifiers is a pain.</li>\n</ul>",
          "url": "https://buttondown.com/hillelwayne/archive/new-blog-post-some-silly-z3-scripts-i-wrote/",
          "published": "2026-02-23T16:49:10.000Z",
          "updated": "2026-02-23T16:49:10.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/stream-of-consciousness-driven-development/",
          "title": "Stream of Consciousness Driven Development",
          "description": "<p>This is something I just tried out last week but it seems to have enough potential to be worth showing unpolished. I was pairing with a client on writing a spec. I saw a problem with the spec, a convoluted way of fixing the spec. Instead of trying to verbally explain it, I started by creating a new markdown file:</p>\n<div class=\"codehilite\"><pre><span></span><code>NameOfProblem.md\n</code></pre></div>\n\n<p>Then I started typing. First the problem summary, then a detailed description, then the solution and why it worked. When my partner asked questions, I incorporated his question and our discussion of it into the flow. If we hit a dead end with the solution, we marked it out as a dead end. Eventually the file looked something like this:</p>\n<div class=\"codehilite\"><pre><span></span><code>Current state of spec\nProblems caused by this\n    Elaboration of problems\n    What we tried that didn't work\nProposed Solution\n    Theory behind proposed solution\n    How the solution works\n    Expected changes\n    Other problems this helps solve\n    Problems this does *not* help with\n</code></pre></div>\n\n<p>Only once this was done, my partner fully understood the chain of thought, <em>and</em> we agreed it represented the right approach, did we start making changes to the spec. </p>\n<h3>How is this better than just making the change?</h3>\n<p>The change was <em>conceptually</em> complex. A rough analogy: imagine pairing with a beginner who wrote an insertion sort, and you want to replace it with quicksort. You need to explain why the insertion sort is too slow, why the quicksort isn't slow, and how quicksort actually correctly sorts a list. This could involve tangents into computational complexity, big-o notation, recursion, etc. These are all concepts you have internalized, so the change is simple to you, but the solution uses concepts the beginner does not know. So it's conceptually complex to them.</p>\n<p>I wasn't pairing with a beginning programmer or even a beginning specifier. This was a client who could confidently write complex specs on their own. But they don't work on specifications full time like I do. Any time there's a relative gap in experience in a pair, there's solutions that are conceptually simple to one person and complex to the other.</p>\n<p>I've noticed too often that when one person doesn't fully understand the concepts behind a change, they just go \"you're the expert, I trust you.\" That eventually leads to a totally unmaintainable spec. Hence, writing it all out. </p>\n<p>As I said before, I've only tried this once (though I've successfully used a similar idea when teaching workshops). It worked pretty well, though! Just be prepared for a lot of typing.</p>",
          "url": "https://buttondown.com/hillelwayne/archive/stream-of-consciousness-driven-development/",
          "published": "2026-02-18T16:33:08.000Z",
          "updated": "2026-02-18T16:33:08.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/proving-whats-possible/",
          "title": "Proving What's Possible",
          "description": "<p>As a formal methods consultant I have to mathematically express properties of systems. I generally do this with two \"temporal operators\": </p>\n<ul>\n<li>A(x) means that <code>x</code> is always true. For example, a database table <em>always</em> satisfies all record-level constraints, and a state machine <em>always</em> makes valid transitions between states. If <code>x</code> is a statement about an individual state (as in the database but not state machine example), we further call it an <strong>invariant</strong>.</li>\n<li>E(x) means that <code>x</code> is \"eventually\" true, conventionally meaning \"guaranteed true at some point in the future\". A database transaction <em>eventually</em> completes or rolls back, a state machine <em>eventually</em> reaches the \"done\" state, etc. </li>\n</ul>\n<p>These come from linear temporal logic, which is the mainstream notation for expressing system properties. <sup id=\"fnref:modal\"><a class=\"footnote-ref\" href=\"#fn:modal\">1</a></sup> We like these operators because they elegantly cover <a href=\"https://www.hillelwayne.com/post/safety-and-liveness/\" target=\"_blank\">safety and liveness properties</a>, and because <a href=\"https://buttondown.com/hillelwayne/archive/formalizing-stability-and-resilience-properties/\" target=\"_blank\">we can combine them</a>. <code>A(E(x))</code> means <code>x</code> is true an infinite number of times, while <code>A(x => E(y)</code> means that <code>x</code> being true guarantees <code>y</code> true in the future. </p>\n<p>There's a third class of properties, that I will call <em>possibility</em> properties: <code>P(x)</code> is \"can x happen in this model\"? Is it possible for a table to have more than ten records? Can a state machine transition from \"Done\" to \"Retry\", even if it <em>doesn't</em>? Importantly, <code>P(x)</code> does not need to be possible <em>immediately</em>, just at some point in the future. It's possible to lose 100 dollars betting on slot machines, even if you only bet one dollar at a time. If <code>x</code> is a statement about an individual state, we can further call it a <a href=\"https://en.wikipedia.org/wiki/Reachability\" target=\"_blank\"><em>reachability</em> property</a>. I'm going to use the two interchangeably for flow. </p>\n<p><code>A(P(x))</code> says that <code>x</code> is <em>always</em> possible. No matter what we've done in our system, we can make <code>x</code> happen again. There's no way to do this with just <code>A</code> and <code>E</code>. Other meaningful combinations include:</p>\n<ul>\n<li><code>P(A(x))</code>: there is a reachable state from which <code>x</code> is always true.</li>\n<li><code>A(x => P(y))</code>: <code>y</code> is possible from any state where <code>x</code> is true.</li>\n<li><code>E(x && P(y))</code>: There is always a future state where x is true and y is reachable.</li>\n<li><code>A(P(x) => E(x))</code>: If <code>x</code> is ever possible, it will eventually happen.</li>\n<li><code>E(P(x))</code> and <code>P(E(x))</code> are the same as <code>P(x)</code>.</li>\n</ul>\n<p>See the paper <a href=\"https://dl.acm.org/doi/epdf/10.1145/567446.567463\" target=\"_blank\">\"Sometime\" is sometimes \"not never\"</a> for a deeper discussion of <code>E</code> and <code>P</code>.</p>\n<h3>The use case</h3>\n<p>Possibility properties are \"something good <em>can</em> happen\", which is generally less useful (<em>in specifications</em>) than \"something bad <em>can't</em> happen\" (safety) and \"something good <em>will</em> happen\" (liveness). But it still comes up as an important property! My favorite example:</p>\n<p><img alt=\"A guy who can't shut down his computer because system preferences interrupts shutdown\" class=\"newsletter-image\" src=\"https://www.hillelwayne.com/post/safety-and-liveness/img/tweet2.png\" /></p>\n<p>The big use I've found for the idea is as a sense-check that we wrote the spec properly. Say I take the property \"A worker in the 'Retry' state eventually leaves that state\":</p>\n<div class=\"codehilite\"><pre><span></span><code>A(state == 'Retry' => E(state != 'Retry'))\n</code></pre></div>\n\n<p>The model checker checks this property and confirms it holds of the spec. Great! Our system is correct! ...Unless the system can never <em>reach</em> the \"Retry\" state, in which case the expression is trivially true. I need to verify that 'Retry' is reachable, eg <code>P(state == 'Retry')</code>. Notice I can't use <code>E</code> to do this, because I don't want to say \"the worker always needs to retry at least once\". </p>\n<h3>It's not supported though</h3>\n<p>I say \"use I've found for <em>the idea</em>\" because the main formalisms I use (Alloy and TLA+) don't natively support <code>P</code>. <sup id=\"fnref:tla\"><a class=\"footnote-ref\" href=\"#fn:tla\">2</a></sup> On top of <code>P</code> being less useful than <code>A</code> and <code>E</code>, simple reachability properties are <a href=\"https://www.hillelwayne.com/post/software-mimicry/\" target=\"_blank\">mimickable</a> with A(x). <code>P(x)</code> <em>passes</em> whenever <code>A(!x)</code> <em>fails</em>, meaning I can verify <code>P(state == 'Retry')</code> by testing that <code>A(!(state == 'Retry'))</code> finds a counterexample. We <em>cannot</em> mimic combined operators this way like <code>A(P(x))</code> but those are significantly less common than state-reachability. </p>\n<p>(Also, refinement doesn't preserve possibility properties, but that's a whole other kettle of worms.)</p>\n<p>The one that's bitten me a little is that we can't mimic \"<code>P(x)</code> from every starting state\". \"<code>A(!x)</code>\" fails if there's at least one path from one starting state that leads to <code>x</code>, but other starting states might not make <code>x</code> possible.</p>\n<p>I suspect there's also a chicken-and-egg problem here. Since my tools can't verify possibility properties, I'm not used to noticing them in systems. I'd be interested in hearing if anybody works with codebases where possibility properties are important, especially if it's something complex like <code>A(x => P(y))</code>.</p>\n<div class=\"footnote\">\n<hr />\n<ol>\n<li id=\"fn:modal\">\n<p>Instead of <code>A(x)</code>, the literature uses <code>[]x</code> or <code>Gx</code> (\"globally x\") and instead of <code>E(x)</code> it uses <code><>x</code> or <code>Fx</code> (\"finally x\"). I'm using A and E because this isn't teaching material. <a class=\"footnote-backref\" href=\"#fnref:modal\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n<li id=\"fn:tla\">\n<p>There's <a href=\"https://github.com/tlaplus/tlaplus/issues/860\" target=\"_blank\">some discussion to add it to TLA+, though</a>. <a class=\"footnote-backref\" href=\"#fnref:tla\" title=\"Jump back to footnote 2 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/proving-whats-possible/",
          "published": "2026-02-11T18:36:53.000Z",
          "updated": "2026-02-11T18:36:53.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/logic-for-programmers-new-release-and-next-steps/",
          "title": "Logic for Programmers New Release and Next Steps",
          "description": "<p><img alt=\"cover.jpg\" class=\"newsletter-image\" src=\"https://assets.buttondown.email/images/f821145f-d310-403c-88f4-327758a66606.jpg?w=480&fit=max\" /></p>\n<p>It's taken four months, but the next release of <a href=\"https://logicforprogrammers.com\" target=\"_blank\">Logic for Programmers is now available</a>! v0.13 is over 50,000 words, making it both 20% larger than v0.12 and officially the longest thing I have ever written.<sup id=\"fnref:longest\"><a class=\"footnote-ref\" href=\"#fn:longest\">1</a></sup> Full release notes are <a href=\"https://github.com/logicforprogrammers/book-assets/blob/master/CHANGELOG.md\" target=\"_blank\">here</a>, but I'll talk a bit about the biggest changes. </p>\n<p>For one, every chapter has been rewritten. Every single one. They span from <em>relatively</em> minor changes to complete chapter rewrites. After some rough git diffing, I think I deleted about 11,000 words?<sup id=\"fnref:gross-additions\"><a class=\"footnote-ref\" href=\"#fn:gross-additions\">2</a></sup> The biggest change is probably to the Alloy chapter. After many sleepless nights, I realized the right approach wasn't to teach Alloy as a <em>data modeling</em> tool but to teach it as a <em>domain modeling</em> tool. Which technically means the book no longer covers data modeling.</p>\n<p>There's also a lot more connections between the chapters. The introductory math chapter, for example, foreshadows how each bit of math will be used in the future techniques. I also put more emphasis on the general \"themes\" like the expressiveness-guarantees tradeoff (working title). One theme I'm really excited about is compatibility (extremely working title). It turns out that the <a href=\"https://buttondown.com/hillelwayne/archive/the-liskov-substitution-principle-does-more-than/\" target=\"_blank\">Liskov substitution principle</a>/subtyping in general, <a href=\"https://buttondown.com/hillelwayne/archive/refinement-without-specification/\" target=\"_blank\">database migrations</a>, backwards-compatible API changes, and <a href=\"https://hillelwayne.com/post/refinement/\" target=\"_blank\">specification refinement</a> all follow <em>basically</em> the same general principles. I'm calling this \"compatibility\" for now but prolly need a better name.</p>\n<p>Finally, there's just a lot more new topics in the various chapters. <code>Testing</code> properly covers structural and metamorphic properties. <code>Proofs</code> covers proof by induction and proving recursive functions (in an exercise). <code>Logic Programming</code> now finally has a section on answer set programming. You get the picture.</p>\n<h3>Next Steps</h3>\n<p>There's a lot I still want to add to the book: proper data modeling, data structures, type theory, model-based testing, etc. But I've added new material for two year, and if I keep going it will never get done. So with this release, all the content is in!</p>\n<p>Just like all the content was in <a href=\"https://buttondown.com/hillelwayne/archive/five-unusual-raku-features/\" target=\"_blank\">two Novembers ago</a> and <a href=\"https://buttondown.com/hillelwayne/archive/logic-for-programmers-project-update/\" target=\"_blank\">two Januaries ago</a> and <a href=\"https://buttondown.com/hillelwayne/archive/logic-for-programmers-turns-one/\" target=\"_blank\">last July</a>. To make it absolutely 100% for sure that I won't be tempted to add anything else, I passed the whole manuscript over to a copy editor. So if I write more, it won't get edits. That's a pretty good incentive to stop.</p>\n<p>I also need to find a technical reviewer and proofreader. Once all three phases are done then it's \"just\" a matter of fixing the layout and finding a good printer. I don't know what the timeline looks like but I really want to have something I can hold in my hands before the summer.</p>\n<p>(I also need to get notable-people testimonials. Hampered a little in this because I'm trying real hard not to quid-pro-quo, so I'd like to avoid anybody who helped me or is mentioned in the book. And given I tapped most of my network to help me... I've got some ideas though!)</p>\n<p>There's still a lot of work ahead. Even so, for the first time in two years I don't have research to do or sections to write and it feels so crazy. Maybe I'll update my blog again! Maybe I'll run a workshop! Maybe I'll go outside if Chicago ever gets above 6°F! </p>\n<hr />\n<h2>Conference Season</h2>\n<p>After a pretty slow 2025, the 2026 conference season is looking to be pretty busy! Here's where I'm speaking so far:</p>\n<ul>\n<li><a href=\"https://qconlondon.com/\" target=\"_blank\">QCon London</a>, March 16-19</li>\n<li><a href=\"https://craft-conf.com/2026\" target=\"_blank\">Craft Conference</a>, Budapest, June 4-5</li>\n<li><a href=\"https://softwareshould.work/\" target=\"_blank\">Software Should Work</a>, Missouri, July 16-17</li>\n<li><a href=\"https://hfpug.org/\" target=\"_blank\">Houston Functional Programmers</a>, Virtual, December 3</li>\n</ul>\n<p>For the first three I'm giving variations of my talk \"How to find bugs in systems that don't exist\", which I gave last year at <a href=\"https://systemsdistributed.com/\" target=\"_blank\">Systems Distributed</a>. Last one will ideally be a talk based on LfP. </p>\n<div class=\"footnote\">\n<hr />\n<ol>\n<li id=\"fn:longest\">\n<p>The second longest was my 2003 NaNoWriMo. The third longest was <em>Practical TLA+</em>. <a class=\"footnote-backref\" href=\"#fnref:longest\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n<li id=\"fn:gross-additions\">\n<p>This means I must have written 20,000 words total. For comparison, the v0.1 release was 19,000 words. <a class=\"footnote-backref\" href=\"#fnref:gross-additions\" title=\"Jump back to footnote 2 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/logic-for-programmers-new-release-and-next-steps/",
          "published": "2026-02-04T14:00:00.000Z",
          "updated": "2026-02-04T14:00:00.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/refinement-without-specification/",
          "title": "Refinement without Specification",
          "description": "<p>Imagine we have a SQL database with a <code>user</code> table, and users have a non-nullable <code>is_activated</code> boolean column. Having read <a href=\"https://ntietz.com/blog/that-boolean-should-probably-be-something-else/\" target=\"_blank\">That Boolean Should Probably Be Something else</a>, you decide to migrate it to a nullable <code>activated_at</code> column. You can change any of the SQL queries that read/update the <code>user</code> table but not any of the code that uses the results of these queries. Can we make this change in a way that preserves all external properties? </p>\n<p>Yes. If an update would set <code>is_activated</code> to true, instead set it to the current date. Now define the <strong>refinement mapping</strong> that takes a <code>new_user</code> and returns an <code>old_user</code>. All columns will be unchanged <em>except</em> <code>is_activated</code>, which will be</p>\n<div class=\"codehilite\"><pre><span></span><code>f(new_user).is_activated = \n    if new_user.activated_at == NULL \n    then FALSE\n    else TRUE\n</code></pre></div>\n\n<p>Now new code can use <code>new_user</code> directly while legacy code can use <code>f(new_user)</code> instead, which will behave indistinguishably from the <code>old_user</code>. </p>\n<p>A little more time passes and you decide to switch to an <a href=\"https://martinfowler.com/eaaDev/EventSourcing.html\" target=\"_blank\">event sourcing</a>-like model. So instead of an <code>activated_at</code> column, you have a <code>user_events</code> table, where every record is <code>(user_id, timestamp, event)</code>. So adding an <code>activate</code> event will activate the user, adding a <code>deactivate</code> event will deactivate the user. Once again, we can update the queries but not any of the code that uses the results of these queries. Can we make a change that preserves all external properties?</p>\n<p>Yes. If an update would change <code>is_activated</code>, instead have it add an appropriate record to the event table. Now, define the refinement mapping that takes <code>newer_user</code> and returns <code>new_user</code>. The <code>activated_at</code> field will be computed like this:</p>\n<div class=\"codehilite\"><pre><span></span><code>g(newer_user).activated_at =\n        # last_activated_event\n    let lae = \n            newer_user.events\n                      .filter(event = \"activate\" | \"deactivate\")\n                      .last,\n    in\n        if lae.event == \"activate\" \n        then lae.timestamp\n        else NULL\n</code></pre></div>\n\n<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<p>Now new code can use <code>newer_user</code> directly while old code can use <code>g(newer_user)</code> and the really old code can use <code>f(g(newer_user))</code>.</p>\n<h3>Mutability constraints</h3>\n<div class=\"subscribe-form\"></div>\n<p>I said \"these preserve all external properties\" and that was a lie. It depends on the properties we explicitly have, and I didn't list any. The real interesting properties for me are mutability constraints on how the system can evolve. So let's go back in time and add a constraint to <code>user</code>:</p>\n<div class=\"codehilite\"><pre><span></span><code>C1(u) = u.is_activated => u.is_activated'\n</code></pre></div>\n\n<p>This constraint means that if a user is activated, any change will preserve its activated-ness. This means a user can go from deactivated to activated but not the other way. It's not a particular good constraint but it's good enough for teaching purposes. Such a SQL constraint can be enforced with <a href=\"https://www.postgresql.org/docs/current/sql-createeventtrigger.html\" target=\"_blank\">triggers</a>. </p>\n<p>Now we can throw a constraint on <code>new_user</code>:</p>\n<div class=\"codehilite\"><pre><span></span><code>C2(nu) = nu.activated_at != NULL => nu.activated_at' != NULL\n</code></pre></div>\n\n<p>If <code>nu</code> satisfies <code>C2</code>, then <code>f(nu)</code> satisfies <code>C1</code>. So the refinement still holds.</p>\n<p>With <code>newer_u</code>, we <em>cannot</em> guarantee that <code>g(newer_u)</code> satisfies <code>C2</code> because we can go from \"activated\" to \"deactivated\" just by appending a new event. So it's not a refinement. This is fixable by removing deactivation events, that would work too.</p>\n<p>So a more interesting case is <code>bad_user</code>, a refinement of <code>user</code> that has both <code>activated_at</code> and <code>activated_until</code>. We propose the refinement mapping <code>b</code>:</p>\n<div class=\"codehilite\"><pre><span></span><code>b(bad_user).activated =\n    if bad_user.activated_at == NULL && activated_until == NULL\n    then FALSE\n    else bad_user.activated_at <= now() < bad_user.activated_until\n</code></pre></div>\n\n<p>But now if enough time passes, <code>b(bad_user).activated' = false</code>, so this is not a refinement either.</p>\n<h3>The punchline</h3>\n<p>Refinement is one of the most powerful techniques in formal specification, but also one of the hardest for people to understand. I'm starting to think that the reason it's so hard is because they learn refinement while they're <em>also</em> learning formal methods, so are faced with an unfamiliar topic in an unfamiliar context. If that's the case, then maybe it's easier introducing refinement in a more common context like databases.</p>\n<p>I've written a bit about refinement in the normal context <a href=\"https://hillelwayne.com/post/refinement/\" target=\"_blank\">here</a> (showing one specification is an implementation of another). I kinda want to work this explanation into the book but it might be too late for big content additions like this.</p>\n<p>(Food for thought: how do refinement mappings relate to database views?)</p>",
          "url": "https://buttondown.com/hillelwayne/archive/refinement-without-specification/",
          "published": "2026-01-20T17:49:07.000Z",
          "updated": "2026-01-20T17:49:07.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/my-gripes-with-prolog/",
          "title": "My Gripes with Prolog",
          "description": "<p>For the next release of <a href=\"https://leanpub.com/logic/\" target=\"_blank\">Logic for Programmers</a>, I'm finally adding the sections on Answer Set Programming and Constraint Logic Programming that I TODOd back in version 0.9. And this is making me re-experience some of my pain points with Prolog, which I will gripe about now.  If you want to know more about why Prolog is cool instead, go <a href=\"https://buttondown.com/hillelwayne/archive/a48fce5b-8a05-4302-b620-9b26f057f145/\" target=\"_blank\">here</a> or <a href=\"https://www.metalevel.at/prolog\" target=\"_blank\">here</a> or <a href=\"https://ianthehenry.com/posts/drinking-with-datalog/\" target=\"_blank\">here</a> or <a href=\"https://logicprogramming.org/\" target=\"_blank\">here</a>. </p>\n<h3>No standardized strings</h3>\n<p>ISO \"strings\" are just atoms or lists of single-character atoms (or lists of integer character codes). The various implementations of Prolog add custom string operators but they are not cross compatible, so code written with strings in SWI-Prolog will not work in Scryer Prolog. </p>\n<h3>No functions</h3>\n<p>Code logic is expressed entirely in <em>rules</em>, predicates which return true or false for certain values. For example if you wanted to get the length of a Prolog list, you write this:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"s s-Atom\">?-</span> <span class=\"nf\">length</span><span class=\"p\">([</span><span class=\"s s-Atom\">a</span><span class=\"p\">,</span> <span class=\"s s-Atom\">b</span><span class=\"p\">,</span> <span class=\"s s-Atom\">c</span><span class=\"p\">],</span> <span class=\"nv\">Len</span><span class=\"p\">).</span>\n\n   <span class=\"nv\">Len</span> <span class=\"o\">=</span> <span class=\"mf\">3.</span>\n</code></pre></div>\n<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<p>Now this is pretty cool in that it allows bidirectionality, or running predicates \"in reverse\". To generate lists of length 3, you can write <code>length(L, 3)</code>. But it also means that if you want to get the length a list <em>plus one</em>, you can't do that in one expression, you have to write <code>length(List, Out), X is Out+1</code>.</p>\n<p>For a while I thought no functions was necessary evil for bidirectionality, but then I discovered <a href=\"https://picat-lang.org/\" target=\"_blank\">Picat</a> has functions and works just fine. That by itself is a reason for me to prefer Picat for my LP needs.</p>\n<p>(Bidirectionality is a killer feature of Prolog, so it's a shame I so rarely run into situations that use it.)</p>\n<h3>No standardized collection types besides lists</h3>\n<p>Aside from atoms (<code>abc</code>) and numbers, there are two data types:</p>\n<ul>\n<li>Linked lists like <code>[a,b,c,d]</code>.</li>\n<li>Compound terms like <code>dog(rex, poodle)</code>, which <em>seem</em> like record types but are actually tuples. You can even convert compound terms to linked lists with <code>=..</code>:</li>\n</ul>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"s s-Atom\">?-</span> <span class=\"nv\">L</span> <span class=\"s s-Atom\">=..</span> <span class=\"p\">[</span><span class=\"s s-Atom\">a</span><span class=\"p\">,</span> <span class=\"s s-Atom\">b</span><span class=\"p\">,</span> <span class=\"s s-Atom\">c</span><span class=\"p\">].</span>\n   <span class=\"nv\">L</span> <span class=\"o\">=</span> <span class=\"nf\">a</span><span class=\"p\">(</span><span class=\"s s-Atom\">b</span><span class=\"p\">,</span> <span class=\"s s-Atom\">c</span><span class=\"p\">).</span>\n<span class=\"s s-Atom\">?-</span> <span class=\"nf\">a</span><span class=\"p\">(</span><span class=\"s s-Atom\">b</span><span class=\"p\">,</span> <span class=\"nf\">c</span><span class=\"p\">(</span><span class=\"s s-Atom\">c</span><span class=\"p\">))</span> <span class=\"s s-Atom\">=..</span> <span class=\"nv\">L</span><span class=\"p\">.</span>\n   <span class=\"nv\">L</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s s-Atom\">a</span><span class=\"p\">,</span> <span class=\"s s-Atom\">b</span><span class=\"p\">,</span> <span class=\"nf\">c</span><span class=\"p\">(</span><span class=\"s s-Atom\">c</span><span class=\"p\">)].</span>\n</code></pre></div>\n<p>There's no proper key-value maps or even struct types. Again, this is something that individual distributions can fix (without cross compatibility), but these never feel integrated with the rest of the language. </p>\n<h3>No boolean values</h3>\n<p><code>true</code> and <code>false</code> aren't values, they're control flow statements. <code>true</code> is a noop and <code>false</code> says that the current search path is a dead end, so backtrack and start again. You can't explicitly store true and false as values, you have to implicitly have them in facts (<code>passed(test)</code> instead of <code>test.passed? == true</code>).</p>\n<p>This hasn't made any tasks impossible, and I can usually find a workaround to whatever I want to do. But I do think it makes things more inconvenient! Sometimes I want to do something dumb like \"get all atoms that don't pass at least three of these rules\", and that'd be a lot easier if I could shove intermediate results into a sack of booleans. </p>\n<p>(This is called \"<a href=\"https://en.wikipedia.org/wiki/Negation_as_failure\" target=\"_blank\">Negation as Failure</a>\". I think this might be necessary to make Prolog a Turing complete general programming language. Picat fixes a lot of Prolog's gripes and still has negation as failure. ASP has regular negation but it's not Turing complete.) </p>\n<h3>Cuts are confusing</h3>\n<div class=\"subscribe-form\"></div>\n<p>Prolog finds solutions through depth first search, and a \"cut\" (<code>!</code>) symbol prevents backtracking past a certain point. This is necessary for optimization but can lead to invalid programs. </p>\n<p>You're not supposed to use cuts if you can avoid it, so I pretended cuts didn't exist. Which is why I was surprised to find that <a href=\"https://eu.swi-prolog.org/pldoc/doc_for?object=(-%3E)/2\" target=\"_blank\">conditionals</a> are implemented with cuts. Because cuts are spooky dark magic conditionals <em>sometimes</em> conditionals work as I expect them to and sometimes leave out valid solutions and I have no idea how to tell which it'll be. Usually I find it safer to just avoid conditionals entirely, which means my code gets a lot longer and messier. </p>\n<h3>Non-cuts are confusing</h3>\n<p>The original example in the last section was this: </p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"nf\">foo</span><span class=\"p\">(</span><span class=\"nv\">A</span><span class=\"p\">,</span> <span class=\"nv\">B</span><span class=\"p\">)</span> <span class=\"p\">:-</span>\n    <span class=\"s s-Atom\">\\+</span> <span class=\"p\">(</span><span class=\"nv\">A</span> <span class=\"o\">=</span> <span class=\"nv\">B</span><span class=\"p\">),</span>\n    <span class=\"nv\">A</span> <span class=\"o\">=</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n    <span class=\"nv\">B</span> <span class=\"o\">=</span> <span class=\"mf\">2.</span>\n</code></pre></div>\n<p><code>foo(1, 2)</code> returns true, so you'd expect <code>f(A, B)</code> to return <code>A=1, B=2</code>. But it returns <code>false</code>.  Whereas this works as expected.</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"nf\">bar</span><span class=\"p\">(</span><span class=\"nv\">A</span><span class=\"p\">,</span> <span class=\"nv\">B</span><span class=\"p\">)</span> <span class=\"p\">:-</span>\n    <span class=\"nv\">A</span> <span class=\"o\">=</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n    <span class=\"nv\">B</span> <span class=\"o\">=</span> <span class=\"mi\">2</span><span class=\"p\">,</span>\n    <span class=\"s s-Atom\">\\+</span> <span class=\"p\">(</span><span class=\"nv\">A</span> <span class=\"o\">=</span> <span class=\"nv\">B</span><span class=\"p\">).</span>\n</code></pre></div>\n<p>I <em>thought</em> this was because <code>\\+</code> was implemented with cuts, and the <a href=\"https://www.amazon.com/Programming-Prolog-Using-ISO-Standard/dp/3540006788\" target=\"_blank\">Clocksin book</a> suggests it's <code>call(P), !, fail</code>, so this was my prime example about how cuts are confusing. But then I tried this:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"s s-Atom\">?-</span> <span class=\"nf\">member</span><span class=\"p\">(</span><span class=\"nv\">A</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span><span class=\"mi\">2</span><span class=\"p\">,</span><span class=\"mi\">3</span><span class=\"p\">]),</span> <span class=\"s s-Atom\">\\+</span> <span class=\"p\">(</span><span class=\"nv\">A</span> <span class=\"o\">=</span> <span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"nv\">A</span> <span class=\"o\">=</span> <span class=\"mf\">3.</span>\n<span class=\"nv\">A</span> <span class=\"o\">=</span> <span class=\"mf\">3.</span> <span class=\"c1\">% wtf?</span>\n</code></pre></div>\n<p>There's no way to get that behavior with cuts! I don't think <code>\\+</code> uses cuts at all! And now I have to figure out why \n<code>foo(A, B)</code> doesn't returns results. Is it <a href=\"https://github.com/dtonhofer/prolog_notes/blob/master/other_notes/about_negation/floundering.md\" target=\"_blank\">floundering</a>? Is it because <code>\\+ P</code> only succeeds if <code>P</code> fails, and <code>A = B</code> always succeeds? A closed-world assumption? Something else?<sup id=\"fnref:dif\"><a class=\"footnote-ref\" href=\"#fn:dif\">1</a></sup></p>\n<h3>Straying outside of default queries is confusing</h3>\n<p>Say I have a program like this:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"nf\">tree</span><span class=\"p\">(</span><span class=\"s s-Atom\">n</span><span class=\"p\">,</span> <span class=\"s s-Atom\">n1</span><span class=\"p\">).</span>\n<span class=\"nf\">tree</span><span class=\"p\">(</span><span class=\"s s-Atom\">n</span><span class=\"p\">,</span> <span class=\"s s-Atom\">n2</span><span class=\"p\">).</span>\n<span class=\"nf\">tree</span><span class=\"p\">(</span><span class=\"s s-Atom\">n1</span><span class=\"p\">,</span> <span class=\"s s-Atom\">n11</span><span class=\"p\">).</span>\n<span class=\"nf\">tree</span><span class=\"p\">(</span><span class=\"s s-Atom\">n2</span><span class=\"p\">,</span> <span class=\"s s-Atom\">n21</span><span class=\"p\">).</span>\n<span class=\"nf\">tree</span><span class=\"p\">(</span><span class=\"s s-Atom\">n2</span><span class=\"p\">,</span> <span class=\"s s-Atom\">n22</span><span class=\"p\">).</span>\n<span class=\"nf\">tree</span><span class=\"p\">(</span><span class=\"s s-Atom\">n11</span><span class=\"p\">,</span> <span class=\"s s-Atom\">n111</span><span class=\"p\">).</span>\n<span class=\"nf\">tree</span><span class=\"p\">(</span><span class=\"s s-Atom\">n11</span><span class=\"p\">,</span> <span class=\"s s-Atom\">n112</span><span class=\"p\">).</span>\n\n<span class=\"nf\">branch</span><span class=\"p\">(</span><span class=\"nv\">N</span><span class=\"p\">)</span> <span class=\"p\">:-</span> <span class=\"c1\">% two children</span>\n    <span class=\"nf\">tree</span><span class=\"p\">(</span><span class=\"nv\">N</span><span class=\"p\">,</span> <span class=\"nv\">C1</span><span class=\"p\">),</span>\n    <span class=\"nf\">tree</span><span class=\"p\">(</span><span class=\"nv\">N</span><span class=\"p\">,</span> <span class=\"nv\">C2</span><span class=\"p\">),</span>\n    <span class=\"nv\">C1</span> <span class=\"s s-Atom\">@<</span> <span class=\"nv\">C2</span><span class=\"p\">.</span> <span class=\"c1\">% ordering</span>\n</code></pre></div>\n<p>And I want to know all of the nodes that are parents of branches. The normal way to do this is with a query:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"s s-Atom\">?-</span> <span class=\"nf\">tree</span><span class=\"p\">(</span><span class=\"nv\">A</span><span class=\"p\">,</span> <span class=\"nv\">N</span><span class=\"p\">),</span> <span class=\"nf\">branch</span><span class=\"p\">(</span><span class=\"nv\">N</span><span class=\"p\">).</span>\n<span class=\"nv\">A</span> <span class=\"o\">=</span> <span class=\"s s-Atom\">n</span><span class=\"p\">,</span> <span class=\"nv\">N</span> <span class=\"o\">=</span> <span class=\"s s-Atom\">n2</span><span class=\"p\">;</span> <span class=\"c1\">% show more...</span>\n<span class=\"nv\">A</span> <span class=\"o\">=</span> <span class=\"s s-Atom\">n1</span><span class=\"p\">,</span> <span class=\"nv\">N</span> <span class=\"o\">=</span> <span class=\"s s-Atom\">n11</span><span class=\"p\">.</span>\n</code></pre></div>\n<p>This is interactively making me query for every result. That's usually not what I want, I know the result of my query is finite and I want all of the results at once, so I can count or farble or whatever them. It took a while to figure out that the proper solution is <a href=\"https://www.swi-prolog.org/pldoc/man?predicate=bagof/3\" target=\"_blank\"><code>bagof(Template, Goal, Bag)</code></a>, which will \"Unify Bag with the alternatives of Template\":</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"s s-Atom\">?-</span> <span class=\"nf\">bagof</span><span class=\"p\">(</span><span class=\"nv\">A</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"nf\">tree</span><span class=\"p\">(</span><span class=\"nv\">A</span><span class=\"p\">,</span> <span class=\"nv\">N</span><span class=\"p\">),</span> <span class=\"nf\">branch</span><span class=\"p\">(</span><span class=\"nv\">N</span><span class=\"p\">)),</span> <span class=\"nv\">As</span><span class=\"p\">).</span>\n\n<span class=\"nv\">As</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s s-Atom\">n1</span><span class=\"p\">],</span> <span class=\"nv\">N</span> <span class=\"o\">=</span> <span class=\"s s-Atom\">n11</span><span class=\"p\">;</span>\n<span class=\"nv\">As</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s s-Atom\">n</span><span class=\"p\">],</span> <span class=\"nv\">N</span> <span class=\"o\">=</span> <span class=\"s s-Atom\">n2</span><span class=\"p\">.</span>\n</code></pre></div>\n<p>Wait crap that's still giving one result at a time, because <code>N</code> is a free variable in <code>bagof</code> so it backtracks over that. It surprises me but I guess it's good to have as an option. So how do I get all of the results at once?</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"s s-Atom\">?-</span> <span class=\"nf\">bagof</span><span class=\"p\">(</span><span class=\"nv\">A</span><span class=\"p\">,</span> <span class=\"nv\">N</span><span class=\"s s-Atom\">^</span><span class=\"p\">(</span><span class=\"nf\">tree</span><span class=\"p\">(</span><span class=\"nv\">A</span><span class=\"p\">,</span> <span class=\"nv\">N</span><span class=\"p\">),</span> <span class=\"nf\">branch</span><span class=\"p\">(</span><span class=\"nv\">N</span><span class=\"p\">)),</span> <span class=\"nv\">As</span><span class=\"p\">).</span>\n\n<span class=\"nv\">As</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s s-Atom\">n</span><span class=\"p\">,</span> <span class=\"s s-Atom\">n1</span><span class=\"p\">]</span>\n</code></pre></div>\n<p>The only difference is the <code>N^Goal</code>, which tells <code>bagof</code> to ignore and group the results of <code>N</code>. As far as I can tell, this is the <em>only</em> place the ISO standard uses <code>^</code> to mean anything besides exponentiation. Supposedly it's the <a href=\"https://sicstus.sics.se/sicstus/docs/latest4/html/sicstus.html/ref_002dall_002dsum.html\" target=\"_blank\">existential quantifier</a>? In general whenever I try to stray outside simpler use-cases, especially if I try to do things non-interactively, I run into trouble.</p>\n<h3>I have mixed feelings about symbol terms</h3>\n<p>It took me a long time to realize the reason <code>bagof</code>  \"works\" is because infix symbols are mapped to prefix compound terms, so that  <code>a^b</code> is <code>^(a, b)</code>, and then different predicates can decide to do different things with <code>^(a, b)</code>.</p>\n<p>This is also why you can't just write <code>A = B+1</code>: that unifies <code>A</code> with the <em>compound term</em> <code>+(B, 1)</code>. <code>A+1 = B+2</code> is <em>false</em>, as <code>1 \\= 2</code>. You have to write <code>A+1 is B+2</code>, as <code>is</code> is the operator that converts <code>+(B, 1)</code> to a mathematical term.</p>\n<p>(And <em>that</em> fails because <code>is</code> isn't fully bidirectional. The lhs <em>must</em> be a single variable. You have to import <code>clpfd</code> and write <code>A + 1 #= B + 2</code>.)</p>\n<p>I don't like this, but I'm a hypocrite for saying that because I appreciate the idea and don't mind custom symbols in other languages. I guess what annoys me is there's no official definition of what <code>^(a, b)</code> is, it's purely a convention. ISO Prolog uses <code>-(a, b)</code> (aka <code>a-b</code>) as a convention to mean \"pairs\", and the only way to realize that is to see that an awful lot of standard modules use that convention. But you can use <code>-(a, b)</code> to mean something else in your own code and nothing will warn you of the inconsistency.</p>\n<p>Anyway I griped about pairs so I can gripe about <code>sort</code>.</p>\n<h3>go home sort, ur drunk</h3>\n<p>This one's just a blunder:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"s s-Atom\">?-</span> <span class=\"nf\">sort</span><span class=\"p\">([</span><span class=\"mi\">3</span><span class=\"p\">,</span><span class=\"mi\">1</span><span class=\"p\">,</span><span class=\"mi\">2</span><span class=\"p\">,</span><span class=\"mi\">1</span><span class=\"p\">,</span><span class=\"mi\">3</span><span class=\"p\">],</span> <span class=\"nv\">Out</span><span class=\"p\">).</span>\n   <span class=\"nv\">Out</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">].</span> <span class=\"c1\">% wat</span>\n</code></pre></div>\n<p>According to an expert online this is because sort is supposed to return a sorted <em>set</em>, not a sorted list. If you want to preserve duplicates you're supposed to lift all of the values into <code>-($key, $value)</code> compound terms, then use <a href=\"https://eu.swi-prolog.org/pldoc/doc_for?object=keysort/2\" target=\"_blank\">keysort</a>, then extract the values. And, since there's no functions, this process takes at least three lines. This is also how you're supposed to sort by a custom predicate, like \"the second value of a compound term\". </p>\n<p>(Most (but not all) distributions have a duplicate merge like <a href=\"https://eu.swi-prolog.org/pldoc/doc_for?object=msort/2\" target=\"_blank\">msort</a>. SWI-Prolog also has a <a href=\"https://eu.swi-prolog.org/pldoc/doc_for?object=predsort/3\" target=\"_blank\">sort by key</a> but it removes duplicates.)</p>\n<h3>Please just let me end rules with a trailing comma instead of a period, I'm begging you</h3>\n<p>I don't care if it makes fact parsing ambiguous, I just don't want \"reorder two lines\" to be a syntax error anymore</p>\n<hr/>\n<p>I expect by this time tomorrow I'll have been Cunningham'd and there will be a 2000 word essay about how all of my gripes are either easily fixable by doing XYZ or how they are the best possible choice that Prolog could have made. I mean, even in writing this I found out some fixes to problems I had. Like I was going to gripe about how I can't run SWI-Prolog queries from the command line but, in doing do diligence finally <em>finally</em> figured it out:</p>\n<div class=\"codehilite\"><pre><span></span><code>swipl<span class=\"w\"> </span>-t<span class=\"w\"> </span>halt<span class=\"w\"> </span>-g<span class=\"w\"> </span><span class=\"s2\">\"bagof(X, Goal, Xs), print(Xs)\"</span><span class=\"w\"> </span>./file.pl\n</code></pre></div>\n<p>It's pretty clunky but still better than the old process of having to enter an interactive session every time I wanted to validate a script change.</p>\n<p>(Also, answer set programming is pretty darn cool. Excited to write about it in the book!)</p>\n<div class=\"footnote\">\n<hr/>\n<ol>\n<li id=\"fn:dif\">\n<p>A couple of people mentioned using <a href=\"https://eu.swi-prolog.org/pldoc/doc_for?object=dif/2\" target=\"_blank\">dif/2</a> instead of <code>\\+ A = B</code>. Dif is great but usually I hit the negation footgun with things like <code>\\+ foo(A, B), bar(B, C), baz(A, C)</code>, where <code>dif/2</code> isn't applicable. <a class=\"footnote-backref\" href=\"#fnref:dif\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/my-gripes-with-prolog/",
          "published": "2026-01-14T16:48:51.000Z",
          "updated": "2026-01-14T16:48:51.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/the-liskov-substitution-principle-does-more-than/",
          "title": "The Liskov Substitution Principle does more than you think",
          "description": "<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<p>Happy New Year! I'm done with the newsletter hiatus and am going to try updating weekly again. To ease into things a bit, I'll try to keep posts a little more off the cuff and casual for a while, at least until <a href=\"https://leanpub.com/logic/\" target=\"_blank\"><em>Logic for Programmers</em></a> is done. Speaking of which, v0.13 should be out by the end of this month.</p>\n<p>So for this newsletter I want to talk about the <a href=\"https://en.wikipedia.org/wiki/Liskov_substitution_principle\" target=\"_blank\">Liskov Substitution Principle</a> (LSP). Last week I read <a href=\"https://loup-vaillant.fr/articles/solid-bull\" target=\"_blank\">A SOLID Load of Bull</a> by cryptographer Loupe Vaillant, where he argues the <a href=\"https://en.wikipedia.org/wiki/SOLID\" target=\"_blank\">SOLID</a> principles of OOP are not worth following. He makes an exception for LSP, but also claims that it's \"just subtyping\" and further:</p>\n<blockquote>\n<p>If I were trying really hard to be negative about the Liskov substitution principle, I would stress that <strong>it only applies when inheritance is involved</strong>, and inheritance is strongly discouraged anyway.</p>\n</blockquote>\n<p>LSP is more interesting than that! In the original paper, <a href=\"https://www.cs.cmu.edu/~wing/publications/LiskovWing94.pdf\" target=\"_blank\">A Behavioral Notion of Subtyping</a>, Barbara Liskov and Jeannette Wing start by defining a \"correct\" subtyping as follows:</p>\n<blockquote>\n<p>Subtype Requirement: Let ϕ(x) be a property provable about objects x of type T. Then ϕ(y) should be true for objects y of type S where S is a subtype of T.</p>\n</blockquote>\n<p>From then on, the paper determine what <em>guarantees</em> that a subtype is correct.<sup id=\"fnref:safety\"><a class=\"footnote-ref\" href=\"#fn:safety\">1</a></sup>  They identify three conditions: </p>\n<ol>\n<li>Each of the subtype's methods has the same or weaker preconditions and the same or stronger postconditions as the corresponding supertype method.<sup id=\"fnref:cocontra\"><a class=\"footnote-ref\" href=\"#fn:cocontra\">2</a></sup> </li>\n<li>The subtype satisfies all state invariants of the supertype. </li>\n<li>The subtype satisfies all \"history properties\" of the supertype. <sup id=\"fnref:refinement\"><a class=\"footnote-ref\" href=\"#fn:refinement\">3</a></sup> e.g. if a supertype has an immutable field, the subtype cannot make it mutable. </li>\n</ol>\n<p>(Later, Elisa Baniassad and Alexander Summers <a href=\"https://www.cs.ubc.ca/~alexsumm/papers/BaniassadSummers21.pdf\" target=\"_blank\">would realize</a> these are equivalent to \"the subtype passes all black-box tests designed for the supertype\", which I wrote a little bit more about <a href=\"https://www.hillelwayne.com/post/lsp/\" target=\"_blank\">here</a>.)</p>\n<p>I want to focus on the first rule about preconditions and postconditions. This refers to the method's <strong>contract</strong>.  For a function <code>f</code>, <code>f.Pre</code> is what must be true going into the function, and <code>f.Post</code> is what the function guarantees on execution. A canonical example is square root: </p>\n<div class=\"codehilite\"><pre><span></span><code>sqrt.Pre(x) = x >= 0\nsqrt.Post(x, out) = out >= 0 && out*out == x\n</code></pre></div>\n<div class=\"subscribe-form\"></div>\n<p>Mathematically we would write this as <code>all x: f.Pre(x) => f.Post(x)</code> (where <code>=></code> is the <a href=\"https://en.wikipedia.org/wiki/Material_conditional\" target=\"_blank\">implication operator</a>). If that relation holds for all <code>x</code>, we say the function is \"correct\". With this definition we can actually formally deduce the first  subtyping requirement. Let <code>caller</code> be some code that uses a method, which we will call <code>super</code>, and let both <code>caller</code> and <code>super</code> be correct. Then we know the following statements are true:</p>\n<div class=\"codehilite\"><pre><span></span><code>  1. caller.Pre && stuff => super.Pre\n  2. super.Pre => super.Post\n  3. super.Post && more_stuff => caller.Post\n</code></pre></div>\n<p>Now let's say we substitute <code>super</code> with <code>sub</code>, which is also correct. Here is what we now know is true: </p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"w\"> </span> 1. caller.Pre => super.Pre\n<span class=\"gd\">- 2. super.Pre => super.Post</span>\n<span class=\"gi\">+ 2. sub.Pre => sub.Post</span>\n<span class=\"w\"> </span> 3. super.Post => caller.Post\n</code></pre></div>\n<p>When is <code>caller</code> still correct? When we can fill in the \"gaps\" in the chain, aka if <code>super.Pre => sub.Pre</code> and <code>sub.Post => super.Post</code>. In other words, if <code>sub</code>'s preconditions are weaker than (or equivalent to) <code>super</code>'s preconditions and if <code>sub</code>'s postconditions are stronger than (or equivalent to) <code>super</code>'s postconditions.</p>\n<p>Notice that I never actually said <code>sub</code> was from a subtype of <code>super</code>! The LSP conditions (at least, the contract rule of LSP) doesn't just apply to <em>subtypes</em> but can be applied in any situation where we substitute a function or block of code for another. Subtyping is a common place where this happens, but by no means the only! We can also substitute across time.Any time we modify some code's behavior, we are effectively substituting the new version in for the old version, and so the new version's contract must be compatible with the old version's to guarantee no existing code is broken.</p>\n<p>For example, say we maintain an API or function with two required inputs, <code>X</code> and <code>Y</code>, and one optional input, <code>Z</code>. Making <code>Z</code> required strengthens the precondition (\"input must have Z\" is stronger than \"input may have Z\"), so potentially breaks existing users of our API. Making <code>Y</code> optional weakens the precondition (\"input may have Y\" is weaker than \"input must have Y\"), so is guaranteed to be compatible.</p>\n<p>(This also underpins <a href=\"https://en.wikipedia.org/wiki/Robustness_principle\" target=\"_blank\">The robustness principle</a>: \"be conservative in what you send, be liberal in what you accept\".)</p>\n<p>Now the dark side of all this is <a href=\"https://www.hyrumslaw.com/\" target=\"_blank\">Hyrum's Law</a>. In the below code, are <code>new</code>'s postconditions stronger than <code>old</code>'s postconditions? </p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">old</span><span class=\"p\">():</span>\n    <span class=\"k\">return</span> <span class=\"p\">{</span><span class=\"s2\">\"a\"</span><span class=\"p\">:</span> <span class=\"s2\">\"foo\"</span><span class=\"p\">,</span> <span class=\"s2\">\"b\"</span><span class=\"p\">:</span> <span class=\"s2\">\"bar\"</span><span class=\"p\">}</span>\n\n<span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">new</span><span class=\"p\">():</span>\n    <span class=\"k\">return</span> <span class=\"p\">{</span><span class=\"s2\">\"a\"</span><span class=\"p\">:</span> <span class=\"s2\">\"foo\"</span><span class=\"p\">,</span> <span class=\"s2\">\"b\"</span><span class=\"p\">:</span> <span class=\"s2\">\"bar\"</span><span class=\"p\">,</span> <span class=\"s2\">\"c\"</span><span class=\"p\">:</span> <span class=\"s2\">\"baz\"</span><span class=\"p\">}</span>\n</code></pre></div>\n<p>On a first appearance, this is a strengthened postcondition: <code>out.contains_keys([a, b, c]) => out.contains_keys([a, b])</code>. But now someone does this:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"n\">my_dict</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">\"c\"</span><span class=\"p\">:</span> <span class=\"s2\">\"blat\"</span><span class=\"p\">}</span> \n<span class=\"n\">my_dict</span> <span class=\"o\">|=</span> <span class=\"n\">new</span><span class=\"p\">()</span>\n<span class=\"k\">assert</span> <span class=\"n\">my_dict</span><span class=\"p\">[</span><span class=\"n\">c</span><span class=\"p\">]</span> <span class=\"o\">==</span> <span class=\"s2\">\"blat\"</span>\n</code></pre></div>\n<p>Oh no, their code now breaks! They saw <code>old</code> had the postcondition \"<code>out</code> does NOT contain \"c\" as a key\", and then wrote their code expecting that postcondition. In a sense, <em>any</em> change the postcondition can potentially break <em>someone</em>. \"All observable behaviors of your system\nwill be depended on by somebody\", as <a href=\"https://www.hyrumslaw.com/\" target=\"_blank\">Hyrum's Law</a> puts it.</p>\n<p>So we need to be explicit in what our postconditions actually are, and properties of the output that are not part of our explicit postconditions are subject to be violated on the next version. You'll break people's workflows but you also have grounds to say \"I warned you\".</p>\n<p>Overall, Liskov and Wing did their work in the context of subtyping, but the principles are more widely applicable, certainly to more than just the use of inheritance.</p>\n<div class=\"footnote\">\n<hr/>\n<ol>\n<li id=\"fn:safety\">\n<p>Though they restrict it to just <a href=\"https://www.hillelwayne.com/post/safety-and-liveness/\" target=\"_blank\">safety properties</a>. <a class=\"footnote-backref\" href=\"#fnref:safety\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n<li id=\"fn:cocontra\">\n<p>The paper lists a couple of other authors as introduce the idea of \"contra/covariance rules\", but part of being \"off-the-cuff and casual\" means not diving into every referenced paper. So they might have gotten the pre/postconditions thing from an earlier author, dunno for sure! <a class=\"footnote-backref\" href=\"#fnref:cocontra\" title=\"Jump back to footnote 2 in the text\">↩</a></p>\n</li>\n<li id=\"fn:refinement\">\n<p>I <em>believe</em> that this is equivalent to the formal methods notion of a <a href=\"https://www.hillelwayne.com/post/refinement/\" target=\"_blank\">refinement</a>. <a class=\"footnote-backref\" href=\"#fnref:refinement\" title=\"Jump back to footnote 3 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/the-liskov-substitution-principle-does-more-than/",
          "published": "2026-01-06T16:51:26.000Z",
          "updated": "2026-01-06T16:51:26.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/some-fun-software-facts/",
          "title": "Some Fun Software Facts",
          "description": "<p>Last newsletter of the year!</p>\n<p>First some news on <em>Logic for Programmers</em>. Thanks to everyone who donated to the <a href=\"https://buttondown.com/hillelwayne/archive/get-logic-for-programmers-50-off-support-chicago\" target=\"_blank\">feedchicago charity drive</a>! In total we raised $2250 for Chicago food banks. Proof <a href=\"https://link.fndrsp.net/CL0/https:%2F%2Fgiving.chicagosfoodbank.org%2Freceipts%2FBMDDDCAF%3FreceiptType=oneTime%26emailLog=YS699MZW/2/0100019ae2b7eb92-7c917ad0-c94e-4fe2-8ee1-1b9dc521c607-000000/brmxoTOvoJN94I9nQH26s7fRrmyFDj_Jir1FySSoxCw=434\" target=\"_blank\">here</a>.</p>\n<p>If you missed buying <em>Logic for Programmers</em> real cheap in the charity drive, you can still get it for $10 off with the holiday code <a href=\"https://leanpub.com/logic/c/hannukah-presents\" target=\"_blank\">hannukah-presents</a>. This will last from now until the end of the year. After that, I'll be raising the price from $25 to $30.</p>\n<p>Anyway, to make this more than just some record keeping, let's close out with something light. I'm one of those people who loves hearing \"fun facts\" about stuff. So here's some random fun facts I accumulated about software over the years:</p>\n<ul>\n<li>In 2017, a team of eight+ programmers <a href=\"https://codegolf.stackexchange.com/questions/11880/build-a-working-game-of-tetris-in-conways-game-of-life\" target=\"_blank\">successfully implemented Tetris</a> as a <a href=\"https://en.wikipedia.org/wiki/Conway's_Game_of_Life\" target=\"_blank\">game of life simulation</a>. The GoL grid had an area of 30 trillion pixels and implemented a full programmable CPU as part of the project.</li>\n</ul>\n<ul>\n<li>Computer systems have to deal with leap seconds in order to keep UTC (where one day is 86,400 seconds) in sync with UT1 (where one day is exactly one full earth rotation). The people in charge recently passed a resolution to abolish the leap second by 2035, letting UTC and UT1 slowly drift out of sync.</li>\n</ul>\n<ul>\n<li><a href=\"https://buttondown.com/hillelwayne/archive/vim-is-turing-complete/\" target=\"_blank\">Vim is Turing complete</a>.</li>\n</ul>\n<div class=\"subscribe-form\"></div>\n<ul>\n<li>The backslash character basically didn't exist in writing before 1930, and <a href=\"http://dump.deadcodersociety.org/ascii.pdf\" target=\"_blank\">was only added to ASCII</a> so mathematicians (and ALGOLists) could write <code>/\\</code> and <code>\\/</code>. It's popular use in computing stems entirely from being a useless key on the keyboard.</li>\n</ul>\n<ul>\n<li><a href=\"https://en.wikipedia.org/wiki/Galactic_algorithm\" target=\"_blank\">Galactic Algorithms</a> are algorithms that are theoretically faster than algorithms we use, but only at scales that make them impractical. For example, matrix multiplication of NxN is <a href=\"https://en.wikipedia.org/wiki/Strassen_algorithm\" target=\"_blank\">normally</a> O(N^2.81). The <a href=\"https://www-auth.cs.wisc.edu/lists/theory-reading/2009-December/pdfmN6UVeUiJ3.pdf\" target=\"_blank\">Coppersmith Winograd</a> algorithm is O(N^2.38), but is so complex that it's vastly slower for even <a href=\"https://mathoverflow.net/questions/1743/what-is-the-constant-of-the-coppersmith-winograd-matrix-multiplication-algorithm\" target=\"_blank\">10,000 x 10,000 matrices</a>. It's still interesting in advancing our mathematical understanding of algorithms!</li>\n</ul>\n<ul>\n<li>Cloudflare generates random numbers by, in part, <a href=\"https://www.cloudflare.com/learning/ssl/lava-lamp-encryption/\" target=\"_blank\">taking pictures of 100 lava lamps</a>.</li>\n</ul>\n<ul>\n<li>Mergesort is older than bubblesort. Quicksort is slightly younger than bubblesort but older than the <em>term</em> \"bubblesort\". Bubblesort, btw, <a href=\"https://buttondown.com/hillelwayne/archive/when-would-you-ever-want-bubblesort/\" target=\"_blank\">does have some uses</a>.</li>\n</ul>\n<ul>\n<li>Speaking of mergesort, most implementations of mergesort pre-2006 <a href=\"https://research.google/blog/extra-extra-read-all-about-it-nearly-all-binary-searches-and-mergesorts-are-broken/\" target=\"_blank\">were broken</a>. Basically the problem was that the \"find the midpoint of a list\" step <em>could</em> overflow if the list was big enough. For C with 32-bit signed integers, \"big enough\" meant over a billion elements, which was why the bug went unnoticed for so long.</li>\n</ul>\n<ul>\n<li><a href=\"https://nibblestew.blogspot.com/2023/09/circles-do-not-exist.html\" target=\"_blank\">PDF's drawing model cannot render perfect circles</a>.</li>\n</ul>\n<ul>\n<li>People make fun of how you have to flip USBs three times to get them into a computer, but there's supposed to be a guide: according to the standard, USBs are supposed to be inserted <em>logo-side up</em>. Of course, this assumes that the port is right-side up, too, which is why USB-C is just symmetric. </li>\n</ul>\n<ul>\n<li>I was gonna write a fun fact about how all spreadsheet software treats 1900 as a leap year, as that was a bug in Lotus 1-2-3 and everybody preserved backwards compatibility. But I checked and Google sheets considers it a normal year. So I guess the fun fact is that things have changed!</li>\n</ul>\n<ul>\n<li>Speaking of spreadsheet errors, in 2020 <a href=\"https://www.engadget.com/scientists-rename-genes-due-to-excel-151748790.html\" target=\"_blank\">biologists changed the official nomenclature</a> of 27 genes because Excel kept parsing their names as dates. F.ex MARCH1 was renamed to MARCHF1 to avoid being parsed as \"March 1st\". Microsoft rolled out a fix for this... three years later.</li>\n</ul>\n<ul>\n<li>It is possible to encode any valid JavaScript program with just the characters <code>()+[]!</code>. This encoding is called <a href=\"https://en.wikipedia.org/wiki/JSFuck\" target=\"_blank\">JSFuck</a> and was once used to distribute malware on <a href=\"https://arstechnica.com/information-technology/2016/02/ebay-has-no-plans-to-fix-severe-bug-that-allows-malware-distribution/\" target=\"_blank\">Ebay</a>.</li>\n</ul>\n<p>Happy holidays everyone, and see you in 2026!</p>\n<div class=\"footnote\">\n<hr/>\n<ol>\n<li id=\"fn:status\">\n<p>Current status update: I'm finally getting line by line structural editing done and it's turning up lots of improvements, so I'm doing more rewrites than I expected to be doing. <a class=\"footnote-backref\" href=\"#fnref:status\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/some-fun-software-facts/",
          "published": "2025-12-10T18:45:37.000Z",
          "updated": "2025-12-10T18:45:37.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/one-more-week-to-the-logic-for-programmers-food/",
          "title": "One more week to the Logic for Programmers Food Drive",
          "description": "<p>A couple of weeks ago I started a fundraiser for the <a href=\"https://www.chicagosfoodbank.org/\" target=\"_blank\">Greater Chicago Food Depository</a>: get <a href=\"https://leanpub.com/logic/c/feedchicago\" target=\"_blank\">Logic for Programmers 50% off</a> and all the royalties will go to charity.<sup id=\"fnref:royalties\"><a class=\"footnote-ref\" href=\"#fn:royalties\">1</a></sup> Since then, we've raised a bit over $1600. Y'all are great! </p>\n<p>The fundraiser is going on until the end of November, so you still have one more week to get the book real cheap.</p>\n<p>I feel a bit weird about doing two newsletter adverts without raw content, so here's a teaser from a old project I really need to get back to. <a href=\"https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/#what-is-a-goto-statement-anyway\" target=\"_blank\">Notes on structured concurrency</a> argues that old languages had a \"old-testament fire-and-brimstone <code>goto</code>\" that could send control flow anywhere, like from the body of one function into the body of another function. This \"wild goto\", the article claims, what Dijkstra was railing against in <a href=\"https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf\" target=\"_blank\">Go To Statement Considered Harmful</a>, and that modern goto statements are much more limited, \"tame\" if you will, and wouldn't invoke Dijkstra's ire.</p>\n<p>I've shared this historical fact about Dijkstra many times, but recently two <a href=\"https://without.boats/blog/\" target=\"_blank\">separate</a> <a href=\"https://matklad.github.io/\" target=\"_blank\">people</a> have told me it doesn't makes sense: Dijkstra used ALGOL-60, which <em>already had</em> tame gotos. All of the problems he raises with <code>goto</code> hold even for tame ones, none are exclusive to wild gotos. So </p>\n<p>This got me looking to see which languages, if any, ever had the wild goto. I define this as any goto which lets you jump from outside to into a loop or function scope. Turns out, FORTRAN had tame gotos from the start, BASIC has wild gotos, and COBOL is a nonsense language intentionally designed to horrify me. I mean, look at this:</p>\n<p><img alt=\"The COBOL ALTER statement, which redefines a goto target\" class=\"newsletter-image\" src=\"https://assets.buttondown.email/images/e4dfa0fd-fdd5-4fef-b813-4053a183be2f.png?w=960&fit=max\"/></p>\n<p>The COBOL ALTER statement <em>changes a <code>goto</code>'s target at runtime</em>. </p>\n<p>(Early COBOL has tame gotos but only on a technicality: there are no nested scopes in COBOL so no jumping from outside and into a nested scope.)</p>\n<p>Anyway I need to write up the full story (and complain about COBOL more) but this is pretty neat! Reminder, <a href=\"https://leanpub.com/logic/c/feedchicago\" target=\"_blank\">fundraiser here</a>. Let's get it to 2k.</p>\n<div class=\"footnote\">\n<hr/>\n<ol>\n<li id=\"fn:royalties\">\n<p>Royalties are 80% so if you already have the book you get a bit more bang for your buck by donating to the GCFD directly <a class=\"footnote-backref\" href=\"#fnref:royalties\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/one-more-week-to-the-logic-for-programmers-food/",
          "published": "2025-11-24T18:21:49.000Z",
          "updated": "2025-11-24T18:21:49.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/get-logic-for-programmers-50-off-support-chicago/",
          "title": "Get Logic for Programmers 50% off & Support Chicago Foodbanks",
          "description": "<p>From now until the end of the month, you can get <a href=\"https://leanpub.com/logic/c/feedchicago\" target=\"_blank\">Logic for Programmers at half price</a> with the coupon <code>feedchicago</code>. All royalties from that coupon will go to the <a href=\"https://www.chicagosfoodbank.org/\" target=\"_blank\">Greater Chicago Food Depository</a>. Thank you!</p>",
          "url": "https://buttondown.com/hillelwayne/archive/get-logic-for-programmers-50-off-support-chicago/",
          "published": "2025-11-10T16:31:11.000Z",
          "updated": "2025-11-10T16:31:11.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/im-taking-a-break/",
          "title": "I'm taking a break",
          "description": "<p>Hi everyone,</p>\n<p>I've been getting burnt out on writing a weekly software essay. It's gone from taking me an afternoon to write a post to taking two or three days, and that's made it really difficult to get other writing done. That, plus some short-term work and life priorities, means now feels like a good time for a break. </p>\n<p>So I'm taking off from <em>Computer Things</em> for the rest of the year. There <em>might</em> be some announcements and/or one or two short newsletters in the meantime but I won't be attempting a weekly cadence until 2026.</p>\n<p>Thanks again for reading!</p>\n<p>Hillel</p>",
          "url": "https://buttondown.com/hillelwayne/archive/im-taking-a-break/",
          "published": "2025-10-27T21:02:37.000Z",
          "updated": "2025-10-27T21:02:37.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/modal-editing-is-a-weird-historical-contingency/",
          "title": "Modal editing is a weird historical contingency we have through sheer happenstance",
          "description": "<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<p>A while back my friend <a href=\"https://morepablo.com/\" target=\"_blank\">Pablo Meier</a> was reviewing some 2024 videogames and wrote <a href=\"https://morepablo.com/2025/03/games-of-2024.html\" target=\"_blank\">this</a>:</p>\n<blockquote>\n<p>I feel like some artists, if they didn't exist, would have the resulting void filled in by someone similar (e.g. if Katy Perry didn't exist, someone like her would have). But others don't have successful imitators or comparisons (thinking Jackie Chan, or Weird Al): they are irreplaceable.  </p>\n</blockquote>\n<p>He was using it to describe auteurs but I see this as a property of opportunity, in that \"replaceable\" artists are those who work in bigger markets. Katy Perry's market is large, visible and obviously (but not <em>easily</em>) exploitable, so there are a lot of people who'd compete in her niche. Weird Al's market is unclear: while there were successful parody songs in the past, it wasn't clear there was enough opportunity there to support a superstar.</p>\n<p>I think that modal editing is in the latter category. Vim is now very popular and has spawned numerous successors. But its key feature, <strong>modes</strong>, is not obviously-beneficial, to the point that if Bill Joy didn't make vi (vim's direct predecessor) fifty years ago I don't think we'd have any modal editors today. </p>\n<h3>A quick overview of \"modal editing\"</h3>\n<p>In a non-modal editor, pressing the \"u\" key adds a \"u\" to your text, as you'd expect. In a <strong>modal editor</strong>, pressing \"u\" does something different depending on the \"mode\" you are in. In Vim's default \"normal\" mode, \"u\" undoes the last change to the text, while in the \"visual\" mode it lowercases all selected text. It only inserts the character in \"insert\" mode. All other keys, as well as chorded shortcuts (<code>ctrl-x</code>), work the same way. </p>\n<p>The clearest benefit to this is you can densely pack the keyboard with advanced commands. The standard US keyboard has 48ish keys dedicated to inserting characters. With the ctrl and shift modifiers that becomes at least ~150 extra shortcuts for each other mode. This is also what IMO \"spiritually\" distinguishes modal editing from contextual shortcuts. Even if a unimodal editor lets you change a keyboard shortcut's behavior based on languages or focused panel, without global user-controlled modes it simply can't achieve that density of shortcuts.</p>\n<p>Now while modal editing today is widely beloved (the Vim plugin for <a href=\"https://marketplace.visualstudio.com/items?itemName=vscodevim.vim\" target=\"_blank\">VSCode</a> has at least eight million downloads), I suspect it was \"carried\" by the popularity of vi, as opposed to driving vi's popularity.</p>\n<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<h3>Modal editing is an unusual idea</h3>\n<p>Pre-vi editors weren't modal. Some, like <a href=\"https://en.wikipedia.org/wiki/EDT_(Digital)\" target=\"_blank\">EDT/KED</a>, used chorded commands, while others like <a href=\"https://en.wikipedia.org/wiki/Ed_(software)\" target=\"_blank\">ed</a> or <a href=\"https://en.wikipedia.org/wiki/TECO_(text_editor)\" target=\"_blank\">TECO</a> basically REPLs for text-editing DSLs. Both of these ideas widely reappear in modern editors.</p>\n<p>As far as I can tell, the first modal editor was Butler Lampson's <a href=\"https://en.wikipedia.org/wiki/Bravo_(editor)\" target=\"_blank\">Bravo</a> in 1974. Bill Joy <a href=\"https://web.archive.org/web/20120210184000/http://web.cecs.pdx.edu/~kirkenda/joy84.html\" target=\"_blank\">admits he used it for inspiration</a>: </p>\n<blockquote>\n<p>A lot of the ideas for the screen editing mode were stolen from a Bravo manual I surreptitiously looked at and copied. Dot is really the double-escape from Bravo, the redo command. Most of the stuff was stolen. </p>\n</blockquote>\n<p>Bill Joy probably took the idea because he was working on <a href=\"https://en.wikipedia.org/wiki/ADM-3A\" target=\"_blank\">dumb terminals</a> that were slow to register keystrokes, which put pressure to minimize the number needed for complex operations.</p>\n<p>Why did Bravo have modal editing? Looking at the <a href=\"https://www.microsoft.com/en-us/research/wp-content/uploads/2016/11/15a-AltoHandbook.pdf\" target=\"_blank\">Alto handbook</a>, I get the impression that Xerox was trying to figure out the best mouse and GUI workflows. Bravo was an experiment with modes, one hand on the mouse and one issuing commands on the keyboard. Other experiments included context menus (the Markup program) and toolbars (Draw).</p>\n<p>Xerox very quickly decided <em>against</em> modes, as the successors <a href=\"http://www.bitsavers.org/pdf/xerox/alto/memos_1975/Gypsy_The_Ginn_Typescript_System_Apr75.pdf\" target=\"_blank\">Gypsy</a> and <a href=\"http://www.bitsavers.org/pdf/xerox/alto/BravoXMan.pdf\" target=\"_blank\">BravoX</a> were modeless. Commands originally assigned to English letters were moved to graphical menus, special keys, and chords. </p>\n<p>It seems to me that modes started as an unsuccessful experiment deal with a specific constraint and then later successfully adopted to deal with a different constraint. It was a specialized feature as opposed to a generally useful feature like chords.</p>\n<h3>Modal editing didn't popularize vi</h3>\n<p>While vi was popular at Bill Joy's coworkers, he doesn't <a href=\"https://web.archive.org/web/20120210184000/http://web.cecs.pdx.edu/~kirkenda/joy84.html\" target=\"_blank\">attribute its success to its features</a>:</p>\n<blockquote>\n<p>I think the wonderful thing about vi is that it has such a good market share because we gave it away. Everybody has it now. So it actually had a chance to become part of what is perceived as basic UNIX. EMACS is a nice editor too, but because it costs hundreds of dollars, there will always be people who won't buy it. </p>\n</blockquote>\n<p>Vi was distributed for free with the popular <a href=\"https://en.wikipedia.org/wiki/Berkeley_Software_Distribution\" target=\"_blank\">BSD Unix</a> and was standardized in <a href=\"https://pubs.opengroup.org/onlinepubs/9799919799/\" target=\"_blank\">POSIX Issue 2</a>, meaning all Unix OSes had to have vi. That arguably is what made it popular, and why so many people ended up learning a modal editor. </p>\n<h3>Modal editing doesn't really spread outside of vim</h3>\n<div class=\"subscribe-form\"></div>\n<p>I think by the 90s, people started believing that modal editing was a Good Idea, if not an obvious one. That's why we see direct descendants of vi, most famously vim. It's also why extensible editors like Emacs and VSCode have vim-mode extensions, but these are but these are always simple emulation layers on top of a unimodal baselines. This was good for getting people used to the vim keybindings (I learned on <a href=\"https://en.wikipedia.org/wiki/Kile\" target=\"_blank\">Kile</a>) but it means people weren't really <em>doing</em> anything with modal editing. It was always \"The Vim Gimmick\".</p>\n<p>Modes also didn't take off anywhere else. There's no modal word processor, spreadsheet editor, or email client.<sup id=\"fnref:gmail\"><a class=\"footnote-ref\" href=\"#fn:gmail\">1</a></sup> <a href=\"https://www.visidata.org/\" target=\"_blank\">Visidata</a> is an extremely cool modal data exploration tool but it's pretty niche. Firefox used to have <a href=\"https://en.wikipedia.org/wiki/Vimperator\" target=\"_blank\">vimperator</a> (which was inspired by Vim) but that's defunct now. Modal software means modal editing which means vi.</p>\n<p>This has been changing a little, though! Nowadays we do see new modal text editors, like <a href=\"https://kakoune.org/\" target=\"_blank\">kakoune</a> and <a href=\"https://helix-editor.com/\" target=\"_blank\">Helix</a>, that don't just try to emulate vi but do entirely new things. These were made, though, in response to perceived shortcomings in vi's editing model. I think they are still classifiable as descendants. If vi never existed, would the developers of kak and helix have still made modal editors, or would they have explored different ideas? </p>\n<h3>People aren't clamouring for more experiments</h3>\n<p>Not too related to the overall picture, but a gripe of mine. Vi and vim have a set of hardcoded modes, and adding an entirely new mode is impossible. Like if a plugin (like vim's default <code>netrw</code>) adds a file explorer it should be able to add a filesystem mode, right? But it can't, so instead it waits for you to open the filesystem and then <a href=\"https://github.com/vim/vim/blob/0124320c97b0fbbb44613f42fc1c34fee6181fc8/runtime/pack/dist/opt/netrw/autoload/netrw.vim#L4867\" target=\"_blank\">adds 60 new mappings to normal mode</a>. There's no way to properly add a \"filesystem\" mode, a \"diff\" mode, a \"git\" mode, etc, so plugin developers have to <a href=\"https://www.hillelwayne.com/post/software-mimicry/\" target=\"_blank\">mimic</a> them.</p>\n<p>I don't think people see this as a problem, though! Neovim, which aims to fix all of the baggage in vim's legacy, didn't consider creating modes an important feature. Kak and Helix, which reimagine modal editing from from the ground up, don't support creating modes either.<sup id=\"fnref:helix\"><a class=\"footnote-ref\" href=\"#fn:helix\">2</a></sup> People aren't clamouring for new modes!</p>\n<h2>Modes are a niche power user feature</h2>\n<p>So far I've been trying to show that vi is, in Pablo's words, \"irreplaceable\". Editors weren't doing modal editing before Bravo, and even after vi became incredibly popular, unrelated editors did not adapt modal editing. At most, they got a vi emulation layer. Kak and helix complicate this story but I don't think they refute it; they appear much later and arguably count as descendants (so are related). </p>\n<p>I think the best explanation is that in a vacuum modal editing sounds like a bad idea. The mode is global state that users always have to know, which makes it dangerous. To use new modes well you have to memorize all of the keybindings, which makes it difficult. Modal editing has a brutal skill floor before it becomes more efficient than a unimodal, chorded editor like VSCode.</p>\n<p>That's why it originally appears in very specific circumstances, as early experiments in mouse UX and as a way of dealing with modem latencies. The fact we have vim today is a historical accident. </p>\n<p>And I'm glad for it! You can pry Neovim from my cold dead hands, you monsters.</p>\n<hr/>\n<h1><a href=\"https://www.p99conf.io/\" target=\"_blank\">P99 talk this Thursday</a>!</h1>\n<p>My talk, \"Designing Low-Latency Systems with TLA+\", is happening 10/23 at 11:40 central time. Tickets are free, the conf is online, and the talk's only 16 minutes, so come check it out!</p>\n<div class=\"footnote\">\n<hr/>\n<ol>\n<li id=\"fn:gmail\">\n<p>I guess if you squint <a href=\"https://support.google.com/mail/answer/6594?hl=en&co=GENIE.Platform%3DDesktop\" target=\"_blank\">gmail kinda counts</a> but it's basically an antifeature <a class=\"footnote-backref\" href=\"#fnref:gmail\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n<li id=\"fn:helix\">\n<p>It looks like Helix supports <a href=\"https://docs.helix-editor.com/remapping.html\" target=\"_blank\">creating minor modes</a>, but these are only active for one keystroke, making them akin to a better, more ergonomic version of vim multikey mappings. <a class=\"footnote-backref\" href=\"#fnref:helix\" title=\"Jump back to footnote 2 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/modal-editing-is-a-weird-historical-contingency/",
          "published": "2025-10-21T16:46:24.000Z",
          "updated": "2025-10-21T16:46:24.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/the-phase-change/",
          "title": "The Phase Change",
          "description": "<p>Last week I ran my first 10k.</p>\n<p>It wasn't a race or anything. I left that evening planning to run a 5k, and then three miles later thought \"what if I kept going?\"<sup id=\"fnref:distance\"><a class=\"footnote-ref\" href=\"#fn:distance\">1</a></sup></p>\n<p>I've been running for just over two years now. My goal was to run a mile, then three, then three at a pace faster than a power-walk. I wish I could say that I then found joy in running, but really I was just mad at myself for being so bad at it. Spite has always been my brightest muse.</p>\n<p>Looking back, the thing I find most fascinating is what progress looked like. I couldn't tell you if I was physically progressing steadily, but for sure mental progress moved in discrete jumps. For a long time a 5k was me pushing myself, then suddenly a \"phase change\" happens and it becomes something I can just do on a run. Sometime in the future the 10k will feel the same way.</p>\n<p>I've noticed this in a lot of other places. For every skill I know, my sense of myself follows a phase change. In every programming language I've ever learned, I lurch from \"bad\" to \"okay\" to \"good\". There's no \"20% bad / 80% normal\" in between. Pedagogical experts say that learning is about steadily building a <a href=\"https://teachtogether.tech/en/index.html#s:models\" target=\"_blank\">mental model</a> of the topic. It really feels like knowledge grows continuously, and then it suddenly becomes a model.</p>\n<p>Now, for all the time I spend writing about software history and software theory and stuff, my actually job boils down to <a href=\"https://www.hillelwayne.com/consulting/\" target=\"_blank\">teaching formal methods</a>. So I now have two questions about phase changes.</p>\n<p>The first is \"can we make phase changes happen faster?\" I don't know if this is even possible! I've found lots of ways to teach concepts faster, cover more ground in less time, so that people know the material more quickly. But it doesn't seem to speed up that very first phase change from \"this is foreign\" to \"this is normal\". Maybe we can't really do that until we've spent enough effort on understanding.</p>\n<p>So the second may be more productive: \"can we motivate people to keep going until the phase change?\" This is a lot easier to tackle! For example, removing frustration makes a huge difference. Getting a proper pair of running shoes made running so much less unpleasant, and made me more willing to keep putting in the hours. For teaching tech topics like formal methods, this often takes the form of better tooling and troubleshooting info.</p>\n<p>We can also reduce the effort of investing time. This is also why I prefer to pair on writing specifications with clients and not just write specs for them. It's more work for them than fobbing it all off on me, but a whole lot <em>less</em> work than writing the spec by themselves, so they'll put in time and gradually develop skills on their own.</p>\n<p>Question two seems much more fruitful than question one but also so much less interesting! Speeding up the phase change feels like the kind of dream that empires are built on. I know I'm going to keep obsessing over it, even if that leads nowhere.</p>\n<div class=\"footnote\">\n<hr/>\n<ol>\n<li id=\"fn:distance\">\n<p>For non-running Americans: 5km is about 3.1 miles, and 10km is 6.2. <a class=\"footnote-backref\" href=\"#fnref:distance\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/the-phase-change/",
          "published": "2025-10-16T14:59:25.000Z",
          "updated": "2025-10-16T14:59:25.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/three-ways-formally-verified-code-can-go-wrong-in/",
          "title": "Three ways formally verified code can go wrong in practice",
          "description": "<h3>New Logic for Programmers Release!</h3>\n<p><a href=\"https://leanpub.com/logic/\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">v0.12 is now available</a>! This should be the last major content release. The next few months are going to be technical review, copyediting and polishing, with a hopeful 1.0 release in March. <a href=\"https://github.com/logicforprogrammers/book-assets/blob/master/CHANGELOG.md\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">Full release notes here</a>.</p>\n<figure><img alt=\"Cover of the boooooook\" draggable=\"false\" src=\"https://assets.buttondown.email/images/92b4a35d-2bdd-416a-92c7-15ff42b49d8d.jpg?w=960&fit=max\"/><figcaption></figcaption></figure>\n<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<h1>Three ways formally verified code can go wrong in practice</h1>\n<p>I run this small project called <a href=\"https://github.com/hwayne/lets-prove-leftpad\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">Let's Prove Leftpad</a>, where people submit formally verified proofs of the <a href=\"https://en.wikipedia.org/wiki/Npm_left-pad_incident\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">eponymous meme</a>. Recently I read <a href=\"https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">Breaking “provably correct” Leftpad</a>, which argued that most (if not all) of the provably correct leftpads have bugs! The lean proof, for example, <em>should</em> render <code>leftpad('-', 9, אֳֽ֑)</code> as <code>---------אֳֽ֑</code>, but actually does <code>------אֳֽ֑</code>.</p>\n<p>You can read the article for a good explanation of why this goes wrong (Unicode). The actual problem is that correct can mean two different things, and this leads to confusion about how much formal methods can actually guarantee us. So I see this as a great opportunity to talk about the nature of proof, correctness, and how \"correct\" code can still have bugs.</p>\n<h2>What we talk about when we talk about correctness</h2>\n<p>In most of the real world, correct means \"no bugs\". Except \"bugs\" isn't a very clear category. A bug is anything that causes someone to say \"this isn't working right, there's a bug.\" Being too slow is a bug, a typo is a bug, etc. \"correct\" is a little fuzzy.</p>\n<p>In formal methods, \"correct\" has a very specific and precise meaning: the code conforms to a <strong>specification</strong> (or \"spec\"). The spec is a higher-level description of what is supposed the code's properties, usually something we can't just directly implement. Let's look at the most popular kind of proven specification:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"c1\">-- Haskell</span>\n<span class=\"nf\">inc</span><span class=\"w\"> </span><span class=\"ow\">::</span><span class=\"w\"> </span><span class=\"kt\">Int</span><span class=\"w\"> </span><span class=\"o\">-&</span><span class=\"n\">gt</span><span class=\"p\">;</span><span class=\"w\"> </span><span class=\"kt\">Int</span>\n<span class=\"nf\">inc</span><span class=\"w\"> </span><span class=\"n\">x</span><span class=\"w\"> </span><span class=\"ow\">=</span><span class=\"w\"> </span><span class=\"n\">x</span><span class=\"w\"> </span><span class=\"o\">+</span><span class=\"w\"> </span><span class=\"mi\">1</span>\n</code></pre></div>\n<p>The type signature <code>Int -> Int</code> is a specification! It corresponds to the logical statement <code>all x in Int: inc(x) in Int</code>. The Haskell type checker can automatically verify this for us. It cannot, however, verify properties like <code>all x in Int: inc(x) > x</code>. Formal verification is concerned with verifying arbitrary properties beyond what is (easily) automatically verifiable. Most often, this takes the form of proof. A human manually writes a proof that the code conforms to its specification, and the prover checks that the proof is correct.</p>\n<p>Even if we have a proof of \"correctness\", though, there's a few different ways the code can still have bugs.</p>\n<h3>1. The proof is invalid</h3>\n<p>For some reason the proof doesn't actually show the code matches the specification. This is pretty common in pencil-and-paper verification, where the proof is checked by someone saying \"yep looks good to me\". It's much rarer when doing formal verification but it can still happen in a couple of specific cases:</p>\n<ol><li><p>The theorem prover itself has a bug (in the code or introduced in the compiled binary) that makes it accept an incorrect proof. This is something people are really concerned about but it's so much rarer than every other way verified code goes wrong, so is only included for completeness.</p></li><li><p>For convenience, most provers and FM languages have an \"just accept this statement is true\" feature. This helps you work on the big picture proof and fill in the details later. If you leave in a shortcut, <em>and</em> the compiler is configured to allow code-with-proof-assumptions to compile, <em>then</em> you can compile incorrect code that \"passes the proof checker\". You really should know better, though.</p></li></ol>\n<div class=\"subscribe-form\"></div>\n<h3>2. The properties are wrong</h3>\n<blockquote><figure><img alt=\"The horrible bug you had wasn't covered in the specification/came from some other module/etc\" draggable=\"false\" src=\"https://cdn.prod.website-files.com/673b407e535dbf3b547179ff/681ca0bf4a045f39f785faeb_AD_4nXfFhdn6DGmgLAcmaUNHl9a3Nog8gH8Hluve5Kof7zLk4CyOlD4zCmCqVJaowKqu-pTicwZ393jE7anIrjYZTSuRvGiYhFhAkkX9vifNt9vEWYwZUp65hsbrRTmZzRgb9vgu7n7buA.png\"/><figcaption></figcaption></figure><p><a href=\"https://www.galois.com/articles/what-works-and-doesnt-selling-formal-methods\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">Galois</a></p></blockquote>\n<p>This code is provably correct:</p>\n<div class=\"codehilite\"><pre><span></span><code><span class=\"nf\">inc</span><span class=\"w\"> </span><span class=\"ow\">::</span><span class=\"w\"> </span><span class=\"kt\">Int</span><span class=\"w\"> </span><span class=\"o\">-&</span><span class=\"n\">gt</span><span class=\"p\">;</span><span class=\"w\"> </span><span class=\"kt\">Int</span>\n<span class=\"nf\">inc</span><span class=\"w\"> </span><span class=\"n\">x</span><span class=\"w\"> </span><span class=\"ow\">=</span><span class=\"w\"> </span><span class=\"n\">x</span><span class=\"o\">-</span><span class=\"mi\">1</span>\n</code></pre></div>\n<p>The only specification I've given is the type signature <code>Int -> Int</code>. At no point did I put the property <code>inc(x) > x</code> in my specification, so it doesn't matter that it doesn't hold, the code is still \"correct\".</p>\n<p>This is what \"went wrong\" with the leftpad proofs. They do <em>not</em> prove the property \"<code>leftpad(c, n, s)</code> will take up either <code>n</code> spaces on the screen or however many characters <code>s</code> takes up (if more than <code>n</code>)\". They prove the weaker property \"<code>len(leftpad(c, n, s)) == max(n, len(s))</code>, for however you want to define <code>len(string)</code>\". The second is a rough proxy for the first that works in most cases, but if someone really needs the former property they are liable to experience a bug.</p>\n<p>Why don't we prove the stronger property? Sometimes it's because the code is meant to be used one way and people want to use it another way. This can lead to accusations that the developer is \"misusing the provably correct code\" but this should more often be seen as the verification expert failing to educate devs on was actually \"proven\".</p>\n<p>Sometimes it's because the property is too hard to prove. \"Outputs are visually aligned\" is a proof about Unicode inputs, and the <em>core</em> Unicode specification is <a href=\"https://www.unicode.org/versions/Unicode17.0.0/UnicodeStandard-17.0.pdf\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">1,243 pages long</a>.</p>\n<p>Sometimes it's because the property we want is too hard to <em>express</em>. How do you mathematically represent \"people will perceive the output as being visually aligned\"? Is it OS and font dependent? These two lines are exactly five characters but not visually aligned:</p>\n<blockquote><p>|||||</p><p>MMMMM</p></blockquote>\n<p>Or maybe they are aligned for you! I don't know, lots of people read email in a monospace font. \"We can't express the property\" comes up a lot when dealing with human/business concepts as opposed to mathematical/computational ones.</p>\n<p>Finally, there's just the possibility of a brain fart. All of the proofs in <a href=\"https://research.google/blog/extra-extra-read-all-about-it-nearly-all-binary-searches-and-mergesorts-are-broken/\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">Nearly All Binary Searches and Mergesorts are Broken</a> are like this. They (informally) proved the correctness of binary search with unbound integers, forgetting that many programming languages use <em>machine</em> integers, where a large enough sum can overflow.</p>\n<h3>3. The assumptions are wrong</h3>\n<p>This is arguably the most important and most subtle source of bugs. Most properties we prove aren't \"<code>X</code> is always true\". They are \"<em>assuming</em> <code>Y</code> is true, <code>X</code> is also true\". Then if <code>Y</code> is not true, the proof no longer guarantees <code>X</code>. A good example of this is binary <s>sort</s> <em>search</em>, which only correctly finds elements <em>assuming</em> the input list is sorted. If the list is not sorted, it will not work correctly.</p>\n<p>Formal verification adds two more wrinkles. One: sometimes we need assumptions to make the property valid, but we can also add them to make the proof easier. So the code can be bug-free even if the assumptions used to verify it no longer hold! Even if a leftpad implements visual alignment for all Unicode glyphs, it will be a lot easier to <em>prove</em> visual alignment for just ASCII strings and padding.</p>\n<p>Two: we need make a lot of <em>environmental</em> assumptions that are outside our control. Does the algorithm return output or use the stack? Need to assume that there's sufficient memory to store stuff. Does it use any variables? Need to assume nothing is concurrently modifying them. Does it use an external service? Need to assume the vendor doesn't change the API or response formats. You need to assume the compiler worked correctly, the hardware isn't faulty, and the OS doesn't mess with things, etc. Any of these could change well after the code is proven and deployed, meaning formal verification can't be a one-and-done thing.</p>\n<p>You don't actually have to assume most of these, but each assumption drop makes the proof harder and the properties you can prove more restricted. Remember, the code might still be bug-free even if the environmental assumptions change, so there's a tradeoff in time spent proving vs doing other useful work.</p>\n<p>Another common source of \"assumptions\" is when verified code depends on unverified code. The Rust compiler can prove that safe code doesn't have a memory bug <em>assuming</em> unsafe code does not have one either, but depends on the human to confirm that assumption. <a href=\"https://ucsd-progsys.github.io/liquidhaskell/\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">Liquid Haskell</a> is verifiable but can also call regular Haskell libraries, which are unverified. We need to assume that code is correct (in the \"conforms to spec\") sense, and if it's not, our proof can be \"correct\" and still cause bugs.</p>\n<hr/><p>These boundaries are fuzzy. I wrote that the \"binary search\" bug happened because they proved the wrong property, but you can just as well argue that it was a broken assumption (that integers could not overflow). What really matters is having a clear understanding of what \"this code is proven correct\" actually <em>tells</em> you. Where can you use it safely? When should you worry? How do you communicate all of this to your teammates?</p>\n<p>Good lord it's already Friday</p>",
          "url": "https://buttondown.com/hillelwayne/archive/three-ways-formally-verified-code-can-go-wrong-in/",
          "published": "2025-10-10T17:06:19.000Z",
          "updated": "2025-10-10T17:06:19.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/new-blog-post-a-very-early-history-of-algebraic/",
          "title": "New Blog Post: \" A Very Early History of Algebraic Data Types\"",
          "description": "<p>Last week I said that this week's newsletter would be a brief history of algebraic data types.</p>\n<p>I was wrong.</p>\n<p>That history is now a <a href=\"https://www.hillelwayne.com/post/algdt-history/\" target=\"_blank\">3500 word blog post</a>.</p>\n<p><a href=\"https://www.patreon.com/posts/blog-notes-very-139696324?utm_medium=clipboard_copy&utm_source=copyLink&utm_campaign=postshare_creator&utm_content=join_link\" target=\"_blank\">Patreon blog notes here</a>.</p>\n<hr/>\n<h3>I'm speaking at <a href=\"https://www.p99conf.io/\" target=\"_blank\">P99 Conf</a>!</h3>\n<p>My talk, \"Designing Low-Latency Systems with TLA+\", is happening 10/23 at 11:30 central time. It's an online conf and the talk's only 16 minutes, so come check it out!</p>",
          "url": "https://buttondown.com/hillelwayne/archive/new-blog-post-a-very-early-history-of-algebraic/",
          "published": "2025-09-25T16:50:58.000Z",
          "updated": "2025-09-25T16:50:58.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/many-hard-leetcode-problems-are-easy-constraint/",
          "title": "Many Hard Leetcode Problems are Easy Constraint Problems",
          "description": "<p>In my first interview out of college I was asked the change counter problem:</p>\n<blockquote>\n<p>Given a set of coin denominations, find the minimum number of coins required to make change for a given number. IE for USA coinage and 37 cents, the minimum number is four (quarter, dime, 2 pennies).</p>\n</blockquote>\n<p>I implemented the simple greedy algorithm and immediately fell into the trap of the question: the greedy algorithm only works for \"well-behaved\" denominations. If the coin values were <code>[10, 9, 1]</code>, then making 37 cents would take 10 coins in the greedy algorithm but only 4 coins optimally (<code>10+9+9+9</code>). The \"smart\" answer is to use a dynamic programming algorithm, which I didn't know how to do. So I failed the interview.</p>\n<p>But you only need dynamic programming if you're writing your own algorithm. It's really easy if you throw it into a constraint solver like <a href=\"https://www.minizinc.org/\" target=\"_blank\">MiniZinc</a> and call it a day. </p>\n<div class=\"codehilite\"><pre><span></span><code>int: total;\narray[int] of int: values = [10, 9, 1];\narray[index_set(values)] of var 0..: coins;\n\nconstraint sum (c in index_set(coins)) (coins[c] * values[c]) == total;\nsolve minimize sum(coins);\n</code></pre></div>\n<p>You can try this online <a href=\"https://play.minizinc.dev/\" target=\"_blank\">here</a>. It'll give you a prompt to put in <code>total</code> and then give you successively-better solutions:</p>\n<div class=\"codehilite\"><pre><span></span><code>coins = [0, 0, 37];\n----------\ncoins = [0, 1, 28];\n----------\ncoins = [0, 2, 19];\n----------\ncoins = [0, 3, 10];\n----------\ncoins = [0, 4, 1];\n----------\ncoins = [1, 3, 0];\n----------\n</code></pre></div>\n<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<p>Lots of similar interview questions are this kind of mathematical optimization problem, where we have to find the maximum or minimum of a function corresponding to constraints. They're hard in programming languages because programming languages are too low-level. They are also exactly the problems that constraint solvers were designed to solve. Hard leetcode problems are easy constraint problems.<sup id=\"fnref:leetcode\"><a class=\"footnote-ref\" href=\"#fn:leetcode\">1</a></sup> Here I'm using MiniZinc, but you could just as easily use Z3 or OR-Tools or whatever your favorite generalized solver is.</p>\n<h3>More examples</h3>\n<p>This was a question in a different interview (which I thankfully passed):</p>\n<blockquote>\n<p>Given a list of stock prices through the day, find maximum profit you can get by buying one stock and selling one stock later.</p>\n</blockquote>\n<p>It's easy to do in O(n^2) time, or if you are clever, you can do it in O(n). Or you could be not clever at all and just write it as a constraint problem:</p>\n<div class=\"codehilite\"><pre><span></span><code>array[int] of int: prices = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8];\nvar int: buy;\nvar int: sell;\nvar int: profit = prices[sell] - prices[buy];\n\nconstraint sell > buy;\nconstraint profit > 0;\nsolve maximize profit;\n</code></pre></div>\n<p>Reminder, link to trying it online <a href=\"https://play.minizinc.dev/\" target=\"_blank\">here</a>. While working at that job, one interview question we tested out was:</p>\n<blockquote>\n<p>Given a list, determine if three numbers in that list can be added or subtracted to give 0? </p>\n</blockquote>\n<p>This is a satisfaction problem, not a constraint problem: we don't need the \"best answer\", any answer will do. We eventually decided against it for being too tricky for the engineers we were targeting. But it's not tricky in a solver; </p>\n<div class=\"codehilite\"><pre><span></span><code>include \"globals.mzn\";\narray[int] of int: numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8];\narray[index_set(numbers)] of var {0, -1, 1}: choices;\n\nconstraint sum(n in index_set(numbers)) (numbers[n] * choices[n]) = 0;\nconstraint count(choices, -1) + count(choices, 1) = 3;\nsolve satisfy;\n</code></pre></div>\n<p>Okay, one last one, a problem I saw last year at <a href=\"https://chicagopython.github.io/algosig/\" target=\"_blank\">Chipy AlgoSIG</a>. Basically they pick some leetcode problems and we all do them. I failed to solve <a href=\"https://leetcode.com/problems/largest-rectangle-in-histogram/description/\" target=\"_blank\">this one</a>:</p>\n<blockquote>\n<p>Given an array of integers heights representing the histogram's bar height where the width of each bar is 1, return the area of the largest rectangle in the histogram.</p>\n<p><img alt=\"example from leetcode link\" class=\"newsletter-image\" src=\"https://assets.buttondown.email/images/63337f78-7138-4b21-87a0-917c0c5b1706.jpg?w=960&fit=max\"/></p>\n</blockquote>\n<p>The \"proper\" solution is a tricky thing involving tracking lots of bookkeeping states, which you can completely bypass by expressing it as constraints:</p>\n<div class=\"codehilite\"><pre><span></span><code>array[int] of int: numbers = [2,1,5,6,2,3];\n\nvar 1..length(numbers): x; \nvar 1..length(numbers): dx;\nvar 1..: y;\n\nconstraint x + dx <= length(numbers);\nconstraint forall (i in x..(x+dx)) (y <= numbers[i]);\n\nvar int: area = (dx+1)*y;\nsolve maximize area;\n\noutput [\"(\\(x)->\\(x+dx))*\\(y) = \\(area)\"]\n</code></pre></div>\n<p>There's even a way to <a href=\"https://docs.minizinc.dev/en/2.9.3/visualisation.html\" target=\"_blank\">automatically visualize the solution</a> (using <code>vis_geost_2d</code>), but I didn't feel like figuring it out in time for the newsletter.</p>\n<h3>Is this better?</h3>\n<p>Now if I actually brought these questions to an interview the interviewee could ruin my day by asking \"what's the runtime complexity?\" Constraint solvers runtimes are unpredictable and almost always slower than an ideal bespoke algorithm because they are more expressive, in what I refer to as the <a href=\"https://buttondown.com/hillelwayne/archive/the-capability-tractability-tradeoff/\" target=\"_blank\">capability/tractability tradeoff</a>. But even so, they'll do way better than a <em>bad</em> bespoke algorithm, and I'm not experienced enough in handwriting algorithms to consistently beat a solver.</p>\n<p>The real advantage of solvers, though, is how well they handle new constraints. Take the stock picking problem above. I can write an O(n²) algorithm in a few minutes and the O(n) algorithm if you give me some time to think. Now change the problem to</p>\n<blockquote>\n<p>Maximize the profit by buying and selling up to <code>max_sales</code> stocks, but you can only buy or sell one stock at a given time and you can only hold up to <code>max_hold</code> stocks at a time?</p>\n</blockquote>\n<p>That's a way harder problem to write even an inefficient algorithm for! While the constraint problem is only a tiny bit more complicated:</p>\n<div class=\"codehilite\"><pre><span></span><code>include \"globals.mzn\";\nint: max_sales = 3;\nint: max_hold = 2;\narray[int] of int: prices = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8];\narray [1..max_sales] of var int: buy;\narray [1..max_sales] of var int: sell;\narray [index_set(prices)] of var 0..max_hold: stocks_held;\nvar int: profit = sum(s in 1..max_sales) (prices[sell[s]] - prices[buy[s]]);\n\nconstraint forall (s in 1..max_sales) (sell[s] > buy[s]);\nconstraint profit > 0;\n\nconstraint forall(i in index_set(prices)) (stocks_held[i] = (count(s in 1..max_sales) (buy[s] <= i) - count(s in 1..max_sales) (sell[s] <= i)));\nconstraint alldifferent(buy ++ sell);\nsolve maximize profit;\n\noutput [\"buy at \\(buy)\\n\", \"sell at \\(sell)\\n\", \"for \\(profit)\"];\n</code></pre></div>\n<p class=\"empty-line\" style=\"height:16px; margin:0px !important;\"></p>\n<p>Most constraint solving examples online are puzzles, like <a href=\"https://docs.minizinc.dev/en/stable/modelling2.html#ex-sudoku\" target=\"_blank\">Sudoku</a> or \"<a href=\"https://docs.minizinc.dev/en/stable/modelling2.html#ex-smm\" target=\"_blank\">SEND + MORE = MONEY</a>\". Solving leetcode problems would be a more interesting demonstration. And you get more interesting opportunities to teach optimizations, like symmetry breaking.</p>\n<hr/>\n<h3>Update for the Internet</h3>\n<p>This was sent as a weekly newsletter, which is usually on topics like <a href=\"https://buttondown.com/hillelwayne/archive/why-do-we-call-it-boilerplate-code\" target=\"_blank\">software history</a>, <a href=\"https://buttondown.com/hillelwayne/archive/the-seven-specification-ur-languages/\" target=\"_blank\">formal methods</a>, <a href=\"https://buttondown.com/hillelwayne/archive/i-formally-modeled-dreidel-for-no-good-reason/\" target=\"_blank\">unusual technologies</a>, and the <a href=\"https://buttondown.com/hillelwayne/archive/be-suspicious-of-success/\" target=\"_blank\">theory of software engineering</a>. You can subscribe here: </p>\n<div class=\"subscribe-form\"></div>\n<div class=\"footnote\">\n<hr/>\n<ol>\n<li id=\"fn:leetcode\">\n<p>Because my dad will email me if I don't explain this: \"leetcode\" is slang for \"tricky algorithmic interview questions that have little-to-no relevance in the actual job you're interviewing for.\" It's from <a href=\"https://leetcode.com/\" target=\"_blank\">leetcode.com</a>. <a class=\"footnote-backref\" href=\"#fnref:leetcode\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/many-hard-leetcode-problems-are-easy-constraint/",
          "published": "2025-09-10T13:00:00.000Z",
          "updated": "2025-09-10T13:00:00.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        },
        {
          "id": "https://buttondown.com/hillelwayne/archive/the-angels-and-demons-of-nondeterminism/",
          "title": "The Angels and Demons of Nondeterminism",
          "description": "<p>Greetings everyone! You might have noticed that it's September and I don't have the next version of <em>Logic for Programmers</em> ready. As penance, <a href=\"https://leanpub.com/logic/c/september-2025-kuBCrhBnUzb7\" target=\"_blank\">here's ten free copies of the book</a>.</p>\n<p>So a few months ago I wrote <a href=\"https://buttondown.com/hillelwayne/archive/five-kinds-of-nondeterminism/\" target=\"_blank\">a newsletter</a> about how we use nondeterminism in formal methods.  The overarching idea:</p>\n<ol>\n<li>Nondeterminism is when multiple paths are possible from a starting state.</li>\n<li>A system preserves a property if it holds on <em>all</em> possible paths. If even one path violates the property, then we have a bug.</li>\n</ol>\n<p>An intuitive model of this is that for this is that when faced with a nondeterministic choice, the system always makes the <em>worst possible choice</em>. This is sometimes called <strong>demonic nondeterminism</strong> and is favored in formal methods because we are paranoid to a fault.</p>\n<p>The opposite would be <strong>angelic nondeterminism</strong>, where the system always makes the <em>best possible choice</em>. A property then holds if <em>any</em> possible path satisfies that property.<sup id=\"fnref:duals\"><a class=\"footnote-ref\" href=\"#fn:duals\">1</a></sup> This is not as common in FM, but it still has its uses! \"Players can access the secret level\" or \"<a href=\"https://www.hillelwayne.com/post/safety-and-liveness/#other-properties\" target=\"_blank\">We can always shut down the computer</a>\" are <strong>reachability</strong> properties, that something is possible even if not actually done.</p>\n<p>In broader computer science research, I'd say that angelic nondeterminism is more popular, due to its widespread use in complexity analysis and programming languages.</p>\n<h3>Complexity Analysis</h3>\n<p>P is the set of all \"decision problems\" (<em>basically</em>, boolean functions) can be solved in polynomial time: there's an algorithm that's worst-case in <code>O(n)</code>, <code>O(n²)</code>, <code>O(n³)</code>, etc.<sup id=\"fnref:big-o\"><a class=\"footnote-ref\" href=\"#fn:big-o\">2</a></sup>  NP is the set of all problems that can be solved in polynomial time by an algorithm with <em>angelic nondeterminism</em>.<sup id=\"fnref:TM\"><a class=\"footnote-ref\" href=\"#fn:TM\">3</a></sup> For example, the question \"does list <code>l</code> contain <code>x</code>\" can be solved in O(1) time by a nondeterministic algorithm:</p>\n<div class=\"codehilite\"><pre><span></span><code>fun is_member(l: List[T], x: T): bool {\n  if l == [] {return false};\n\n  guess i in 0..<(len(l)-1);\n  return l[i] == x;\n}\n</code></pre></div>\n<p>Say call <code>is_member([a, b, c, d], c)</code>. The best possible choice would be to guess <code>i = 2</code>, which would correctly return true. Now call <code>is_member([a, b], d)</code>. No matter what we guess, the algorithm correctly returns false. and just return false. Ergo, O(1). NP stands for \"Nondeterministic Polynomial\". </p>\n<p>(And I just now realized something pretty cool: you can say that P is the set of all problems solvable in polynomial time under <em>demonic nondeterminism</em>, which is a nice parallel between the two classes.)</p>\n<p>Computer scientists have proven that angelic nondeterminism doesn't give us any more \"power\": there are no problems solvable with AN that aren't also solvable deterministically. The big question is whether AN is more <em>efficient</em>: it is widely believed, but not <em>proven</em>, that there are problems in NP but not in P. Most famously, \"Is there any variable assignment that makes this boolean formula true?\" A polynomial AN algorithm is again easy:</p>\n<div class=\"codehilite\"><pre><span></span><code>fun SAT(f(x1, x2, …: bool): bool): bool {\n   N = num_params(f)\n   for i in 1..=num_params(f) {\n     guess x_i in {true, false}\n   }\n\n   return f(x_1, x_2, …)\n}\n</code></pre></div>\n<p>The best deterministic algorithms we have to solve the same problem are worst-case exponential with the number of boolean parameters. This a real frustrating problem because real computers don't have angelic nondeterminism, so problems like SAT remain hard. We can solve most \"well-behaved\" instances of the problem <a href=\"https://www.hillelwayne.com/post/np-hard/\" target=\"_blank\">in reasonable time</a>, but the worst-case instances get intractable real fast.</p>\n<h3>Means of Abstraction</h3>\n<div class=\"subscribe-form\"></div>\n<p>We can directly turn an AN algorithm into a (possibly much slower) deterministic algorithm, such as by <a href=\"https://en.wikipedia.org/wiki/Backtracking\" target=\"_blank\">backtracking</a>. This makes AN a pretty good abstraction over what an algorithm is doing. Does the regex <code>(a+b)\\1+</code> match \"abaabaabaab\"? Yes, if the regex engine nondeterministically guesses that it needs to start at the third letter and make the group <code>aab</code>. How does my PL's regex implementation find that match? I dunno, backtracking or <a href=\"https://swtch.com/~rsc/regexp/regexp1.html\" target=\"_blank\">NFA construction</a> or something, I don't need to know the deterministic specifics in order to use the nondeterministic abstraction.</p>\n<p>Neel Krishnaswami has <a href=\"https://semantic-domain.blogspot.com/2013/07/what-declarative-languages-are.html\" target=\"_blank\">a great definition of 'declarative language'</a>: \"any language with a semantics has some nontrivial existential quantifiers in it\". I'm not sure if this is <em>identical</em> to saying \"a language with an angelic nondeterministic abstraction\", but they must be pretty close, and all of his examples match:</p>\n<ul>\n<li>SQL's selects and joins</li>\n<li>Parsing DSLs</li>\n<li>Logic programming's unification</li>\n<li>Constraint solving</li>\n</ul>\n<p>On top of that I'd add CSS selectors and <a href=\"https://www.hillelwayne.com/post/picat/\" target=\"_blank\">planner's actions</a>; all nondeterministic abstractions over a deterministic implementation. He also says that the things programmers hate most in declarative languages are features that \"that expose the operational model\": constraint solver search strategies, Prolog cuts, regex backreferences, etc. Which again matches my experiences with angelic nondeterminism: I dread features that force me to understand the deterministic implementation. But they're necessary, since P probably != NP and so we need to worry about operational optimizations.</p>\n<h3>Eldritch Nondeterminism</h3>\n<p>If you need to know the <a href=\"https://en.wikipedia.org/wiki/PP_(complexity)\" target=\"_blank\">ratio of good/bad paths</a>, <a href=\"https://en.wikipedia.org/wiki/%E2%99%AFP\" target=\"_blank\">the number of good paths</a>, or probability, or anything more than \"there is a good path\" or \"there is a bad path\", you are beyond the reach of heaven or hell.</p>\n<div class=\"footnote\">\n<hr/>\n<ol>\n<li id=\"fn:duals\">\n<p>Angelic and demonic nondeterminism are <a href=\"https://buttondown.com/hillelwayne/archive/logical-duals-in-software-engineering/\" target=\"_blank\">duals</a>: angelic returns \"yes\" if <code>some choice: correct</code> and demonic returns \"no\" if <code>!all choice: correct</code>, which is the same as <code>some choice: !correct</code>. <a class=\"footnote-backref\" href=\"#fnref:duals\" title=\"Jump back to footnote 1 in the text\">↩</a></p>\n</li>\n<li id=\"fn:big-o\">\n<p>Pet peeve about Big-O notation: <code>O(n²)</code> is the <em>set</em> of all algorithms that, for sufficiently large problem sizes, grow no faster that quadratically. \"Bubblesort has <code>O(n²)</code> complexity\" <em>should</em> be written <code>Bubblesort in O(n²)</code>, <em>not</em> <code>Bubblesort = O(n²)</code>. <a class=\"footnote-backref\" href=\"#fnref:big-o\" title=\"Jump back to footnote 2 in the text\">↩</a></p>\n</li>\n<li id=\"fn:TM\">\n<p>To be precise, solvable in polynomial time by a <em>Nondeterministic Turing Machine</em>, a very particular model of computation. We can broadly talk about P and NP without framing everything in terms of Turing machines, but some details of complexity classes (like the existence \"weak NP-hardness\") kinda need Turing machines to make sense. <a class=\"footnote-backref\" href=\"#fnref:TM\" title=\"Jump back to footnote 3 in the text\">↩</a></p>\n</li>\n</ol>\n</div>",
          "url": "https://buttondown.com/hillelwayne/archive/the-angels-and-demons-of-nondeterminism/",
          "published": "2025-09-04T14:00:00.000Z",
          "updated": "2025-09-04T14:00:00.000Z",
          "content": null,
          "image": null,
          "media": [],
          "authors": [],
          "categories": []
        }
      ]
    }
    Analyze Another View with RSS.Style