<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Steno &amp; PL</title>
    <description>This is a personal blog. Unless otherwise stated, the opinions
expressed here are my own, and not those of my past or present
employers.</description>
    <link>https://blog.waleedkhan.name/</link>
    <atom:link href="https://blog.waleedkhan.name/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Fri, 03 Apr 2026 23:57:14 +0000</pubDate>
    <lastBuildDate>Fri, 03 Apr 2026 23:57:14 +0000</lastBuildDate>
    <generator>Jekyll v4.3.4</generator>
    
      <item>
        <title>🙏: please or thank you?</title>
        <description>&lt;div class=&quot;publication-notes&quot;&gt;
  &lt;table&gt;
    &lt;tr&gt;
      &lt;td&gt;Intended audience&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Linguistics enthusiasts.&lt;/li&gt;
        &lt;li&gt;Autistic individuals working in a professional setting with access to emoji.&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Occasion&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;&lt;a href=&quot;https://www.aprilcools.club/&quot;&gt;April Cools&apos;&lt;/a&gt; 2026 — late as usual!&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Origin&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Original research from &lt;time datetime=&quot;2025-02-28&quot;&gt;2025-02-28&lt;/time&gt;&lt;/li&gt;
        &lt;li&gt;Informal surveys from a few of my group chats.&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mood&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Curious.&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/div&gt;

&lt;div class=&quot;series-and-toc&quot;&gt;

  &lt;p class=&quot;toc-header&quot;&gt;Table of contents:&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#question&quot; id=&quot;markdown-toc-question&quot;&gt;Question&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#results&quot; id=&quot;markdown-toc-results&quot;&gt;Results&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#responses&quot; id=&quot;markdown-toc-responses&quot;&gt;Responses&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#chat-a-n--3&quot; id=&quot;markdown-toc-chat-a-n--3&quot;&gt;Chat A (n = 3)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#chat-b-n--4&quot; id=&quot;markdown-toc-chat-b-n--4&quot;&gt;Chat B (n = 4)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#chat-c-n--2&quot; id=&quot;markdown-toc-chat-c-n--2&quot;&gt;Chat C (n = 2)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#chat-d-n--2&quot; id=&quot;markdown-toc-chat-d-n--2&quot;&gt;Chat D (n = 2)&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#related-posts&quot; id=&quot;markdown-toc-related-posts&quot;&gt;Related posts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#comments&quot; id=&quot;markdown-toc-comments&quot;&gt;Comments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;p&gt;At work, I started using the &lt;a href=&quot;https://en.wiktionary.org/wiki/%F0%9F%99%8F&quot;&gt;🙏 emoji&lt;/a&gt; to reply to Slack messages. Then I realized that it might have different meanings about&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;either the &lt;em&gt;past&lt;/em&gt;: expressing thanks, appreciation, or sympathy,&lt;/li&gt;
  &lt;li&gt;or the &lt;em&gt;future&lt;/em&gt;: expressing a hope, wish, or request,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and maybe not everyone uses it the same way that I do.&lt;/p&gt;

&lt;p&gt;It would be unfortunate if someone thought I was expressing gratitude for their situation when I meant to express sympathy!&lt;/p&gt;

&lt;h2 id=&quot;question&quot;&gt;Question&lt;/h2&gt;

&lt;p&gt;I surveyed various of my group chats with the following poll:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Which of the following situations do you use 🙏 emoji for?&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Indicate request (“please”)&lt;/li&gt;
    &lt;li&gt;Indicate gratitude (“thanks”)&lt;/li&gt;
    &lt;li&gt;Indicate hope for a future event&lt;/li&gt;
    &lt;li&gt;Indicate sympathy or commiseration for a past event&lt;/li&gt;
    &lt;li&gt;Other (add your own option)&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;results&quot;&gt;Results&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Situation&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;A1&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;A2&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;A3&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;B1&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;B2&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;B3&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;B4&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;C1&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;C2&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;D1&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Total&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Indicate request (“please”)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;❌&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;❌&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;8&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Indicate gratitude (“thanks”)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;❌&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;❌&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;8&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Indicate hope for a future event&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;❌&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;❌&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;8&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Indicate sympathy or commiseration for a past event&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;❌&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;❌&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;✅&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;❌&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;❌&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;❌&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;❌&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;❌&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;p&gt;Some take-aways:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;More commonly: request/gratitude/hope were equally (!) common.&lt;/li&gt;
  &lt;li&gt;Less commonly: some people use it to indicate sympathy/commiseration for a past event, but one person even expressed explicit confusion about this meaning.&lt;/li&gt;
  &lt;li&gt;Less commonly: it can have connotations of prayer. (Note that the official description is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PERSON WITH FOLDED HANDS&lt;/code&gt;, which doesn’t specifically indicate prayer.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This suggests that my original concern of expressing gratitude instead of sympathy is not unjustified.&lt;/p&gt;

&lt;h2 id=&quot;responses&quot;&gt;Responses&lt;/h2&gt;

&lt;h3 id=&quot;chat-a-n--3&quot;&gt;Chat A (n = 3)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Indicate request (“please”) — A1, A2&lt;/li&gt;
  &lt;li&gt;Indicate gratitude (“thanks”) — A1, A2, A3&lt;/li&gt;
  &lt;li&gt;Indicate hope for a future event — A1, A2, A3&lt;/li&gt;
  &lt;li&gt;Indicate sympathy or commiseration for a past event — A2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A1:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;[…] I use it for the first three&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A2:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I don’t use it but if I did those all seem like situations where it’s applicable&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A3:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Sometimes thanks (mostly I see it used by others and I don’t find it cringe)&lt;/p&gt;

  &lt;p&gt;If you mean it as please, it might come off as over-emphatic/passive aggressive&lt;/p&gt;

  &lt;p&gt;Hope for a future event (like “I pray”) - in a somewhat humorous context of a group chat it is entirely appropriate&lt;/p&gt;

  &lt;p&gt;I don’t get how it fits the sympathy/commiseration context&lt;/p&gt;

  &lt;p&gt;Since it has a praying connotation and because it also looks like a gesture used more by some cultures than others, I try to avoid it myself for fear of seeming insensitive/being misunderstood&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;chat-b-n--4&quot;&gt;Chat B (n = 4)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Indicate request (“please”) — B1, B2, B3.&lt;/li&gt;
  &lt;li&gt;Indicate gratitude (“thanks”) — B1, B3.&lt;/li&gt;
  &lt;li&gt;Indicate hope for a future event — B2, B4.&lt;/li&gt;
  &lt;li&gt;Indicate sympathy or commiseration for a past event — B1, B2&lt;/li&gt;
  &lt;li&gt;➕ Replacement for “care” emoji where there isn’t one — B2&lt;/li&gt;
  &lt;li&gt;➕ Praying — B4.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;B3:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I’d use 🤞 for hope&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;chat-c-n--2&quot;&gt;Chat C (n = 2)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Indicate request (“please”) — C1, C2&lt;/li&gt;
  &lt;li&gt;Indicate gratitude (“thanks”) — C1, C2&lt;/li&gt;
  &lt;li&gt;Indicate hope for a future event — C1, C2&lt;/li&gt;
  &lt;li&gt;Indicate sympathy or commiseration for a past event — (none)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;chat-d-n--2&quot;&gt;Chat D (n = 2)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Indicate request (“please”) — D1&lt;/li&gt;
  &lt;li&gt;Indicate gratitude (“thanks”) — D1&lt;/li&gt;
  &lt;li&gt;Indicate hope for a future event — D1, D2&lt;/li&gt;
  &lt;li&gt;Indicate sympathy or commiseration for a past event — (none)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;D2:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I use it as „praying for you” […] Like if someone is having trouble with something&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;related-posts&quot;&gt;Related posts&lt;/h2&gt;

&lt;p&gt;The following are hand-curated posts which you might find interesting.&lt;/p&gt;

&lt;table class=&quot;related-posts&quot;&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th&gt;Date&lt;/th&gt;
    &lt;th&gt;&lt;/th&gt;
    &lt;th&gt;Title&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;


  &lt;tr&gt;
    &lt;td&gt;24&amp;nbsp;Aug&amp;nbsp;2016&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/steno-journal/&quot;&gt;Steno Journal: Weeks 1-12&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;06&amp;nbsp;Sep&amp;nbsp;2016&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/steno-adventures-part-1/&quot;&gt;Stenography adventures with Plover and the Ergodox EZ, part 1&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;06&amp;nbsp;Sep&amp;nbsp;2016&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/steno-adventures-part-2/&quot;&gt;Stenography adventures with Plover and the Ergodox EZ, part 2&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;13&amp;nbsp;Oct&amp;nbsp;2016&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/messenger-conversation-macros/&quot;&gt;Analyzing all of my Messenger conversations to create conversational macros&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;21&amp;nbsp;May&amp;nbsp;2018&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/my-steno-system/&quot;&gt;My steno system&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;02&amp;nbsp;Jan&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/on-bullet-points/&quot;&gt;On bullet points&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;11&amp;nbsp;Jan&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/interactive-blogs/&quot;&gt;Interactive blogs&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;05&amp;nbsp;Apr&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/language-learning-anecdotes/&quot;&gt;Language-learning anecdotes&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;03&amp;nbsp;Apr&amp;nbsp;2026&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      (this&amp;nbsp;post)
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/please-or-thank-you/&quot;&gt;🙏: please or thank you?&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Want to see more of my posts? Follow me &lt;a href=&quot;https://twitter.com/arxanas&quot;&gt;on Twitter&lt;/a&gt; or subscribe &lt;a href=&quot;/feed.xml&quot;&gt;via RSS&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;comments&quot;&gt;Comments&lt;/h2&gt;

&lt;ul&gt;



&lt;/ul&gt;

&lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/github-comment-links.js&quot;&gt;&lt;/script&gt;

&lt;div id=&quot;disqus_thread&quot;&gt;&lt;/div&gt;
&lt;script&gt;

/**
 *  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
 *  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables */
var disqus_config = function () {
    this.page.url = &quot;https://blog.waleedkhan.name/please-or-thank-you/&quot;;
    this.page.identifier = &quot;please-or-thank-you/&quot;;
};

(function() { // DON&apos;T EDIT BELOW THIS LINE
    var d = document, s = d.createElement(&apos;script&apos;);
    s.src = &apos;//waleedkhan-name.disqus.com/embed.js&apos;;
    s.setAttribute(&apos;data-timestamp&apos;, +new Date());
    (d.head || d.body).appendChild(s);
})();
&lt;/script&gt;

&lt;noscript&gt;Please enable JavaScript to view the &lt;a href=&quot;https://disqus.com/?ref_noscript&quot;&gt;comments powered by Disqus.&lt;/a&gt;&lt;/noscript&gt;

</description>
        <pubDate>Fri, 03 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://blog.waleedkhan.name/please-or-thank-you/</link>
        <guid isPermaLink="true">https://blog.waleedkhan.name/please-or-thank-you/</guid>
        
        <category>linguistics</category>
        
        
      </item>
    
      <item>
        <title>Datalog DSL detects defective dependency declarations, defanging dodgy development discipline</title>
        <description>&lt;div class=&quot;publication-notes&quot;&gt;
  &lt;table&gt;
    &lt;tr&gt;
      &lt;td&gt;Intended audience&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Software engineers interested in logic programming or static analysis, and actually using it in production.&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Origin&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Originally presented at &lt;a href=&quot;https://groups.io/g/Seattle-Build-Enthusiasts/topic/115859647&quot;&gt;Seattle Build Enthusiasts Meetup (Autumn 2025)&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;Frustration at work due to not having a hermetic build system.&lt;/li&gt;
        &lt;li&gt;Prior experience with static analysis techniques.&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mood&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Excited to share a useful tool with like-minded colleagues.&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/div&gt;

&lt;div class=&quot;series-and-toc&quot;&gt;

  &lt;p class=&quot;toc-header&quot;&gt;Table of contents:&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#slides&quot; id=&quot;markdown-toc-slides&quot;&gt;Slides&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#speaker-notes&quot; id=&quot;markdown-toc-speaker-notes&quot;&gt;Speaker notes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#related-posts&quot; id=&quot;markdown-toc-related-posts&quot;&gt;Related posts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#comments&quot; id=&quot;markdown-toc-comments&quot;&gt;Comments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;h2 id=&quot;slides&quot;&gt;Slides&lt;/h2&gt;

&lt;p&gt;This talk was given at the &lt;a href=&quot;https://groups.io/g/Seattle-Build-Enthusiasts/topic/115859647&quot;&gt;Seattle Build Enthusiasts Autumn meetup&lt;/a&gt; (&lt;time datetime=&quot;2025-10-30&quot;&gt;2025-10-30&lt;/time&gt;).&lt;/p&gt;

&lt;div class=&quot;note-block &quot;&gt;
          &lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;Main goal:&lt;/span&gt; Show some use-cases where Datalog shines, and try to convince the audience to use it.&lt;/p&gt;

        &lt;/div&gt;

&lt;p&gt;Resources:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.google.com/presentation/d/17LmyLkBt_RtLBhKpOY7IFoun9V2rRIsJTD4fXleagBc/edit?usp=sharing&quot;&gt;Direct Google Slides link&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://s-arash.github.io/ascent/&quot;&gt;Ascent homepage&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;I’ve included rough speaker notes below, and also transcribed a few of the interesting Ascent code snippets directly inline.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;iframe-container&quot;&gt;
&lt;iframe src=&quot;https://docs.google.com/presentation/d/e/2PACX-1vRNY7x7D2H0WO_3eKszgJtmj6_2I4Dx6ZvMtJabSb81DKLLCNrWvqi2DLtq0N6aB1PpqtFkqYtQYoX3/pubembed?start=false&amp;amp;loop=false&amp;amp;delayms=3000&quot; frameborder=&quot;0&quot; width=&quot;960&quot; height=&quot;569&quot; allowfullscreen=&quot;true&quot; mozallowfullscreen=&quot;true&quot; webkitallowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&quot;speaker-notes&quot;&gt;Speaker notes&lt;/h2&gt;

&lt;p&gt;(1) Hi everyone, my name is Waleed Khan. I work at my company not on the Bazel team. Therefore, I’m going to show you how not to migrate Bazel.&lt;/p&gt;

&lt;p&gt;Instead, I’m going to try to sell you on a cool tool, which is a library for Datalog, to rapidly build certain kinds of analyses, especially graph-oriented ones. And I’ll show you how I used it in our repository to statically detect entire classes of misconfigured CI jobs, and fix them before they broke the build.&lt;/p&gt;

&lt;p&gt;The context for this is that, recently, we had a “Quality Week” at work. I took the initiative to enable a few rules for ESLint in our monorepo. (If you’re not familiar, ESLint is a linter for JavaScript and Typescript).&lt;/p&gt;

&lt;p&gt;I just turned on the rules in the global config file, opened a pull request, and saw a bunch of new lint violations in CI, as expected. Then I either fixed or suppressed each of them, got CI green, and finally committed my change.&lt;/p&gt;

&lt;p&gt;Naturally, this immediately broke a bunch of builds. That’s because our CI tries to run only relevant jobs, and, in this case, it was wrong about which ones were “relevant”.&lt;/p&gt;

&lt;p&gt;(2) How could this possibly happen? Well, here’s what our CI job definitions look like. There’s a set of globs that match changed files, and they trigger the corresponding build and test commands.&lt;/p&gt;

&lt;p&gt;As Build System Enthusiasts, you’ll probably spot two problems here.&lt;/p&gt;

&lt;p&gt;The first one is that this is a hellish custom YAML-based configuration language, which is its own entirely separate issue.&lt;/p&gt;

&lt;p&gt;The second is that there is no guarantee that this set of globs is necessary or sufficient to trigger the associated command. Notably, if my/package has a dependency on some other directory in the codebase, then changes to that code won’t automatically cause this job to trigger.&lt;/p&gt;

&lt;p&gt;(3) So what can we do about it?&lt;/p&gt;

&lt;p&gt;The best option is probably to use a hermetic build system like Bazel. Then this kind of stuff just can’t happen.&lt;/p&gt;

&lt;p&gt;And I specifically asked Dan if he could please spend a week or two on it, and just quickly migrate the entire codebase to Bazel, but I guess he’s busy with something else, because it kind of looks like the codebase has not yet been migrated to Bazel? I’m sure he’ll get around to it.&lt;/p&gt;

&lt;p&gt;So what can I do in the meantime to kind of stop the bleeding, as a kind of band-aid solution?&lt;/p&gt;

&lt;p&gt;Well, If we look back at this job definition, we could probably make a guess about the necessary dependencies here. Like, if the script is called “lint”, then it probably depends on linting configuration files.&lt;/p&gt;

&lt;p&gt;And you know what’s really good at making unsubstantiated guesses? That’s right. AI. But I didn’t want to wade through a bunch of noisy, non-deterministic AI output to flag the real issues.&lt;/p&gt;

&lt;p&gt;So, instead, I turned to a different approach, which is: AI. By which I mean “abstract interpretation”. We’ll discuss exactly what that means, but it’s basically a form of static analysis.&lt;/p&gt;

&lt;p&gt;(4) Diagram: &lt;a href=&quot;https://excalidraw.com/#json=DVyPsfKEa1RYdy62i5DW9,dVPy7Cm8QLVeEyaUcNuCwQ&quot;&gt;https://excalidraw.com/#json=DVyPsfKEa1RYdy62i5DW9,dVPy7Cm8QLVeEyaUcNuCwQ&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I wrote a static analysis tool using Datalog, and I’ll just show you an example of an actual trace that the tool managed to produce.&lt;/p&gt;

&lt;p&gt;In this case, the CI job calls the npm script called “test” inside the my/package directory. And if we look at the package.json file in that directory, we can see that “test” is defined to call some tools.&lt;/p&gt;

&lt;p&gt;But, misleadingly, it’s not just running tests. It’s also calling “npm run lint”. And if we go look at the “lint” script, then we see, “oh, it’s calling eslint in this directory”.&lt;/p&gt;

&lt;p&gt;Now I know from the documentation that eslint always looks for configuration files named in certain patterns. And if we look for the associated configuration file, we can follow the imports and see that it extends from a different configuration file.&lt;/p&gt;

&lt;p&gt;And that configuration file is not matched by the job’s globs. Notably, it’s not even the configuration file in the root of the monorepo, and I don’t even know why that was the case, but it is what it is.&lt;/p&gt;

&lt;p&gt;The point is that, by a series of simple logical steps, we can automatically conclude that the globs must be missing a pattern to match this parent configuration file.&lt;/p&gt;

&lt;p&gt;(5) So I looked at this and asked myself, “how can I quickly prototype a cross-language analysis that can detect these kinds of issues?” I decided to use Datalog for this.&lt;/p&gt;

&lt;p&gt;Has anyone used Datalog or Prolog before?&lt;/p&gt;

&lt;p&gt;Datalog is basically like: what if SQL didn’t suck? Datalog uses the relation model, like SQL. But it’s much better than SQL, and many standard programming languages, at expressing specific kinds of problems.&lt;/p&gt;

&lt;p&gt;For example, this is how you express transitive closure in Datalog. There’s two “relations”, which are kinds of “facts” that the Datalog engine infers as it runs. In this case, we’re provided a “ground” set of “edges”. And we infer that a node is reachable from another node via some sequence of edges when either:
as the base case, there’s a direct edge between them,
or, as the recursive case, if there’s a transitive sequence of edges between them. That is, if x can reach y, and y is a neighbor of z, then x can reach z.&lt;/p&gt;

&lt;p&gt;The syntax is a little backwards because the conclusion comes before the premises, but you get used to it.&lt;/p&gt;

&lt;p&gt;You can see that the SQL version of this is pretty awful to write in comparison.&lt;/p&gt;

&lt;p&gt;(6) So what kinds of things is Datalog good at? I have a few things listed here.&lt;/p&gt;

&lt;p&gt;Recursive algorithms. That also includes situations where there might be “non-determinism”, in the sense that the algorithm might choose one of many possible futures. Datalog engines can also parallelize this stuff pretty easily.
Graph analysis, including dependency graphs and transitive closures.
Static analysis, including control flow and data flow analysis.
“Rules” engines. When you want to express a bunch of “rules” succinctly and declaratively, and not focus so much on the implementation. Datalog is especially good if you find yourself needing to have multi-directional indexes or “joins”, or if you have rules from several domains that you want to combine, which is what my analysis did.&lt;/p&gt;

&lt;p&gt;(7) I would typically not want to use a domain-specific language unless it granted me an order of magnitude more expressiveness. But Datalog delivers. Here’s an example from a blog post that one of my colleagues wrote, where he was working on some compiler analyses, and I challenged him to write it in Datalog instead.&lt;/p&gt;

&lt;p&gt;So we did, and you can see the result here; the stuff on the left translates to two statements of Datalog. And, actually, that was the cleaned up version, because, originally, we only had one statement that we later broke into two statements for readability.&lt;/p&gt;

&lt;p&gt;(8) So how do we actually use Datalog? I used a library called “Ascent”, which is a Datalog DSL for Rust. The fact that the tool is just a library is pretty important because it means that you don’t have to introduce some weird academic binary into your toolchain.&lt;/p&gt;

&lt;p&gt;It’s also important because you can still just drop into Rust for various things. Like if you want to just use a Vec or a HashMap, you don’t have to fight with the language to get things done.&lt;/p&gt;

&lt;p&gt;You can also use this library to compile to WASM, which is useful if you need to deploy it to other platforms.&lt;/p&gt;

&lt;p&gt;(9) Here’s an excerpt from the original paper, where they implement the core of the Rust borrow checker in 46 lines of code. I, personally, couldn’t describe the Rust borrow checker in 46 lines of prose.&lt;/p&gt;

&lt;p&gt;(10) So here’s a full example. You can see that we populate the initial “edges” relation with this expression using the “edges” parameter. The ascent_run macro will then run the program and return all of the relations, and we just access the “reachable” relation and convert it into a sorted set and return it.&lt;/p&gt;

&lt;p&gt;(11) So, given these edges, the output would look like this. We can see that there’s an entry for (0, 3) because it’s reachable by visiting all the edges.&lt;/p&gt;

&lt;p&gt;(12) Another great feature is that you can parallelize your analysis pretty easily by just adding “par” to your invocation. When you structure your analysis as a Datalog program, it also becomes pretty easy to parallelize it.&lt;/p&gt;

&lt;p&gt;(13) Here’s a simple example which finds any local development services that run Bazel binaries, but which aren’t tested in CI. The “tdep” here stands for “transitive dependency.&lt;/p&gt;

&lt;p&gt;Code listing:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Check: Bazel targets referenced by services that aren&apos;t tested in CI.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;relation&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;service_untested_target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BazelLabel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;service_untested_target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;--&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Service&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BazelTarget&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_ci_job_tdep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(14) GitHub issue: https://github.com/bazelbuild/bazel/issues/27496&lt;/p&gt;

&lt;p&gt;It turns out that, when using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bzlmod&lt;/code&gt;, there’s no way to determine that a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go_library&lt;/code&gt; target depends on the repo’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go.mod&lt;/code&gt;. In that case, we can argument the dependency graph to add this dependency. Note that we’re modifying the existing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dep&lt;/code&gt; relation to add new inferred dependencies.&lt;/p&gt;

&lt;p&gt;Code listing:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Rule: All `go_*` targets depend on `go.mod`. (Actually, there should be&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// more dependencies; I don&apos;t see how to query the bzlmod graph for them, so&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// just hardcode this rule for now.)&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BazelSourceFile&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;PathBuf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;go.mod&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;--&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;bazel_target_is_kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BazelTarget&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.starts_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;go_&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(15) (remark on how we can express the semantics of Bazel ellipsis (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/...&lt;/code&gt;) in just two fairly-straightforward rules: one to pretend that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;...&lt;/code&gt; is a target that depends on its siblings, and one to pretend that subpackages are a type of target)&lt;/p&gt;

&lt;p&gt;Code listing:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Rule: Bazel label &apos;//pkg:...&apos; depends on all sibling &apos;//pkg: target&apos;s.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Note that sibling subpackages are represented as sibling targets, so this&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// recursively includes all subpackage targets.&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;dep&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nn&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BazelTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt;
    &lt;span class=&quot;nn&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BazelTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;——&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BazelTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Rule: For every package (represented as a target named &apos;//:pkg of&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// made-up type &apos;package), create a synthetic target named &apos;//pkg:...&apos;.&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;dep&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;nn&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BazelTarget&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subpackage&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt;
  &lt;span class=&quot;nn&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BazelTarget&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;format!&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{package}/{subpackage}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;--&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BazelTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subpackage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;nn&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BazelTargetKind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;package&quot;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;. to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(16) So getting into the meat of it, here’s an actual rule. This detects the general case of “CI jobs that call Bazel somewhere, but whose globs don’t include all of the Bazel dependencies”.&lt;/p&gt;

&lt;p&gt;It’s a lattice instead of a relation for technical reasons. In this case, it helps us produce the shortest trace, instead of all traces.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;So, if we inferred any transitive dependency path between a CI job, and a file that we know is a source file for a Bazel target, via this trace,&lt;/li&gt;
  &lt;li&gt;and if we successfully processed that CI job, because I didn’t implement everything, so I just left some of them as “unimplemented”,&lt;/li&gt;
  &lt;li&gt;and if we look up the globs for this CI job, and none of them match that source file’s path,&lt;/li&gt;
  &lt;li&gt;then we fetch the glob matcher object, which basically just contains the list of globs so that I can present it to the user later,&lt;/li&gt;
  &lt;li&gt;and we fetch the source file that defines the CI job, which is expressed as a dependency too, because that information was actually resolved in another rule,&lt;/li&gt;
  &lt;li&gt;then we record the result for this job and source file pair (and you can ignore the fact that I store both the node and the path here, that was just for convenience).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code listing:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;lattice&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;bazel_glob_miss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PathBuf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;ascent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Dual&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GlobMiss&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;bazel_glob_miss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;ascent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Dual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;glob_miss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;--&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;ci_tdep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ci_job&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CiJob&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BazelSourceFile&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;ascent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Dual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;trace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ci_job_has_error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ci_job_glob_matches_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ci_job&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;ci_job_glob_matcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ci_job&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;glob_matcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ci_job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SourcePath&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;glob_miss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GlobMiss&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;trace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trace&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;glob_matcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Arc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;glob_matcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(17) (remark that “facts” are just a big enum which I serialize/deserialize with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serde&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;(18) (explain how I just dumped the Bazel dependency graph into a list of “facts”)&lt;/p&gt;

&lt;p&gt;(19) (show how we can handle these 9 data sources in the same system and track cross-language control-/data-flow; remark that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strace&lt;/code&gt; is different from all the rest, since it’s dynamic instrumentation, but it works compositionally anyways)&lt;/p&gt;

&lt;p&gt;(20) Diagram: &lt;a href=&quot;https://excalidraw.com/#json=DVyPsfKEa1RYdy62i5DW9,dVPy7Cm8QLVeEyaUcNuCwQ&quot;&gt;https://excalidraw.com/#json=DVyPsfKEa1RYdy62i5DW9,dVPy7Cm8QLVeEyaUcNuCwQ&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(remark on how the original trace crossed between YAML, shell, package.json, and Javascript languages compositionally)&lt;/p&gt;

&lt;p&gt;(21) (brief explanation of abstract interpretation)&lt;/p&gt;

&lt;p&gt;(22) (explanation of simple shell abstract interpretation:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;simple abstract interpretation: split on statement separators and pretend all commands run in sequence; not correct, but works in practice&lt;/li&gt;
  &lt;li&gt;track calls to cd and set internal state&lt;/li&gt;
  &lt;li&gt;track calls to bazel, npm, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;)&lt;/p&gt;

&lt;p&gt;(23) (snippet of CESK machine from the Ascent paper, and remark that you can basically copy the rules, Greek letters and all)&lt;/p&gt;

&lt;p&gt;(24) (links to resources)&lt;/p&gt;

&lt;h2 id=&quot;related-posts&quot;&gt;Related posts&lt;/h2&gt;

&lt;p&gt;The following are hand-curated posts which you might find interesting.&lt;/p&gt;

&lt;table class=&quot;related-posts&quot;&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th&gt;Date&lt;/th&gt;
    &lt;th&gt;&lt;/th&gt;
    &lt;th&gt;Title&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;


  &lt;tr&gt;
    &lt;td&gt;09&amp;nbsp;Apr&amp;nbsp;2017&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/parsing-contextual-keywords/&quot;&gt;How to parse contextual keywords in a programming language&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;24&amp;nbsp;Jul&amp;nbsp;2017&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/union-vs-sum-types/&quot;&gt;Null-tracking, or the difference between union and sum types&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;23&amp;nbsp;Dec&amp;nbsp;2017&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/data-comprehension-syntaxes/&quot;&gt;Why LINQ syntax differs from SQL, list comprehensions, etc.&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;20&amp;nbsp;Apr&amp;nbsp;2020&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/monotonicity/&quot;&gt;Monotonicity is a halfway point between mutability and immutability&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;14&amp;nbsp;Jun&amp;nbsp;2022&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/functions-are-constants/&quot;&gt;Functions are constants too&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;19&amp;nbsp;Oct&amp;nbsp;2022&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/build-aware-sparse-checkouts/&quot;&gt;Build-aware sparse checkouts&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;17&amp;nbsp;Jun&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/encoding-ml-style-modules-in-rust/&quot;&gt;Encoding ML-style modules in Rust&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;24&amp;nbsp;Aug&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/writing-brittle-code/&quot;&gt;Writing brittle code&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;01&amp;nbsp;Sep&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/on-trivial-changes/&quot;&gt;On trivial changes&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;30&amp;nbsp;Dec&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/testing-tui-apps/&quot;&gt;Testing terminal user interface apps&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;02&amp;nbsp;Oct&amp;nbsp;2024&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/incremental-watchman/&quot;&gt;Incremental processing with Watchman — don&apos;t daemonize that build!&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;14&amp;nbsp;Aug&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/lithe-less-analysis-with-datalog/&quot;&gt;Lithe, less analysis with Datalog&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;23&amp;nbsp;Aug&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/half-my-work-is-adding-a-cache/&quot;&gt;Half my work is adding a cache&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;30&amp;nbsp;Oct&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      (this&amp;nbsp;post)
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/what-if-sql-were-good/&quot;&gt;Datalog DSL detects defective dependency declarations, defanging dodgy development discipline&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Want to see more of my posts? Follow me &lt;a href=&quot;https://twitter.com/arxanas&quot;&gt;on Twitter&lt;/a&gt; or subscribe &lt;a href=&quot;/feed.xml&quot;&gt;via RSS&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;comments&quot;&gt;Comments&lt;/h2&gt;

&lt;ul&gt;



&lt;/ul&gt;

&lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/github-comment-links.js&quot;&gt;&lt;/script&gt;

&lt;div id=&quot;disqus_thread&quot;&gt;&lt;/div&gt;
&lt;script&gt;

/**
 *  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
 *  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables */
var disqus_config = function () {
    this.page.url = &quot;https://blog.waleedkhan.name/what-if-sql-were-good/&quot;;
    this.page.identifier = &quot;what-if-sql-were-good/&quot;;
};

(function() { // DON&apos;T EDIT BELOW THIS LINE
    var d = document, s = d.createElement(&apos;script&apos;);
    s.src = &apos;//waleedkhan-name.disqus.com/embed.js&apos;;
    s.setAttribute(&apos;data-timestamp&apos;, +new Date());
    (d.head || d.body).appendChild(s);
})();
&lt;/script&gt;

&lt;noscript&gt;Please enable JavaScript to view the &lt;a href=&quot;https://disqus.com/?ref_noscript&quot;&gt;comments powered by Disqus.&lt;/a&gt;&lt;/noscript&gt;

</description>
        <pubDate>Thu, 30 Oct 2025 00:00:00 +0000</pubDate>
        <link>https://blog.waleedkhan.name/what-if-sql-were-good/</link>
        <guid isPermaLink="true">https://blog.waleedkhan.name/what-if-sql-were-good/</guid>
        
        <category>bazel</category>
        
        <category>build-systems</category>
        
        <category>programming-languages</category>
        
        <category>reprint</category>
        
        <category>rust</category>
        
        <category>software-engineering</category>
        
        <category>software-verification</category>
        
        
      </item>
    
      <item>
        <title>Half my work is adding a cache</title>
        <description>&lt;div class=&quot;publication-notes&quot;&gt;
  &lt;table&gt;
    &lt;tr&gt;
      &lt;td&gt;Intended audience&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Back-end software engineers, particularly in big tech.&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Origin&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Experience in big tech working on highly-scalable developer tooling.&lt;/li&gt;
        &lt;li&gt;Sophie Alpert&apos;s blog post &lt;i&gt;&lt;a href=&quot;https://sophiebits.com/2025/08/22/materialized-views-are-obviously-useful&quot;&gt;Materialized views are obviously useful&lt;/a&gt;&lt;/i&gt;.&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mood&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Frustrated.&lt;/li&gt;
        &lt;li&gt;Determined that we should do better as an industry.&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/div&gt;

&lt;div class=&quot;series-and-toc&quot;&gt;

  &lt;p class=&quot;toc-header&quot;&gt;Table of contents:&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#related-posts&quot; id=&quot;markdown-toc-related-posts&quot;&gt;Related posts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#comments&quot; id=&quot;markdown-toc-comments&quot;&gt;Comments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;p&gt;When I was a kid, I wrote a &lt;a href=&quot;https://github.com/arxanas/schmacebook&quot;&gt;little social networking site for my school&lt;/a&gt;. Back then, &lt;em&gt;in 2010&lt;/em&gt;, I was surprised to learn that MySQL didn’t seem to support “materialized views”. Of course, I didn’t even know the name for the concept back then; but I understood that the concept should &lt;em&gt;exist&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In her blog post &lt;em&gt;&lt;a href=&quot;https://sophiebits.com/2025/08/22/materialized-views-are-obviously-useful&quot;&gt;Materialized views are obviously useful&lt;/a&gt;&lt;/em&gt;, Sophie Alpert points out that incremental view maintenance is still unreasonably difficult for trivial applications. And &lt;em&gt;it’s 2025&lt;/em&gt;. In those 15 intervening years, we’ve barely accomplished anything as an industry to make incremental computation more tractable for the masses.&lt;/p&gt;

&lt;p&gt;At best, we have &lt;em&gt;&lt;a href=&quot;https://arxiv.org/abs/2203.16684&quot;&gt;DBSP: Automatic Incremental View Maintenance for Rich Query Languages&lt;/a&gt;&lt;/em&gt; (2022), which is a great paper and formalization, but was also probably realizable in 2010. We have a few startups (&lt;a href=&quot;https://materialize.com/&quot;&gt;Materialize&lt;/a&gt;, &lt;a href=&quot;https://www.feldera.com/&quot;&gt;Feldera&lt;/a&gt;) who offer incremental view maintenance as a service. More expansive efforts like &lt;a href=&quot;https://skiplang.com/&quot;&gt;Skiplang&lt;/a&gt; petered out. This is sad.&lt;/p&gt;

&lt;p&gt;I’ve reproduced my &lt;a href=&quot;https://lobste.rs/c/oalnwv&quot;&gt;Lobste.rs comment on the aforementioned blog post&lt;/a&gt; below.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;This has been my thesis for a while:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Half of my work in big tech has just been “adding cache” or “removing a cache” in response to scaling latency/throughput requirements.&lt;/li&gt;
  &lt;li&gt;We absolutely need higher-level primitives for developing incremental systems, particularly in distributed contexts.&lt;/li&gt;
  &lt;li&gt;Somehow, our best alternative at each point has been to just build another ad-hoc, informally specified, bug-ridden, slow implementation of a build system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m excited for efforts like Feldera (DBSP) to help provide&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;concretely, a specific implementation of a “build system as a library”, but also&lt;/li&gt;
  &lt;li&gt;abstractly, a reliable pattern that can be reimplemented across multiple environments where it’s unavailable (kind of like ReactiveX).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I think the main missing piece is that it’s hard to reconcile “persistent state” across many different environments/runtimes in a well-patterned way:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;DBSP gives you a formula for effective incremental computation, but I don’t yet know how hard it is to wire it up to arbitrary data sources for input and output.&lt;/li&gt;
  &lt;li&gt;More specifically, the pattern seems to require that the underlying data source has a linear notion of “time”, which is not how most people have been designing their databases in practice.
    &lt;ul&gt;
      &lt;li&gt;I can’t go take an arbitrary database at work that somebody implemented five years ago and easily ask Postgres for the delta stream.&lt;/li&gt;
      &lt;li&gt;I mean, I’m sure it’s possible, but if it’s not dead simple, then I as a random developer am not going to be equipped to do it.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;related-posts&quot;&gt;Related posts&lt;/h2&gt;

&lt;p&gt;The following are hand-curated posts which you might find interesting.&lt;/p&gt;

&lt;table class=&quot;related-posts&quot;&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th&gt;Date&lt;/th&gt;
    &lt;th&gt;&lt;/th&gt;
    &lt;th&gt;Title&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;


  &lt;tr&gt;
    &lt;td&gt;20&amp;nbsp;Apr&amp;nbsp;2020&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/monotonicity/&quot;&gt;Monotonicity is a halfway point between mutability and immutability&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;19&amp;nbsp;Oct&amp;nbsp;2022&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/build-aware-sparse-checkouts/&quot;&gt;Build-aware sparse checkouts&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;02&amp;nbsp;Oct&amp;nbsp;2024&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/incremental-watchman/&quot;&gt;Incremental processing with Watchman — don&apos;t daemonize that build!&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;14&amp;nbsp;Aug&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/lithe-less-analysis-with-datalog/&quot;&gt;Lithe, less analysis with Datalog&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;23&amp;nbsp;Aug&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      (this&amp;nbsp;post)
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/half-my-work-is-adding-a-cache/&quot;&gt;Half my work is adding a cache&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;30&amp;nbsp;Oct&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/what-if-sql-were-good/&quot;&gt;Datalog DSL detects defective dependency declarations, defanging dodgy development discipline&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Want to see more of my posts? Follow me &lt;a href=&quot;https://twitter.com/arxanas&quot;&gt;on Twitter&lt;/a&gt; or subscribe &lt;a href=&quot;/feed.xml&quot;&gt;via RSS&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;comments&quot;&gt;Comments&lt;/h2&gt;

&lt;ul&gt;



&lt;/ul&gt;

&lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/github-comment-links.js&quot;&gt;&lt;/script&gt;

&lt;div id=&quot;disqus_thread&quot;&gt;&lt;/div&gt;
&lt;script&gt;

/**
 *  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
 *  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables */
var disqus_config = function () {
    this.page.url = &quot;https://blog.waleedkhan.name/half-my-work-is-adding-a-cache/&quot;;
    this.page.identifier = &quot;half-my-work-is-adding-a-cache/&quot;;
};

(function() { // DON&apos;T EDIT BELOW THIS LINE
    var d = document, s = d.createElement(&apos;script&apos;);
    s.src = &apos;//waleedkhan-name.disqus.com/embed.js&apos;;
    s.setAttribute(&apos;data-timestamp&apos;, +new Date());
    (d.head || d.body).appendChild(s);
})();
&lt;/script&gt;

&lt;noscript&gt;Please enable JavaScript to view the &lt;a href=&quot;https://disqus.com/?ref_noscript&quot;&gt;comments powered by Disqus.&lt;/a&gt;&lt;/noscript&gt;

</description>
        <pubDate>Sat, 23 Aug 2025 00:00:00 +0000</pubDate>
        <link>https://blog.waleedkhan.name/half-my-work-is-adding-a-cache/</link>
        <guid isPermaLink="true">https://blog.waleedkhan.name/half-my-work-is-adding-a-cache/</guid>
        
        <category>build-systems</category>
        
        <category>rant</category>
        
        <category>reprint</category>
        
        <category>software-engineering</category>
        
        
      </item>
    
      <item>
        <title>Lithe, less analysis with Datalog</title>
        <description>&lt;div class=&quot;publication-notes&quot;&gt;
  &lt;table&gt;
    &lt;tr&gt;
      &lt;td&gt;Intended audience&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Static analysis enthusiasts&lt;/li&gt;
        &lt;li&gt;Logic programming enthusiasts&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Origin&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Recent experience with &lt;a href=&quot;https://souffle-lang.github.io/&quot;&gt;Soufflé&lt;/a&gt; Datalog.&lt;/li&gt;
        &lt;li&gt;Discussion with Max Bernstein about his blog post &lt;a href=&quot;https://bernsteinbear.com/blog/linear-scan/&quot;&gt;&lt;i&gt;Linear scan register allocation on SSA&lt;/i&gt;&lt;/a&gt;, resulting in his blog post &lt;a href=&quot;https://bernsteinbear.com/blog/liveness-datalog/&quot;&gt;&lt;i&gt;Liveness analysis with Datalog&lt;/i&gt;&lt;/a&gt;.&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mood&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Liberated&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/div&gt;

&lt;div class=&quot;series-and-toc&quot;&gt;

  &lt;p class=&quot;toc-header&quot;&gt;Table of contents:&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#soufflé&quot; id=&quot;markdown-toc-soufflé&quot;&gt;Soufflé&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#rapid-prototyping&quot; id=&quot;markdown-toc-rapid-prototyping&quot;&gt;Rapid prototyping&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-curse-of-brevity&quot; id=&quot;markdown-toc-the-curse-of-brevity&quot;&gt;The curse of brevity&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#related-posts&quot; id=&quot;markdown-toc-related-posts&quot;&gt;Related posts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#comments&quot; id=&quot;markdown-toc-comments&quot;&gt;Comments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;h2 id=&quot;soufflé&quot;&gt;Soufflé&lt;/h2&gt;

&lt;p&gt;I recently experimented with &lt;a href=&quot;https://souffle-lang.github.io/&quot;&gt;Soufflé&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Datalog&quot;&gt;Datalog&lt;/a&gt; for a certain work problem: comparing CI jobs’ declared dependencies with dependencies inferred via abstract interpretation (which is worth a separate post).&lt;/p&gt;

&lt;p&gt;Recently, I saw the blog post &lt;a href=&quot;https://bernsteinbear.com/blog/linear-scan/&quot;&gt;&lt;em&gt;Linear scan register allocation on SSA&lt;/em&gt;&lt;/a&gt; and noted that there was quite a lot of imperative implementation code, to the point where it might obstruct experimentating with the underlying ideas.&lt;/p&gt;

&lt;p&gt;I suggested that the author could use Datalog instead to simplify the analyses, and we worked through the example of rewriting the &lt;a href=&quot;https://en.wikipedia.org/wiki/Live-variable_analysis&quot;&gt;liveness analysis&lt;/a&gt; in Soufflé, resulting in the blog post &lt;a href=&quot;https://bernsteinbear.com/blog/liveness-datalog/&quot;&gt;&lt;em&gt;Liveness analysis with Datalog&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;rapid-prototyping&quot;&gt;Rapid prototyping&lt;/h2&gt;

&lt;p&gt;As per its homepage, Soufflé promises to enable “rapid prototyping”:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Rapid-prototyping for your analysis problems with logic; enabling deep design-space explorations; designed for large-scale static analysis; e.g., points-to analysis for Java, taint-analysis, security checks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And from my recent experience, I found that it delivered!&lt;/p&gt;

&lt;div class=&quot;note-block note-info&quot;&gt;
          &lt;p&gt;&lt;span class=&quot;note-tag note-info&quot;&gt;For example:&lt;/span&gt; When I wanted to implement the rule that, in &lt;a href=&quot;https://bazel.build/&quot;&gt;Bazel&lt;/a&gt;, the target
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foo/...&lt;/code&gt; expands to all targets under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foo&lt;/code&gt;, I just had to add one rule:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-datalog&quot;&gt;BazelLDep(repo, package, &quot;...&quot;, $BazelTarget(repo, package, target)) :-
  BazelLDep(repo, package, target, _),
  target != &quot;...&quot;.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It can be read as:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A target of the form &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;package&amp;gt;/...&lt;/code&gt; has a dependency on every target of the form &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;package&amp;gt;/&amp;lt;target&amp;gt;&lt;/code&gt;, as long as that target is not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;...&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And then this rule automatically combines with all the other rules about dependencies that I’d written in the analysis.&lt;/p&gt;

&lt;p&gt;Who originally created the nodes of the form &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foo/...&lt;/code&gt;? Where are all the places that might consume them? I don’t know! I don’t care! I just let the Datalog engine figure it out.&lt;/p&gt;

        &lt;/div&gt;

&lt;h2 id=&quot;the-curse-of-brevity&quot;&gt;The curse of brevity&lt;/h2&gt;

&lt;p&gt;In that &lt;a href=&quot;https://bernsteinbear.com/blog/liveness-datalog/&quot;&gt;blog post&lt;/a&gt;, the demonstration seems more blithe than lithe, because we end up showcasing basically two lines of Datalog:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-datalog&quot;&gt;live_out(b, v) :- block_succ(s, b), live_in(s, v).
live_in(b, v) :- (live_out(b, v) ; block_use(b, v)), !block_def(b, v).
&lt;/code&gt;&lt;/pre&gt;

&lt;div class=&quot;note-block &quot;&gt;
          &lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;Not even that:&lt;/span&gt; Our original formulation used only a single rule! We split it into two rules for clarity.&lt;/p&gt;

        &lt;/div&gt;

&lt;p&gt;So what’s the big deal? Well, it’s an order-of-magnitude reduction compared to the original code!&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;analyze_liveness&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post_order&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kill&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compute_initial_liveness_sets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;live_in&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;changed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;changed&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;changed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;block_live&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;successors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;succ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;live_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;succ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:|&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;block_live&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;block_live&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;live_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;block_live&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;changed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;live_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;block_live&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;live_in&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The Soufflé &lt;a href=&quot;https://souffle-lang.github.io/examples&quot;&gt;examples page&lt;/a&gt; has the same problem. They look trivial. Does this look like a substantial program to you?&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-datalog&quot;&gt;.decl alias( a:var, b:var )
.output alias
alias(X,X) :- assign(X,_).
alias(X,X) :- assign(_,X).
alias(X,Y) :- assign(X,Y).
alias(X,Y) :- ld(X,A,F), alias(A,B), st(B,F,Y).

.decl pointsTo( a:var, o:obj )
.output pointsTo
pointsTo(X,Y) :- new(X,Y).
pointsTo(X,Y) :- alias(X,Z), pointsTo(Z,Y).
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But when I first read that page, I was blown away — only because I already knew how much code it would take in a traditional programming language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update 2025-08-23&lt;/strong&gt;: I see that Hillel Wayne has discussed this in their blog post &lt;em&gt;&lt;a href=&quot;https://www.hillelwayne.com/post/persuasive-examples/&quot;&gt;Instructive and Persuasive Examples&lt;/a&gt;&lt;/em&gt; (2017). It’s too easy to “brush off” trivial-looking examples of logic programming!&lt;/p&gt;

&lt;h2 id=&quot;related-posts&quot;&gt;Related posts&lt;/h2&gt;

&lt;p&gt;The following are hand-curated posts which you might find interesting.&lt;/p&gt;

&lt;table class=&quot;related-posts&quot;&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th&gt;Date&lt;/th&gt;
    &lt;th&gt;&lt;/th&gt;
    &lt;th&gt;Title&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;


  &lt;tr&gt;
    &lt;td&gt;09&amp;nbsp;Apr&amp;nbsp;2017&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/parsing-contextual-keywords/&quot;&gt;How to parse contextual keywords in a programming language&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;24&amp;nbsp;Jul&amp;nbsp;2017&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/union-vs-sum-types/&quot;&gt;Null-tracking, or the difference between union and sum types&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;23&amp;nbsp;Dec&amp;nbsp;2017&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/data-comprehension-syntaxes/&quot;&gt;Why LINQ syntax differs from SQL, list comprehensions, etc.&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;20&amp;nbsp;Apr&amp;nbsp;2020&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/monotonicity/&quot;&gt;Monotonicity is a halfway point between mutability and immutability&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;14&amp;nbsp;Jun&amp;nbsp;2022&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/functions-are-constants/&quot;&gt;Functions are constants too&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;19&amp;nbsp;Oct&amp;nbsp;2022&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/build-aware-sparse-checkouts/&quot;&gt;Build-aware sparse checkouts&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;17&amp;nbsp;Jun&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/encoding-ml-style-modules-in-rust/&quot;&gt;Encoding ML-style modules in Rust&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;02&amp;nbsp;Oct&amp;nbsp;2024&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/incremental-watchman/&quot;&gt;Incremental processing with Watchman — don&apos;t daemonize that build!&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;14&amp;nbsp;Aug&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      (this&amp;nbsp;post)
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/lithe-less-analysis-with-datalog/&quot;&gt;Lithe, less analysis with Datalog&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;23&amp;nbsp;Aug&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/half-my-work-is-adding-a-cache/&quot;&gt;Half my work is adding a cache&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;30&amp;nbsp;Oct&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/what-if-sql-were-good/&quot;&gt;Datalog DSL detects defective dependency declarations, defanging dodgy development discipline&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Want to see more of my posts? Follow me &lt;a href=&quot;https://twitter.com/arxanas&quot;&gt;on Twitter&lt;/a&gt; or subscribe &lt;a href=&quot;/feed.xml&quot;&gt;via RSS&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;comments&quot;&gt;Comments&lt;/h2&gt;

&lt;ul&gt;



&lt;/ul&gt;

&lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/github-comment-links.js&quot;&gt;&lt;/script&gt;

&lt;div id=&quot;disqus_thread&quot;&gt;&lt;/div&gt;
&lt;script&gt;

/**
 *  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
 *  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables */
var disqus_config = function () {
    this.page.url = &quot;https://blog.waleedkhan.name/lithe-less-analysis-with-datalog/&quot;;
    this.page.identifier = &quot;lithe-less-analysis-with-datalog/&quot;;
};

(function() { // DON&apos;T EDIT BELOW THIS LINE
    var d = document, s = d.createElement(&apos;script&apos;);
    s.src = &apos;//waleedkhan-name.disqus.com/embed.js&apos;;
    s.setAttribute(&apos;data-timestamp&apos;, +new Date());
    (d.head || d.body).appendChild(s);
})();
&lt;/script&gt;

&lt;noscript&gt;Please enable JavaScript to view the &lt;a href=&quot;https://disqus.com/?ref_noscript&quot;&gt;comments powered by Disqus.&lt;/a&gt;&lt;/noscript&gt;

</description>
        <pubDate>Thu, 14 Aug 2025 00:00:00 +0000</pubDate>
        <link>https://blog.waleedkhan.name/lithe-less-analysis-with-datalog/</link>
        <guid isPermaLink="true">https://blog.waleedkhan.name/lithe-less-analysis-with-datalog/</guid>
        
        <category>programming-languages</category>
        
        <category>reprint</category>
        
        <category>software-engineering</category>
        
        
      </item>
    
      <item>
        <title>Searching source control graphs</title>
        <description>&lt;div class=&quot;publication-notes&quot;&gt;
  &lt;table&gt;
    &lt;tr&gt;
      &lt;td&gt;Intended audience&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Build system developers and enthusiasts&lt;/li&gt;
        &lt;li&gt;Source control developers and enthusiasts&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Origin&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Attempts to generalize the library behind &lt;a href=&quot;https://github.com/arxanas/git-branchless/wiki/Command:-git-test&quot;&gt;&lt;code&gt;git test&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;Originally presented at &lt;a href=&quot;https://groups.io/g/Seattle-Build-Enthusiasts/message/12&quot;&gt;Seattle Build Enthusiasts Meetup (Summer 2025)&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mood&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Instructive&lt;/li&gt;
        &lt;li&gt;Enlightened&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/div&gt;

&lt;div class=&quot;series-and-toc&quot;&gt;

  &lt;p class=&quot;toc-header&quot;&gt;Table of contents:&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#slides&quot; id=&quot;markdown-toc-slides&quot;&gt;Slides&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#speaker-notes&quot; id=&quot;markdown-toc-speaker-notes&quot;&gt;Speaker notes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#related-posts&quot; id=&quot;markdown-toc-related-posts&quot;&gt;Related posts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#comments&quot; id=&quot;markdown-toc-comments&quot;&gt;Comments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;h2 id=&quot;slides&quot;&gt;Slides&lt;/h2&gt;

&lt;p&gt;This talk was given at the &lt;a href=&quot;https://groups.io/g/Seattle-Build-Enthusiasts/message/12&quot;&gt;Seattle Build Enthusiasts Summer 2025 meetup&lt;/a&gt; (&lt;time datetime=&quot;2025-07-22&quot;&gt;2025-07-22&lt;/time&gt;).&lt;/p&gt;

&lt;div class=&quot;note-block &quot;&gt;
          &lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;In essence:&lt;/span&gt; The main idea is implementing parallelizable “generalized binary search” via a structure similar to a comonad, although the talk is not presented that way.&lt;/p&gt;

        &lt;/div&gt;

&lt;p&gt;Resources:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Deleted slides with extra information are available at the end.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.google.com/presentation/d/1RRm-H8PwgSLaY5ng1YL2_DmtIPiI41P-SJTL_CKTO3o/edit?usp=sharing&quot;&gt;Direct Google Slides link&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/arxanas/git-branchless/wiki/Command:-git-test&quot;&gt;Documentation for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git test&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;The alluded-to library is &lt;a href=&quot;https://crates.io/crates/scm-bisect&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scm-bisect&lt;/code&gt;&lt;/a&gt;,
    &lt;ul&gt;
      &lt;li&gt;but it’s not production-ready,&lt;/li&gt;
      &lt;li&gt;and needs to be updated with e.g. my work on minimization.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;iframe-container&quot;&gt;
&lt;!-- &lt;iframe src=&quot;https://docs.google.com/presentation/d/e/2PACX-1vRSwp0EbldxIkD72bzEoKDHzLzah95w2ABa1doSwqeKTMneSz1_O1DqNNb7qJCi4xXbPdp1A7Blk5PV/embed?start=false&amp;loop=false&amp;delayms=3000&quot; frameborder=&quot;0&quot; width=&quot;960&quot; height=&quot;569&quot; allowfullscreen=&quot;true&quot; mozallowfullscreen=&quot;true&quot; webkitallowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt; --&gt;
&lt;iframe src=&quot;https://docs.google.com/presentation/d/e/2PACX-1vRzfn2qPvsjZ9gWMHsM7ERN540r-5VPbDsVi4Q4u0CXDY-5wzt9ha3J5927bMOmiflUfoO_sIjccMFj/pubembed?start=false&amp;amp;loop=false&amp;amp;delayms=3000&quot; frameborder=&quot;0&quot; width=&quot;960&quot; height=&quot;569&quot; allowfullscreen=&quot;true&quot; mozallowfullscreen=&quot;true&quot; webkitallowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&quot;speaker-notes&quot;&gt;Speaker notes&lt;/h2&gt;

&lt;p&gt;There are some speaker notes in Google Slides. They didn’t correspond to what I actually said.&lt;/p&gt;

&lt;p&gt;(1–2) Hi, everyone. My name is Waleed Khan. I’m going to be talking about something that I do every day at work, which is:&lt;/p&gt;

&lt;p&gt;(3) Writing buggy code.&lt;/p&gt;

&lt;p&gt;(3) I love it. Can’t get enough of it, and the great thing is, since the last time I was here, my ability to churn out buggy code has increased by an order of magnitude, thanks to the power of AI.&lt;/p&gt;

&lt;p&gt;(4) So, naturally, I use git bisect a lot. For those of you who aren’t familiar: git bisect is a command that lets you basically binary search for the commit that introduced a certain bug.&lt;/p&gt;

&lt;p&gt;(4) Show of hands: how many of you have used git bisect (or equivalent) before?&lt;/p&gt;

&lt;p&gt;(4) And have any of you used the ‘automatic’ mode of git bisect run, where you pass a command, and it runs the entire bisection automatically?&lt;/p&gt;

&lt;p&gt;(5) I love git bisect because we practice trunk-based development at work, so I have these massive stacks of commits that I’m constantly running tests on. But I had to make it faster.&lt;/p&gt;

&lt;p&gt;(6) So I made a tool called git test, which is, confusingly, inspired by Spotify’s tool of the same name. It’s like git bisect, but way cooler. Let’s take a look.&lt;/p&gt;

&lt;p&gt;(7) It’s hard to see, but I’m running git test run with cargo fmt to check the codebase formatting, with 8 jobs, and using binary search.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;It’s running on 46 commits, and it flags 3 failing commits.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;It concludes that the formatting was broken by this buggy commit I inserted into the middle of the stack.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;It also checked three different branches, shown here.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(8) So I kept wanting to add more features. And I realized a few things.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Firstly: I’ve invented my own mini-continuous-integration system.&lt;/li&gt;
  &lt;li&gt;Secondly: Those features look different, but are actually instances of the same problem.&lt;/li&gt;
  &lt;li&gt;Thirdly: I would really love it if my work’s CI had those features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(9) How many of you have at least one system in your organization that can automatically bisect build breakages, test failures, or anything else?&lt;/p&gt;

&lt;p&gt;(9) Now here’s some slightly different questions.&lt;/p&gt;

&lt;p&gt;(9) How many of you have a “merge queue” system to run your continuous integration system on a “pipeline” of commits?&lt;/p&gt;

&lt;p&gt;(9) And does anyone have a system that automatically detects and triages flaky tests?&lt;/p&gt;

&lt;p&gt;(9) And how about for performance regressions?&lt;/p&gt;

&lt;p&gt;(9) Today, I’m going to talk about how to design a library for generalized binary search. My hope is that you can sprinkle some careful abstractions into your CI system and add support for like 400% more new and substantially different workflows with maybe 50% more effort.&lt;/p&gt;

&lt;p&gt;(10) The basic idea behind git bisect, and the search problems we’ll see, is the “monotonic assumption”.&lt;/p&gt;

&lt;p&gt;(11) Formally, it preserves some ordering relation when you apply a predicate. If element X has a certain property, and element Y comes after X, then Y also has that property.&lt;/p&gt;

&lt;p&gt;(11) In practice, this is often true. Thinking of commits, once you commit a bug, it’s usually present in subsequent commits, at least until it’s fixed. So our commit graphs are monotonic with respect to the property of whether a commit has a certain bug.&lt;/p&gt;

&lt;p&gt;(11) And the point of this is basically that you can stop your search early. Binary search relies on this in order to guarantee that it finds the first element with a certain property.&lt;/p&gt;

&lt;p&gt;(12) I have invented a convenient mnemonic to help you remember this: “Once you begin to fail your predicate, to later pass would be poor etiquette.”&lt;/p&gt;

&lt;p&gt;(13–20) (missing: list/discussion of search, minimization, noisy search, and optimization problems that occur in practice)&lt;/p&gt;

&lt;p&gt;(21) When I was in elementary school, I learned binary search as “that thing you do in the phone book”.&lt;/p&gt;

&lt;p&gt;(22) When I got to college, I learned that binary search is an algorithm operating over a sorted array with worst-case logarithmic time complexity.&lt;/p&gt;

&lt;p&gt;(23) After I started working, I learned that binary search is an algorithm operating on monotone predicates over a lattice structure.&lt;/p&gt;

&lt;p&gt;(24) Now that I’m older and wiser, I realize that I was overcomplicating things. Binary search is really as simple as 1-2-3!&lt;/p&gt;

&lt;p&gt;(25) It’s just the problem of “determining a one-dimensional binary-valued threshold function from a finite class of such functions based on queries taking the form of point samples of the function”, what’s the problem?&lt;/p&gt;

&lt;p&gt;(26) Have any of you read Build Systems a la Carte?&lt;/p&gt;

&lt;p&gt;(26) It’s a paper that tries to break down various build systems into a bunch of orthogonal axes, then recombine them to create a new build system with various properties.&lt;/p&gt;

&lt;p&gt;(26) That’s what I’m going to try to do here. We’ll break down binary search into a bunch of components, and recombine them to solve different problems.&lt;/p&gt;

&lt;p&gt;(26) I’m not guaranteeing that this is the perfect design — but hopefully you’ll be able to adapt it to fit your needs.&lt;/p&gt;

&lt;p&gt;(27) As an exercise, let’s start by trying to generalize linear search and binary search and abstract out the common details.&lt;/p&gt;

&lt;p&gt;(27) So here’s linear search.&lt;/p&gt;

&lt;p&gt;(27) And here’s binary search.&lt;/p&gt;

&lt;p&gt;(28) (missing: abstracted search algorithm)&lt;/p&gt;

&lt;p&gt;(29) Does anyone know how Git selects the next commit to test in this case?&lt;/p&gt;

&lt;p&gt;(29) For each commit in the search range, it computes the number of ancestor and non-ancestor commits in that range. It takes the absolute value of the difference, and picks the minimal such commit. That way, it maximizes the information gained in the worst case, just like traditional binary search.&lt;/p&gt;

&lt;p&gt;(29) Actually, Git makes the assumption that there’s exactly one buggy commit in the range. If you don’t make that assumption, then it’s technically better to compute the number of ancestors minus descendants. But it’s approximately the same.&lt;/p&gt;

&lt;p&gt;(30–31) (missing: generalizing bounds to multiple nodes)&lt;/p&gt;

&lt;p&gt;(32) Now, in practice, not every test returns true or false. Sometimes, maybe there’s a compilation error in the codebase, and you can’t run the test at all. In those cases, we want to include an “indeterminate” result.&lt;/p&gt;

&lt;p&gt;(33) For linear search, I think it’s pretty clear how to continue searching when you get an indeterminate result. You just skip over that index and continue.&lt;/p&gt;

&lt;p&gt;(33) But what do we do for binary search? Does anyone know what Git does?&lt;/p&gt;

&lt;p&gt;(33) One reasonable approach is to just remove the commit from the range and pretend it doesn’t exist. The main problem is this heuristic: we’d probably select the parent or child of that commit instead. But there’s a good chance that the build is also broken for that commit. So ideally, we’d pick a commit that’s a little farther away, to maximize the chance that we find a testable commit.&lt;/p&gt;

&lt;p&gt;(33) So Git just does the simple and obvious thing here. Which is…&lt;/p&gt;

&lt;p&gt;(34) …it generates a pseudorandom number biased according to the square root of the size of the range and uses that offset to select the next commit. Perfectly sane and reasonable, right?&lt;/p&gt;

&lt;p&gt;(34) I hope this isn’t contentious, but I’d prefer my search algorithm to be deterministic. We’ll come back to this point with a better solution.&lt;/p&gt;

&lt;p&gt;(35) Now, if we wanted to run a bisection system in production, we’d have to scale it up. So, any ideas: how can we run binary search in parallel?&lt;/p&gt;

&lt;p&gt;(35) One approach is, at each step, instead of bisection to split the search range into two chunks of approximately equal size, instead we could do n-section to split it into n chunks.&lt;/p&gt;

&lt;p&gt;(35) Does anyone see any downsides to this approach?&lt;/p&gt;

&lt;p&gt;(35) One problem is that it’s not adaptive. You want to respond to results immediately as you get them; you don’t want to have to wait for all workers to complete. And the amount of parallelism available to you at runtime might fluctuate.&lt;/p&gt;

&lt;p&gt;(36) Let’s look at an example:&lt;/p&gt;

&lt;p&gt;(36) If we start testing three commits in parallel, and we learn that the first one failed…&lt;/p&gt;

&lt;p&gt;(37) …then we want to cancel the other two and start working on the search range to the left of the first one, as soon as any parallelism becomes available.&lt;/p&gt;

&lt;p&gt;(38) On the other hand, if the first commit passes, then the new n-section boundaries don’t necessarily overlap with the old ones. If there’s a lot of build overhead to start testing a new commit, then we don’t want to cancel the pending workers and throw away their work, because it’s still useful information, and they’re probably almost done at this point. But we might still want to direct our newly-freed worker to start testing another commit, anyways, so which commit would we pick in that case?&lt;/p&gt;

&lt;p&gt;(38) And the other problem is just an engineering problem. The implementation for parallel linear search on a directed acyclic graph is non-trivial, and we’d have to implement it separately from binary search. And we’re actually going to see a bunch of other search procedures in this talk, and it would be painful to have to parallelize each of them individually.&lt;/p&gt;

&lt;p&gt;(38) And like I told you, my typical workday looks like this: I head into work, I bang out an incorrect implementation of binary search, and I call it good and then I head home.&lt;/p&gt;

&lt;p&gt;(38) It’s a miracle I passed the coding interview.&lt;/p&gt;

&lt;p&gt;(38) And it’s not just me. You might have heard that the first published implementations of binary search were wrong for several years, due to unhandled overflow issues. Nobody can write correct code, especially not correct concurrent code, so let’s minimize the surface area here and do this &lt;em&gt;once&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;(39) So what’s the trick? Speculation. We’re going to write our code using primitive for non-determinism, and speculate on multiple possible futures at once.&lt;/p&gt;

&lt;p&gt;(39) We’re going to distinguish two things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The Traversal strategy,&lt;/li&gt;
  &lt;li&gt;and the Search algorithm&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(39) and we’re going to put all the speculation logic into the Search in such a way that it’s independent of the Traversal.&lt;/p&gt;

&lt;p&gt;(39) Here’s the idea: whenever the Traversal would have us test a certain commit, we’re going to speculate on both the Pass and Fail outcomes, and figure out what we would test next in each future. [slide: speculation tree for binary search]&lt;/p&gt;

&lt;p&gt;(39) To parallelize this, all we have to do is take the first n nodes in this speculation tree and start testing them in parallel.&lt;/p&gt;

&lt;p&gt;(39) And what’s cool is that, for binary search, this reduces to recursive bisection instead of n-section, which, for powers of two, is equivalent, and for other values, it’s still asymptotically O(log(n)).&lt;/p&gt;

&lt;p&gt;(39) And for linear search, this ends up speculating the first n nodes in the range, because the Fail outcomes will terminate the speculation tree immediately, and the Pass outcomes will continue.&lt;/p&gt;

&lt;p&gt;(40) The search returns a lazy sequence corresponding to the speculation tree. Internally, it’ll clone the traversal object and speculate on the pass-fail outcomes to produce nodes deeper in the tree.&lt;/p&gt;

&lt;p&gt;(41) Each instance of this traversal represents a node in the speculation tree. The speculative search notifies the traversal about a test result, and it updates its internal state, and prunes any unnecessary search nodes and so on, and it returns the updated Bounds for the benefit of the caller.&lt;/p&gt;

&lt;p&gt;(41) Then the search queries the “midpoints” of the remaining range. For linear search, those are the next nodes in a breadth-first search in the DAG. For binary search, we compute the ancestor depth to select the midpoints.&lt;/p&gt;

&lt;p&gt;(41) Any questions so far? This is one of the main abstractions.&lt;/p&gt;

&lt;p&gt;(42) Thinking back to the question of indeterminate commits, we also get a solution for free. Whenever the traversal returns a node that was previously marked as indeterminate, then we can just speculate both pass and fail cases.&lt;/p&gt;

&lt;p&gt;(42) For linear search, this again just does the right thing and “skips over” the indeterminate nodes.&lt;/p&gt;

&lt;p&gt;(42) And for binary search, we get the ability to select an asymptotically-optimal commit that’s far away from the broken commit, but in a fully deterministic manner. So I think that’s pretty elegant.&lt;/p&gt;

&lt;p&gt;(43) Why is it so elegant? Well, let’s look back at the traversal interface. You can duplicate instances of this type; you can extract the current search nodes from the instance; and you can extend the instance into the future by speculating on the search node outcomes…&lt;/p&gt;

&lt;p&gt;(43) Eh? I know what you’re thinking. “Wow, that sounds a lot like a comonad!”&lt;/p&gt;

&lt;p&gt;(43) Okay, not really, and if any of you were thinking that, then I don’t know if any search algorithm can find relief for your tortured soul.&lt;/p&gt;

&lt;p&gt;(44) (missing; re merge queueing)&lt;/p&gt;

&lt;p&gt;(45) You can consider normal bisection to be a special case where you can only select sets of adjacent nodes in the graph.&lt;/p&gt;

&lt;p&gt;(45) The idea here is to lift the graph such that it operates on combinations of commits, extracted and applied independently, instead of individual commits with all of their ancestors.&lt;/p&gt;

&lt;p&gt;(45) Formally speaking, we want to operate over the power set of the search range, which is the set of all subsets of the search range.&lt;/p&gt;

&lt;p&gt;(45) The great thing is: the power set naturally forms a directed acyclic graph, where there’s an edge between a parent and a child when the parent is a strict subset of the child. And we just described a scheme to do fancy search on DAGs.&lt;/p&gt;

&lt;p&gt;(45) Easy, right?&lt;/p&gt;

&lt;p&gt;(46) Okay, so what’s the problem with running the search on this graph?&lt;/p&gt;

&lt;p&gt;(46) Normal source control graphs are pretty linear, and have relatively low branching factor. But this graph is maximally dense, with a high branching factor — and it contains an exponential number of nodes.&lt;/p&gt;

&lt;p&gt;(46) How do we resolve that?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Avoid materializing entire graph&lt;/li&gt;
  &lt;li&gt;Make assumptions about search space&lt;/li&gt;
  &lt;li&gt;Accept a local minimum&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(47) (missing: discussion of how to implement conflict analyzer and weighted probabilistic speculation in the same system)&lt;/p&gt;

&lt;p&gt;(48–50) (missing: discussion of implementing “delta-debugging” in the same system)&lt;/p&gt;

&lt;p&gt;(51) (missing: discussion of minimizing order-sensitive failures)&lt;/p&gt;

&lt;p&gt;(52) (missing: discussion of “flake-aware culprit finding”; discussion of weighted-median approach to running multiple tests on the same graph)&lt;/p&gt;

&lt;p&gt;(53–55) (missing: going from “generalized binary search” to “noisy generalized binary search” via Bayesian inference)&lt;/p&gt;

&lt;p&gt;(56–60) (missing: numerically-incorrect example of updating posterior probabilities, which needs to be fixed)&lt;/p&gt;

&lt;p&gt;(61–63) (missing: conclusion and remark on AI-accelerated development increasing the need for clever search systems)&lt;/p&gt;

&lt;p&gt;(64–78) (deleted slides with various bullet points)&lt;/p&gt;

&lt;h2 id=&quot;related-posts&quot;&gt;Related posts&lt;/h2&gt;

&lt;p&gt;The following are hand-curated posts which you might find interesting.&lt;/p&gt;

&lt;table class=&quot;related-posts&quot;&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th&gt;Date&lt;/th&gt;
    &lt;th&gt;&lt;/th&gt;
    &lt;th&gt;Title&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;


  &lt;tr&gt;
    &lt;td&gt;24&amp;nbsp;Jul&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      (this&amp;nbsp;post)
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/searching-source-control-graphs/&quot;&gt;Searching source control graphs&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Want to see more of my posts? Follow me &lt;a href=&quot;https://twitter.com/arxanas&quot;&gt;on Twitter&lt;/a&gt; or subscribe &lt;a href=&quot;/feed.xml&quot;&gt;via RSS&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;comments&quot;&gt;Comments&lt;/h2&gt;

&lt;ul&gt;



&lt;/ul&gt;

&lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/github-comment-links.js&quot;&gt;&lt;/script&gt;

&lt;div id=&quot;disqus_thread&quot;&gt;&lt;/div&gt;
&lt;script&gt;

/**
 *  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
 *  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables */
var disqus_config = function () {
    this.page.url = &quot;https://blog.waleedkhan.name/searching-source-control-graphs/&quot;;
    this.page.identifier = &quot;searching-source-control-graphs/&quot;;
};

(function() { // DON&apos;T EDIT BELOW THIS LINE
    var d = document, s = d.createElement(&apos;script&apos;);
    s.src = &apos;//waleedkhan-name.disqus.com/embed.js&apos;;
    s.setAttribute(&apos;data-timestamp&apos;, +new Date());
    (d.head || d.body).appendChild(s);
})();
&lt;/script&gt;

&lt;noscript&gt;Please enable JavaScript to view the &lt;a href=&quot;https://disqus.com/?ref_noscript&quot;&gt;comments powered by Disqus.&lt;/a&gt;&lt;/noscript&gt;

</description>
        <pubDate>Thu, 24 Jul 2025 00:00:00 +0000</pubDate>
        <link>https://blog.waleedkhan.name/searching-source-control-graphs/</link>
        <guid isPermaLink="true">https://blog.waleedkhan.name/searching-source-control-graphs/</guid>
        
        <category>build-systems</category>
        
        <category>git</category>
        
        <category>reprint</category>
        
        <category>software-engineering</category>
        
        <category>software-verification</category>
        
        
      </item>
    
      <item>
        <title>Language-learning anecdotes</title>
        <description>&lt;div class=&quot;publication-notes&quot;&gt;
  &lt;table&gt;
    &lt;tr&gt;
      &lt;td&gt;Intended audience&lt;/td&gt;
      &lt;td&gt;Those with a passing interest in linguistics.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Occasion&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.aprilcools.club/&quot;&gt;April Cools&apos;&lt;/a&gt; 2025 — a little late!&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Origin&lt;/td&gt;
      &lt;td&gt;Personal experience.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mood&lt;/td&gt;
      &lt;td&gt;Amused.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;These are some anecdotes from my various experiences learning languages (including &lt;a href=&quot;https://en.wikipedia.org/wiki/Urdu&quot;&gt;Urdu&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Polish_language&quot;&gt;Polish&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Latin&quot;&gt;Latin&lt;/a&gt;, and &lt;a href=&quot;https://en.wikipedia.org/wiki/American_Sign_Language&quot;&gt;American Sign Language&lt;/a&gt;). The order of entries is mostly arbitrary. Hopefully you find some of them entertaining!&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#miscellany&quot; id=&quot;markdown-toc-miscellany&quot;&gt;Miscellany&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#we-all-scream-for-ice-cream&quot; id=&quot;markdown-toc-we-all-scream-for-ice-cream&quot;&gt;We all scream for ice cream?&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#on-hedgehogs&quot; id=&quot;markdown-toc-on-hedgehogs&quot;&gt;On hedgehogs&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#social-deduction&quot; id=&quot;markdown-toc-social-deduction&quot;&gt;Social deduction&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#forgetting-my-native-language&quot; id=&quot;markdown-toc-forgetting-my-native-language&quot;&gt;Forgetting my native language&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#sentence-formation&quot; id=&quot;markdown-toc-sentence-formation&quot;&gt;Sentence formation&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#hacking-the-latin-word-ordering&quot; id=&quot;markdown-toc-hacking-the-latin-word-ordering&quot;&gt;Hacking the Latin word ordering&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#making-my-english-worse-with-latin&quot; id=&quot;markdown-toc-making-my-english-worse-with-latin&quot;&gt;Making my English worse with Latin&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#making-my-english-worse-with-polish&quot; id=&quot;markdown-toc-making-my-english-worse-with-polish&quot;&gt;Making my English worse with Polish&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#preferring-a-completely-foreign-word-order&quot; id=&quot;markdown-toc-preferring-a-completely-foreign-word-order&quot;&gt;Preferring a completely-foreign word order&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#failing-to-communicate-privately-in-american-sign-language&quot; id=&quot;markdown-toc-failing-to-communicate-privately-in-american-sign-language&quot;&gt;Failing to communicate privately in American sign language&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#pronunciation&quot; id=&quot;markdown-toc-pronunciation&quot;&gt;Pronunciation&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#phonological-production-in-polish-but-not-distinction&quot; id=&quot;markdown-toc-phonological-production-in-polish-but-not-distinction&quot;&gt;Phonological production in Polish, but not distinction&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#subvocalizing-written-chinese&quot; id=&quot;markdown-toc-subvocalizing-written-chinese&quot;&gt;Subvocalizing written Chinese&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#adjusting-my-english-accent&quot; id=&quot;markdown-toc-adjusting-my-english-accent&quot;&gt;Adjusting my English accent&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#making-others-polish-accents-worse&quot; id=&quot;markdown-toc-making-others-polish-accents-worse&quot;&gt;Making others’ Polish accents worse&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#failing-to-improve-my-polish-accent&quot; id=&quot;markdown-toc-failing-to-improve-my-polish-accent&quot;&gt;Failing to improve my Polish accent&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#grammar&quot; id=&quot;markdown-toc-grammar&quot;&gt;Grammar&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#urdu-gender-confusion&quot; id=&quot;markdown-toc-urdu-gender-confusion&quot;&gt;Urdu gender confusion&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#polish-gender-confusion&quot; id=&quot;markdown-toc-polish-gender-confusion&quot;&gt;Polish gender confusion&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#urdu-grammar-confusion&quot; id=&quot;markdown-toc-urdu-grammar-confusion&quot;&gt;Urdu grammar confusion&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#related-posts&quot; id=&quot;markdown-toc-related-posts&quot;&gt;Related posts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#comments&quot; id=&quot;markdown-toc-comments&quot;&gt;Comments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;miscellany&quot;&gt;Miscellany&lt;/h2&gt;

&lt;h3 id=&quot;we-all-scream-for-ice-cream&quot;&gt;We all scream for ice cream?&lt;/h3&gt;

&lt;p&gt;Here’s an error screen from &lt;a href=&quot;https://en.wikipedia.org/wiki/Twitter&quot;&gt;Twitter&lt;/a&gt; (from many years ago), localized to &lt;a href=&quot;https://en.wikipedia.org/wiki/Polish_language&quot;&gt;Polish&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;figure&quot;&gt;
        &lt;a href=&quot;/assets/posts/language-learning-anecdotes/we-all-scream-for-ice-cream.png&quot;&gt;&lt;img class=&quot;center-image&quot; src=&quot;/assets/posts/language-learning-anecdotes/we-all-scream-for-ice-cream.png&quot; alt=&quot;Error page with text in Polish and a fallen ice cream cone graphic in the background.&quot; title=&quot;Error page with text in Polish and a fallen ice cream cone graphic in the background.&quot; /&gt;&lt;/a&gt;
        &lt;figcaption class=&quot;caption&quot;&gt;&lt;p&gt;On this error page, the text along with the ice cream cone in the background refer to the rhyme &lt;a href=&quot;https://en.wikipedia.org/wiki/Ice_Cream_(I_Scream,_You_Scream,_We_All_Scream_for_Ice_Cream)&quot;&gt;&amp;#8220;I Scream, You Scream, We All Scream for Ice Cream&amp;#8221;&lt;/a&gt;.&lt;/p&gt;
&lt;/figcaption&gt;
      &lt;/figure&gt;

&lt;p&gt;The text roughly translates to:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The page isn’t working&lt;/p&gt;

  &lt;p&gt;I yell. You yell. We all yell… to finally fix this page. […]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Given that the ice cream rhyme relies on wordplay, it seems very likely that this would be unintelligible to Polish speakers.&lt;/p&gt;

&lt;div class=&quot;note-block &quot;&gt;
          &lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;Regrettably:&lt;/span&gt; Even in English, it relies on the reader making a semantic connection with the background image, which is its own accessibility issue.&lt;/p&gt;

        &lt;/div&gt;

&lt;h3 id=&quot;on-hedgehogs&quot;&gt;On hedgehogs&lt;/h3&gt;

&lt;p&gt;Here’s &lt;a href=&quot;https://www.reddit.com/r/CasualUK/comments/1bfhpdw/comment/kv1acu2/?utm_source=share&amp;amp;utm_medium=mweb3x&amp;amp;utm_name=mweb3xcss&amp;amp;utm_term=1&amp;amp;utm_content=share_button&quot;&gt;someone else’s recollection of a story&lt;/a&gt;, which I thought was funny:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I was telling my German colleague about how my parents, who were living in Spain, kept having problems with eagles killing their chickens.&lt;/p&gt;

  &lt;p&gt;Me: yeah, these eagles come down out of the mountains, they steal the chickens right out of the pen!&lt;/p&gt;

  &lt;p&gt;Frank: an eagle!? AN EAGLE DOES THIS!?&lt;/p&gt;

  &lt;p&gt;Me: yes, they have eagles over there, mad isn’t it?&lt;/p&gt;

  &lt;p&gt;Frank: AN EAGLE IS STEALING THE CHICKENS?&lt;/p&gt;

  &lt;p&gt;Me: errr, yeah, they’re predators you know?&lt;/p&gt;

  &lt;p&gt;Frank: THEY ARE PREDATORS?&lt;/p&gt;

  &lt;p&gt;Me: yeah, and they fly down and kill the chickens and fly off with them&lt;/p&gt;

  &lt;p&gt;Frank: THEY FLY… wait, hang on… Oh…. I see… Err… In German, Igel is… Errr…&lt;/p&gt;

  &lt;p&gt;(long, long pause)…&lt;/p&gt;

  &lt;p&gt;AH! IN GERMAN, EIN IGEL IS A HEDGEHOG!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I had assumed that the German word &lt;a href=&quot;https://en.wiktionary.org/wiki/Igel&quot;&gt;&lt;em&gt;Igel&lt;/em&gt;&lt;/a&gt; (“hedgehog”) might be a cognate of Polish &lt;a href=&quot;https://en.wiktionary.org/wiki/ig%C5%82a&quot;&gt;&lt;em&gt;igła&lt;/em&gt;&lt;/a&gt; (“needle”), although I didn’t find a direct etymological connection.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Here’s a humorous misspelling in &lt;a href=&quot;https://en.wikipedia.org/wiki/Polish_language&quot;&gt;Polish&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;figure&quot;&gt;
        &lt;a href=&quot;/assets/posts/language-learning-anecdotes/jeżu-ufam-tobie.jpg&quot;&gt;&lt;img class=&quot;center-image&quot; src=&quot;/assets/posts/language-learning-anecdotes/jeżu-ufam-tobie.jpg&quot; alt=&quot;A Facebook post that says &amp;quot;Jeżu Ufam Tobie&amp;quot; with an attached portrait of Jesus. The Facebook auto-translation reads &amp;quot;Hedgehog I trust you&amp;quot;.&quot; title=&quot;A Facebook post that says &amp;quot;Jeżu Ufam Tobie&amp;quot; with an attached portrait of Jesus. The Facebook auto-translation reads &amp;quot;Hedgehog I trust you&amp;quot;.&quot; /&gt;&lt;/a&gt;
        &lt;figcaption class=&quot;caption&quot;&gt;&lt;p&gt;A Facebook post that says “Jeżu Ufam Tobie” with an attached portrait of Jesus. The Facebook auto-translation reads “Hedgehog I trust you”.&lt;/p&gt;
&lt;/figcaption&gt;
      &lt;/figure&gt;

&lt;p&gt;This misspells &lt;a href=&quot;https://en.wiktionary.org/wiki/Jezu#Polish&quot;&gt;&lt;em&gt;Jezu&lt;/em&gt;&lt;/a&gt; (singular &lt;a href=&quot;https://en.wikipedia.org/wiki/Vocative_case&quot;&gt;vocative&lt;/a&gt; form of &lt;a href=&quot;https://en.wiktionary.org/wiki/Jezus#Polish&quot;&gt;&lt;em&gt;Jezus&lt;/em&gt;&lt;/a&gt; “Jesus”) as &lt;em&gt;jeżu&lt;/em&gt; (singular vocative form of &lt;a href=&quot;https://en.wiktionary.org/wiki/je%C5%BC#Polish&quot;&gt;&lt;em&gt;jeż&lt;/em&gt;&lt;/a&gt;, “hedgehog”). &lt;span class=&quot;note-tag&quot;&gt;Naturally:&lt;/span&gt; I’ve adopted “jeżu” as my expletive of choice in Polish.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Polish_language&quot;&gt;Polish&lt;/a&gt; word for “porcupine” is &lt;a href=&quot;https://en.wiktionary.org/wiki/je%C5%BCozwierz&quot;&gt;&lt;em&gt;jeżozwierz&lt;/em&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;https://en.wiktionary.org/wiki/je%C5%BC#Polish&quot;&gt;jeż&lt;/a&gt; + &lt;a href=&quot;https://en.wiktionary.org/wiki/-o-#Polish&quot;&gt;-o-&lt;/a&gt; + &lt;a href=&quot;https://en.wiktionary.org/wiki/zwierz#Polish&quot;&gt;zwierz&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;whose components might translate to “hedgehogobeast”, which I thought was fun.&lt;/p&gt;

&lt;h3 id=&quot;social-deduction&quot;&gt;Social deduction&lt;/h3&gt;

&lt;p&gt;My family took my wife and I to buy her a &lt;a href=&quot;https://en.wikipedia.org/wiki/Lehenga&quot;&gt;&lt;em&gt;lehenga&lt;/em&gt;&lt;/a&gt; for our wedding celebration. My father advised me that they expected to &lt;a href=&quot;https://en.wikipedia.org/wiki/Bargaining&quot;&gt;haggle&lt;/a&gt;, as is tradition:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;On the buyer’s side: We wanted to avoid giving away information and losing negotiating leverage, so I communicated with my wife in &lt;a href=&quot;https://en.wikipedia.org/wiki/Polish_language&quot;&gt;Polish&lt;/a&gt;, which only my wife and I spoke.&lt;/li&gt;
  &lt;li&gt;On the seller’s side: They spoke with my parents in &lt;a href=&quot;https://en.wikipedia.org/wiki/Urdu&quot;&gt;Urdu&lt;/a&gt;, which my wife doesn’t speak.&lt;/li&gt;
  &lt;li&gt;We would switch to &lt;a href=&quot;https://en.wikipedia.org/wiki/English_language&quot;&gt;English&lt;/a&gt; when we all needed to converse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a few minutes at a time, I would undergo what I found to be perhaps the most mentally-taxing activity of my life: conducting a many-way negotiation with hidden information across three languages.&lt;/p&gt;

&lt;div class=&quot;note-block &quot;&gt;
          &lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;To elaborate:&lt;/span&gt; I had to translate between Urdu (weak) and Polish (non-native), going through my native English as an intermediary, in close to real-time.&lt;/p&gt;

&lt;p&gt;Since my grasp of Urdu is weak, I missed several contextual and cultural clues, and had to explain&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;not only the facts I understood,&lt;/li&gt;
  &lt;li&gt;but the inferences I was making&lt;/li&gt;
  &lt;li&gt;and the confidence I had in those inferences.&lt;/li&gt;
  &lt;li&gt;But in Polish.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;Furthermore:&lt;/span&gt; I had to play a careful kind of &lt;a href=&quot;https://en.wikipedia.org/wiki/Social_deduction_game&quot;&gt;social deduction game&lt;/a&gt;, in which:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I had to keep in mind which information each of the parties had:
    &lt;ul&gt;
      &lt;li&gt;my wife and her judgments;&lt;/li&gt;
      &lt;li&gt;myself and my own judgments;&lt;/li&gt;
      &lt;li&gt;my and my wife’s joint public statements;&lt;/li&gt;
      &lt;li&gt;my family’s public statements;&lt;/li&gt;
      &lt;li&gt;and the seller’s public statements;&lt;/li&gt;
      &lt;li&gt;what I know my family knows and believes privately, which I have to be careful not to undermine;&lt;/li&gt;
      &lt;li&gt;how my family would interpret my and my wife’s joint statements, based on what they know we know.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;I had to be sure to convey public information between all relevant parties, while avoiding leaking private information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;For example:&lt;/span&gt; Suppose my family were to claim “we’re visiting a few more shops after this”, when in fact we had nothing else on the agenda for the day:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Then I would have to be careful to secretly get my wife to corroborate, in order to avoid undermining our negotiating position,&lt;/li&gt;
  &lt;li&gt;or I’d have to consider what I was signaling if I chose to contradict my family.&lt;/li&gt;
&lt;/ul&gt;

        &lt;/div&gt;

&lt;h3 id=&quot;forgetting-my-native-language&quot;&gt;Forgetting my native language&lt;/h3&gt;

&lt;p&gt;I largely dropped &lt;a href=&quot;https://en.wikipedia.org/wiki/Urdu&quot;&gt;Urdu&lt;/a&gt; at home:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;My parents would speak in Urdu and I would reply in &lt;a href=&quot;https://en.wikipedia.org/wiki/English_language&quot;&gt;English&lt;/a&gt;.
    &lt;ul&gt;
      &lt;li&gt;This is a common situation in immigrant households. I don’t know if it has a name.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Since they were already fluent in English, over time, they largely switched to English at home.
    &lt;ul&gt;
      &lt;li&gt;Having failed to acquire Urdu well, I also got frustrated easily, or failed to understand some speech entirely,&lt;/li&gt;
      &lt;li&gt;which no doubt led to a feedback loop of encouraging English to be spoken at home.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;span class=&quot;note-tag note-warning&quot;&gt;Oops:&lt;/span&gt; In university, I took an introductory linguistics course. We all filled out a brief survey, including information about which languages we spoke. By that point, I forgot to fill in ‘Urdu’ 🙃.&lt;/p&gt;

&lt;h2 id=&quot;sentence-formation&quot;&gt;Sentence formation&lt;/h2&gt;

&lt;h3 id=&quot;hacking-the-latin-word-ordering&quot;&gt;Hacking the Latin word ordering&lt;/h3&gt;

&lt;p&gt;In university, I took a &lt;a href=&quot;https://en.wikipedia.org/wiki/Latin&quot;&gt;Latin&lt;/a&gt; course:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/English_language&quot;&gt;English&lt;/a&gt; uses &lt;a href=&quot;https://en.wikipedia.org/wiki/Subject%E2%80%93verb%E2%80%93object_word_order&quot;&gt;subject-verb-object (SVO) word order&lt;/a&gt;,&lt;/li&gt;
  &lt;li&gt;while Latin is highly-&lt;a href=&quot;https://en.wikipedia.org/wiki/Inflection&quot;&gt;inflected&lt;/a&gt;. The &lt;a href=&quot;https://en.wikipedia.org/wiki/Word_order&quot;&gt;word order&lt;/a&gt; is largely free-form, but conventionally &lt;a href=&quot;https://en.wikipedia.org/wiki/Subject%E2%80%93object%E2%80%93verb_word_order&quot;&gt;subject-object-verb (SOV)&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I encountered a great deal of mental friction as I tried to order the sentence components in the conventional SOV manner.&lt;/p&gt;

&lt;p&gt;Then I realized — my other native language, &lt;a href=&quot;https://en.wikipedia.org/wiki/Urdu&quot;&gt;Urdu&lt;/a&gt;, &lt;em&gt;also&lt;/em&gt; has SOV word order. And suddenly it became trivial!&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;note-tag note-info&quot;&gt;Interestingly:&lt;/span&gt; It was as if I mentally rearranged merely the sentence’s concepts in SOV order by passing them through some Urdu circuit in my brain, while relying on my English ability for everything else relating to sentence formation.&lt;/p&gt;

&lt;div class=&quot;note-block &quot;&gt;
          &lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;To elaborate:&lt;/span&gt; I didn’t have enough familiarity or vocabulary to actually translate anything into Urdu as an intermediate step.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;So this only leveraged my instinctive syntactic and parsing ability, and nothing semantic.&lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;Just as before:&lt;/span&gt; I didn’t get to leverage instinctive &lt;a href=&quot;https://en.wikipedia.org/wiki/Agreement_(linguistics)&quot;&gt;noun agreement&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;In any event:&lt;/span&gt; English vocabulary is much more similar to Latin (via &lt;a href=&quot;https://en.wikipedia.org/wiki/French_language&quot;&gt;French&lt;/a&gt;) than Urdu vocabulary is, so the literal translation of concepts is easier directly from English to Latin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;Concretely:&lt;/span&gt; I found SOV a little difficult when thinking in SVO:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;In SVO, once you reach the &lt;a href=&quot;https://en.wikipedia.org/wiki/Verb&quot;&gt;verb&lt;/a&gt;, you know you that you’re done parsing the subject’s &lt;a href=&quot;https://en.wikipedia.org/wiki/Noun_phrase&quot;&gt;noun phrase&lt;/a&gt;, and can “unwind” it,&lt;/li&gt;
  &lt;li&gt;whereas in SOV, you might have to build up two complicated noun phrases — and distinguish their boundary — and keep them in mind simultaneously.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I felt that I primarily relied on the syntactic ability to juggle both noun phrases at once.&lt;/p&gt;

  &lt;/li&gt;
&lt;/ul&gt;

        &lt;/div&gt;

&lt;h3 id=&quot;making-my-english-worse-with-latin&quot;&gt;Making my English worse with Latin&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Latin&quot;&gt;Latin&lt;/a&gt; uses &lt;a href=&quot;https://en.wikipedia.org/wiki/Participle&quot;&gt;participles&lt;/a&gt; more often than &lt;a href=&quot;https://en.wikipedia.org/wiki/English_language&quot;&gt;English&lt;/a&gt;, which is &lt;a href=&quot;https://en.wikipedia.org/wiki/Participle#Latin&quot;&gt;described well here&lt;/a&gt;. This bled into my “natural” or “default” English, and has led to me using sometimes-unconventional participial phrasing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag note-info&quot;&gt;For example:&lt;/span&gt; &lt;span class=&quot;note-braces&quot;&gt;“Someone having learned Latin might use participles more often” &lt;span class=&quot;note-conj&quot;&gt;rather than&lt;/span&gt; “Someone who’s learned Latin might use participles more often”&lt;/span&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag note-info&quot;&gt;For example:&lt;/span&gt; &lt;span class=&quot;note-braces&quot;&gt;“With me having learned Latin, I use participles more often” &lt;span class=&quot;note-conj&quot;&gt;rather than&lt;/span&gt; “Given that I’ve learned Latin, I use participles more often”&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;making-my-english-worse-with-polish&quot;&gt;Making my English worse with Polish&lt;/h3&gt;

&lt;p&gt;Learning &lt;a href=&quot;https://en.wikipedia.org/wiki/Polish_language&quot;&gt;Polish&lt;/a&gt; has made my &lt;a href=&quot;https://en.wikipedia.org/wiki/English_language&quot;&gt;English&lt;/a&gt; more non-standard:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I sometimes build up &lt;a href=&quot;https://en.wikipedia.org/wiki/Adjective&quot;&gt;adjectival&lt;/a&gt; or &lt;a href=&quot;https://en.wikipedia.org/wiki/Adverb&quot;&gt;adverbial&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Clause&quot;&gt;clauses&lt;/a&gt; that are uncomfortable to parse, rather than spreading them throughout a sentence more naturally.
    &lt;ul&gt;
      &lt;li&gt;&lt;span class=&quot;note-tag note-info&quot;&gt;For example:&lt;/span&gt; &lt;span class=&quot;note-braces&quot;&gt;“I learned Polish, which caused me to more-unusually order adverbs” &lt;span class=&quot;note-conj&quot;&gt;rather than&lt;/span&gt; “I learned Polish, which caused me to order adverbs more unusually”&lt;/span&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;I sometimes use &lt;a href=&quot;https://en.wikipedia.org/wiki/Verbal_noun&quot;&gt;verbal nouns&lt;/a&gt; when &lt;a href=&quot;https://en.wikipedia.org/wiki/Dependent_clause&quot;&gt;subordinate clauses&lt;/a&gt; would be more conventional.
    &lt;ul&gt;
      &lt;li&gt;&lt;span class=&quot;note-tag note-info&quot;&gt;For example:&lt;/span&gt; &lt;span class=&quot;note-braces&quot;&gt;“My having learned Polish caused me to use verbal nouns unconventionally &lt;span class=&quot;note-conj&quot;&gt;rather than&lt;/span&gt; “I learned Polish, which caused me to use verbal nouns unconventionally”&lt;/span&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;preferring-a-completely-foreign-word-order&quot;&gt;Preferring a completely-foreign word order&lt;/h3&gt;

&lt;p&gt;Similarly to &lt;a href=&quot;https://en.wikipedia.org/wiki/Latin&quot;&gt;Latin&lt;/a&gt;, Polish is a language with conventionally &lt;a href=&quot;https://en.wikipedia.org/wiki/Subject%E2%80%93verb%E2%80%93object_word_order&quot;&gt;SVO word order&lt;/a&gt;, but with enough &lt;a href=&quot;https://en.wikipedia.org/wiki/Inflection&quot;&gt;inflection&lt;/a&gt; to support technically free-form &lt;a href=&quot;https://en.wikipedia.org/wiki/Word_order&quot;&gt;word order&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After achieving basic fluency in Polish, I noticed that my preferred word order was most often &lt;a href=&quot;https://en.wikipedia.org/wiki/Verb%E2%80%93subject%E2%80%93object_word_order&quot;&gt;verb-subject-object (VSO)&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;note-tag note-warning&quot;&gt;This was unexpected:&lt;/span&gt; None of my languages conventionally use VSO, so it’s interesting that I might prefer it, despite having no (grammatical) way to express it in my available languages.&lt;/p&gt;

&lt;div class=&quot;note-block note-info&quot;&gt;
          &lt;p&gt;&lt;span class=&quot;note-tag note-info&quot;&gt;Possibly:&lt;/span&gt; It might reflect a desire to express &lt;a href=&quot;https://en.wikipedia.org/wiki/Topic_and_comment&quot;&gt;topic-comment&lt;/a&gt; word order,&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;rather than specifically VSO, supposing that I’m most often commenting on actions.&lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;Possibly:&lt;/span&gt; My inclination to structure discourse as a top-level comment followed by hierarchical detail might be the same thing.&lt;/li&gt;
  &lt;li&gt;Likewise for my inclination to
    &lt;ul&gt;
      &lt;li&gt;extract and format mood/uncertainty/provenance signifiers, like “Possibly:”,&lt;/li&gt;
      &lt;li&gt;separately from the semantic or factual content of the sentence.&lt;/li&gt;
      &lt;li&gt;It’s information that I want to include, but without muddling the syntax.
        &lt;ul&gt;
          &lt;li&gt;You may disagree that the result is any clearer 🙃.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;

  &lt;/li&gt;
&lt;/ul&gt;

        &lt;/div&gt;

&lt;div class=&quot;note-block &quot;&gt;
          &lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;Note:&lt;/span&gt; In Polish, the &lt;a href=&quot;https://en.wikipedia.org/wiki/Verb&quot;&gt;verb&lt;/a&gt; embeds the &lt;a href=&quot;https://en.wikipedia.org/wiki/Grammatical_number&quot;&gt;grammatical number&lt;/a&gt; of the subject, which is a fairly common language feature.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;Therefore:&lt;/span&gt; Saying the verb first also reduces the latency of information transfer, in some sense.&lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag note-info&quot;&gt;Despite this:&lt;/span&gt; I would often still be inclined to include the subject explicitly after the verb.
    &lt;ul&gt;
      &lt;li&gt;Even if it’s a largely-redundant subject &lt;a href=&quot;https://en.wikipedia.org/wiki/Pronoun&quot;&gt;pronoun&lt;/a&gt;, due to the grammatical number already being embedded in the verb.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;For example:&lt;/span&gt; I might say “zrobili oni to” rather than just “zrobili to”.
    &lt;ul&gt;
      &lt;li&gt;“zrobili”: “to do” third-&lt;a href=&quot;https://en.wikipedia.org/wiki/Grammatical_person&quot;&gt;person&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Plural&quot;&gt;plural&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Past_tense&quot;&gt;past-tense&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Perfective_aspect&quot;&gt;perfective&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;“oni”: “they” third-person plural&lt;/li&gt;
      &lt;li&gt;“to”: “it”/”this” third-person singular&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;For a native speaker, I expect that it would be interpreted as emphasizing the subject much more strongly than I intended it?&lt;/p&gt;

  &lt;/li&gt;
&lt;/ul&gt;

        &lt;/div&gt;

&lt;h3 id=&quot;failing-to-communicate-privately-in-american-sign-language&quot;&gt;Failing to communicate privately in American sign language&lt;/h3&gt;

&lt;p&gt;My wife and I learned a few signs of &lt;a href=&quot;https://en.wikipedia.org/wiki/American_Sign_Language&quot;&gt;American sign language (ASL)&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;note-block note-info&quot;&gt;
          &lt;p&gt;&lt;span class=&quot;note-tag note-info&quot;&gt;Originally:&lt;/span&gt; I thought that ASL could be useful when traveling:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;Particularly:&lt;/span&gt; In loud or distant contexts, such as restaurants, parties, public transit, sightseeing, and so forth.&lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag note-info&quot;&gt;For example:&lt;/span&gt; It can be convenient to communicate from across a museum hall my intention to go to the bathroom, so that my partner isn’t confused about where I’m going, and knows that I’ll return shortly.&lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag note-info&quot;&gt;For example:&lt;/span&gt; It’s great for excitedly and efficiently summoning the other partner to come see a cute local cat or dog which might soon leave sight.&lt;/li&gt;
&lt;/ul&gt;

        &lt;/div&gt;

&lt;p&gt;We’re often able to speak in &lt;a href=&quot;https://en.wikipedia.org/wiki/Polish_language&quot;&gt;Polish&lt;/a&gt; to communicate privately, but that’s not an option if we happen to be around other Polish speakers, or if we simply can’t hear each other. So I thought it might be convenient to sometimes use ASL for private communication.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;note-tag note-warning&quot;&gt;But it turns out:&lt;/span&gt; Communicating a desire to leave the current venue by &lt;span class=&quot;note-braces&quot;&gt;gesturing at ourselves &lt;span class=&quot;note-conj&quot;&gt;and then&lt;/span&gt; pointing at the exit with both index fingers&lt;/span&gt; is not a terribly polite way to coordinate our departure from a friend’s event.&lt;/p&gt;

&lt;h2 id=&quot;pronunciation&quot;&gt;Pronunciation&lt;/h2&gt;

&lt;h3 id=&quot;phonological-production-in-polish-but-not-distinction&quot;&gt;Phonological production in Polish, but not distinction&lt;/h3&gt;

&lt;p&gt;Polish has two phonologically-distinct ‘ch’ sounds, as opposed to English’s one:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The two sounds are usually written ‘cz’ and ‘ć’
    &lt;ul&gt;
      &lt;li&gt;corresponding to &lt;a href=&quot;https://en.wikipedia.org/wiki/International_Phonetic_Alphabet&quot;&gt;IPA&lt;/a&gt; /t͡ʂ/ and /t͡ɕ/&lt;/li&gt;
      &lt;li&gt;although with some additional orthographic rules, such as ‘ć’ sometimes being written ‘ci’.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Polish also has two ‘sh’ and two ‘zh’ sounds with the same phonological relationships.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I can &lt;em&gt;produce&lt;/em&gt; these sounds reasonably well, but I nonetheless can’t reliably &lt;em&gt;distinguish&lt;/em&gt; them:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;When I hear a new word that uses one of the sounds, I usually ask the speaker “is it (version with cz) or (version with ć)?”.
    &lt;ul&gt;
      &lt;li&gt;It doesn’t help that the word for “or” in these questions is ‘czy’, involving another instance of the ‘ch’ sound.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Then they repeat back one of the forms to me, after which I realize I can’t distinguish it anyways.&lt;/li&gt;
  &lt;li&gt;Then I give up and either ask them to spell it, or I look up the word later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is unsurprising from a linguistic perspective, but amusing to me nonetheless.&lt;/p&gt;

&lt;h3 id=&quot;subvocalizing-written-chinese&quot;&gt;Subvocalizing written Chinese&lt;/h3&gt;

&lt;p&gt;I was briefly learning &lt;a href=&quot;https://en.wikipedia.org/wiki/Written_Chinese&quot;&gt;written Chinese&lt;/a&gt;, but not spoken &lt;a href=&quot;https://en.wikipedia.org/wiki/Chinese_language&quot;&gt;Chinese&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;(I have fun learning and writing the &lt;a href=&quot;https://en.wikipedia.org/wiki/Chinese_characters&quot;&gt;characters&lt;/a&gt;, but I don’t have anyone to speak Chinese with.)&lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;Consequently:&lt;/span&gt; I didn’t have names or &lt;a href=&quot;https://en.wikipedia.org/wiki/Speech_production&quot;&gt;vocalizations&lt;/a&gt; for any of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Chinese_characters&quot;&gt;characters&lt;/a&gt;, beyond some of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Chinese_character_radicals&quot;&gt;radicals&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So what did I do when I happened to &lt;a href=&quot;https://en.wikipedia.org/wiki/Subvocalization&quot;&gt;subvocalize&lt;/a&gt; characters while practicing? I would just pronounce the Polish word for the same concept!&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;For example:&lt;/span&gt; “水” would become “woda” rather than “water” or “shuǐ”.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;note-tag note-warning&quot;&gt;Apparently:&lt;/span&gt; I just compartmentalize my vocabulary into “native” and “foreign”, without much nuance beyond that!&lt;/p&gt;

&lt;h3 id=&quot;adjusting-my-english-accent&quot;&gt;Adjusting my English accent&lt;/h3&gt;

&lt;p&gt;At work, we once had several visitors from my team’s other offices. In one instance, at lunch, we had a majority of people with &lt;a href=&quot;https://en.wikipedia.org/wiki/Regional_accents_of_English#Britain_and_Ireland&quot;&gt;British&lt;/a&gt; or &lt;a href=&quot;https://en.wikipedia.org/wiki/Regional_accents_of_English#Australia&quot;&gt;Australian accents&lt;/a&gt; rather than &lt;a href=&quot;https://en.wikipedia.org/wiki/General_American_English&quot;&gt;American&lt;/a&gt; accents (perhaps 3–4 vs 2).&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;note-tag note-warning&quot;&gt;Somewhat embarassingly:&lt;/span&gt; Once the conversation seemed primarily British-/Australian- rather than American-accented, I found myself adopting their accents without voluntarily deciding to do so 😬:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I had said e.g. “queue” instead of “line”.&lt;/li&gt;
  &lt;li&gt;I switched to using &lt;a href=&quot;https://en.wikipedia.org/wiki/High_rising_terminal&quot;&gt;rising inflection&lt;/a&gt; for my questions rather than falling inflection,&lt;/li&gt;
  &lt;li&gt;Nobody else at the table commented, so I don’t know if they noticed, but perhaps it came off as pretentious 🙃.&lt;/li&gt;
  &lt;li&gt;I don’t recall this ever happening again for my English.
    &lt;ul&gt;
      &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;At least:&lt;/span&gt; Not to such an alarming degree, anyways. I do naturally adopt various other speech patterns, code-switch, etc.&lt;/li&gt;
      &lt;li&gt;My team lead at the time (whom I saw most days, as was normal in the Before Times) had a British accent:
        &lt;ul&gt;
          &lt;li&gt;So I had probably ingested it passively over time,&lt;/li&gt;
          &lt;li&gt;but I’d never voluntarily or involuntarily mimicked it.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;making-others-polish-accents-worse&quot;&gt;Making others’ Polish accents worse&lt;/h3&gt;

&lt;p&gt;In &lt;a href=&quot;https://en.wikipedia.org/wiki/English_language&quot;&gt;English&lt;/a&gt;, out of enthusiasm, I’ll often lengthen certain vowel sounds, such as &lt;span style=&quot;white-space: nowrap&quot;&gt;“hi-iii”&lt;/span&gt; or &lt;span style=&quot;white-space: nowrap&quot;&gt;“tha-ank you-uu”&lt;/span&gt;. Without consciously considering it, I carried over the same behavior to &lt;a href=&quot;https://en.wikipedia.org/wiki/Polish_language&quot;&gt;Polish&lt;/a&gt;, such as &lt;span style=&quot;white-space: nowrap&quot;&gt;“cze-eść”&lt;/span&gt; (&lt;a href=&quot;https://en.wiktionary.org/wiki/cze%C5%9B%C4%87#Polish&quot;&gt;&lt;em&gt;cześć&lt;/em&gt;&lt;/a&gt;, “hello”) or &lt;span style=&quot;white-space: nowrap&quot;&gt;“dzięku-uję-ę”&lt;/span&gt; (&lt;a href=&quot;https://en.wiktionary.org/wiki/dzi%C4%99kuj%C4%99&quot;&gt;&lt;em&gt;dziękuję&lt;/em&gt;&lt;/a&gt;, “thank you”).&lt;/p&gt;

&lt;p&gt;My wife and brother-in-law have informed me that this is simply not done in Polish, and, in fact, I have started to poison their own speech patterns 😈.&lt;/p&gt;

&lt;h3 id=&quot;failing-to-improve-my-polish-accent&quot;&gt;Failing to improve my Polish accent&lt;/h3&gt;

&lt;p&gt;I learned &lt;a href=&quot;https://en.wikipedia.org/wiki/Polish_language&quot;&gt;Polish&lt;/a&gt; at a basic level to communicate with my wife’s family. When speaking to them in Polish, particularly her parents, I don’t tend to pick up their accent or pronunciation.&lt;/p&gt;

&lt;p&gt;But, when abroad, dining with my wife’s cousins (and their respective dinner partners), I noticed involuntary effort on my part to match their pronunciations. I don’t know what the difference is.&lt;/p&gt;

&lt;div class=&quot;note-block &quot;&gt;
          &lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;To conjecture:&lt;/span&gt; It’s hard to decide whether it’s primarily due to low engagement (and compensating to make up for it) or high engagement (encouraging me to absorb more). Maybe a linguist would know. Some ideas:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I think my wife’s parents speak more quickly than average. It tends to be harder for me to follow/understand them.
    &lt;ul&gt;
      &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;Maybe:&lt;/span&gt; I can’t “hear” the pronunciation as well when the speech is quicker.&lt;/li&gt;
      &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;Maybe:&lt;/span&gt; I need to expend more mental effort on semantic comprehension, and have less available mental throughput to spend on comprehending and analyzing our styles, and matching mine to theirs.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;Maybe:&lt;/span&gt; I talk about more engaging or interesting topics (to me) with people my own age, somehow motivating me to analyze the pronunciation more.&lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;Maybe:&lt;/span&gt; I’m naturally more engaged with unfamiliar pronunciations and accents, and perhaps don’t “hear” familiar pronunciation/accents anymore.&lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;Maybe:&lt;/span&gt; Some specific accents are easier for me to analyze and match.&lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;Maybe:&lt;/span&gt; The accents of Polish speakers in America have integrated the ambient phonology somewhat, thus causing the accents to stand out less to me, to the point where I don’t feel the inclination to match them.&lt;/li&gt;
&lt;/ul&gt;

        &lt;/div&gt;

&lt;h2 id=&quot;grammar&quot;&gt;Grammar&lt;/h2&gt;

&lt;h3 id=&quot;urdu-gender-confusion&quot;&gt;Urdu gender confusion&lt;/h3&gt;

&lt;p&gt;I grew up in &lt;a href=&quot;https://en.wikipedia.org/wiki/Michigan&quot;&gt;Michigan&lt;/a&gt;, with my immediate family &lt;a href=&quot;https://en.wikipedia.org/wiki/History_of_Middle_Eastern_people_in_Metro_Detroit&quot;&gt;having settled there&lt;/a&gt;. I was raised with primarily &lt;a href=&quot;https://en.wikipedia.org/wiki/English_language&quot;&gt;English&lt;/a&gt; and secondarily &lt;a href=&quot;https://en.wikipedia.org/wiki/Urdu&quot;&gt;Urdu&lt;/a&gt; as native languages.&lt;/p&gt;

&lt;div class=&quot;note-block &quot;&gt;
          &lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;Additional background:&lt;/span&gt; I always spoke Urdu haltingly:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Both of my parents spoke English fluently, but would usually speak Urdu at home.&lt;/li&gt;
  &lt;li&gt;My vocabulary was very limited,&lt;/li&gt;
  &lt;li&gt;but I largely understood the syntax in that intuitive first-language way.
    &lt;ul&gt;
      &lt;li&gt;Presumably due to having acquired it during the &lt;a href=&quot;https://en.wikipedia.org/wiki/Critical_period_hypothesis&quot;&gt;critical period&lt;/a&gt;.&lt;/li&gt;
      &lt;li&gt;My brain will parse utterances I overhear into syntax trees “in the background”:
        &lt;ul&gt;
          &lt;li&gt;without deliberately choosing to expend mental effort,&lt;/li&gt;
          &lt;li&gt;and despite the fact that I don’t understand almost all of the words.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;This is unlike &lt;a href=&quot;https://en.wikipedia.org/wiki/Polish_language&quot;&gt;Polish&lt;/a&gt;, which I learned as a second language to a basic conversational level.&lt;/li&gt;
      &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;Note:&lt;/span&gt; While I had a solid grasp on the &lt;em&gt;syntax&lt;/em&gt;, I had a poor grasp of the &lt;em&gt;grammar&lt;/em&gt;, which involves semantic &lt;a href=&quot;https://en.wikipedia.org/wiki/Agreement_(linguistics)&quot;&gt;agreement&lt;/a&gt;; see later.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I was never literate in Urdu.&lt;/p&gt;

  &lt;/li&gt;
&lt;/ul&gt;

        &lt;/div&gt;

&lt;p&gt;My mother would take my sister and I to visit our extended family in &lt;a href=&quot;https://en.wikipedia.org/wiki/Pakistan&quot;&gt;Pakistan&lt;/a&gt; over &lt;a href=&quot;https://en.wikipedia.org/wiki/Summer_vacation&quot;&gt;summer break&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Everyone lived in one large home and spoke Urdu.&lt;/li&gt;
  &lt;li&gt;During the day:
    &lt;ul&gt;
      &lt;li&gt;The women would stay at home to do the housework and childcare, or leave for errands.&lt;/li&gt;
      &lt;li&gt;The children would stay at home. The other children were girls (my sister and cousin).&lt;/li&gt;
      &lt;li&gt;The men would commute to their respective workplaces in the morning and return in the evening.
        &lt;ul&gt;
          &lt;li&gt;One of my uncles would actually stay in another city during the workweek, and only come home for the weekends! That seems uncommon in the States.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;span class=&quot;note-tag note-info&quot;&gt;Consequently:&lt;/span&gt; The majority of my language-acquisition time was spent among exclusively women.&lt;/p&gt;

&lt;p&gt;In Urdu, &lt;a href=&quot;https://en.wikibooks.org/wiki/Urdu/Verbs&quot;&gt;verbs are conjugated&lt;/a&gt; according the subject’s &lt;a href=&quot;https://en.wikipedia.org/wiki/Grammatical_gender&quot;&gt;grammatical gender&lt;/a&gt; (but see below). Despite my having masculine &lt;a href=&quot;https://en.wikipedia.org/wiki/Pronoun&quot;&gt;pronouns&lt;/a&gt;, I primarily observed speakers with feminine pronouns, and hence I learned all of the feminine forms instead.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;note-tag note-warning&quot;&gt;That is:&lt;/span&gt; I only knew how to refer to myself as a woman!&lt;/p&gt;

&lt;div class=&quot;note-block &quot;&gt;
          &lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;To elaborate:&lt;/span&gt; Here’s some more details on the grammar:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;For example:&lt;/span&gt; The first-person singular pronoun is always &lt;a href=&quot;https://en.wikibooks.org/wiki/Urdu/Pronouns&quot;&gt;“mai”&lt;/a&gt;. But to say “I do”, one would say either “mai karta hun” (masculine) or “mai karti hun” (feminine). I only learned the latter form.&lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;Furthermore:&lt;/span&gt; Urdu has &lt;a href=&quot;https://en.wikipedia.org/wiki/Split_ergativity#Hindi%E2%80%93Urdu&quot;&gt;split ergativity&lt;/a&gt;, so, under specific conditions, the verb agrees with the object instead of the subject.
    &lt;ul&gt;
      &lt;li&gt;This would have been completely inscrutable to me as a child.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;Truly:&lt;/span&gt; The &lt;a href=&quot;https://en.wikipedia.org/wiki/Poverty_of_the_stimulus&quot;&gt;poverty of the stimulus&lt;/a&gt; lays in shambles.&lt;/p&gt;

        &lt;/div&gt;

&lt;h3 id=&quot;polish-gender-confusion&quot;&gt;Polish gender confusion&lt;/h3&gt;

&lt;p&gt;&lt;span class=&quot;note-tag note-info&quot;&gt;Amusingly:&lt;/span&gt; Like Urdu, Polish also conjugates some verbs according to the speaker’s gender. Also like Urdu, I mis-trained the gender of some verb forms based on my available practice partners!&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;I learned/trained primarily with my now-wife, as well as her Polish friends who lived in the same building as us.&lt;/li&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;Consequently:&lt;/span&gt; I overtrained the feminine second-person verb forms, and had to untrain them to speak correctly when encountering a non-feminine audience.
    &lt;ul&gt;
      &lt;li&gt;Unlike Urdu, I knew the correct grammar. I had just built incorrect habits and heuristics.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;urdu-grammar-confusion&quot;&gt;Urdu grammar confusion&lt;/h3&gt;

&lt;p&gt;Despite having acquired some facets of Urdu as a first language, I failed to acquire several critical features:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;span class=&quot;note-tag&quot;&gt;For example:&lt;/span&gt; I don’t have any instinctual noun classification/&lt;a href=&quot;https://en.wikipedia.org/wiki/Agreement_(linguistics)&quot;&gt;agreement&lt;/a&gt; beyond what exists in English,&lt;/li&gt;
  &lt;li&gt;which is unfortunate, because it might have been nice to automatically have instinctive noun agreement in other languages, such as &lt;a href=&quot;https://en.wikipedia.org/wiki/Spanish_language&quot;&gt;Spanish&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;span class=&quot;note-tag&quot;&gt;For whatever reason:&lt;/span&gt; My younger sister learned Urdu more fluently than I did.&lt;/p&gt;

&lt;h2 id=&quot;related-posts&quot;&gt;Related posts&lt;/h2&gt;

&lt;p&gt;The following are hand-curated posts which you might find interesting.&lt;/p&gt;

&lt;table class=&quot;related-posts&quot;&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th&gt;Date&lt;/th&gt;
    &lt;th&gt;&lt;/th&gt;
    &lt;th&gt;Title&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;


  &lt;tr&gt;
    &lt;td&gt;24&amp;nbsp;Aug&amp;nbsp;2016&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/steno-journal/&quot;&gt;Steno Journal: Weeks 1-12&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;06&amp;nbsp;Sep&amp;nbsp;2016&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/steno-adventures-part-1/&quot;&gt;Stenography adventures with Plover and the Ergodox EZ, part 1&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;06&amp;nbsp;Sep&amp;nbsp;2016&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/steno-adventures-part-2/&quot;&gt;Stenography adventures with Plover and the Ergodox EZ, part 2&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;13&amp;nbsp;Oct&amp;nbsp;2016&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/messenger-conversation-macros/&quot;&gt;Analyzing all of my Messenger conversations to create conversational macros&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;21&amp;nbsp;May&amp;nbsp;2018&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/my-steno-system/&quot;&gt;My steno system&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;02&amp;nbsp;Jan&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/on-bullet-points/&quot;&gt;On bullet points&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;11&amp;nbsp;Jan&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/interactive-blogs/&quot;&gt;Interactive blogs&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;05&amp;nbsp;Apr&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      (this&amp;nbsp;post)
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/language-learning-anecdotes/&quot;&gt;Language-learning anecdotes&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;03&amp;nbsp;Apr&amp;nbsp;2026&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/please-or-thank-you/&quot;&gt;🙏: please or thank you?&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Want to see more of my posts? Follow me &lt;a href=&quot;https://twitter.com/arxanas&quot;&gt;on Twitter&lt;/a&gt; or subscribe &lt;a href=&quot;/feed.xml&quot;&gt;via RSS&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;comments&quot;&gt;Comments&lt;/h2&gt;

&lt;ul&gt;



&lt;/ul&gt;

&lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/github-comment-links.js&quot;&gt;&lt;/script&gt;

&lt;div id=&quot;disqus_thread&quot;&gt;&lt;/div&gt;
&lt;script&gt;

/**
 *  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
 *  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables */
var disqus_config = function () {
    this.page.url = &quot;https://blog.waleedkhan.name/language-learning-anecdotes/&quot;;
    this.page.identifier = &quot;language-learning-anecdotes/&quot;;
};

(function() { // DON&apos;T EDIT BELOW THIS LINE
    var d = document, s = d.createElement(&apos;script&apos;);
    s.src = &apos;//waleedkhan-name.disqus.com/embed.js&apos;;
    s.setAttribute(&apos;data-timestamp&apos;, +new Date());
    (d.head || d.body).appendChild(s);
})();
&lt;/script&gt;

&lt;noscript&gt;Please enable JavaScript to view the &lt;a href=&quot;https://disqus.com/?ref_noscript&quot;&gt;comments powered by Disqus.&lt;/a&gt;&lt;/noscript&gt;

</description>
        <pubDate>Sat, 05 Apr 2025 00:00:00 +0000</pubDate>
        <link>https://blog.waleedkhan.name/language-learning-anecdotes/</link>
        <guid isPermaLink="true">https://blog.waleedkhan.name/language-learning-anecdotes/</guid>
        
        <category>linguistics</category>
        
        <category>learning</category>
        
        
      </item>
    
      <item>
        <title>Incremental processing with Watchman — don&apos;t daemonize that build!</title>
        <description>&lt;div class=&quot;publication-notes&quot;&gt;
  &lt;table&gt;
    &lt;tr&gt;
      &lt;td&gt;Intended audience&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Build system developers and enthusiasts&lt;/li&gt;
        &lt;li&gt;Developers of tooling that doesn&apos;t fit into the build system&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Origin&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Lessons synthesized from 8-year career in incremental computation and build systems&lt;/li&gt;
        &lt;li&gt;Originally presented at &lt;a href=&quot;https://groups.io/g/Seattle-Build-Enthusiasts/topic/seattle_build_enthusiast/108248211&quot;&gt;Seattle Build Enthusiasts Meetup (Fall 2024)&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mood&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Instructive&lt;/li&gt;
        &lt;li&gt;Passionate that people should avoid my mistakes&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/div&gt;

&lt;div class=&quot;series-and-toc&quot;&gt;

  &lt;p class=&quot;toc-header&quot;&gt;Table of contents:&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#slides&quot; id=&quot;markdown-toc-slides&quot;&gt;Slides&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#speaker-notes&quot; id=&quot;markdown-toc-speaker-notes&quot;&gt;Speaker notes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#related-posts&quot; id=&quot;markdown-toc-related-posts&quot;&gt;Related posts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#comments&quot; id=&quot;markdown-toc-comments&quot;&gt;Comments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;h2 id=&quot;slides&quot;&gt;Slides&lt;/h2&gt;

&lt;p&gt;This talk was given at the &lt;a href=&quot;https://groups.io/g/Seattle-Build-Enthusiasts/topic/seattle_build_enthusiast/108248211&quot;&gt;Seattle Build Enthusiasts Fall 2024 meetup&lt;/a&gt; (&lt;time datetime=&quot;2024-10-02&quot;&gt;2024-10-02&lt;/time&gt;).&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Deleted slides with extra information are available at the end.&lt;/li&gt;
  &lt;li&gt;Direct Google Slides link: &lt;a href=&quot;https://docs.google.com/presentation/d/1geE7rGTCpIk5UTjH-sw9aL_Zszi1ncCRnFi55t0CDlk/&quot;&gt;https://docs.google.com/presentation/d/1geE7rGTCpIk5UTjH-sw9aL_Zszi1ncCRnFi55t0CDlk/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;iframe-container&quot;&gt;
&lt;iframe src=&quot;https://docs.google.com/presentation/d/e/2PACX-1vRSwp0EbldxIkD72bzEoKDHzLzah95w2ABa1doSwqeKTMneSz1_O1DqNNb7qJCi4xXbPdp1A7Blk5PV/embed?start=false&amp;amp;loop=false&amp;amp;delayms=3000&quot; frameborder=&quot;0&quot; width=&quot;960&quot; height=&quot;569&quot; allowfullscreen=&quot;true&quot; mozallowfullscreen=&quot;true&quot; webkitallowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&quot;speaker-notes&quot;&gt;Speaker notes&lt;/h2&gt;

&lt;p&gt;I’ve transcribed the speaker notes below, which correspond to the intended transcript.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;(1) Hi everyone, thanks for attending. My name is Waleed Khan and today I’ll talking about building incremental systems on top of the Watchman file-watching service.&lt;/p&gt;

&lt;p&gt;(2) If you want to follow along with the slides at your own pace, you should take this opportunity to open them up now. You can visit my website waleedkhan.name and you can navigate to the blog post with these slides.&lt;/p&gt;

&lt;p&gt;(2) A little about me: I used to work at Meta on the Hack programming language, and Twitter on their Scala build tooling, as well as source control. Most recently, I’ve been working at Hudson River Trading on their C++ and Python build system.&lt;/p&gt;

&lt;p&gt;(2) My career has been spent shortening the developer feedback loop, largely by implementing custom incremental build systems. Watchman is an open-source file-watching tool that can be used as a high-level primitive for those kinds of incremental systems. In this talk, I’d like to share some design ideas for implementing similar incremental build systems.&lt;/p&gt;

&lt;p&gt;(3) There’s two parts to this talk.&lt;/p&gt;

&lt;p&gt;(3) In the first part, I’ll talk about the justification for Watchman, the problems it solves, and the abstractions it offers.&lt;/p&gt;

&lt;p&gt;(3) In the second part, I’ll just be listing several helpful tips and pieces of advice for implementing systems on top of Watchman.&lt;/p&gt;

&lt;p&gt;(4) Let’s give an example of what kind of problems might be solved by Watchman.&lt;/p&gt;

&lt;p&gt;(5) I mentioned I’ve spent a lot of time working on custom incremental build tooling. When would you need that? I’ll give a few examples that I’ve personally used Watchman for in order to implement custom incremental build systems.&lt;/p&gt;

&lt;p&gt;(5) One example is file syncing. A lot of companies have been embracing “remote development” workflows, by which I mean your code is sent from your local machine to be compiled and run on a remote machine somewhere else. Many companies have invented solutions to sync your local source code to the remote machine as part of the development process. File syncing may be outside the scope of your build system, or it may just not be integrated yet.&lt;/p&gt;

&lt;p&gt;(5) Another example is dynamic dependencies in the build system. Many companies have Bazel, but those kind of build systems may not be able to express certain kinds of dynamic dependencies. For Bazel, there’s a tool called “Gazelle” which is used to programmatically generate BUILD files, which are then read by Bazel in the next build. There may be some fans of the buck2 build system in the audience who scoff at this, given its native support for dynamic dependencies. [We can’t all use buck2, unfortunately.]&lt;/p&gt;

&lt;p&gt;(5) Less common, but still important, is building latency-sensitive IDE services. In those cases, the build system may not be able to generate the appropriate build artifacts, possibly because of dynamic dependencies, or the overhead may be too much to serve queries in real-time as the code changes. In those cases, more specialized infrastructure is necessary.&lt;/p&gt;

&lt;p&gt;(6) For demonstration, I cloned the “nixpkgs” repository from GitHub. It’s not a huge repository by industry standards, with 43k files in the working copy, but it’s enough that walking the entire repository is slow on my machine.&lt;/p&gt;

&lt;p&gt;(7) For example, on my machine, just visiting all the files in the repository without reading them takes around 750ms. And that’s without any additional processing. If you wanted to actually perform a build, you could expect significantly more overhead.&lt;/p&gt;

&lt;p&gt;(8) If we were doing remote development, we might simply use a tool like rsync to efficiently sync all the local files to the remote machine.&lt;/p&gt;

&lt;p&gt;(9) If we just naively run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rsync&lt;/code&gt; on my machine, from one directory to another, it takes around 1.4 seconds for a no-op sync. That is, if I haven’t changed any files, it takes 1.4 seconds to do nothing. That’s because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rsync&lt;/code&gt; has to walk and hash all files on the local and remote sides to avoid transferring data that’s already present on the remote.&lt;/p&gt;

&lt;p&gt;(10) So what can we do? I’ll give a few ideas.&lt;/p&gt;

&lt;p&gt;(10) One is to maintain a persistent data store to try to avoid reading files that probably haven’t changed, similarly to how Git maintains its index. This is helpful but still scales linearly with the size of the repository, so it’s not necessarily sufficient by itself.&lt;/p&gt;

&lt;p&gt;(10) Another is to launch a background process (a “daemon”) that waits for the operating system to notify it about changed files, and then syncs the files preemptively. This is a workable, but I’ll argue hugely complex, solution.&lt;/p&gt;

&lt;p&gt;(10) Now I have to explain the subtitle for this talk, “don’t daemonize that build!”.&lt;/p&gt;

&lt;p&gt;(11) By “daemon”, I basically mean a background process that runs independently of explicit user interaction. Oftentimes, I see daemonization proposed as a natural next step when a build task is starting to take too long to run end-to-end.&lt;/p&gt;

&lt;p&gt;(12) There are a few common reasons to want to daemonize.&lt;/p&gt;

&lt;p&gt;(12) One is to keep previously-computed data in memory without having to recompute it or reload it. We’ll discuss some ways to address that without a daemon in this talk.&lt;/p&gt;

&lt;p&gt;(12) Another one is just the fact that operating system APIs require that you subscribe to filesystem notifications in a long-running process in order to actually receive the events as they happen. We’ll discuss how Watchman fits in here.&lt;/p&gt;

&lt;p&gt;(12) One last thing is specifically to reduce startup latency due to the choice of programming language or runtime. This talk unfortunately doesn’t address that issue.&lt;/p&gt;

&lt;p&gt;(13) So why not daemonize? I’m not going to read off every point on this slide, but, basically, it’s a ton of incidental complexity, by which I mean complexity you have to deal with that’s not really helping you accomplish your engineering goals.&lt;/p&gt;

&lt;p&gt;(13) Daemonization is an attractive solution to improve performance at first glance, but it incurs a lot of unexpected and insidious costs down the road.&lt;/p&gt;

&lt;p&gt;(14) So my recommendation is: avoid daemonizing your build tasks for as long as possible.&lt;/p&gt;

&lt;p&gt;(15) This talk is about using Watchman, a file-watching service, as a way to abstract out a lot of the incidental complexity and help you focus on tackling your actual engineering problems.&lt;/p&gt;

&lt;p&gt;(15) You can use Watchman as a high-level primitive in custom build systems. Watchman is a great tool in my opinion because it can completely change the design of your system for the better. And, in fact, even if you do one day decide to daemonize your system, you may benefit a lot from continuing to use Watchman and designing your system around the abstractions that it offers.&lt;/p&gt;

&lt;p&gt;(15) I’ll also mention that other systems offer similar interfaces. For example, the build system buck2 supports a similar interface via its “incremental actions”, and the Language Server Protocol supports subscribing to file-watching notifications.&lt;/p&gt;

&lt;p&gt;(16) So let’s get started with Watchman.&lt;/p&gt;

&lt;p&gt;(17) The first thing we have to do is ask Watchman to start watching a directory with the watch-project command. Easy enough.&lt;/p&gt;

&lt;p&gt;(18) Next, let’s issue a basic query.&lt;/p&gt;

&lt;p&gt;(18) Notice that we’re calling Watchman via the command-line interface with a JSON payload. This is already a huge improvement from an engineering perspective, just because it’s way easier to experiment with this rather than, for example, the inotify syscall interface for Linux in a long-running C program.&lt;/p&gt;

&lt;p&gt;(18) Here’s, I’m piping in a JSON payload to standard input using bash. The subcommand is query, the watched directory is the current directory, which is nixpkgs in this case, and the last parameter is the query options.&lt;/p&gt;

&lt;p&gt;(18) By default, if we don’t pass any query options, Watchman returns data about all of the files in the watched directory. In this case, I piped the result to jq to just get the first file for demonstration purposes. We’ll see how to do incremental processing shortly.&lt;/p&gt;

&lt;p&gt;(18) In the result, you can see the filename — in this case, it’s happens to be the .git directory — and a few other metadata fields that are returned with the default query options. You can ask Watchman for a different set of metadata if you want.&lt;/p&gt;

&lt;p&gt;(19) Now let’s look at the metadata returned by Watchman for the query itself, rather than the metadata for individual files.&lt;/p&gt;

&lt;p&gt;(19) There’s two main things to look at. The first one is clock, which is the key to working with Watchman. Basically, this value represents the point in time immediately after the query returned. I can use this value in subsequent queries to get a list of changed files since that previous clock ID or point in time. It’s very similar to a database cursor in that respect.&lt;/p&gt;

&lt;p&gt;(19) The second one is is_fresh_instance, which basically means that I, the caller, may have missed some filesystem notifications since the last time I queried Watchman. We’ll discuss the implications of this in more detail later.&lt;/p&gt;

&lt;p&gt;(20) To use the clock ID, you can add since to your query with your previous clock ID, and Watchman will only return files that have changed since that clock ID.&lt;/p&gt;

&lt;p&gt;(20/21) In this example, I made an initial query and got an initial clock ID, and then I immediately use it in the next query via the since field, and Watchman returns an empty files array, meaning that no files have changed since that clock ID. Then I create a new file called foo and query since the same clock ID as before, and this time Watchman returns the file foo.&lt;/p&gt;

&lt;p&gt;(22) Watchman will also tell you about files that have been deleted since the clock ID. You can see here that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exists&lt;/code&gt; is now set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;, meaning Watchman thinks that the file has been deleted on disk since the last clock ID. We often need this for the correctness of build tasks in order to invalidate data corresponding to deleted files.&lt;/p&gt;

&lt;p&gt;(23) And just a few more features. Here, we’re explicitly setting metadata fields we’re interested in, including the SHA1 content hash for each file. If you want, Watchman can compute and store the content hashes for you in memory, which can be convenient for caching as discussed later.&lt;/p&gt;

&lt;p&gt;(23) And we can provide arbitrarily complex expressions to limit what kinds of entries Watchman returns. In this case, we’re limiting the returned entries to files of type f, which is to say that they’re real files, and not directories or symlinks or something else, mainly so that I can show an example of Watchman returning the content hash. And we’re excluding files under the .git directory, so that Watchman returns a real source file instead of some random Git metadata file.&lt;/p&gt;

&lt;p&gt;(23) In the result, we can see that Watchman returned some Main.java file along with its metadata and hash as the first file.&lt;/p&gt;

&lt;p&gt;(24) So let’s show a real example of working with Watchman.&lt;/p&gt;

&lt;p&gt;(25) This is a bash script to implement incremental rsync. Instead of unconditionally syncing all files, we’ll sync only the files that have changed since the last sync.&lt;/p&gt;

&lt;p&gt;(25) First, we start watching the project. We load the previous clock ID from persistent state on disk, or a default clock ID if it’s not available. We query Watchman, and based on is_fresh_instance, we do either a full sync or an incremental sync.&lt;/p&gt;

&lt;p&gt;(25) We have to do a full sync if it’s the first sync, or if we missed any filesystem updates, to bring the remote side back to a known state. That includes deleting any extra files on the remote side with rsync’s –delete-after option.&lt;/p&gt;

&lt;p&gt;(25) In the incremental sync case, we limit rsync to syncing only the changed set of files. Note that this set may include deleted files. We pass –delete-missing-args to rsync to indicate that a missing file is not an error, but that we actually want rsync to delete that file from the remote side.&lt;/p&gt;

&lt;p&gt;(25) Then we run rsync, and we make sure to only save the new Watchman clock if it succeeds. You can think of this as similar to “committing” a database transaction. If the rsync failed for some reason, then the next attempt will try to sync all of the old changed files again, plus any newly-changed files.&lt;/p&gt;

&lt;p&gt;(26) If we try it out, you can see that it’s around 4.5x times faster than the naive rsync solution, with not a lot of development effort. And I want to emphasize that we could definitely improve on the efficiency of the script.&lt;/p&gt;

&lt;p&gt;(27) Hopefully you’re starting to see the utility of Watchman as a primitive for your build tasks by now.&lt;/p&gt;

&lt;p&gt;(28) There’s much more detail about Watchman advantages and disadvantages in the online slides, but I’ll just include this snippet from a blog post by an engineer at Stripe. [read quote aloud]&lt;/p&gt;

&lt;p&gt;(29) The real advantage of Watchman is that encourages you to design and implement correct systems.&lt;/p&gt;

&lt;p&gt;(29) If you think about it, your filesystem is a kind of distributed system. You can send messages to files in your filesystem and ask them about their current state, but it’s always possible that someone else will come and modify a file when you’re not looking. So, fundamentally, it’s much harder to work with a filesystem than just a hash map in memory, for example.&lt;/p&gt;

&lt;p&gt;(29) The Watchman design lifts the level of abstraction from “synchronizing against a shared mutable data store” to “processing an ordered, replayable stream of events”. And that’s the big advantage of using Watchman. It’s just a nice bonus that it has a bunch of other practically-useful features for building incremental systems.&lt;/p&gt;

&lt;p&gt;(30) I’ll talk a little bit more about a couple of features of the Watchman design.&lt;/p&gt;

&lt;p&gt;(31) Practically-speaking Watchman supports three main workflows:&lt;/p&gt;

&lt;p&gt;(31) One is where you run a program which queries and processes changes on-demand, which is the model we’ve been talking about so far, and that can take you a long way.&lt;/p&gt;

&lt;p&gt;(31) But with the trigger subcommand, you can invert the control flow and ask Watchman to call you when something changes. This is very useful if you want to process changes immediately, instead of when the user asks you to. Watchman will ensure that there’s only one instance of your command running at once, and it can also do things like batch changes for efficiency.&lt;/p&gt;

&lt;p&gt;(31) Or you can subscribe to changes in a long-running process, similarly to using the operating system APIs, but at a higher level of abstraction. So you can still use Watchman with a daemonized build.&lt;/p&gt;

&lt;p&gt;(32) Underneath the hood, you can consider Watchman as maintaining a linked hashmap in memory, from path to file metadata. When Watchman receives a filesystem notification for a path, it looks up that entry, updates the metadata, and bumps it to the head of the list in constant time. When you ask Watchman for recent changes, it iterates the list and returns entries until it reaches one older than the clock ID you provided, then terminates the traversal.&lt;/p&gt;

&lt;p&gt;(33) Next, I’ll shift gears to giving practical advice to using Watchman. But before that, I’d like to take any questions that you might have.&lt;/p&gt;

&lt;p&gt;(34) First, I’ll discuss some rules to follow in order to build correct systems on top of Watchman.&lt;/p&gt;

&lt;p&gt;(35) I recommend you read the linked Stack Overflow answer on what is_fresh_instance means. Basically, you need to assume that any persistent state may now be invalid. You have to either clear the persistent state or validate it before using it again.&lt;/p&gt;

&lt;p&gt;(36) Be mindful when using queries that might change. One example is if you construct a query using .gitignore files in the repository, you may have a cyclic dependency between .gitignore files and the query that watches those .gitignore files for changes. If you’re not careful, you might end up in a situation where you can’t detect certain changes to ignored files.&lt;/p&gt;

&lt;p&gt;(37) Like we discussed earlier, the local filesystem is basically a racy distributed system. For example, if Watchman reports a changed file, you may try to read it from disk, and discover that it’s been deleted since the last Watchman query. You should plan to handle that case up-front.&lt;/p&gt;

&lt;p&gt;(38) Now some tips and common patterns for using Watchman.&lt;/p&gt;

&lt;p&gt;(39) One quick algorithmic tip. I recommend that you try to unify your full and incremental processing paths as much as possible. The way to do that is to treat Watchman results not as a set of files to traverse in the filesystem, but instead a set of files to limit your traversal of the filesystem. Here’s an example code snippet: we put all the changed paths into the set paths_to_visit, plus their transitive ancestor directories. If we got is_fresh_instance from Watchman, then we set paths_to_visit equals None and don’t limit traversal at all.&lt;/p&gt;

&lt;p&gt;(40) Then, in the filesystem traversal code, we just check should_visit before processing each path, to ensure that we skip processing most files in the incremental case. It really helps improve the reliability of your incremental system when you have fewer distinct code paths to exercise.&lt;/p&gt;

&lt;p&gt;(41) If you want to store persistent state, then you probably want a mapping from path to metadata, with the ability to update — and invalidate —individual key-value pairs efficiently.&lt;/p&gt;

&lt;p&gt;(41) An easy way is to just store a big JSON blob on disk, read it on startup, make any changes in-memory, and write it on shutdown. This can still be much faster than processing the entire repository contents every time.&lt;/p&gt;

&lt;p&gt;(41) Cleverer alternatives include SQLite, which an on-disk relational database, and RocksDB, which an on-disk key-value store.&lt;/p&gt;

&lt;p&gt;(41) I’ve used all three of these approaches.&lt;/p&gt;

&lt;p&gt;(42) You may need to compute data that’s not indexed directly by path. For example, in an IDE, you may take a file path as a key and compute the list of symbols defined in that file as the value. To support go-to-definition, you would want a mapping in the other direction, from symbol name back to file path.&lt;/p&gt;

&lt;p&gt;(42) Maintaining the reverse mapping is just a general build system problem at this point. One technique I want to point out is that it’s oftentimes feasible to not fully update the reverse mapping and continue to store stale edges. For example, if I store a reverse mapping that says that class Bar is defined in path foo.py, and then I delete foo.py, I might not update the reverse mapping at all. Instead, when I query the path associated with class Bar, my code could actually go re-parse file foo.py and confirm that class Bar is still there before returning it as a result.&lt;/p&gt;

&lt;p&gt;(43) For performance, if you want to keep your persistent state alive across is_fresh_instances, you can use an extra key to confirm that data is still valid. One idea is to store the content hash associated with a file, so that you know that the data is still valid without needing to reprocess the file.&lt;/p&gt;

&lt;p&gt;(43) If you didn’t clear the entire persistent state when getting is_fresh_instance, then keep in mind that the set of paths in your persistent state might include extra stale paths if you don’t explicitly clean them up.&lt;/p&gt;

&lt;p&gt;(44) If your caching scheme is machine-independent, and your build artifact is expensive enough to compute, then it might make sense to rely on a recent precomputed artifact or service as a base, and compute the changes since then. Supposing that I have a recent build artifact at tag warm, I can download it, and then ask source control for the files changed between the warm commit and HEAD, and rely on Watchman from that point onwards. This is what I did to support Hack IDE services at Meta.&lt;/p&gt;

&lt;p&gt;(44) To do this correctly, make sure you have a strategy for handling files deleted since the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;warm&lt;/code&gt; tag. For path-addressable data stores, you can employ a technique like tombstones. For content-addressable data stores, you might effectively handle deletions for free.&lt;/p&gt;

&lt;p&gt;(45) That’s all of the practical items. Thanks everyone for watching! Hopefully you learned something about incremental computation or will consider using Watchman to avoid daemonizing your next build. If you’re following along with the slides, you can go to the next slide to see some of the content I cut.&lt;/p&gt;

&lt;p&gt;(45) If you enjoyed this talk, I’m on the job market currently and would love to continue working in the developer infrastructure and tooling space. You can reach out to me in person or at my email, which is me@waleedkhan.name.&lt;/p&gt;

&lt;p&gt;(45) Now I’ll open up the floor to questions.&lt;/p&gt;

&lt;p&gt;[no notes for deleted slides]&lt;/p&gt;

&lt;h2 id=&quot;related-posts&quot;&gt;Related posts&lt;/h2&gt;

&lt;p&gt;The following are hand-curated posts which you might find interesting.&lt;/p&gt;

&lt;table class=&quot;related-posts&quot;&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th&gt;Date&lt;/th&gt;
    &lt;th&gt;&lt;/th&gt;
    &lt;th&gt;Title&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;


  &lt;tr&gt;
    &lt;td&gt;20&amp;nbsp;Apr&amp;nbsp;2020&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/monotonicity/&quot;&gt;Monotonicity is a halfway point between mutability and immutability&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;19&amp;nbsp;Oct&amp;nbsp;2022&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/build-aware-sparse-checkouts/&quot;&gt;Build-aware sparse checkouts&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;02&amp;nbsp;Oct&amp;nbsp;2024&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      (this&amp;nbsp;post)
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/incremental-watchman/&quot;&gt;Incremental processing with Watchman — don&apos;t daemonize that build!&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;14&amp;nbsp;Aug&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/lithe-less-analysis-with-datalog/&quot;&gt;Lithe, less analysis with Datalog&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;23&amp;nbsp;Aug&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/half-my-work-is-adding-a-cache/&quot;&gt;Half my work is adding a cache&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;30&amp;nbsp;Oct&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/what-if-sql-were-good/&quot;&gt;Datalog DSL detects defective dependency declarations, defanging dodgy development discipline&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Want to see more of my posts? Follow me &lt;a href=&quot;https://twitter.com/arxanas&quot;&gt;on Twitter&lt;/a&gt; or subscribe &lt;a href=&quot;/feed.xml&quot;&gt;via RSS&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;comments&quot;&gt;Comments&lt;/h2&gt;

&lt;ul&gt;



&lt;/ul&gt;

&lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/github-comment-links.js&quot;&gt;&lt;/script&gt;

&lt;div id=&quot;disqus_thread&quot;&gt;&lt;/div&gt;
&lt;script&gt;

/**
 *  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
 *  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables */
var disqus_config = function () {
    this.page.url = &quot;https://blog.waleedkhan.name/incremental-watchman/&quot;;
    this.page.identifier = &quot;incremental-watchman/&quot;;
};

(function() { // DON&apos;T EDIT BELOW THIS LINE
    var d = document, s = d.createElement(&apos;script&apos;);
    s.src = &apos;//waleedkhan-name.disqus.com/embed.js&apos;;
    s.setAttribute(&apos;data-timestamp&apos;, +new Date());
    (d.head || d.body).appendChild(s);
})();
&lt;/script&gt;

&lt;noscript&gt;Please enable JavaScript to view the &lt;a href=&quot;https://disqus.com/?ref_noscript&quot;&gt;comments powered by Disqus.&lt;/a&gt;&lt;/noscript&gt;

</description>
        <pubDate>Wed, 02 Oct 2024 00:00:00 +0000</pubDate>
        <link>https://blog.waleedkhan.name/incremental-watchman/</link>
        <guid isPermaLink="true">https://blog.waleedkhan.name/incremental-watchman/</guid>
        
        <category>build-systems</category>
        
        <category>reprint</category>
        
        <category>software-engineering</category>
        
        
      </item>
    
      <item>
        <title>Patch terminology</title>
        <description>&lt;div class=&quot;publication-notes&quot;&gt;
  &lt;table&gt;
    &lt;tr&gt;
      &lt;td&gt;Intended audience&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Developers of version control systems, specifically &lt;a href=&quot;https://github.com/martinvonz/jj&quot;&gt;jj&lt;/a&gt;.&lt;/li&gt;
        &lt;li&gt;Those interested in the version control pedagogy.&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Origin&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Reprint of &lt;a href=&quot;https://docs.google.com/document/d/1qrv8I_IqRz8CdohZw6VcMssBEetoGmQj33vPz1_XlYI/edit?usp=sharing&quot;&gt;research originally published on Google Docs&lt;/a&gt;.&lt;/li&gt;
        &lt;li&gt;Surveyed five participants from various &quot;big tech&quot; companies (&amp;gt;$1B valuation).&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mood&lt;/td&gt;
      &lt;td&gt;Investigative.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/div&gt;

&lt;div class=&quot;series-and-toc&quot;&gt;

  &lt;p class=&quot;toc-header&quot;&gt;Table of contents:&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#methodology&quot; id=&quot;markdown-toc-methodology&quot;&gt;Methodology&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#results&quot; id=&quot;markdown-toc-results&quot;&gt;Results&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#remarks&quot; id=&quot;markdown-toc-remarks&quot;&gt;Remarks&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusions&quot; id=&quot;markdown-toc-conclusions&quot;&gt;Conclusions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#related-posts&quot; id=&quot;markdown-toc-related-posts&quot;&gt;Related posts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#comments&quot; id=&quot;markdown-toc-comments&quot;&gt;Comments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;h2 id=&quot;methodology&quot;&gt;Methodology&lt;/h2&gt;

&lt;p&gt;Q: I am doing research for a source control project, can you answer what the following nouns mean to you in the context of source control, if anything? (Ordered alphabetically)&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a “change”&lt;/li&gt;
  &lt;li&gt;a “commit”&lt;/li&gt;
  &lt;li&gt;a “patch”&lt;/li&gt;
  &lt;li&gt;a “revision”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;results&quot;&gt;Results&lt;/h2&gt;

&lt;p&gt;P1 (Google, uses Piper + CLs):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Change: a difference in code. What isn’t committed yet.&lt;/li&gt;
  &lt;li&gt;Commit: a cl. Code that is ready to push and has a description along with it&lt;/li&gt;
  &lt;li&gt;Patch: a commit number that that someone made that may or may not be pushed yet […] A change that’s not yours&lt;/li&gt;
  &lt;li&gt;Revision: a change to a commit?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;P2 (big tech, uses GitLab + MRs):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Change: added/removed/updated files&lt;/li&gt;
  &lt;li&gt;Commit: a group of related changes with a description&lt;/li&gt;
  &lt;li&gt;Patch: a textual representation of changes between two versions&lt;/li&gt;
  &lt;li&gt;Revision: a version of the repository, like the state of all files after a set of commits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;P3 (Google, uses Fig + CLs):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Change: A change to me is any difference in code. Uncommitted to pushed. I’ve heard people say I’ve pushed the change.&lt;/li&gt;
  &lt;li&gt;Commit: A commit is a saved code diff with a description.&lt;/li&gt;
  &lt;li&gt;Patch: A patch is a diff between any two commits how to turn commit a into into b.&lt;/li&gt;
  &lt;li&gt;Revision: Revisions idk. I think at work they are snapshots of a code base so all changes at a point in time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;P4 (Microsoft, uses GitHub + PRs):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Change: the entire change I want to check into the codebase this can be multiple commits but it’s what I’m putting up for review&lt;/li&gt;
  &lt;li&gt;Commit: a portion of my change&lt;/li&gt;
  &lt;li&gt;Patch: a group of commits or a change I want to use on another repo/branch&lt;/li&gt;
  &lt;li&gt;Revision: An id for a group of commits or a single commit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;P5 (big tech, uses GitHub + PRs):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Change: your update to source files in a repository&lt;/li&gt;
  &lt;li&gt;Commit: description of change&lt;/li&gt;
  &lt;li&gt;Patch: I don’t really use this but I would think a quick fix (image, imports, other small changes etc)&lt;/li&gt;
  &lt;li&gt;Revision: some number or set of numbers corresponding to change&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;remarks&quot;&gt;Remarks&lt;/h2&gt;

&lt;p&gt;Take-aways:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Change: People largely don’t think of a “change” as an physical object, rather just a diff or abstract object.
    &lt;ul&gt;
      &lt;li&gt;It can potentially range from uncommitted to committed to pushed (P1–P5).&lt;/li&gt;
      &lt;li&gt;Unlike others, P4 thinks of it as a &lt;em&gt;larger&lt;/em&gt; unit than a commit (more like a “review”), probably due to the GitHub PR workflow.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Commit: Universally, commits are considered to have messages. However, the interpretation of a commit as a &lt;em&gt;snapshot&lt;/em&gt; vs &lt;em&gt;diff&lt;/em&gt; appears to be implicit (compare P2’s “commit” vs “revision”).&lt;/li&gt;
  &lt;li&gt;Patch: Split between interpretations:
    &lt;ul&gt;
      &lt;li&gt;Either it represents a &lt;em&gt;diff&lt;/em&gt; between two versions of the code (P2, P3).&lt;/li&gt;
      &lt;li&gt;Or it’s a higher-level interpretation of a patch as a &lt;em&gt;transmissible change&lt;/em&gt;. Particularly for getting a change from someone else (P1), but can also refer to a change that you want to use on a different branch (P4).&lt;/li&gt;
      &lt;li&gt;P5 merely considers a “patch” to be a “small fix”, which is also a generally accepted meaning, although a little imprecise in terms of source control (refers to the &lt;em&gt;intention&lt;/em&gt; of the patch, rather than the mechanics of the patch itself).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Revision: This is really interesting. The underlying mental models are very different, but the semantic implications end up aligning, more so than for the term “commit”!
    &lt;ul&gt;
      &lt;li&gt;P1: Not a specific source control term, just “the effect of revising”.&lt;/li&gt;
      &lt;li&gt;P2, P3: Effect of “applying all commits”. This implies that they consider “commits” as &lt;em&gt;diffs&lt;/em&gt; and “revisions” as &lt;em&gt;snapshots&lt;/em&gt;.&lt;/li&gt;
      &lt;li&gt;P4, P5, Some notions that it’s specifically the &lt;em&gt;identifier&lt;/em&gt; of a change/commit. It’s something that you can reference or send to others.&lt;/li&gt;
      &lt;li&gt;Surprisingly to me, P2–P5 actually all essentially agree that “revision” means a &lt;em&gt;snapshot&lt;/em&gt; of the codebase. The mental models are quite different (“accumulation of diffs” vs “stable identifier”) but they refer to the same ultimate result: a specific state of the codebase (…or a way to refer to it — what’s in a name?). This is essentially the opposite of “commit”, where everyone &lt;em&gt;thinks&lt;/em&gt; that they agree on what they are, but they’re actually split — roughly evenly? — into &lt;em&gt;snapshot&lt;/em&gt; vs &lt;em&gt;diff&lt;/em&gt; mental models.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h2&gt;

&lt;p&gt;Conclusions for &lt;a href=&quot;https://github.com/martinvonz/jj&quot;&gt;jj&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We already knew that “change” is a difficult term, syntactically speaking. It’s also now apparent that it’s semantically unclear. Only P4 thought of it as a “reviewable unit”, which would probably most closely match the jj interpretation. We should switch away from this term.&lt;/li&gt;
  &lt;li&gt;People are largely settled on what “commits” are in the ways that we thought.
    &lt;ul&gt;
      &lt;li&gt;There are two main mental models, where participants appear to implicitly consider them to be either snapshots or diffs, as we know.&lt;/li&gt;
      &lt;li&gt;They &lt;em&gt;have&lt;/em&gt; to have messages according to participants (unlike in jj, where a commit/change may not yet have a message).
        &lt;ul&gt;
          &lt;li&gt;It’s possible this is an artifact of the Git mental model, rather than fundamental. We don’t see a lot of confusion when we tell people “your commits can have empty messages”.&lt;/li&gt;
          &lt;li&gt;I think the real implication is that the set of changes is packaged/finalized into &lt;em&gt;one unit&lt;/em&gt;, as opposed to “changes”, which might be in flux or not properly packaged into one unit for publishing/sharing.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Half of respondents think that “patch” primarily refers to a &lt;em&gt;diff&lt;/em&gt;, while half think that it refers to a &lt;em&gt;transmissible change&lt;/em&gt;.
    &lt;ul&gt;
      &lt;li&gt;In my opinion, the “transmissible change” interpretation aligns most closely with jj changes at present. In particular, you put those up for review and people can download them.&lt;/li&gt;
      &lt;li&gt;I also think the “diff” interpretation aligns with jj interpretation (as you can rebase patches around, and the semantic content of the patch doesn’t change); however, there is a &lt;a href=&quot;https://discord.com/channels/968932220549103686/1187096737639321742&quot;&gt;great deal of discussion on Discord&lt;/a&gt; suggesting that people think of “patches” as immutable, and this doesn’t match the jj semantics where you can rebase them around (IIUC).&lt;/li&gt;
      &lt;li&gt;Overall, I think “patch” is still the best term we have as a replacement for jj “changes” (unless somebody can propose a better one), and it’s clear that we should move away from “change” as a term.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;“Revision” is much more semantically clear than I thought it was. This means that we can adopt/coopt the existing term and ascribe the specific “snapshot” meaning that we do today.
    &lt;ul&gt;
      &lt;li&gt;We already &lt;em&gt;do&lt;/em&gt; use “revision” in many places, most notably “revsets”. For consistency, we likely want to standardize “revision” instead of “commit” as a term.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;related-posts&quot;&gt;Related posts&lt;/h2&gt;

&lt;p&gt;The following are hand-curated posts which you might find interesting.&lt;/p&gt;

&lt;table class=&quot;related-posts&quot;&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th&gt;Date&lt;/th&gt;
    &lt;th&gt;&lt;/th&gt;
    &lt;th&gt;Title&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;


  &lt;tr&gt;
    &lt;td&gt;19&amp;nbsp;Jun&amp;nbsp;2021&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/git-undo/&quot;&gt;git undo: We can do better&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;12&amp;nbsp;Oct&amp;nbsp;2021&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/in-memory-rebases/&quot;&gt;Lightning-fast rebases with git-move&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;19&amp;nbsp;Oct&amp;nbsp;2022&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/build-aware-sparse-checkouts/&quot;&gt;Build-aware sparse checkouts&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;16&amp;nbsp;Nov&amp;nbsp;2022&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/bringing-revsets-to-git/&quot;&gt;Bringing revsets to Git&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;05&amp;nbsp;Jan&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/git-ui-features/&quot;&gt;Where are my Git UI features from the future?&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;11&amp;nbsp;Jan&amp;nbsp;2024&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      (this&amp;nbsp;post)
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/patch-terminology/&quot;&gt;Patch terminology&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Want to see more of my posts? Follow me &lt;a href=&quot;https://twitter.com/arxanas&quot;&gt;on Twitter&lt;/a&gt; or subscribe &lt;a href=&quot;/feed.xml&quot;&gt;via RSS&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;comments&quot;&gt;Comments&lt;/h2&gt;

&lt;ul&gt;



&lt;/ul&gt;

&lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/github-comment-links.js&quot;&gt;&lt;/script&gt;

&lt;div id=&quot;disqus_thread&quot;&gt;&lt;/div&gt;
&lt;script&gt;

/**
 *  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
 *  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables */
var disqus_config = function () {
    this.page.url = &quot;https://blog.waleedkhan.name/patch-terminology/&quot;;
    this.page.identifier = &quot;patch-terminology/&quot;;
};

(function() { // DON&apos;T EDIT BELOW THIS LINE
    var d = document, s = d.createElement(&apos;script&apos;);
    s.src = &apos;//waleedkhan-name.disqus.com/embed.js&apos;;
    s.setAttribute(&apos;data-timestamp&apos;, +new Date());
    (d.head || d.body).appendChild(s);
})();
&lt;/script&gt;

&lt;noscript&gt;Please enable JavaScript to view the &lt;a href=&quot;https://disqus.com/?ref_noscript&quot;&gt;comments powered by Disqus.&lt;/a&gt;&lt;/noscript&gt;

</description>
        <pubDate>Thu, 11 Jan 2024 00:00:00 +0000</pubDate>
        <link>https://blog.waleedkhan.name/patch-terminology/</link>
        <guid isPermaLink="true">https://blog.waleedkhan.name/patch-terminology/</guid>
        
        <category>git</category>
        
        <category>linguistics</category>
        
        <category>reprint</category>
        
        <category>software-engineering</category>
        
        
      </item>
    
      <item>
        <title>Testing terminal user interface apps</title>
        <description>&lt;div class=&quot;publication-notes&quot;&gt;
  &lt;table&gt;
    &lt;tr&gt;
      &lt;td&gt;Intended audience&lt;/td&gt;
      &lt;td&gt;Software engineers developing interactive terminal applications.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Origin&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Work notes published internally.&lt;/li&gt;
        &lt;li&gt;My experience developing several TUI applications.&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mood&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Instructive.&lt;/li&gt;
        &lt;li&gt;I have a lot of work notes that I&apos;d like to publish more widely henceforth.&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/div&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;Terminal user interface (TUI) applications are an efficient way of surfacing data in a visual and interactive manner while reusing significant portions of an existing program which normally prints to stdout/stderr. However, bugs in TUI applications can quickly cause users to lose confidence in the visualization tools. This article details methods for testing TUI applications.&lt;/p&gt;

&lt;div class=&quot;series-and-toc&quot;&gt;

  &lt;p class=&quot;toc-header&quot;&gt;Table of contents:&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#introduction&quot; id=&quot;markdown-toc-introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#primer-on-ttys-and-ptys&quot; id=&quot;markdown-toc-primer-on-ttys-and-ptys&quot;&gt;Primer on TTYs and PTYs&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#escape-codes&quot; id=&quot;markdown-toc-escape-codes&quot;&gt;Escape codes&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#terminal-emulation&quot; id=&quot;markdown-toc-terminal-emulation&quot;&gt;Terminal emulation&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#testing-patterns&quot; id=&quot;markdown-toc-testing-patterns&quot;&gt;Testing patterns&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#integration-vs-end-to-end&quot; id=&quot;markdown-toc-integration-vs-end-to-end&quot;&gt;Integration vs end-to-end&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#snapshot-testing&quot; id=&quot;markdown-toc-snapshot-testing&quot;&gt;Snapshot testing&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#expect-style-testing&quot; id=&quot;markdown-toc-expect-style-testing&quot;&gt;“Expect”-style testing&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#end-to-end-example&quot; id=&quot;markdown-toc-end-to-end-example&quot;&gt;End-to-end example&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#integration-example&quot; id=&quot;markdown-toc-integration-example&quot;&gt;Integration example&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#related-posts&quot; id=&quot;markdown-toc-related-posts&quot;&gt;Related posts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#comments&quot; id=&quot;markdown-toc-comments&quot;&gt;Comments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;h2 id=&quot;primer-on-ttys-and-ptys&quot;&gt;Primer on TTYs and PTYs&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/questions/4426280/what-do-pty-and-tty-mean&quot;&gt;A “TTY” is a “teletype” (or “teletypewriter”) and a “PTY” is a “pseudo-teletype” (or nowadays “pseudo-terminal”)&lt;/a&gt;. In the earliest days of Unix, users interfaced with the computer primarily via a TTY, but these days, the TTY is usually more of an abstraction over the terminal’s visual interface. See&lt;a href=&quot;https://www.linusakesson.net/programming/tty/&quot;&gt; The TTY demystified&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;The TTY abstraction is actually part of the operating system. The TTY devices available to programs can be found as the special files of the form &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/tty*&lt;/code&gt;. You can determine if a file descriptor refers to a TTY using the&lt;a href=&quot;https://man7.org/linux/man-pages/man3/isatty.3.html&quot;&gt; isatty syscall&lt;/a&gt;. This function call is usually available in non-C languages, such as via Python’s&lt;a href=&quot;https://docs.python.org/3/library/os.html?highlight=isatty#os.isatty&quot;&gt; os.isatty&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;TTYs can be queried for information such as its dimensions via&lt;a href=&quot;https://man7.org/linux/man-pages/man4/tty_ioctl.4.html&quot;&gt; ioctl&lt;/a&gt;. It’s helpful to have a library to wrap querying and rendering to the TTY, or which offers a higher-level abstraction for the terminal UI.&lt;/p&gt;

&lt;p&gt;When running automated tests, a TTY is probably not available (or if it is, it would be the TTY for the person who is running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pytest&lt;/code&gt;, etc., which would not be good to use because other programs are already rendering to it). Instead, you could construct a PTY (&lt;a href=&quot;https://man7.org/linux/man-pages/man3/openpty.3.html&quot;&gt;with the opentty(3) syscall&lt;/a&gt;) or with a higher-level library.&lt;/p&gt;

&lt;h3 id=&quot;escape-codes&quot;&gt;Escape codes&lt;/h3&gt;

&lt;p&gt;Communication with a TTY is strictly by sending and receiving bytes, so how does the program actually draw a terminal interface on the TTY? It does so by sending special escape codes, such as&lt;a href=&quot;https://en.wikipedia.org/wiki/ANSI_escape_code&quot;&gt; ANSI escape codes&lt;/a&gt;. These are special sequences of bytes which can indicate things like “move the cursor to (X, Y)” or “enable bold and red coloring”.&lt;/p&gt;

&lt;p&gt;The&lt;a href=&quot;https://en.wikipedia.org/wiki/VT100&quot;&gt; VT100&lt;/a&gt; was a popular physical teletypewriter device which implemented the ANSI escape codes. You may sometimes see the ANSI escape codes referred to as VT100 escape codes.&lt;/p&gt;

&lt;h3 id=&quot;terminal-emulation&quot;&gt;Terminal emulation&lt;/h3&gt;

&lt;p&gt;Since we don’t use VT100s anymore, we instead use &lt;em&gt;terminal emulators&lt;/em&gt;: programs which implement the reading/writing of the TTY device and rendering on screen. The basic set of ANSI escape codes is handled by every serious terminal emulator program (Terminal.app, iTerm2, PuTTY, Alacritty, etc.). You might also say that these terminal emulators are “VT100”-compatible.&lt;/p&gt;

&lt;p&gt;Some terminal emulators support features outside those in the ANSI escape code set. For example,&lt;a href=&quot;https://iterm2.com/documentation-images.html&quot;&gt; iTerm2 can render inline pictures&lt;/a&gt; (as well as many other terminal emulators).&lt;/p&gt;

&lt;p&gt;When we construct a PTY, we only get a bidirectional stream of bytes, not an actual picture of the screen. In automated testing, we therefore have to interpret all the escape codes and render the screen contents ourselves. To do so, we can use a terminal emulation library, such as&lt;a href=&quot;https://pyte.readthedocs.io/en/latest/&quot;&gt; pyte&lt;/a&gt; for Python or&lt;a href=&quot;https://crates.io/crates/portable-pty&quot;&gt; portable-pty&lt;/a&gt; for Rust. Typically, we would write a small “expect”-style framework on top of this to test the target application (see&lt;a href=&quot;https://docs.google.com/document/d/1aitauk0xLBzI9LbbXZcczM1YluVMfY4xcanVEq9gmM0/edit#heading=h.8slxfli9jhcz&quot;&gt; “Expect”-style testing&lt;/a&gt;).&lt;/p&gt;

&lt;h2 id=&quot;testing-patterns&quot;&gt;Testing patterns&lt;/h2&gt;

&lt;h3 id=&quot;integration-vs-end-to-end&quot;&gt;Integration vs end-to-end&lt;/h3&gt;

&lt;p&gt;After having spent some time testing TUI apps, I find there are two general categories of testing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Integration testing&lt;/strong&gt;:
    &lt;ul&gt;
      &lt;li&gt;Allows us to hook into the application event loop to inject synthetic events. Examples:
        &lt;ul&gt;
          &lt;li&gt;Execute a function and synchronously wait for it to complete.&lt;/li&gt;
          &lt;li&gt;Take a “screenshot” of the application at the current time.&lt;/li&gt;
          &lt;li&gt;Specify non-deterministic event data (such as an event that renders timing data).&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;It is highly desirable to structure such TUI apps as event-driven for testing purposes!&lt;/li&gt;
      &lt;li&gt;Input can be at the user level (e.g. simulate “down arrow keypress”) or at the application level (e.g. trigger “open menu X”).
        &lt;ul&gt;
          &lt;li&gt;With user-level input, the interface itself is tested more completely, but it can make it difficult to write tests which involve complicated maneuvers (e.g. “scroll down to the third item representing menu X and then press enter”), and such tests will be more brittle when the interface changes.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Can choose to omit or sanitize non-deterministic data from the interface if necessary (e.g. timing data).&lt;/li&gt;
      &lt;li&gt;Can mock out certain facilities as desired (e.g. do not trigger an actual build from the interface).&lt;/li&gt;
      &lt;li&gt;Allows us to redirect rendering to a “virtual canvas” instead of rendering directly to the TTY/PTY. This is not strictly necessary (see end-to-end testing approaches with a PTY) but can simplify the testing interface (no need to use a library/write complicated TTY logic; taking “screenshots” will likely be easier to implement).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;End-to-end testing&lt;/strong&gt;:
    &lt;ul&gt;
      &lt;li&gt;Simulate a program under test using only normal user input methods, such as keystrokes.&lt;/li&gt;
      &lt;li&gt;Typically uses a PTY to simulate the interface. More discussion on TTY/PTY later.
        &lt;ul&gt;
          &lt;li&gt;Example: see&lt;a href=&quot;https://man7.org/linux/man-pages/man1/script.1.html&quot;&gt; script(1)&lt;/a&gt; for a tool which simulates the terminal session and can play it back later.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;May be difficult to prevent side-effectful operations from occurring (e.g. triggering a build from the interface).&lt;/li&gt;
      &lt;li&gt;May need to sanitize the screen contents to remove non-deterministic data (e.g. timing data).&lt;/li&gt;
      &lt;li&gt;Not possible to wait synchronously on internal operations, which requires some sort of external indication when an internal operation is complete, and tends to be flakier.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generally speaking, integration testing will be more direct and less flaky, but end-to-end testing can be a good solution for very broad coverage of a program and to test programs which aren’t currently set up to support integration testing.&lt;/p&gt;

&lt;h3 id=&quot;snapshot-testing&quot;&gt;Snapshot testing&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;NB: This style of testing is also called “golden” testing and “expect” testing, along with several other names that don’t come to mind right now.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a category of testing where the output from a function or program is gathered and compared to a known-good “snapshot” of that output. A “snapshot” can refer to simple textual data, structured data, or even actual snapshots of the UI of a program. Snapshot testing libraries typically come with a tool to quickly compare and update snapshot output.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Example: the&lt;a href=&quot;https://insta.rs/&quot;&gt; cargo-insta library for Rust&lt;/a&gt; implements snapshot testing.&lt;/li&gt;
  &lt;li&gt;Example: the&lt;a href=&quot;https://vcrpy.readthedocs.io/en/latest/&quot;&gt; VCR.py library&lt;/a&gt; implements snapshot testing for network requests specifically.&lt;/li&gt;
  &lt;li&gt;Example: the&lt;a href=&quot;https://jestjs.io/docs/snapshot-testing&quot;&gt; Jest library for Javascript&lt;/a&gt; implements snapshot testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are several advantages to snapshot testing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Can be used in situations where there are multiple “correct” outputs, such as user interfaces.&lt;/li&gt;
  &lt;li&gt;Good tooling will make it possible to quickly compare and update snapshot tests across all tests. This is important if a change to the UI affects most/all tests.
    &lt;ul&gt;
      &lt;li&gt;Good tooling will also make it possible to update multiple snapshot serial assertions in the &lt;em&gt;same&lt;/em&gt; test without failing on the first failure (which would require you to re-run the snapshot tool repeatedly until each successive snapshot is updated).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Can be used to determine and capture the behavior of an existing system.&lt;/li&gt;
  &lt;li&gt;Encourages program design such that components can be driven independently and their state can be dumped/examined in a meaningful way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Snapshot testing is a better fit for testing TUIs than standard assertions, as much of the interface has functional requirements that aren’t easy to express with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert&lt;/code&gt; statements (or similar).&lt;/p&gt;

&lt;p&gt;Some snapshot tools store the snapshots inline with the code, while others store them in separate files (or the tool may be configurable). I prefer to store my snapshots inline because I find it easier to reference them when writing or reviewing a test, but this obviously bloats the size of the test file significantly and could make it hard to navigate around the actual test code.&lt;/p&gt;

&lt;h3 id=&quot;expect-style-testing&quot;&gt;“Expect”-style testing&lt;/h3&gt;

&lt;p&gt;“Expect”-style testing originally refers to the&lt;a href=&quot;https://linux.die.net/man/1/expect&quot;&gt; expect(1)&lt;/a&gt; program:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Expect is a program that “talks” to other interactive programs according to a script. Following the script, Expect knows what can be expected from a program and what the correct response should be. An interpreted language provides branching and high-level control structures to direct the dialogue. In addition, the user can take control and interact directly when desired, afterward returning control to the script.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes “expect”-style testing is used synonymously with “snapshot” testing, such as in&lt;a href=&quot;https://jestjs.io/docs/snapshot-testing&quot;&gt; Jest&lt;/a&gt; or&lt;a href=&quot;https://pypi.org/project/expecttest/&quot;&gt; the expecttest Python library&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The basic structure of a program is to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send&lt;/code&gt; some input, then use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect&lt;/code&gt; to wait for certain output. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect&lt;/code&gt; may wait for a simple condition (e.g. wait until the string “Done” appears on the screen) or wait/assert the contents of the entire screen.&lt;/p&gt;

&lt;h3 id=&quot;end-to-end-example&quot;&gt;End-to-end example&lt;/h3&gt;

&lt;p&gt;Here’s an example of what an expect-based test might look like for a tool which makes certain Git commits (&lt;a href=&quot;https://github.com/arxanas/git-branchless/blob/88d9be97535cb4534ccd7f5ff5436618611e1076/git-branchless-record/tests/test_record.rs#L57-L66&quot;&gt;source&lt;/a&gt;, here is the definition of&lt;a href=&quot;https://github.com/arxanas/git-branchless/blob/88d9be97535cb4534ccd7f5ff5436618611e1076/git-branchless-testing/src/lib.rs#L816&quot;&gt; run_pty&lt;/a&gt;):&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;run_in_pty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;record&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;-i&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-m&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;nn&quot;&gt;PtyAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;f&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// expand files&lt;/span&gt;
                &lt;span class=&quot;nn&quot;&gt;PtyAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WaitUntilContains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;contents1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;nn&quot;&gt;PtyAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;q&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// The above should not have committed anything.&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stdout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_stderr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;show&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nn&quot;&gt;insta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;assert_snapshot!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stdout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;r###&quot;
        commit 62fc20d2a290daea0d52bdc2ed2ad4be6491010e
        Author: Testy McTestface &amp;lt;test@example.com&amp;gt;
        Date:   Thu Oct 29 12:34:56 2020 -0100

            create test1.txt

        diff --git a/test1.txt b/test1.txt
        new file mode 100644
        index 0000000..7432a8f
        --- /dev/null
        +++ b/test1.txt
        @@ -0,0 +1 @@
        +test1 contents
        &quot;###&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In fact, the only two primitives for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PtyAction&lt;/code&gt; are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Write&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WaitUntilContains&lt;/code&gt;. A third option could assert the contents of the entire screen. (Instead, a snapshot test of the output from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git show&lt;/code&gt; is included.)&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Tip: in your testing framework, implement a timeout for expectations and, when the timeout is exceeded, print what the test was expecting and what the screen contents actually were.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;integration-example&quot;&gt;Integration example&lt;/h3&gt;

&lt;p&gt;Here is an example of driving the internal state of a TUI program by passing a list of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event&lt;/code&gt;s in directly:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;#[test]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_toggle_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;eyre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;TestingScreenshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;after&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;TestingScreenshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event_source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;EventSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;nn&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ExpandAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
            &lt;span class=&quot;nn&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToggleAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
            &lt;span class=&quot;nn&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;QuitAccept&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;example_contents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recorder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Recorder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event_source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;recorder&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nn&quot;&gt;insta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;assert_display_snapshot!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;r###&quot;
    &quot;[File] [Edit] [Select] [View]                                                   &quot;
    &quot;(~) foo/bar                                                                  (-)&quot;
    &quot;        ⋮                                                                       &quot;
    &quot;       18 this is some text                                                     &quot;
    &quot;       19 this is some text                                                     &quot;
    &quot;       20 this is some text                                                     &quot;
    &quot;  [~] Section 1/1                                                            [-]&quot;
    &quot;    [×] - before text 1                                                         &quot;
    &quot;    [×] - before text 2                                                         &quot;
    &quot;    [×] + after text 1                                                          &quot;
    &quot;    [ ] + after text 2                                                          &quot;
    &quot;       23 this is some trailing text                                            &quot;
    &quot;[×] baz                                                                      [-]&quot;
    &quot;        1 Some leading text 1                                                   &quot;
    &quot;        2 Some leading text 2                                                   &quot;
    &quot;  [×] Section 1/1                                                            [-]&quot;
    &quot;    [×] - before text 1                                                         &quot;
    &quot;    [×] - before text 2                                                         &quot;
    &quot;    [×] + after text 1                                                          &quot;
    &quot;    [×] + after text 2                                                          &quot;
    &quot;###&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nn&quot;&gt;insta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;assert_display_snapshot!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;r###&quot;
    &quot;[File] [Edit] [Select] [View]                                                   &quot;
    &quot;(~) foo/bar                                                                  (-)&quot;
    &quot;        ⋮                                                                       &quot;
    &quot;       18 this is some text                                                     &quot;
    &quot;       19 this is some text                                                     &quot;
    &quot;       20 this is some text                                                     &quot;
    &quot;  [~] Section 1/1                                                            [-]&quot;
    &quot;    [ ] - before text 1                                                         &quot;
    &quot;    [ ] - before text 2                                                         &quot;
    &quot;    [ ] + after text 1                                                          &quot;
    &quot;    [×] + after text 2                                                          &quot;
    &quot;       23 this is some trailing text                                            &quot;
    &quot;[ ] baz                                                                      [-]&quot;
    &quot;        1 Some leading text 1                                                   &quot;
    &quot;        2 Some leading text 2                                                   &quot;
    &quot;  [ ] Section 1/1                                                            [-]&quot;
    &quot;    [ ] - before text 1                                                         &quot;
    &quot;    [ ] - before text 2                                                         &quot;
    &quot;    [ ] + after text 1                                                          &quot;
    &quot;    [ ] + after text 2                                                          &quot;
    &quot;###&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(())&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice how high-level events like “expand all” are passed to the TUI application. Implicitly, the TUI application is designed such that the “expand all” event will be fully processed and the interface will be re-rendered by the time we process the next event &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;after.event()&lt;/code&gt; and capture the screenshot.&lt;/p&gt;

&lt;p&gt;The entire terminal user interface, including components such as a menu bar at the top, is captured. (If the menu bar items change, then the snapshot tests will all need to be updated, which is why good tooling is so important.)&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Tip: when comparing against a screen capture, include delimiters before and after the ends of lines (here we use the double quote character &lt;code&gt;&quot;&lt;/code&gt;) so that you can more easily compare leading and trailing whitespace, and so that editors don’t automatically strip whitespace or flag it as a warning.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;related-posts&quot;&gt;Related posts&lt;/h2&gt;

&lt;p&gt;The following are hand-curated posts which you might find interesting.&lt;/p&gt;

&lt;table class=&quot;related-posts&quot;&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th&gt;Date&lt;/th&gt;
    &lt;th&gt;&lt;/th&gt;
    &lt;th&gt;Title&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;


  &lt;tr&gt;
    &lt;td&gt;24&amp;nbsp;Aug&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/writing-brittle-code/&quot;&gt;Writing brittle code&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;01&amp;nbsp;Sep&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/on-trivial-changes/&quot;&gt;On trivial changes&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;30&amp;nbsp;Dec&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      (this&amp;nbsp;post)
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/testing-tui-apps/&quot;&gt;Testing terminal user interface apps&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;30&amp;nbsp;Oct&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/what-if-sql-were-good/&quot;&gt;Datalog DSL detects defective dependency declarations, defanging dodgy development discipline&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Want to see more of my posts? Follow me &lt;a href=&quot;https://twitter.com/arxanas&quot;&gt;on Twitter&lt;/a&gt; or subscribe &lt;a href=&quot;/feed.xml&quot;&gt;via RSS&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;comments&quot;&gt;Comments&lt;/h2&gt;

&lt;ul&gt;



&lt;/ul&gt;

&lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/github-comment-links.js&quot;&gt;&lt;/script&gt;

&lt;div id=&quot;disqus_thread&quot;&gt;&lt;/div&gt;
&lt;script&gt;

/**
 *  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
 *  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables */
var disqus_config = function () {
    this.page.url = &quot;https://blog.waleedkhan.name/testing-tui-apps/&quot;;
    this.page.identifier = &quot;testing-tui-apps/&quot;;
};

(function() { // DON&apos;T EDIT BELOW THIS LINE
    var d = document, s = d.createElement(&apos;script&apos;);
    s.src = &apos;//waleedkhan-name.disqus.com/embed.js&apos;;
    s.setAttribute(&apos;data-timestamp&apos;, +new Date());
    (d.head || d.body).appendChild(s);
})();
&lt;/script&gt;

&lt;noscript&gt;Please enable JavaScript to view the &lt;a href=&quot;https://disqus.com/?ref_noscript&quot;&gt;comments powered by Disqus.&lt;/a&gt;&lt;/noscript&gt;

</description>
        <pubDate>Sat, 30 Dec 2023 00:00:00 +0000</pubDate>
        <link>https://blog.waleedkhan.name/testing-tui-apps/</link>
        <guid isPermaLink="true">https://blog.waleedkhan.name/testing-tui-apps/</guid>
        
        <category>software-engineering</category>
        
        <category>software-verification</category>
        
        <category>reprint</category>
        
        
      </item>
    
      <item>
        <title>On trivial changes</title>
        <description>&lt;div class=&quot;publication-notes&quot;&gt;
  &lt;table&gt;
    &lt;tr&gt;
      &lt;td&gt;Intended audience&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
        &lt;li&gt;Software engineers.&lt;/li&gt;
        &lt;li&gt;Anyone with checklists as part of their workflows or routines.&lt;/li&gt;
      &lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Origin&lt;/td&gt;
      &lt;td&gt;General software engineering experience.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mood&lt;/td&gt;
      &lt;td&gt;Amused.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/div&gt;

&lt;div class=&quot;series-and-toc&quot;&gt;

  &lt;p class=&quot;toc-header&quot;&gt;Table of contents:&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#related-posts&quot; id=&quot;markdown-toc-related-posts&quot;&gt;Related posts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#comments&quot; id=&quot;markdown-toc-comments&quot;&gt;Comments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;p&gt;One day, perhaps three years into my career, I had a very small, one-line code change to make. I don’t remember exactly what it was, but I think it involved computing a value and interpolating it into a string. I considered committing it directly, but submitted it for code review despite how small of a change it was.&lt;/p&gt;

&lt;p&gt;My reviewers pointed out three different bugs, which makes it perhaps the buggiest commit I’ve ever written (by density of bugs per lines of code). Since that day, I always submit code for review and I always wait for builds and tests to finish, even for changes which “obviously” don’t affect anything.&lt;/p&gt;

&lt;h2 id=&quot;related-posts&quot;&gt;Related posts&lt;/h2&gt;

&lt;p&gt;The following are hand-curated posts which you might find interesting.&lt;/p&gt;

&lt;table class=&quot;related-posts&quot;&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th&gt;Date&lt;/th&gt;
    &lt;th&gt;&lt;/th&gt;
    &lt;th&gt;Title&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;


  &lt;tr&gt;
    &lt;td&gt;24&amp;nbsp;Aug&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/writing-brittle-code/&quot;&gt;Writing brittle code&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;01&amp;nbsp;Sep&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      (this&amp;nbsp;post)
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/on-trivial-changes/&quot;&gt;On trivial changes&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;30&amp;nbsp;Dec&amp;nbsp;2023&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/testing-tui-apps/&quot;&gt;Testing terminal user interface apps&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

  &lt;tr&gt;
    &lt;td&gt;30&amp;nbsp;Oct&amp;nbsp;2025&lt;/td&gt;
    &lt;td class=&quot;this-post&quot;&gt;
      
      &lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;/what-if-sql-were-good/&quot;&gt;Datalog DSL detects defective dependency declarations, defanging dodgy development discipline&lt;/a&gt;
    &lt;/td&gt;
  &lt;/tr&gt;

&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Want to see more of my posts? Follow me &lt;a href=&quot;https://twitter.com/arxanas&quot;&gt;on Twitter&lt;/a&gt; or subscribe &lt;a href=&quot;/feed.xml&quot;&gt;via RSS&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;comments&quot;&gt;Comments&lt;/h2&gt;

&lt;ul&gt;



&lt;/ul&gt;

&lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/github-comment-links.js&quot;&gt;&lt;/script&gt;

&lt;div id=&quot;disqus_thread&quot;&gt;&lt;/div&gt;
&lt;script&gt;

/**
 *  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
 *  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables */
var disqus_config = function () {
    this.page.url = &quot;https://blog.waleedkhan.name/on-trivial-changes/&quot;;
    this.page.identifier = &quot;on-trivial-changes/&quot;;
};

(function() { // DON&apos;T EDIT BELOW THIS LINE
    var d = document, s = d.createElement(&apos;script&apos;);
    s.src = &apos;//waleedkhan-name.disqus.com/embed.js&apos;;
    s.setAttribute(&apos;data-timestamp&apos;, +new Date());
    (d.head || d.body).appendChild(s);
})();
&lt;/script&gt;

&lt;noscript&gt;Please enable JavaScript to view the &lt;a href=&quot;https://disqus.com/?ref_noscript&quot;&gt;comments powered by Disqus.&lt;/a&gt;&lt;/noscript&gt;

</description>
        <pubDate>Fri, 01 Sep 2023 00:00:00 +0000</pubDate>
        <link>https://blog.waleedkhan.name/on-trivial-changes/</link>
        <guid isPermaLink="true">https://blog.waleedkhan.name/on-trivial-changes/</guid>
        
        <category>software-engineering</category>
        
        <category>software-verification</category>
        
        
      </item>
    
  </channel>
</rss>
