<script data-pm-proxy="intercept"></script><?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Burning the Midnight Coffee]]></title><description><![CDATA[The incoherent ramblings of a burnt out programmer. May sporadically contain useful information and tutorials.]]></description><link>https://btmc.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!jhCb!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b8d98a1-d3bc-41ac-9779-ff9a692560a8_512x512.png</url><title>Burning the Midnight Coffee</title><link>https://btmc.substack.com</link></image><generator>Substack</generator><lastBuildDate>Fri, 04 Sep 2026 00:37:47 GMT</lastBuildDate><atom:link href="/__u/btmc.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Sir Whinesalot]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[btmc@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[btmc@substack.com]]></itunes:email><itunes:name><![CDATA[Sir Whinesalot]]></itunes:name></itunes:owner><itunes:author><![CDATA[Sir Whinesalot]]></itunes:author><googleplay:owner><![CDATA[btmc@substack.com]]></googleplay:owner><googleplay:email><![CDATA[btmc@substack.com]]></googleplay:email><googleplay:author><![CDATA[Sir Whinesalot]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[A Language Feature to Rule Them All]]></title><description><![CDATA[The expressive power of Monads and Algebraic Effects.]]></description><link>https://btmc.substack.com/p/a-language-feature-to-rule-them-all</link><guid isPermaLink="false">https://btmc.substack.com/p/a-language-feature-to-rule-them-all</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Thu, 30 Jul 2026 17:38:02 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/159b2f5c-ebdf-4a77-881b-1166b6d7ca14_5760x3240.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Some language features are too powerful for their own good.</p><p>Do you know what a Monad is<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>? People have struggled to explain what they are, which is not particularly surprising since it is a very abstract concept. There&#8217;s even a <a href="https://wiki.haskell.org/Monad_tutorials_timeline">timeline</a> recording the history of (in)famous monad tutorials. Many try to explain monads through analogy, which really doesn&#8217;t work. The tutorials got so bad someone wrote an article <a href="https://byorgey.github.io/blog/posts/2009/01/12/abstraction-intuition-and-the-monad-tutorial-fallacy.html">complaining about them</a> and unintentionally created a meme that <a href="https://blog.plover.com/prog/burritos.html">monads are like burritos</a>.</p><p>Here&#8217;s what a monad is, as far as a programmer (not a mathematician) is concerned, in some Rust-like pseudo code:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">trait Monad&lt;M, A&gt; {
  fn new(v: A) -&gt; M&lt;A&gt;;
  fn and_then&lt;B&gt;(m: M&lt;A&gt;, f: fn(A) -&gt; M&lt;B&gt;) -&gt; M&lt;B&gt;;
}</code></pre></div><p>That&#8217;s it, that&#8217;s all it is. The names of the two functions vary, &#8220;<code>new</code>&#8221; is sometimes called &#8220;<code>pure</code>&#8221; or &#8220;<code>return</code>&#8221;<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>, while &#8220;<code>and_then</code>&#8221; is often called &#8220;<code>bind</code>&#8221; or &#8220;<code>flat_map</code>&#8221;. This is a <em>higher-kinded type</em>, meaning that it is generic over a type constructor (the M above), something most languages can&#8217;t natively express<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>.</p><p>The important part is that &#8220;<code>and_then</code>&#8221; function, the other function just means there&#8217;s a way to construct the type. The &#8220;and_then&#8221; function lets you sequence callbacks that stay &#8220;trapped within the monad&#8221;, so to speak. What&#8217;s that useful for you ask? Well it appears in many different contexts:</p><ul><li><p>Rust&#8217;s <code>Option&lt;T&gt;</code> is a Monad: here&#8217;s <a href="https://doc.rust-lang.org/std/option/enum.Option.html#method.and_then">and_then</a>. Lets you sequence callbacks that work with values if there are any and abort early if one is missing.</p></li><li><p>C#&#8217;s <code>Task&lt;T&gt;</code> is a Monad: and_then is <a href="https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.continuewith?view=net-10.0#system-threading-tasks-task-1-continuewith-1(system-func((system-threading-tasks-task((-0))-0)))">ContinueWith</a>. Lets you sequence asynchronous callbacks. Promises in JavaScript are the mainstream example.</p></li><li><p>Java&#8217;s <code>Stream&lt;T&gt;</code> is a Monad: and_then is <a href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#flatMap-java.util.function.Function-">flatMap</a>. Lets you sequence callbacks that produce streams of values into producing one big stream.</p></li></ul><p>If you have never used these functions/methods, don&#8217;t worry too much about it, they aren&#8217;t that useful most of the time, but now you know the pattern.</p><p><strong>What the &#8220;Monad trait/typeclass/protocol/interface&#8221; makes possible is to write code that is generic over all of these different types.</strong></p><div class="pullquote"><p>But why on earth would you want to generalize over these very distinct types?</p></div><p>For most programmers the fact that these types share a common structure is utterly useless, I honestly struggle to think of any practical use case. BUT, for a <em>language developer</em>, it enables some neat tricks.</p><p>Haskell has something called <a href="https://en.wikibooks.org/wiki/Haskell/do_notation">do notation</a> that works with any monad. It turns imperative-looking code into calls to &#8220;and_then&#8221; (which is called &gt;&gt;= in haskell): </p><ul><li><p>If you use it with the Maybe/Either monads (Option/Result in Rust), you get behavior similar to checked exceptions or the ? operator, but not hardcoded into the language.</p></li><li><p>If you use it on MonadYield from the yield package, you get <a href="https://en.wikipedia.org/wiki/Generator_(computer_programming)https://en.wikipedia.org/wiki/Generator_(computer_programming)">generators</a> that aren&#8217;t hardcoded into the language.</p></li><li><p>If you use it on MonadAsync from the async package, you get <a href="https://en.wikipedia.org/wiki/Async/await">async/await</a> that isn&#8217;t hardcoded into the language.</p></li></ul><p>But also logic programming, constraint programming, probabilistic programming, etc. Here&#8217;s an example of async in Haskell (from an async package test case):</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">async_poll = do
  a &lt;- async (threadDelay 1000000)
  r &lt;- poll a
  when (isJust r) (assertFailure "")
  r &lt;- poll a   -- poll twice, just to check we don't deadlock
  when (isJust r) (assertFailure "")</code></pre></div><p>The async above is not a keyword, it is not built into the language. This is the async monad plus the do notation syntax sugar. It gets expanded to something like:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">async_poll =
  -- (\x -&gt; foo) is how you make anonymous functions in haskell
  async (threadDelay 1000000) &gt;&gt;= (\a -&gt;
    poll a &gt;&gt;= (\r -&gt;
      when (isJust r) (assertFailure "") &gt;&gt; (
        poll a &gt;&gt;= (\r -&gt;
          when (isJust r) (assertFailure "")))))</code></pre></div><p>The &gt;&gt;= (bind/and_then) operators are sequencing the callbacks. The &gt;&gt; operator is just a wrapper that doesn&#8217;t pass along any value. This is similar to how async/await in JavaScript is essentially just syntax sugar for callback hell, but here the syntax sugar works for any type that defines the &gt;&gt;= operator.</p><p>F# has a similar feature in its <a href="https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions">Computation Expressions</a>. It can&#8217;t represent the Monad concept as a type, so it just directly checks for a method called <a href="https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions#creating-a-new-type-of-computation-expression">Bind</a> (our friend <code>and_then</code>). As with Haskell&#8217;s do notation, this gets you checked exception-like early returns, async, generators, etc. <strong>with one language feature</strong>.</p><p>Isn&#8217;t this neat? A two method interface plus some rather trivial syntax sugar lets you implement all these other fancy language features as regular library code. Monads + &#8220;do notation&#8221; creates a sort of &#8220;uber language feature&#8221; that lets you implement other language features.</p><p style="text-align: center;"><strong>By implementing just one language feature, you get all those other fancy language features &#8220;</strong><em><strong>for free&#8221;</strong></em><strong>, even ones you haven&#8217;t thought of yet.</strong></p><p>Sadly, however, you don&#8217;t get their <em>combinations</em> for free.</p><p>Monads don&#8217;t compose very well by default<strong>.</strong> If you want to mix async with exceptions, you either need to implement the combined Monad manually, or you need to use something called <strong><a href="https://www.williamyaoh.com/posts/2023-06-10-monad-transformers-101.html">Monad Transformers</a></strong>. The simplicity is not so simple anymore<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>.</p><p>Thankfully, there is another language feature that shares this magical ability to model nearly any crazy language feature you can think of, is even easier to understand, and composes extremely well: Algebraic Effects.</p><h1>Algebraic Effects</h1><p>An algebraic effect is a conceptual framework in programming that separates the declaration of a side effect (what the code wants to do) from its execution (how it actually happens). A function raises (for example) a <code>yield</code> effect, and then an effect handler installed higher up the callstack handles the request.</p><p>What&#8217;s an effect and what is an effect handler, you ask? Unlike Monads where analogies quickly break down, the easiest way to describe effects is as a generalization of exceptions:</p><ul><li><p>exception &#8594; effect</p></li><li><p>exception handler &#8594; effect handler</p></li></ul><p>The main difference being that effects are <em>resumable</em>, potentially multiple times. Exceptions can be modeled as effects that just never resume.</p><p>Two caveats about this conceptual generalization:</p><ul><li><p>Resumable exceptions are a pretty awful idea, don&#8217;t do that.</p></li><li><p>Performance would suck if implemented as exceptions are usually implemented.</p></li></ul><p>Alongside the runtime aspect, there is also a type system aspect to effects. They&#8217;re a bit like fixed checked exceptions in that regard, you&#8217;ll see what I mean below.</p><p>The closest thing to a mainstream language that supports effects is <a href="https://ocaml.org/manual/5.5/effects.html">OCaml</a>, though it only implements them as a runtime feature. Haskell also supports them as a library by implementing them using <a href="https://hackage.haskell.org/package/heftia-effects">monads</a>. Remember what I said about monads letting you implement nearly any crazy language feature you can think of? Well, that happens to include algebraic effects too somehow.</p><p>Research languages like Koka, Eff and Effekt are where most of the action is happening. We&#8217;ll use Effekt here since it can run in the browser and has a rather familiar looking syntax, if you want to try it out.</p><p>First, the exception effect which will look very familiar:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;scala&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-scala">effect throw(msg: String): Nothing

// We use double in the example because int division already does this
def div(n: Double, m: Double): Double / { throw } = {
  if (m == 0.0) {
    do throw("Division by zero")
  } else {
    n / m
  }
}</code></pre></div><p>This gets you something like Swift&#8217;s approach to error handling, which requires adding a <code>throws</code> annotation to any function that can throw an exception, except here the throw annotation is an effect annotation like any other.</p><p>The effects a function performs are recorded in a set after the return type. Effekt can infer the set of effects, so you don&#8217;t have to explicitly write it out, but it&#8217;s always a good idea to do so. Effects are triggered with the &#8220;do&#8221; keyword, and indirectly by calling a function that performs effects.</p><p>Handling an effect can be done as follows:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;scala&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-scala">def zdiv(a: Double, b: Double): Double / {} = {
  try {
    div(a, b)
  } with throw { msg =&gt;
    0.0
  }
}</code></pre></div><p>Notice how the effect is discharged from the effect set after the return type. So far, this is little more than improved checked exceptions. Let us do generators next:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;scala&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-scala">effect yield[A](x: A): Unit

def fib(limit: Int): Unit / { yield[Int], throw } = {
  if (limit &lt; 0) {
    do throw("expected positive limit")
  }
  var last = 0
  var current = 1
  while (limit == 0 || current &lt; limit) {
    do yield(current)
    val next = last + current
    last = current
    current = next
  }
}</code></pre></div><p>Here&#8217;s a generator of infinite Fibonacci numbers with an optional limit. I have an article explaining how to do this with <a href="/__u/btmc.substack.com/p/implementing-generators-yield-in">coroutines in C</a> if you prefer, but here we&#8217;re using effects. It also throws an &#8220;exception&#8221; if the given limit is a negative number.</p><p>Let&#8217;s create a silly function that uses the yield effect: it keeps printing the sums of the Fibonacci numbers until they exceed the given limit.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;scala&quot;,&quot;nodeId&quot;:&quot;ce1a6f2e-67c8-4e5d-84d5-3e0e66f54e1b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-scala">def sumFibs(limit: Int): Int / {} = {
  var sum = 0
  try {
    fib(limit)
  } with yield { i =&gt;
    sum = sum + i
    println(sum)
    resume(())
  } with throw { msg =&gt;
    println(msg)
    // no resume
  }
}</code></pre></div><p>The language has no support for yield built-in nor &#8220;exceptions&#8221; in the traditional sense, we did all of this as a library. If we wanted we could also allow yield to return a value, so the handler can feed values into the function creating bi-directional communication.</p><h1>I only code in C sir, why do I care?</h1><p>Have I got a treat for you, Algebraic Effects in C: <a href="https://github.com/koka-lang/libhandler">libhandler</a>.</p><p>There&#8217;s also a newer library from the same authors that builds on top of multi-prompt delimited control (another one of these uber features): <a href="https://github.com/koka-lang/libmprompt#the-libmpeff-interface">libmpeff</a>.</p><p>They&#8217;re not the nicest thing in the world to use but it&#8217;s amazing how some assembly tricks are enough to make this work in a language very much not designed for it.</p><h1>Ok, neat, how do I implement these?</h1><p>If you want to handle it at the level of the compiler, rather than delegating the runtime to libhandler/libmpeff, the most efficient way is with <a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2021/08/genev-icfp21.pdf">evidence passing</a>.</p><p>It basically involves rewriting functions into a monadic form and passing in the handlers as a hidden argument. A bit hard to explain (even I struggle to understand exactly how it works) but we can implement a limited form of algebraic effects (one-shot resume, resume is a keyword not a function) without too much pain, by translating functions that do effects to coroutines.</p><p>First, we define effects and their signatures. We&#8217;ll use a tagged union of effects for simplicity (great for the &#8220;internal to the compiler&#8221; case), but if we wanted it extensible it&#8217;s pretty easy to do as well, just more code.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">typedef enum EffectKind {
  EFF_NONE,
  EFF_THROW,
  EFF_YIELD_INT,
} EffectKind;

typedef struct Effect {
  EffectKind kind;
  union {
    struct { const char* msg; } throw;
    struct { int v; } yield_int;
  };
} Effect;

#define DONE (Effect){EFF_NONE}
#define do_throw(msg) (Effect){EFF_THROW, .throw = {msg}}
#define do_yield_int(v) (Effect){EFF_YIELD_INT, .yield_int = {v}}</code></pre></div><p>Next we turn fib into a coroutine. Every time an effect is triggered it &#8220;pauses&#8221; and returns a request for the effect to be performed (or the NONE effect when finished). We&#8217;ll use GCC&#8217;s first class label extension for clarity and to make the code shorter, but it is not necessary (see my article on generators in C to see what to do instead).</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">typedef struct FibFrame {
  // "program counter", tracks where we are in the coroutine
  void* pc_;
  // args
  int limit;
  // locals
  int last;
  int current;
  int next;
} FibFrame;

Effect fib(FibFrame *f) {
  if(f-&gt;pc_) goto *f-&gt;pc_;
  if (f-&gt;limit &lt; 0) {
    f-&gt;pc_ = &amp;&amp;after_throw_1;
    return do_throw("expected positive limit");
  after_throw_1:
  }
  f-&gt;last = 0;
  f-&gt;current = 1;
  while(f-&gt;limit == 0 || f-&gt;current &lt; f-&gt;limit) {
    f-&gt;pc_ = &amp;&amp;after_yield_1;
    return do_yield_int(f-&gt;current);
  after_yield_1:
    f-&gt;next = f-&gt;last + f-&gt;current;
    f-&gt;last = f-&gt;current;
    f-&gt;current = f-&gt;next;
  }
  // reset the function
  f-&gt;pc_ = NULL;
  return DONE;
}</code></pre></div><p>Finally, let us convert sumFib. It triggers no effects of its own and handles all effects of the functions it calls, so it does not need to be a coroutine, but for consistency we will also turn it into one.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;d0a17668-5ae8-4b6a-b087-50e37b5d8473&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">typedef struct SumFibsFrame {
  void* pc_;
  int limit;
  int sum;
  FibFrame fib_frame_1;
  // final return value
  int return_;
} SumFibsFrame;

Effect sum_fibs(SumFibsFrame* f) {
  if (f-&gt;pc_) goto *f-&gt;pc_;
  f-&gt;sum = 0;
  // try block 1
  {
    Effect effect = {EFF_NONE};
    f-&gt;fib_frame_1 = (FibFrame){.limit = f-&gt;limit};
  resume_fib_1:
    effect = fib(&amp;f-&gt;fib_frame_1);
    if (effect.kind != EFF_NONE) goto handlers_1;
    goto try_end_1;
  handlers_1:
    switch(effect.kind) {
    case EFF_YIELD_INT:
      f-&gt;sum += effect.yield_int.v;
      printf("%d\n", f-&gt;sum);
      goto resume_fib_1;
    case EFF_THROW:
      printf("%s\n", effect.throw.msg);
      break;
    }
  try_end_1:
  }
  f-&gt;return_ = f-&gt;sum;
  f-&gt;pc_ = NULL;
  return DONE;
}</code></pre></div><p>Phew&#8230; even for this simplified version the code gets quite gnarly, but this is meant as something a compiler generates. If multi-shot continuations are allowed, the code becomes a lot more complicated as it requires managing heap allocated copies of the frames AND of the handlers because when the function is resumed and it triggers an effect it needs to be handled by the try block that originally captured the resumption<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>.</p><p>Out of scope for an already very large blog article.</p><h1>Absolute Power Corrupts Absolutely</h1><p>These two features, Monads + &#8220;do notation&#8221; and Algebraic Effects, are a little too powerful. The main problem with putting this sort of power in the hands of library developers rather than the language developer is that it can cause a large splintering of the ecosystem: multiple ways to do exceptions, multiple ways to do async, multiple ways to do generators, multiple ways to do implicit contexts, etc.</p><p>You don&#8217;t really want competing implementations of these features, you want those things standardized. This is also a problem in languages like Lisp where everyone makes their own little domain-specific languages using macros.</p><p>So I&#8217;m not sure exposing this as a user-facing language feature is a particularly good idea. What I would do is implement it in the compiler, and then implement user facing features (exceptions, generators, async, etc.) in terms of it. That way their interactions are well define and the implementation far simpler.</p><p>I would also expose the feature publicly but only if an &#8220;-experimental&#8221; flag is passed to the compiler. That allows the community to experiment with interesting uses of effects (like implementing a Prolog-like backtracking engine within the language) before they are standardized, but making it clear it&#8217;s not meant for production code.</p><h1>Conclusion</h1><p>Two fancy language features demystified in one blog post, aren&#8217;t I ambitious?</p><p>I really think these features are too powerful, having them widely used and abused will lead to a messy ecosystem. Most code should be made up of simple function calls.</p><p>But if you&#8217;re making a new language and you want to take it as far as it will go, here are two new tools for your tool-belt.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>It&#8217;s a concept from <a href="https://en.wikipedia.org/wiki/Monad_(category_theory)">Category Theory</a> that happens to be rather useful in programming, mainly (but not exclusively) in functional programming.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>The use of the name &#8220;return&#8221; for what is really just a constructor probably led to a lot of the confusion IMO. Bad mistake on the part of Haskell.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>Haskell and Scala can, for two mainstream-ish languages.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>Implementing the combination of the two in the compiler is not particularly easy either.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>These are called &#8220;deep handler&#8221; semantics. There are also &#8220;shallow handler&#8221; semantics which redirect to whatever handler is around the resume call. Most effect languages have deep handler semantics.</p></div></div>]]></content:encoded></item><item><title><![CDATA[LL Handles Direct Left Recursion]]></title><description><![CDATA[Theory vs Practice]]></description><link>https://btmc.substack.com/p/ll-handles-direct-left-recursion</link><guid isPermaLink="false">https://btmc.substack.com/p/ll-handles-direct-left-recursion</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Thu, 16 Jul 2026 22:14:17 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/4672d40c-506a-4bbb-82b7-a3a6634bf49a_3135x4180.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Consider the following question, which was the impetus for this post:</p><div class="pullquote"><p>Is unability to handle left recursion a signifying trait of LL parsers? I.e., if a parser can handle left recursion, does it make that parser automatically not LL? </p></div><p>That question lead to me asking this followup question:</p><div class="pullquote"><p>If my parser generator accepts as input a subset of left-recursive grammars, and the parsers it generates recognize them using classic table-driven LL(1), are they no longer LL(1)?</p></div><p>If you know any formal language theory, you probably know the following:</p><blockquote><p><span>LL grammars cannot have rules containing </span><a href="https://en.wikipedia.org/wiki/Left_recursion">left recursion</a><span>.</span></p></blockquote><p>Why? Because LL grammars are defined as &#8220;<a href="https://en.wikipedia.org/wiki/Context-free_grammar">context-free grammars</a><span> that can be </span><a href="https://en.wikipedia.org/wiki/Parsing">parsed</a><span> by an </span><a href="https://en.wikipedia.org/wiki/LL_parser">LL parser</a>&#8221;, with an LL parser defined as a parser that &#8220;<span>parses the input from </span><strong>L</strong><span>eft to right, performing </span><strong><a href="https://en.wikipedia.org/wiki/Context-free_grammar#Derivations_and_syntax_trees">L</a></strong><a href="https://en.wikipedia.org/wiki/Context-free_grammar#Derivations_and_syntax_trees">eftmost derivation</a><span> of the sentence&#8221;<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>, and a parser that works like that cannot handle left recursion</span><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a><span>. </span></p><p><span>Different issues occur depending on how the parser is implemented. Consider the following grammar:</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">expr : expr op term
     | term;

op : "+" | "-"</code></pre></div><p><span>A recursive descent parser (which fits the definition above) will enter an infinite loop:</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def expression():
  expression()
  op()
  term()</code></pre></div><p>A table-driven parser will detect a conflict because it cannot decide how to make progress, usually with a cryptic message if it hasn&#8217;t been explicitly designed to detect and point out the left recursion.</p><p>So far, the sentence seems to be true. But it&#8217;s also not very useful, because in practice it is only really true for indirect left recursion.</p><h1>Eliminating Left Recursion</h1><p>It is well known that simple cases of left recursion can be <a href="https://en.wikipedia.org/wiki/Left_recursion#Removing_left_recursion">eliminated</a> pretty easily. Some tweaks to the grammar mostly avoid the problem. The grammar above (presented again for convenience):</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;086eb1d9-bb02-469d-9c63-101f1829e3b2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">expr : expr op term
     | term;</code></pre></div><p>Can be rewritten into the following:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">expr : term expr_tail;
expr_tail: op term expr_tail | &#949;;</code></pre></div><p>Or, if your LL parser generator allows it, to the following:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">expr : term (op term)* | term;</code></pre></div><p>If you studied formal language theory in university your professor probably taught you this trick. If you read the documentation of an LL parser generator like ANTLR3<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>, this is how it suggests to handle expressions.</p><p>This results in a <a href="https://en.wikipedia.org/wiki/Equivalence_(formal_languages)">weakly equivalent</a> grammar, meaning it recognizes the same language (and generates the same set of strings), but with a different parse tree. The parse tree tilts to the right rather than to the left.</p><p>Given an input like 1 - 2 - 3, the original grammar parses it as (1 - 2) - 3, whereas the transformed grammar parses it as 1 - (2 - 3).</p><p>So why does this matter? You probably didn&#8217;t even think of it, but I snuck in a little gotcha above:</p><div class="callout-block" data-callout="true"><p>Or, <em><strong>if your LL parser generator allows it</strong></em>, to the following:</p><p><code>expr : term (op term)* | term;</code></p></div><p>&#8220;Or if your LL parser generator allows it&#8221;. That little <code>*</code> operator there, is that LL? What about the group <code>()</code>? Is that LL?</p><h1>Reductions</h1><p>If you&#8217;re implementing a table-driven LL parser, the EBNF grammar with those operators needs to be simplified (reduced) to a standard BNF form. That means the grammar that actually ends up being recognized is not the same grammar you fed the parser generator. It was <em><strong>rewritten</strong></em><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>.</p><p>Now, my question to you is, do you consider parser generators that support such operators (like ANTLR3, JavaCC, Yapps, etc.) to be LL parser generators? Most people seem to think so, and they refer to them as such. ANTLR3 is LL(*), others LL(k) or LL(1), but LL regardless.</p><p>They may not do the reduction to standard BNF internally because they&#8217;re not table-driven, instead directly generating recursive descent code, but does that change anything? Recursive descent code can handle full <a href="https://en.wikipedia.org/wiki/Parsing_expression_grammar">PEG</a>. The part that makes it LL and not PEG is the lack of backtracking right? The important part is not that it <em>is</em> parsed by a table-driven solution, but that it <em>could be</em>.</p><p>ANTLR3, being LL(*), should actually be considered to be in the domain of <a href="https://en.wikipedia.org/wiki/Top-down_parsing_language">TDPL</a> (because it has syntactic and semantic predicates), making it closer to a restricted form of PEG, not your typical LL parser. But everyone is ok with calling it an LL(*) parser generator.</p><p>Which gets me to ANTLR4. ANTLR4 is an <em>Adaptive</em> LL(*) parser generator (whatever that means), and it supports <em><strong><span data-color="#ffd966" style="color: rgb(255, 217, 102);">direct</span></strong></em><span data-color="#ffd966" style="color: rgb(255, 217, 102);"> left recursion</span>. Does that disqualify it from being an LL parser? Maybe ALL(*) is a special case and doesn&#8217;t really count.</p><p>But I vibe coded an LL(1) parser generator in Python that handles direct left recursion just fine:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">expr : a=expr "+" b=term { a + b } 
     | a=expr "-" b=term { a - b }
     | t=term { t } ;
term : a=term "*" b=factor { a * b } 
     | a=term "/" b=factor { a / b } 
     | f=factor { f } ;
factor : "(" e=expr ")" { e }
       | "-" f=factor { -f }
       | token=NUMBER { float(token.text) }
       | token=IDENT { ctx[token.text] } ;</code></pre></div><p>This grammar is accepted and evaluates to the result you&#8217;d expect for a given expression with the correct precedence.</p><p>It can work in a table-driven manner, and the left-recursion rewrite sits alongside the same code that rewrites groups <code>()</code> and repetitions <code>*</code>.</p><p>It can also directly generate recursive descent code, in which case it translates the direct left-recursion into a fold, similar to a <a href="/__u/btmc.substack.com/p/how-to-parse-expressions-easy">Pratt parser</a>. Or it can translate the rewritten BNF grammar into a recursive descent parser too; it still works, albeit less efficiently. The rewrite is completely trivial, I showed you the trick previously, but here is the internal BNF, generated automatically:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">expr : t=term _v=$expr_star[t]  { _v } ;
term : f=factor _v=$term_star[f]  { _v } ;
factor : "(" e=expr ")"  { e }
       | "-" f=factor  { -f }
       | token=NUMBER  { float(token.text) }
       | token=IDENT  { ctx[token.text] } ;
$expr_step[a] : "+" b=term  { a + b }
              | "-" b=term  { a - b } ;
$expr_star[a] : _i=$expr_step[a] _v=$expr_star[_i]  { _v }
              | { a } ;
$term_step[a] : "*" b=factor  { a * b }
              | "/" b=factor  { a / b } ;
$term_star[a] : _i=$term_step[a] _v=$term_star[_i]  { _v }
              | { a } ;</code></pre></div><p>Where do you draw the line? Some PEG parsers, which also cannot handle left-recursion, also handle <em>direct</em> left-recursion, like <a href="https://ohmjs.org">OhmJS</a>.</p><p>If the definition of what makes an LL(k) parser is &#8220;parses the input from <strong>L</strong>eft to right, performing <strong>L</strong>eftmost derivation of the sentence, with k tokens of lookahead&#8221;, then my little parser generator above fits the bill.</p><h1>Reductions are the Norm</h1><p>In other computer science areas of study, we&#8217;re not usually too worried about the exact input that goes into something, but what can be <em>reduced</em> to that input.</p><p>A classic example is the <a href="https://en.wikipedia.org/wiki/Boolean_satisfiability_problem">Boolean Satisfiability Problem</a> (SAT), which <span>asks whether there exists an </span><a href="https://en.wikipedia.org/wiki/Interpretation_(logic)">interpretation</a><span> that </span><a href="https://en.wikipedia.org/wiki/Satisfiability">satisfies</a><span> a given </span><a href="https://en.wikipedia.org/wiki/Boolean_logic">Boolean</a><span> </span><a href="https://en.wikipedia.org/wiki/Formula_(mathematical_logic)">formula</a><span>. In other words, it asks whether the formula's variables can be consistently replaced by the values TRUE or FALSE to make the formula evaluate to TRUE.</span></p><p><span>SAT is the prototypical </span><a href="https://en.wikipedia.org/wiki/NP-completeness"><span>NP-complete</span></a><span> problem. If you want to know if a problem is in NP, one way to figure that out is by coming up with a reduction to SAT.</span></p><p><span>What makes SAT useful are precisely these reductions. Not just theoretically, but in practice too. Checking if a propositional logic formula is satisfiable isn&#8217;t particularly interesting, but finding a solution to Sudoku is, and Sudoku can be reduced into SAT.</span></p><p><span>Most SAT solvers don&#8217;t even accept arbitrary boolean formulas, they require them to be </span><em><span>rewritten</span></em><span> into </span><a href="https://en.wikipedia.org/wiki/Conjunctive_normal_form"><span>Conjunctive Normal Form</span></a><span>. You can even reduce the problem to 3-SAT, which only allows a maximum of 3 variables per disjunction. Still just as expressive.</span></p><p><span>It&#8217;s that expressive power that matters. The fact that you can solve Sudoku, not that the solver is actually working with conjunctions of boolean variables internally. Not the input format, but what can reasonably (i.e., in polynomial time) be </span><em><span>reduced</span></em><span> to it.</span></p><p><span>Theory and practice are at odds here as well. NP problems have exponential complexity, O(2</span><sup><span>n</span></sup><span>), but SAT solvers can tackle absolutely massive industrial problems with millions of variables and constraints. The algorithms they use can exploit hidden structures in the problem and converge to a solution incredibly fast.</span></p><p><span>The CPU in your computer probably has a SAT solver to thank for its existence, either for how its internals are laid out, or for its correctness (or both!). This wouldn&#8217;t be possible if SAT solving was </span><em><span>always</span></em><span> </span>O(2<sup>n</sup>). O(2<sup>n</sup>) is the worst case scenario, most problems can be solved much more efficiently. The worst case is important to know, but what can actually be done in practice is perhaps even more so.</p><h1>Back to LL</h1><p>So given the above, I hope I&#8217;ve shown you why the sentence:</p><blockquote><p>LL grammars cannot have rules containing left recursion.</p></blockquote><p>Should instead be:</p><blockquote><p>LL grammars can only have rules containing direct left recursion<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>.</p></blockquote><p>Because an LL grammar is defined by what an LL parser can recognize, and an LL parser can easily deal with direct left recursion. And direct left recursion happens to include the most relevant category of left recursion: mathematical expressions.</p><p>If you search around the web or ask your favorite LLM (including Google&#8217;s if you need a free one) why one should favor LR over LL, they&#8217;ll tell you something like:</p><blockquote><p><strong>Support for Left Recursion:</strong><span> LL parsers will fall into infinite loops if the grammar contains left-recursive rules (e.g., A &#8594; A &#945;). LR parsers natively handle left recursion, making it easier to define structures like mathematical expressions.</span></p></blockquote><p>This statement is <em>mostly false</em>. This is not a reason to use LR over LL. Definitely not for the specific rule example Gemini used (which is directly left recursive). Even the &#8220;infinite loop&#8221; part of the answer is wrong, you&#8217;ll get a conflict unless you&#8217;re translating to a recursive descent parser by hand.</p><p>This type of answer is what you get when you only focus on worst-case theoretical limitations rather than what can be done in practice.</p><p>But the thing that makes the sentence not entirely false is one word: <em>natively</em>. It is true most LL parsers do not <em>natively</em> handle <em>any</em> left recursion. They could handle some, but they don&#8217;t. This is an engineering problem, not a theoretical one.</p><h1>Conclusion</h1><p>When a silly discord question leads a person to vibe coding an LL parser generator. Programmers sure are a weird bunch, or at least I am.</p><p>I&#8217;ve made my point as to what I think the answer to the original question is and why: parsers that can handle direct left recursion can 100% still be considered LL, because what matters is how they work, and preprocessing is normal anyway.</p><p>But I&#8217;m curious to know what you think.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Thanks Wikipedia. Read <a href="https://www.sciencedirect.com/science/article/pii/S0019995870904468?via%3Dihub">this</a> if you want something more formal.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Wait for it &#128521;.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>There&#8217;s a reason I said 3 and not ANTLR4. More on ANTLR4 later.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>Strongly equivalent, sure, but weakly equivalent only matters if you want the same exact parse tree with no transformations applied to it what so ever, and I can&#8217;t think of any case where I&#8217;d want that. At the very least you usually want to filter many tokens out or hide certain auxiliary productions.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>Technically it is possible to eliminate all left-recursion, not just direct, but the process is far more convoluted and dependent on the order in which grammar rules are specified, so I don&#8217;t consider it a <em>reasonable</em> reduction.</p></div></div>]]></content:encoded></item><item><title><![CDATA[I tried making a UI Library]]></title><description><![CDATA[Big mistake &#129401;.]]></description><link>https://btmc.substack.com/p/i-tried-making-a-ui-library</link><guid isPermaLink="false">https://btmc.substack.com/p/i-tried-making-a-ui-library</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Thu, 18 Jun 2026 11:05:50 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b298a147-6536-4e27-bd68-cb4ab013eedf_2194x1281.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>My readers are probably wondering what I&#8217;ve been up to since my articles dried up for a rather long while. Life happened, mostly. Everything from the birth of a new child, to the other child catching pneumonia, to a suicidal cat jumping out the window and needing to be fed through a tube in her neck for a couple of months (she recovered!).</p><p>That was the main reason for the drought, but another reason is a project I&#8217;ve been working on: a UI library called SUIT (Stylish User Interface Toolkit).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!kCJL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba46e000-fad6-4148-a489-83d5543ecf62_1736x1392.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!kCJL!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba46e000-fad6-4148-a489-83d5543ecf62_1736x1392.png 424w, /__u/substackcdn.com/image/fetch/$s_!kCJL!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba46e000-fad6-4148-a489-83d5543ecf62_1736x1392.png 848w, /__u/substackcdn.com/image/fetch/$s_!kCJL!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba46e000-fad6-4148-a489-83d5543ecf62_1736x1392.png 1272w, /__u/substackcdn.com/image/fetch/$s_!kCJL!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba46e000-fad6-4148-a489-83d5543ecf62_1736x1392.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!kCJL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba46e000-fad6-4148-a489-83d5543ecf62_1736x1392.png" width="1456" height="1167" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ba46e000-fad6-4148-a489-83d5543ecf62_1736x1392.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1167,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:550057,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://btmc.substack.com/i/202403487?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba46e000-fad6-4148-a489-83d5543ecf62_1736x1392.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!kCJL!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba46e000-fad6-4148-a489-83d5543ecf62_1736x1392.png 424w, /__u/substackcdn.com/image/fetch/$s_!kCJL!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba46e000-fad6-4148-a489-83d5543ecf62_1736x1392.png 848w, /__u/substackcdn.com/image/fetch/$s_!kCJL!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba46e000-fad6-4148-a489-83d5543ecf62_1736x1392.png 1272w, /__u/substackcdn.com/image/fetch/$s_!kCJL!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba46e000-fad6-4148-a489-83d5543ecf62_1736x1392.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">One of SUIT&#8217;s demo apps running on the AppKit backend. Not depicted are all the animations. For example the tab bar supports scrolling and dragging and dropping. Sadly I&#8217;m not a designer so the styling could be better.</figcaption></figure></div><h1>Think Electron, but in Reverse</h1><p>Every project needs a <em><strong>raison<a href="/__u/www.google.com/search?client=safari&amp;hs=2UQV&amp;sca_esv=25bb37eb50603e0c&amp;rls=en&amp;q=raison+d%27etre&amp;spell=1&amp;sa=X&amp;ved=2ahUKEwiS2bufj46VAxUpKvsDHcyYJFoQkeECKAB6BAgREAE"> </a>d'etre</strong></em>, a reason why it exists. Sometimes that&#8217;s just to scratch an itch or learn something new, other times it&#8217;s an attempt at targeting an unmet market need. SUIT is the latter, I&#8217;m trying to target a similar market to <a href="https://www.electronjs.org">Electron</a>, but in reverse<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>:</p><div class="callout-block" data-callout="true"><p>Electron turns web apps into cross-platform native apps.</p><p>SUIT turns cross-platform native apps into web apps.</p></div><p>There are already various libraries that are used to develop native apps<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> that can also target the web (<a href="https://www.qt.io">Qt</a>, <a href="https://avaloniaui.net">Avalonia</a>, etc.), but that&#8217;s not really what I mean. Those libraries produce native apps and those native apps can then run on the web, but the resulting &#8220;web app&#8221; is not really a &#8220;web-native&#8221; app, so to speak.</p><p>Just as Electron apps can feel somewhat out of place, Qt and Avalonia apps feel <em>extremely</em> out of place on the web, far worse than Electron apps do on the desktop. Qt and Avalonia custom render everything, so you don&#8217;t get the usual HTML and CSS behaviors one would normally expect from a web app. They&#8217;re also very large, and that size adds to the size of the exported web app. Small size still matters on the web, and neither Qt nor Avalonia are very good in this regard.</p><p>SUIT does not work like Qt or Avalonia, it is more like <a href="https://github.com/andlabs/libui">libUI</a> or <a href="https://wxwidgets.org">wxWidgets</a> in that it wraps the native UI toolkits provided by the operating systems, plus HTML + CSS on the web. This introduces some very harsh design constraints, but so far nothing insurmountable.</p><p>What does SUIT do differently from libUI and wxWidgets then? It has to do with the &#8220;S&#8221; in the name (which stands for &#8220;Stylish&#8221;); SUIT neither uses nor mimics the native platform&#8217;s appearance. The core tenet of SUIT is:</p><div class="callout-block" data-callout="true"><p style="text-align: center;"><strong>Custom Look, Native Feel</strong></p></div><p>Why not use the native look? Read on.</p><h1>The Native UI Toolkit Disaster</h1><p>There are no two ways about it, the native UI toolkits mostly suck. I&#8217;ve covered the <a href="/__u/btmc.substack.com/p/you-cant-make-a-native-windows-ui">absolute disaster that is Windows</a> previously, so I won&#8217;t go into it in detail again, but the TL;DR is that Windows has an absolute graveyard of half-finished UI toolkits, and you simply cannot trust any Microsoft provided API not called Direct3D<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>. I&#8217;ll explain what I do regarding Windows later.</p><p>Apple-land fares better, but the cracks are starting to show. AppKit is very good, but even AppKit has quite a few limitations compared to some of the things you can do on the web. As an example, even something as trivial as a different radius per corner is something CoreAnimation does not support (you can mask some to 0, but that&#8217;s it). Same with a different border color per side. You can always do it yourself with multiple vector shape layers or by custom drawing with CoreGraphics, but that&#8217;s a lot of extra work.</p><p>Now there&#8217;s <a href="https://developer.apple.com/swiftui/">SwiftUI</a> which Apple refuses to decide if it&#8217;s just a React-ish wrapper around AppKit, or 100% &#8220;the future&#8221; and AppKit is just an implementation detail for the time being. We might be looking at a Microsoft-like situation here eventually.</p><p>On Linux there are <a href="https://www.qt.io">Qt</a> and <a href="https://www.gtk.org">GTK</a>, and both have issues.</p><p>Qt is quite good, but it&#8217;s been a bit of a mess since the introduction of <a href="https://doc.qt.io/qt-6/qtquick-index.html">QtQuick</a>. Why they thought forcing people into QML instead of it being just a nice wrapper on top of a C++ API I have no idea, not to mention the amount of churn QtQuick has gone through. <a href="https://doc.qt.io/qt-6/qtwidgets-index.html">QtWidgets</a> are still there and work very well but they&#8217;re clearly an afterthought these days.</p><p>GTK is actively developer hostile. They have backtracked on what must have been the <a href="https://blogs.gnome.org/desrt/2016/06/13/gtk-4-0-is-not-gtk-4/">stupidest thing</a> I&#8217;ve ever read from the developer of a &#8220;platform API&#8221; (i.e., actively breaking backwards compatibility every 2 years, on purpose), but the fact that they wrote it at all is horrifying. They&#8217;ve also removed lots of widgets that could have been reimplemented as wrappers around their new APIs, no thought given to developers who just wanted an easier upgrade path from GTK 3 to 4. </p><p>Qt also breaks backwards compatibility between major versions, but it goes <a href="https://doc.qt.io/qt-6/qtcore5-index.html">out of its way</a> to make the transition as painless as possible.</p><h1>The Native UI Style Disaster</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!6e-R!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F449dc55a-5f99-4c7d-834e-994ff744fc22_742x952.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!6e-R!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F449dc55a-5f99-4c7d-834e-994ff744fc22_742x952.png 424w, /__u/substackcdn.com/image/fetch/$s_!6e-R!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F449dc55a-5f99-4c7d-834e-994ff744fc22_742x952.png 848w, /__u/substackcdn.com/image/fetch/$s_!6e-R!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F449dc55a-5f99-4c7d-834e-994ff744fc22_742x952.png 1272w, /__u/substackcdn.com/image/fetch/$s_!6e-R!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F449dc55a-5f99-4c7d-834e-994ff744fc22_742x952.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!6e-R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F449dc55a-5f99-4c7d-834e-994ff744fc22_742x952.png" width="349" height="447.77358490566036" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/449dc55a-5f99-4c7d-834e-994ff744fc22_742x952.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:952,&quot;width&quot;:742,&quot;resizeWidth&quot;:349,&quot;bytes&quot;:162885,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://btmc.substack.com/i/202403487?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F449dc55a-5f99-4c7d-834e-994ff744fc22_742x952.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!6e-R!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F449dc55a-5f99-4c7d-834e-994ff744fc22_742x952.png 424w, /__u/substackcdn.com/image/fetch/$s_!6e-R!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F449dc55a-5f99-4c7d-834e-994ff744fc22_742x952.png 848w, /__u/substackcdn.com/image/fetch/$s_!6e-R!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F449dc55a-5f99-4c7d-834e-994ff744fc22_742x952.png 1272w, /__u/substackcdn.com/image/fetch/$s_!6e-R!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F449dc55a-5f99-4c7d-834e-994ff744fc22_742x952.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">SUIT&#8217;s calculator demo app, mimicking the style of the Windows 7 calculator.</figcaption></figure></div><p>On top of all of this, you also have the native style churn. The shift from Windows 7 to 8 was brutal, there was absolutely no way a &#8220;native app&#8221; could have adapted. The WPF <a href="https://stackoverflow.com/questions/13741313/wpf-ribbonwindow-windows-8-control-box-looks-bad">ribbon control</a> kept the very skeumorphic gradient for a long time, which looked horrifically out of place on Windows 8 (not to mention it broke the window chrome). Windows 8 was also hideous, but that&#8217;s just my subjective opinion.</p><p>Apple has now introduced liquid glass which is 100% the ugliest desktop UI style ever created, and this one is my <em><strong>objective </strong></em>opinion<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>. Here too adapting a native app that followed older styles would be a nightmare, even if it used native widgets almost exclusively (you have to adjust how your app is laid out to make the glass effect actually matter, or you end up like Apple&#8217;s own half-baked apps that look downright schizophrenic).</p><p>And there&#8217;s absolutely no way to design a cross-platform app that would &#8220;fit in&#8221; everywhere. Not only do style guidelines keep changing within the same operating system family across versions, they&#8217;re also different across operating systems. The only way to get something truly native looking is to maintain an entirely separate UI for each platform and to redo each of those UIs whenever an OS provider decides to overhaul the style to distract from the falling quality of their software.</p><p>And for what? Aero and Aqua were beautiful and hard to replicate, it would have cost you a pretty penny to hire a design team that could pull off that sort of UI, so using the OS-provided widgets bought you a lot. But Windows 8 Metro? Single color rectangles? Trivial. Liquid Glass? Hard to replicate but it&#8217;s hideous, why would you want to make your app hideous? If the OS provider design teams suck this hard, you might as well just do your own thing. <strong>And software companies have</strong>.</p><h1>The SUIT Proposition</h1><p>SUIT rejects this whole mess, and uses the system-provided toolkits only where they provide actual value, their default looks be damned. Your app, your branding.</p><p>Nearly every cross platform app these days has given up on &#8220;native look and feel&#8221;. Even when the app isn&#8217;t just a web app bundled with Chromium, the app follows the company&#8217;s own style guidelines and branding, rather than the operating system provider&#8217;s guidelines (with good reason, cuz they suck these days!). If you use Photoshop on Windows or on macOS, it looks the same; if you use Blender on Windows or macOS, it looks the same.</p><p>SUIT is being developed with this reality in mind. It mostly gives up on native look, while trying to retain some of the feel. It achieves this by using only a limited set of native widgets (e.g., text fields) while removing their default styling entirely. Appearance is then almost completely under the control of the developer. It&#8217;s not pixel perfect across platforms, but it doesn&#8217;t need to be. </p><p>Because it uses the native frameworks, you can always host a native widget alongside the custom ones if you so wish. Some widget like scrollbars are particularly relevant because there are system settings that affect their behavior, which is why SUIT supports native scrollbars by default even though that goes against the &#8220;custom appearance&#8221; goal (you can use custom scrollbars as well if you prefer).</p><p>Even some Electron apps put in the effort to support native context menus, because that little extra bit of platform-native feel makes a big difference.</p><h1>How SUIT works</h1><p>SUIT is a C API that communicates with backends written in various languages, like Objective-C on macOS or Javascript on the web.</p><p>Backends are responsible for implementing certain primitive operations (like file dialogs and vector drawing) and providing certain primitive widgets (like text fields and scrolling viewports). Backends must also translate SUIT stylesheets into native styling or drawing commands. </p><p>More complex widgets like tab bars, tree views and split panels are implemented by SUIT itself, on top of the primitive widgets. The design is very much &#8220;composition over inheritance&#8221;, where widget constructors are just plain functions that wire up lower level widgets into higher level ones.</p><p>Memory management is mostly automatic despite SUIT being a C-library, because deleting a widget also deletes all of its children, and widgets are tracked via IDs rather than pointers, so a widget-related use-after-free always causes a crash rather than memory corruption.</p><p>SUIT&#8217;s styling system is heavily inspired by CSS, albeit simpler, as styles only affect the widgets they are applied to, there is no &#8220;cascading&#8221;. The reason it is CSS inspired is not out of preference, but rather to allow a direct translation to CSS on the web. That&#8217;s what makes the web backend work so well.</p><p>Here&#8217;s a little taste of the current API, defining the calculator display style for the Windows 7 lookalike calculator shown previously:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">static const SuitStyleProperty display_style[] = {
    suit_height({.px = 49}),
    suit_height_policy(SUIT_SIZE_POLICY_FIXED),
    suit_padding_left(8),
    suit_padding_right(7),
    suit_h_align(SUIT_ALIGN_END),
    suit_v_align(SUIT_ALIGN_CENTER),
    suit_overflow_x(SUIT_OVERFLOW_HIDDEN),
    suit_font_family("Helvetica"),
    suit_font_size(24),
    suit_text_color(WIN7_TEXT),
    suit_background_image_gradient(display_light_gradient),
    suit_border_width(1),
    suit_border_color(WIN7_DISPLAY_BORDER),
    suit_border_radius({.px = 2}),
};</code></pre></div><p>The API is nowhere near finished and has some very rough areas still, but as I keep working on it the eventual API shape starts to emerge.</p><h1>The Nightmare and the Clankers</h1><p>When I first started this project, I was quite positive on it. And in many ways, I&#8217;m still quite positive on it. The foundational idea <em>does work</em>. SUIT produces highly customizable UIs that nonetheless retain a nice native feel. This is already true today. Typing in a SUIT text field on macOS feels the same as typing in any other macOS text field, all the functionality like autocorrect and such is there. Scrolling a SUIT viewport on macOS feels just like scrolling any other viewport in macOS, including the little bump animation when you scroll against the boundary. It feels native because it <em>is</em> native.</p><p>Same goes for the two Linux backends (though they are unfinished). Windows&#8230; well, we&#8217;ll take about Windows later.</p><p>But sadly I severely underestimated the sheer scope of the project. A UI library on the level of something like Qt needs a lot&#8230; a LOT of features. Layouting, styling, input and event handling, keyboard navigation, animation, accessibility, all the different widgets, etc. Now multiply all of that to 5 different backends. It&#8217;s not insurmountable, but it is massive.</p><p>I also made the mistake of trying to have a &#8220;neutral&#8221; backend, meaning one that does all the rendering itself instead of relying on the native widgets. I should have used <a href="https://skia.org">Skia</a> and <a href="https://github.com/harfbuzz/harfbuzz">Harfbuzz</a> like everyone else, but instead I decided to just target the platform provided vector rendering and text APIs. I wanted to avoid non-OS-provided dependencies to keep SUIT as easy to set up as possible and to keep the resulting executables as small as possible.</p><p>Big mistake. <strong>Huge</strong>. I got it working in the end, but it was a nightmare, totally not worth it. The main culprit? Rich Text.</p><p>You don&#8217;t know hell until you&#8217;re trying to get text alignment and selection to work correctly across runs of bidi text on multiple different text layouting and rendering APIs (<a href="https://learn.microsoft.com/en-us/windows/win32/directwrite/direct-write-portal">DirectWrite</a>, <a href="https://developer.apple.com/documentation/appkit/textkit">TextKit</a>, <a href="https://www.gtk.org/docs/architecture/pango">Pango</a>).</p><p>So I turned to clankers<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a> for help, Claude and GPT to be specific. A <a href="https://en.wikipedia.org/wiki/Deal_with_the_Devil">faustian bargain</a>.</p><h1>LLMs are amazing, and yet they suck</h1><p>Spoiler alert: they eventually pulled it off, but it involved constant, unending regressions (despite plenty of test cases, it&#8217;s a hard problem and mostly visual, which is hard to test).</p><p>People sometimes describe the experience of working with LLMs as being like working with a junior dev, but it&#8217;s not like that at all. </p><p>Juniors start out not really knowing what they&#8217;re doing, but slowly develop into a proper developer. You, as a senior, maintain a mental model of the junior&#8217;s capabilities, allowing you to predict the outcome of their work and thereby delegate tasks to them effectively. You know what to expect and can plan accordingly. Very quickly you begin to <strong>trust</strong> them, as least for the level of tasks you know they can currently tackle. And as they improve, the more you can feel confident in delegating.</p><p>LLMs are nothing like that. LLMs are like working with a consulting company that sends a different person to work on the project every day, sometimes every few hours. Each individual person is actually quite skilled, much more than a junior and often better than many seniors&#8230; but they just arrived at your project. They have no idea what it is about, so they first have to get up to speed. They have no attachment to it, no understanding of the company culture that led to the project being structured the way it is. They&#8217;ll half-heartedly follow the general guidelines, but everyone knows those are always incomplete.</p><p>And because they&#8217;re a &#8220;different person&#8221; each time you give a task to the LLM, you cannot develop a mental model of them, of what they&#8217;ll do. The only thing you can trust is sending them a very detailed spec with automated tests. The quality of the code you get in the end? Who knows. Did they touch stuff they shouldn&#8217;t? Maybe, you gotta check. Pretty much the way it is with a consulting company made up of humans today, only 10x worse because instead of having the same person for a few months, you have them for less than a day.</p><p>But with my life being as exhausting as it was these past few months, I just delegated everything to the LLMs after the whole rich text adventure started. The more they wrecked the codebase the more I delegated to them because I couldn&#8217;t muster the willpower to clean it up. It&#8217;s amazing what they ended up accomplishing, but that lack of consistency in their output is killer. You have to <em>not care</em> about the final result to really take advantage of LLMs at the moment, but I&#8217;m unable to do so.</p><h1>What&#8217;s Next</h1><p>I&#8217;m not going to stop using LLMs for this project, but I will start being a lot more intentional about their use. There&#8217;s no way I&#8217;m releasing a (partially) vibe-coded monstrosity into the world. I will use the LLMs to do experiments and to take care of annoying boilerplate, but I will take full ownership of the code, meaning the most I&#8217;ll allow an LLM to produce at a time will be a few hundred lines of code I can immediately review and fix.</p><p>My goal is to have the first public version of SUIT out by the end of the year. It&#8217;ll be open source, likely under the MIT license.</p><h1>Side-note: The Windows Problem</h1><p>Windows is a bit of a special case, as building things out of the classic Win32 common controls doesn&#8217;t really work very well, because they don&#8217;t compose. They also do not support any sort of alpha transparency. Maybe there&#8217;s a good way to make it work but I haven&#8217;t figured it out yet. Instead the current idea is to custom render with <a href="https://learn.microsoft.com/en-us/windows/win32/direct2d/direct2d-portal">Direct2D</a>, <a href="https://learn.microsoft.com/en-us/windows/win32/directwrite/direct-write-portal">DirectWrite</a>, and <a href="https://learn.microsoft.com/en-us/windows/win32/directcomp/directcomposition-portal">DirectComposition</a>, and use an API called <a href="https://learn.microsoft.com/en-us/windows/win32/controls/windowless-rich-edit-controls">ITextServices</a> to handle all the text editing. Context menus can be native Win32 since they are not part of the main window. Only scrolling viewports will unfortunately not be very native feeling, but not much I can do about that.</p><p>The alternative is to use the latest Microsoft recommended API, <a href="https://learn.microsoft.com/en-us/windows/apps/winui/winui3/">WinUI3</a>, included as part of the <a href="https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/">WindowsAppSDK</a>. Sadly, WinUI3 has the problem that it is not actually distributed with the OS, not in the same way Direct3D is. Rather, the user must install the appropriate version of the runtime.</p><p>There are three different ways to do this. The recommended way is to distribute the application as a <a href="https://learn.microsoft.com/en-us/windows/msix/overview">.msix</a> installer, which can then pull the correct 100+ MB version of the runtime from the Microsoft Store if it is not already installed. Think <a href="https://docs.flatpak.org/en/latest/available-runtimes.html">Flatpak runtimes</a> on Linux for something somewhat equivalent. I might support this as an alternative Windows backend in the future if WinUI3 proves itself, but remember what I said about a graveyard of half-finished UI frameworks? Yeah&#8230;</p><p>The other two ways are:</p><ol><li><p>Distribute the 100+ MB runtime installer alongside your application&#8217;s installer. Fine if your application is huge, not fine if your application is less than half a megabyte like the SUIT notepad demo. You <em>have</em> to install the <em>correct</em> minor version of the runtime, it is <em>not</em> backwards compatible.</p></li><li><p>Distribute just the necessary DLLs with the application. Sadly we&#8217;re talking more than a dozen DLLs here, totaling at a minimum around 30 MB in size.</p></li></ol><p>Considering that SUIT can do what WinUI3 does in tiny fraction of the size, you really have to wonder what in the world WinUI3 is doing under the hood.</p><h1>Conclusion</h1><p>So that&#8217;s what I&#8217;ve been up to, not dead (yet), and I&#8217;ll hopefully get back to writing articles more regularly soon. Thank you all for not unsubscribing meanwhile, I really appreciate the support.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Why is SUIT not called &#8220;Proton&#8221; or &#8220;Positron&#8221; or some such if it is a &#8220;reverse Electron&#8221;? Because it is not a reverse Electron, that&#8217;s just the easiest way to explain the product-market fit. You should never define your projects as the negative of something else, unless they are <em>exactly</em> that. Otherwise you&#8217;re just tying yourself down. If &#8220;Electron&#8221; suddenly loses popularity, then any project defined as its opposite loses relevance. SUIT is like a reverse Electron in what it tries to achieve, but otherwise has no similarities. It would have merit even if Electron did not exist.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Native here meaning &#8220;not a web app bundled with Chromium&#8221;, because they don&#8217;t use the native OS-provided UI frameworks under the hood. Those are a different sort of &#8220;native&#8221;.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>No, not even Win32. You can trust it to always be there, sure, but not that it will get proper high-quality updates over time like Direct3D has had. Dark mode isn&#8217;t even officially supported by the Win32 common controls, you have to use undocumented APIs and they&#8217;re highly incomplete.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>Grey text over an animated glass effect is completely unreadable and totally unacceptable. A stack of different corner radius in different apps from the same company on the same OS is unacceptable. Covering entire context menus in nearly indistinguishable icons is unacceptable. There&#8217;s a reason they backtracked on most of these, but that doesn&#8217;t mean it is no longer terrible, it just makes it usable.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>Clankers = &#8220;AI&#8221; agents, meaning Large Language Models.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Transaction-Oriented Programming]]></title><description><![CDATA[The concept of a transaction transcends databases.]]></description><link>https://btmc.substack.com/p/transaction-oriented-programming</link><guid isPermaLink="false">https://btmc.substack.com/p/transaction-oriented-programming</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Sat, 15 Nov 2025 13:59:50 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/15d805c1-c7ef-4a42-b5f6-a994c03bb066_4505x3006.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Small administrative note: I&#8217;d like to apologize for the long drought between posts. I&#8217;ve been working on an article related to UI, and in order to accurately discuss some topics I&#8217;ve spent quite a bit of time working on a prototype. The issue is that it appears I&#8217;ve bitten off more than I can chew, given my very limited free time. Hopefully I&#8217;ll be able to get that article out eventually, but it is not coming out any time soon, so I decided to make a smaller post in-between.</p></blockquote><p>In this post I want to discuss some ideas I&#8217;ve had regarding software design. I&#8217;ll put a disclaimer right away that I have not put these ideas into practice in any serious capacity. These ideas are, however, based on other existing ideas that have been very successful in practice, but not necessarily described in these terms or placed in the same &#8220;conceptual bucket&#8221;, despite their commonalities.</p><p>The main idea I want to discuss is what I call <strong>Transaction-Oriented Programming</strong> (because I am horrible at naming things). Right away what probably comes to your mind are databases, and databases can be seen as an instance of the idea, but what I&#8217;m thinking of as a &#8220;transaction&#8221; is broader.</p><p>The idea of <strong>Transaction-Oriented Programming</strong> is to figure out &#8220;<strong>user-relevant units of work</strong>&#8221; (more on what those are below), and to implement those units as <strong>transactions</strong> in your application, following the usual <a href="https://en.wikipedia.org/wiki/ACID">ACID</a> properties<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>.</p><p>My hypothesis is that structuring software this way leads to a pit of success. Figuring out these units plus the constraint of making them transactions provides clear and actionable guidelines regarding software architecture, error handling, etc., that I believe ultimately lead to better software.</p><h1>User-Relevant Units of Work</h1><p>What constitutes a User-Relevant Unit of Work is somewhat fuzzy (or at least fuzzy in my head), and I don&#8217;t have a clear definition for it, so instead I&#8217;ll try explain it with examples.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a></p><p>What is a <strong>User-Relevant Unit of Work</strong> for the <code>cd</code> (change directory) command in the terminal? Well, it&#8217;s the whole command. There&#8217;s no subset of work the cd command does that is user-relevant. You either changed directories, or you didn&#8217;t<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>. </p><p>I believe this is why using <code>abort()</code> as an error handling solution on many batch applications doesn&#8217;t feel particularly wrong, not in the way it would for a more interactive application. There&#8217;s no point in propagating errors around nor retrying the operation nor anything of the sort if the only valid response is to terminate the application outright. Consider the following:</p><ul><li><p>If the error was reported by throwing an exception, then the exception would be caught, its message printed, and the application would exit with an error code. </p></li><li><p>If an error code was propagated through a chain of function calls, then main would check the error code, map it to an error message, print it, and exit the application with an error code (<code>Result&lt;T,E&gt;</code> and friends are the same).</p></li><li><p>If the error was placed in some internal error log, and then the rest of the program operated on a bunch of &#8220;null-objects&#8221; (as in <a href="https://www.rfleury.com/p/the-easiest-way-to-handle-errors">Ryan Fleury&#8217;s error handling approach</a>), then eventually main will check the error log, print out the message, and exit the application with an error code.</p></li></ul><p>No matter which error handling strategy you use in this case, you arrive at the same destination, so the correct error handling solution is whichever is the simplest/most convenient/most efficient: in this case, calling abort.</p><p>In this example, the entire command forms a trivial <strong>transaction</strong>. It either does the task completely, or not at all, and since it does not do multiple tasks in parallel it is trivially isolated (durability is not relevant).</p><p>For other commands it&#8217;s less clear. What is a <strong>User-Relevant Unit of Work</strong> for the <code>cp</code> (copy) command? It&#8217;s actually not the whole command anymore, necessarily, because of directories. If you copy a directory, then each file being copied can be considered a user-relevant unit of work most of the time.</p><p>If an error occurs (e.g., one file could not be copied), then this should usually not lead to the complete command failing, it should just log the error and continue on with the larger task at hand. In this case, aborting the program outright on the first file that failed to copy would be a pretty bad error handling solution.</p><p>But what about partially copying a file? Partially copying an individual file is very rarely wanted (since it would end up corrupted), so copying part of a file is not normally a user-relevant unit of work<em>. </em>The copy of each file is a unit, and each unit is a transaction: either the file is copied completely, or not at all.</p><p>But writing part of a file can be a user-relevant unit of work when downloading a file via BitTorrent for example, so what constitutes a user-relevant unit of work varies depending on the context, but in all cases error handling happens around the unit, and each unit is handled as transaction.</p><h1>Guidelines for Error Handling</h1><p>Whichever error handling mechanism (exceptions, error codes, etc.) you use, they will all ultimately have to accomplish the same thing: abort the current transaction and inform the user. If the user is presented with an option when this happens, that&#8217;s a <em>new</em> transaction, the other one already failed.</p><p>So it doesn&#8217;t really matter which error handling mechanism you use, so much as how you use it. For some problems error codes will require you to propagate errors unnecessarily<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>, leading to needlessly noisy code, while for other problems aborting the program outright is clearly not a valid option. All mechanisms have to do the same job in the end, so pick whichever works best for the task.</p><p>You might think that exceptions are the perfect solution here, since you can put a &#8220;try-catch&#8221; block around the block of code that implements the complete &#8220;user-relevant unit of work&#8221;, but you&#8217;d be wrong. What makes you think there&#8217;s such an obvious block of code? The software might be implemented using a task queue and a thread pool, with multiple tasks running per &#8220;user-relevant unit of work&#8221;, which greatly complicates things.</p><p>You need to keep in mind that you are running a transaction, and you need a plan on how you&#8217;re going to ensure it is ACID.</p><h1>Guidelines for Software Architecture</h1><p>In the example above we already saw how these &#8220;units&#8221; provide some guidance on how to structure a software application in terms of error handling: When an error occurs, you need to abort the current transaction.</p><p>But that begs the question: how exactly does one abort a transaction correctly? As a reminder, these are the four ACID properties you need to ensure:</p><ul><li><p><strong>Atomicity</strong>: How do you ensure the transaction either happens in its entirety, or fails in its entirety?</p></li><li><p><strong>Consistency</strong>: How do you ensure that there are no &#8220;leftovers&#8221; polluting the app state and its environment when the transaction is aborted?</p></li><li><p><strong>Isolation</strong>: How do you ensure that tasks can execute concurrently without interfering with each other?</p></li><li><p><strong>Durability</strong>: How do you make the results of the transaction persistent? This may or may not be relevant for the application, but is worth considering.</p></li></ul><p>If you think about it, you need to ensure most of these properties for software to be reliable, specially consistency! If an error occurs and your software is now in a weird in-between state, that corruption will eventually lead to broken behavior and the user needing to restart the application.</p><p>So how do you ensure these properties? Well, there are many ways, and different software architectures can make it either very easy, or very hard.</p><p>Distributed software is obviously a nightmare in regard to the above, since you need a lot of additional coordination to ensure every process stays on the same page. But distributed software often simply stores the serious &#8220;app state&#8221; in a database anyway, and the database has those properties, so it works out in the end.</p><p>Object-oriented software (as in, software architected around communicating objects with isolated state), is just a local and synchronous form of distributed software. The same problem is there, but now there&#8217;s no database to fallback to, so it is a disaster in this regard. It&#8217;s well known in OOP circles that maintaining object invariants is extremely important (the private keyword is there for a reason), but a web of consistent objects is not necessarily consistent as a whole!</p><p><a href="https://en.wikipedia.org/wiki/Smalltalk">Smalltalk</a> works around this problem by being really good at Durability. If you break the Smalltalk image, you can just revert to an older working version. If not for that, you&#8217;d be screwed, since you can very easily change the state of a large web of objects in a way that is essentially impossible to recover from.</p><p>Software with a &#8220;<a href="https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell">Functional Core, Imperative Shell</a>&#8221; fares much better. If you think about it, the &#8220;functional core&#8221; is trivially a <strong>transaction</strong>! It only does compute, producing a command or list of commands that are then handled by the &#8220;shell&#8221;. If there&#8217;s an error, then there cannot have been any pollution of application state since it was never touched. While the shell itself might have issues performing the commands, the &#8220;surface area&#8221; where problems can occur has been greatly reduced.</p><p>Some <a href="http://sevangelatos.com/john-carmack-on/">very prominent software developers</a> vouch for this software architecture, and this paradigm I&#8217;m proposing gives some answers as to <em>why</em> it works well. </p><p>The fact that the core is functional actually matters very little. It might have benefits for testability and such, but the fact that the app state remains consistent is the important part. Similar to how functions with local mutation are not really all that different from 100% pure functions, what matters is the <a href="https://en.wikipedia.org/wiki/Referential_transparency">referential transparency</a>.</p><p>The same outcome could be achieved with a &#8220;double buffered&#8221; application state, where the code imperatively writes to the new state and it is only switched with the old state if no error occurs. Or by being able to &#8220;undo&#8221; the work you did up to the point the error occurred. They all implement the same thing: a <strong>transaction</strong>.</p><p>Immediate mode UIs, Reactive UIs, etc., are also all ways of reducing the chances of the application entering an inconsistent state. The more derived state, the less the app state can go haywire. But having the state be derived is just a mechanism. If you can ensure that every transaction always sets both the app and UI states, or neither if it fails, you end up in the same place! Derived UI state is just the easiest way to achieve the intended outcome.</p><h1>Transaction-Oriented Programming</h1><p>Paradigms aren&#8217;t nearly as important as the specific properties they embody that are beneficial in practice, so I don&#8217;t think a whole new paradigm matters<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>.</p><p>If you are doing &#8220;Functional Core, Imperative Shell&#8221;, you&#8217;re already reaping most of the benefits an imaginary &#8220;Transaction-Oriented Programming&#8221; paradigm would give you. Same is true if you&#8217;re using a database, since that&#8217;s already an implementation of the paradigm by nature.</p><p>But as a fun exercise, what would Transaction-Oriented Programming look like? And how would you implement a generic and composable &#8220;transaction&#8221; concept that programs can be built around? </p><p>I&#8217;m going to assume &#8220;just implement the <a href="https://en.wikipedia.org/wiki/Relational_model">relational model</a>&#8221; isn&#8217;t a valid answer, that&#8217;s another more specific paradigm. Plus you can implement it in a way where the database state is consistent but the application overall isn&#8217;t (i.e., due to side-effects).</p><h1>A Python Prototype</h1><p>There are two ways we can implement transactions: either the transaction collects all the work to be done, and then commits it at the end if everything succeeded, or the transaction does the work right away, but then undoes it if there is an error. Depending on the task one or the other is better. We&#8217;ll implement the undo version but you could also have the two kinds co-existing:</p><pre><code>class Transaction(ABC):
  def __init__(self):
    self._error: Exception | None = None
  
  def run(self):
    self._execute()
    if self._error:
      raise self._error

  def _execute(self) -&gt; bool:
    try:
      self._task()
      return true
    except Exception as e:
      self._error = e
      self._undo()
      return false

  def _abort(self):
    if self._error is None:
      self.undo()
      self._error = AbortedTransactionError()

  @abstractmethod
  def _task(self):
    pass

  @abstractmethod
  def _undo(self):
    pass</code></pre><p>For simplicity, we&#8217;ll assume transactions cannot be canceled midway, which would require using generators or async.</p><p>The only public method of a Transaction is <code>run()</code>, which executes the transaction to completion, possibly throwing an exception.</p><p>The <code>run()</code> method is, however, implemented using a separate <code>_execute()</code> method, which traps any exception that occurs. This split is what will allow us to compose transactions into larger ones that can be handled atomically.</p><p>Each transaction must implement two methods: <code>_task()</code>, which does the actual work, and <code>_undo()</code>, which reverses the work that was done up to that point.</p><p>The <code>_abort()</code> method is used by composite tasks to revert sub tasks that already finished successfully within the larger transaction.</p><p>We can make our first primitive transaction now:</p><pre><code>class CopyFile(Transaction):
  def __init__(self, source: str, dest: str):
     super().__init__()
     self._source = source
     self._dest = dest

  def _task(self):
    shutil.copyfile(self._source, self._dest)

  def _undo(self):
    if self._source != self._dest:
      try:
        os.remove(self._dest)
      except:
        pass

t = CopyFile("hello.txt", "world.txt")
t.run()</code></pre><p>Not particularly interesting, but this is our building block. Now let us create a composite transaction that executes its sub transactions sequentially, aborting if one of them fails:</p><pre><code>class Sequence(Transaction):
  def __init__(self, children: *Transaction):
    super().__init__()
    self._children = children
    self._failed: int = -1

  def _task(self):
    for i, t in enumerate(self._children):
      if not t._execute():
        self._failed = i
        raise t._error
        
  def _undo(self):
    for i in range(0, self._failed + 1):
      self._children[i]._undo()

f1 = CopyFile("a.txt", "b.txt")
f2 = CopyFile("b.txt", "c.txt")
s = Sequence(f1, f2)
s.run()</code></pre><p>We can also make a task that always succeeds, even if there is an error:</p><pre><code>class Optional(Transaction):
  def __init__(self, sub: Transaction):
    super().__init__()
    self._sub = sub

  def _task(self):
    # ignore the error
    if not self._sub._execute():
      self._sub._undo()
  
  def _undo(self):
    if self._sub._error is None:
      self._sub._undo()</code></pre><p>We could then use these building blocks to implement more complex transactions, for example, a transaction that recursively copies files from a directory to another.</p><h1>Transaction-Oriented Language</h1><p>The above would let you create and manipulate transactions in an existing programming language, but what would a transaction-oriented language look like? I&#8217;m honestly not sure, but maybe something like the following:</p><pre><code>transaction copy_folder(source, dest):
  mkdir(dest)
  for p in dir(source):
    if is_directory(p):
      copy_folder(join(source, p), join(dest, p))
    else:
      copy_file(join(source, p), join(dest, p))  

transaction copy_file(source, dest):
  f1 = open(source, "r")
  f2 = open(dest, "w")
  write(f2, read(f1))</code></pre><p>Each operation is a made up of smaller transactions. If any fails, undo is called accordingly, a bit like exceptions + RAII. The main difference is that operations that succeeded are <em>also</em> undone! Either the entire transactions happens, or nothing does.</p><p>Primitive transactions would define different sub-operations:</p><pre><code>foreign transaction mkdir(directory):
  on execute:
    native_mkdir(directory)
  on abort:  
    native_rmdir(directory)</code></pre><p>You could also support a different variant of transaction that only commits its work when everything succeeds, might be important for printing which cannot be undone:</p><pre><code>foreign transaction print(text):
  on execute:
     pass
  on commit:
    native_print(text)</code></pre><p>In this case, every committed operation would be placed in a FIFO queue to be executed when the parent transaction terminates. Two operators could be added to support &#8220;partial success&#8221;: the optional operator (?) and the commit operator (!).</p><pre><code># even if this transaction fails the parent can continue
my_transaction()?
# do not undo this transaction even if the parent fails
my_transaction()!</code></pre><p>Compiling transactions like this into efficient code is somewhat tricky since being able to call transactions in a loop requires keeping track of which particular transactions in the loop succeeded or not, such that they can be undone.</p><p>That will usually mean heap allocation for an undo stack (a bit like how defer works in the Go language) and/or a commit queue, but one could use a temp Arena just for that purpose, which would be reasonably efficient.</p><h1>Conclusion</h1><p>Transactions are good. <a href="https://en.wikipedia.org/wiki/Edgar_F._Codd">Ted Codd</a> was a genius and we&#8217;ve all suffered massively for not listening to him and building software using the relational model, instead chasing silly ideas like object-oriented programming and garbage like SQL<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>.</p><p>Reliable software implements transactions, whether the developers realize it or not. Software architectures can be evaluated in terms of how easy or how hard they make it to implement transactions. Correctness of error handling can be rephrased in terms of implementing transactions correctly.</p><p>We might not need an actual &#8220;Transaction-Oriented Programming&#8221; paradigm, just as most of the benefits of functional programming can be achieved in a procedural language with local mutation and an avoidance of side-effects, but thinking in the &#8220;native language&#8221; of a paradigm provides clarity.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>The final property, Durable, may or may not be relevant to the program and/or task, however. But thinking if it does or does not matter is worth considering!</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Hey, if these days we&#8217;re developing algorithms by presenting an artificial neural network with a bunch of examples, why can&#8217;t I do the same with the (for now) far superior natural neural network in your brain?</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>Showing the help instructions is another User-Relevant Piece of Work but the same situation applies. Running the application to completion is the size of the &#8220;task&#8221;.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>They help you remember to check for the error (at least &#8220;error codes&#8221; of the monadic kind will), which is certainly valuable to avoid some nasty debugging sessions, but it&#8217;s not relevant for the task. The error will just be manually propagated to the exact same place the exception would be caught at.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>I&#8217;m also 100% sure I&#8217;m not the first person to think of this.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>We&#8217;re also reinventing databases and calling them Entity Component Systems and other silly names like that, see <a href="https://www.flecs.dev/flecs/md_docs_2Queries.html">Flecs</a> for an extreme example. But hey, I&#8217;m not complaining! A rose by any other name would smell as sweet.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Thoughts on Visual Programming]]></title><description><![CDATA[It's a shame we never moved past monospaced ASCII text.]]></description><link>https://btmc.substack.com/p/thoughts-on-visual-programming</link><guid isPermaLink="false">https://btmc.substack.com/p/thoughts-on-visual-programming</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Sat, 06 Sep 2025 21:39:30 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!npsi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e849509-30e7-487a-a71c-4d2c53bd00d0_1536x1024.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>My first programming experience, back when I was single digits in age, was a demo CD of <a href="https://en.wikipedia.org/wiki/Delphi_(software)">Delphi</a> 1.0, a so called &#8220;<a href="https://en.wikipedia.org/wiki/Rapid_application_development">RAD</a>&#8221; tool. Delphi (similarly to Visual Basic) allowed you to drag and drop components like buttons and lists into a window and then link various events like clicks to event handling methods. The methods themselves had to be written as monospaced ASCII text (Object Pascal, for the unaware).</p><p>Later I played around with <a href="https://en.wikipedia.org/wiki/Microsoft_FrontPage">FrontPage</a> and <a href="https://en.wikipedia.org/wiki/Adobe_Dreamweaver">Dreamweaver</a>, which enabled me to design websites visually. Very similar in spirit to Delphi, only targeting HTML instead. Events could be mapped to Javascript functions, which had to be written as monospaced ASCII text.</p><p>Next came <a href="https://en.wikipedia.org/wiki/GameMaker">Game Maker</a>, the pre-Studio version developed by <a href="https://en.wikipedia.org/wiki/Mark_Overmars">Mark Overmars</a>. It went even further on the visual aspects. It included an image editor for sprites and backgrounds, a tilemap editor, and a visual editor for &#8220;objects&#8221;, which included a drag-and-drop interface for programming the object&#8217;s behavior<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>. Actual visual coding! But the only way to have any sort of abstraction was to create a &#8220;Script&#8221; and write monospaced ASCII text (which sadly didn&#8217;t map 1-to-1 to the drag-and-drop interface).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!DaoZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff99b4979-4520-4ae2-85e5-71d6b59284b0_699x378.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!DaoZ!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff99b4979-4520-4ae2-85e5-71d6b59284b0_699x378.png 424w, /__u/substackcdn.com/image/fetch/$s_!DaoZ!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff99b4979-4520-4ae2-85e5-71d6b59284b0_699x378.png 848w, /__u/substackcdn.com/image/fetch/$s_!DaoZ!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff99b4979-4520-4ae2-85e5-71d6b59284b0_699x378.png 1272w, /__u/substackcdn.com/image/fetch/$s_!DaoZ!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff99b4979-4520-4ae2-85e5-71d6b59284b0_699x378.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!DaoZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff99b4979-4520-4ae2-85e5-71d6b59284b0_699x378.png" width="699" height="378" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f99b4979-4520-4ae2-85e5-71d6b59284b0_699x378.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:378,&quot;width&quot;:699,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;UCLA Game Lab &#187; Game Maker Tutorial #1&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="UCLA Game Lab &#187; Game Maker Tutorial #1" title="UCLA Game Lab &#187; Game Maker Tutorial #1" srcset="/__u/substackcdn.com/image/fetch/$s_!DaoZ!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff99b4979-4520-4ae2-85e5-71d6b59284b0_699x378.png 424w, /__u/substackcdn.com/image/fetch/$s_!DaoZ!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff99b4979-4520-4ae2-85e5-71d6b59284b0_699x378.png 848w, /__u/substackcdn.com/image/fetch/$s_!DaoZ!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff99b4979-4520-4ae2-85e5-71d6b59284b0_699x378.png 1272w, /__u/substackcdn.com/image/fetch/$s_!DaoZ!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff99b4979-4520-4ae2-85e5-71d6b59284b0_699x378.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The old Game Maker Drag-and-Drop UI to program objects. Screenshot taken from the UCLA Game Lab web page.</figcaption></figure></div><p>From what I can tell, over the years we&#8217;ve only moved backwards towards monospaced ASCII text. Even things that should be visually edited like GUIs are now mostly written as monospaced text, maybe with &#8220;live preview&#8221; to help out. Computers have made absolutely incomprehensible leaps in performance and capability since the 1940s, but we still program them like we did in the 1980s! Even back in the day all that visual editors were really doing was write monospaced ASCII text under the covers.</p><p>The standard interface to most programming language compilers is an emulator of a <a href="https://en.wikipedia.org/wiki/Teleprinter">device from the late 1800s</a>. The <a href="https://survey.stackoverflow.co/2025/technology#1-dev-id-es">most popular editor these days</a>, Visual Studio Code, is downright <a href="https://github.com/microsoft/vscode/issues/41309">alergic to toolbars</a>. We use <a href="https://en.wikipedia.org/wiki/Pretty-printing#Programming_code_formatting">tools</a> whose only job is to move ASCII characters around to avoid bike-shedding about where to place them on a grid, even though the specific positioning of a curly bracket matters very little as long as indentation is done properly. Our IDEs have to repeatedly parse monospaced ASCII text to develop an index with all the types and functions in the project, something the compiler also has to do all over again when invoked. Its silly.</p><p>How did we end up here?</p><h1>Asking a Robot to Bang Rocks</h1><p>As a little side-note, I find it absolutely hilarious that the current &#8220;state-of-the-art&#8221; in software development (according to some people at least) is to chat with the closest thing to artificial intelligence we&#8217;ve built (so far), and to ask it to spit out monospace ASCII text and run commands in a teletype emulator. Regardless of how effective they are at the task, I find the whole setup hilarious, like a caveman asking a robot to bang rocks in order to start a fire. LLMs are only going to exacerbate this local optimum we&#8217;re currently stuck in.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!npsi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e849509-30e7-487a-a71c-4d2c53bd00d0_1536x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!npsi!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e849509-30e7-487a-a71c-4d2c53bd00d0_1536x1024.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!npsi!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e849509-30e7-487a-a71c-4d2c53bd00d0_1536x1024.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!npsi!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e849509-30e7-487a-a71c-4d2c53bd00d0_1536x1024.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!npsi!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e849509-30e7-487a-a71c-4d2c53bd00d0_1536x1024.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!npsi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e849509-30e7-487a-a71c-4d2c53bd00d0_1536x1024.jpeg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2e849509-30e7-487a-a71c-4d2c53bd00d0_1536x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:651873,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://btmc.substack.com/i/172942825?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e849509-30e7-487a-a71c-4d2c53bd00d0_1536x1024.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!npsi!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e849509-30e7-487a-a71c-4d2c53bd00d0_1536x1024.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!npsi!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e849509-30e7-487a-a71c-4d2c53bd00d0_1536x1024.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!npsi!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e849509-30e7-487a-a71c-4d2c53bd00d0_1536x1024.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!npsi!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e849509-30e7-487a-a71c-4d2c53bd00d0_1536x1024.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">What programming with LLM assistance feels like. &#8220;Drawn&#8221; by a robot (ChatGPT).</figcaption></figure></div><h1>The Advantages of ASCII Text</h1><p>In order to understand why Visual Programming doesn&#8217;t seem to catch on outside of some very specific niches, we have to first understand why ASCII text continues (and likely will continue) to be the dominant programmer-machine interface.</p><ol><li><p>The platform APIs are provided (ultimately) as ASCII text. If I want to program an application targeting any of the current mainstream platforms, an API described in a language whose interface is ASCII text is going to be involved at some point. When the limitations of the visual interface are hit, and the programmer has to &#8220;drop down&#8221; to ASCII text, it&#8217;s almost always very painful.</p></li><li><p>The languages that we use do not lend themselves well to visual programming (see: modern Game Maker&#8217;s awful node-based UI). Working with structured programming in the form of a flow-chart or node-graph results in a massive loss of information density and development speed.</p></li><li><p>Directly manipulating the AST of a programming language, as in <a href="https://www.jetbrains.com/mps/">Jetbrains MPS</a>, is worse in nearly every way to working with ASCII text. All the disadvantages with almost no advantages. The loose nature of ASCII text is a feature, not a bug. The ability to copy-paste and tweak some characters or &#8220;find-and-replace&#8221; a bunch of times to refactor some piece of code (with &#8220;broken&#8221; intermediate steps), arrives at the destination much faster than having to follow a limited set of always valid but otherwise rigid transformations.</p></li><li><p>We have lots of tools that work with ASCII text, most available for free or very cheaply. It is a lot easier to develop tools that work with ASCII text than fancy visual editors.</p></li><li><p>Most Visual Programming tools that do exist are designed as low-barrier-to-entry solutions for non-programmers, rather than tools meant to increase the productivity of expert programmers. The so called &#8220;no-code&#8221; tools targeted at large businesses are all vendor-lock-in scams (IMO).</p></li></ol><p>It also doesn&#8217;t help that programmers have been burned very badly with the one major industry attempt at professional &#8220;visual programming&#8221; (or architecting rather): <a href="https://en.wikipedia.org/wiki/Unified_Modeling_Language">UML</a>. UML class diagrams map very poorly to actual code, something everyone that had to deal with UML learnt very quickly and very painfully. <a href="https://en.wikipedia.org/wiki/State_diagram#Harel_statechart">Harel Statecharts</a> (also part of UML) are actually pretty great, but they weren&#8217;t the focus in development circles<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>.</p><p>If programming is to move away from ASCII text, then Visual Programmings needs to shift focus from being low-barrier-to-entry (even if that&#8217;s a nice property to have), to being a productivity boost for experts. It must surpass, in terms of development speed, all the shortcuts ASCII text allows, which these days includes having a robot do some of the work for you.</p><h1>Where Visual Programming Succeeded</h1><p>While most mainstream software development activities have completely abandoned visual programming, including visually editing of parts of the program (like the UI) or even having a freaking build button in the IDE, that&#8217;s not true of every domain.</p><p>Tools like <a href="https://www.construct.net/en">Construct 3</a> and Game Maker are pretty popular despite their UX issues<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>. Godot and Unity don&#8217;t have official visual programming toolkits, but they are otherwise highly visual. Unreal Engine has <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/blueprints-visual-scripting-in-unreal-engine">Blueprints</a> which is a low-barrier-to-entry for non-programmers (e.g., level designers) solution. <a href="https://www.rpgmakerweb.com">RPG Maker</a>, as long as you&#8217;re making a bog-standard turn-based 2D JRPG, requires no &#8220;programming&#8221; in the usual sense. I also have a soft spot for the extremely powerful <a href="https://en.wikipedia.org/wiki/Warcraft_III:_Reign_of_Chaos">Warcraft 3 level editor</a><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!v4Dq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a04bb63-a066-43be-8f35-67a9f3275735_1342x968.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!v4Dq!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a04bb63-a066-43be-8f35-67a9f3275735_1342x968.png 424w, /__u/substackcdn.com/image/fetch/$s_!v4Dq!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a04bb63-a066-43be-8f35-67a9f3275735_1342x968.png 848w, /__u/substackcdn.com/image/fetch/$s_!v4Dq!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a04bb63-a066-43be-8f35-67a9f3275735_1342x968.png 1272w, /__u/substackcdn.com/image/fetch/$s_!v4Dq!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a04bb63-a066-43be-8f35-67a9f3275735_1342x968.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!v4Dq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a04bb63-a066-43be-8f35-67a9f3275735_1342x968.png" width="1342" height="968" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6a04bb63-a066-43be-8f35-67a9f3275735_1342x968.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:968,&quot;width&quot;:1342,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:235846,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://btmc.substack.com/i/172942825?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a04bb63-a066-43be-8f35-67a9f3275735_1342x968.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!v4Dq!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a04bb63-a066-43be-8f35-67a9f3275735_1342x968.png 424w, /__u/substackcdn.com/image/fetch/$s_!v4Dq!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a04bb63-a066-43be-8f35-67a9f3275735_1342x968.png 848w, /__u/substackcdn.com/image/fetch/$s_!v4Dq!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a04bb63-a066-43be-8f35-67a9f3275735_1342x968.png 1272w, /__u/substackcdn.com/image/fetch/$s_!v4Dq!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a04bb63-a066-43be-8f35-67a9f3275735_1342x968.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A modern depiction of the Warcraft 3 Trigger Editor, taken from hiveworkshop.com</figcaption></figure></div><p>So at least for game development some degree of Visual Programming is still around. But it is very high level and with very poor abstraction capabilities. It&#8217;s not really a serious visual alternative to programming in monospaced ASCII text.</p><p>Non-software engineering fares better. Tools like <a href="https://en.wikipedia.org/wiki/Simulink">Simulink</a> and <a href="https://en.wikipedia.org/wiki/Simcenter_Amesim">Amesim</a> are almost entirely geared towards visual modeling. Simulink has MATLAB under the hood but Amesim doesn&#8217;t have any actual textual language as a base. The <a href="https://modelica.org">Modelica</a> language has a textual representation, but the tools all provide graphical modeling.</p><p>These tools, unlike the game development ones, have proper visual-abstraction capabilities, and can be <strong>extremely expensive</strong> (some 10s of thousands of dollars). </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!q1bn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca4e6b09-bfb9-48fe-83b5-6d045a02dfac_831x466.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!q1bn!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca4e6b09-bfb9-48fe-83b5-6d045a02dfac_831x466.png 424w, /__u/substackcdn.com/image/fetch/$s_!q1bn!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca4e6b09-bfb9-48fe-83b5-6d045a02dfac_831x466.png 848w, /__u/substackcdn.com/image/fetch/$s_!q1bn!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca4e6b09-bfb9-48fe-83b5-6d045a02dfac_831x466.png 1272w, /__u/substackcdn.com/image/fetch/$s_!q1bn!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca4e6b09-bfb9-48fe-83b5-6d045a02dfac_831x466.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!q1bn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca4e6b09-bfb9-48fe-83b5-6d045a02dfac_831x466.png" width="831" height="466" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ca4e6b09-bfb9-48fe-83b5-6d045a02dfac_831x466.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:466,&quot;width&quot;:831,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:36209,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://btmc.substack.com/i/172942825?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca4e6b09-bfb9-48fe-83b5-6d045a02dfac_831x466.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!q1bn!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca4e6b09-bfb9-48fe-83b5-6d045a02dfac_831x466.png 424w, /__u/substackcdn.com/image/fetch/$s_!q1bn!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca4e6b09-bfb9-48fe-83b5-6d045a02dfac_831x466.png 848w, /__u/substackcdn.com/image/fetch/$s_!q1bn!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca4e6b09-bfb9-48fe-83b5-6d045a02dfac_831x466.png 1272w, /__u/substackcdn.com/image/fetch/$s_!q1bn!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca4e6b09-bfb9-48fe-83b5-6d045a02dfac_831x466.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The sort of thing you can model in Simulink, note the higher-level UpdateCounter component used twice in the model.</figcaption></figure></div><p>Why have these tools succeeded where others have failed? I believe the main reason is what I like to call <em>immediacy</em>. <strong>It&#8217;s how close the level you&#8217;re working at is to the level you&#8217;re thinking in, how quickly you can get feedback, and how easily you can reflect it back onto the code.</strong></p><p>This is related to, but not equivalent to, low-barrier-to-entry. Software like Simulink and Amesim is not low-barrier-to-entry, these tools need extensive training to use effectively, and their core audience is made up of experienced professionals.</p><p>But they have very high immediacy. I can drag and drop a variable from a port of a block in an Amesim model and instantly get a plot showing how its value evolved over time in a simulation. There is an absolutely monstrous library of ready to use components (6500+), at different levels of abstraction, with familiar looking icons, that can be used to build more complex systems. I don&#8217;t mean packages that can be installed, they&#8217;re all part of the &#8220;standard library&#8221;. It also includes many domain-specific UIs for specific problem domains with custom visualizations.</p><p>Some customers report an 80% reduction in development time. That&#8217;s easily worth the 10s of thousands of dollars a single seat license costs. Is there any visual programming platform for software development that can make a similar claim? Yes, RPG Maker, if you&#8217;re making a turn-based 2D JRPG and don&#8217;t mind its defaults.</p><h1>Not Drawing, Seeing!</h1><p>For programming to escape its monospaced ASCII shackles, focus must be placed on what actually helps, and it&#8217;s not drawing lines and rectangles. We&#8217;ll talk about node-graphs in a bit because they <em>enable</em> certain things, but rectangles and polylines are not what changes the game.</p><p>The really helpful part of Visual Programming is not the &#8220;drawing&#8221;, it&#8217;s the &#8220;seeing&#8221;<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>. Even in the monospaced ASCII world we make use of visualization: Indentation and syntax highlighting are visual aids to help our brains map ASCII text to a mental AST more quickly. Debuggers let us see how variables change over time as we step through the code. In VSCode I can hover over, e.g., a Rust identifier and get useful information like its documentation, type, size, etc. or jump to its declaration and implementations.</p><p>These are all helpful forms of visualization, but also very limited. We can (and should) take this further, <strong>much</strong> further.</p><h1>Visualized Programming</h1><p>One of my favorite things awhile back was the <a href="https://elm-lang.org/news/time-travel-made-easy">Elm Reactor</a>. It was a time traveling debugger that took advantage of Elm&#8217;s event driven architecture and functional purity to allow &#8220;rolling back time&#8221; (with a literal slider) on a running application, unlike post-mortem time traveling debuggers like <a href="https://rr-project.org">rr</a>. After rolling back time you could continue to interact with the application and &#8220;change the future&#8221;. You could even change the code to some extent and keep going! It was really incredible to me.</p><p>Creating something like the Elm Reactor is only possible if the architecture of the application allows for it. Elm imposed an architecture, and that made the Reactor work for every application developed in Elm (until they broke it but that&#8217;s another story). General purpose time traveling debuggers cannot compete with something like Elm Reactor in performance or UX, because they have to support all sorts of code.</p><p>Architecture and semantics have many second order effects, and that is also where &#8220;nodes&#8221; come in. But representing a function&#8217;s body as a flowchart is almost completely useless<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>. Most of the time a function&#8217;s control flow is pretty obvious, and your brain can already &#8220;see&#8221; everything it needs just fine. A drawing is only useful if it helps you <em>see</em> something that you otherwise couldn&#8217;t, or at least not as quickly.</p><p>At the level of an individual function, ASCII text is a very good interface. <a href="https://en.wikipedia.org/wiki/Smalltalk">Smalltalk</a> understands this for example. You&#8217;re editing a live program when you program in Smalltalk: there are no source files, there is no ASCII text. You directly manipulate classes and methods that exist in memory as bytecode, but they&#8217;re presented to you, and edited, as monospaced ASCII text. At that level text works just fine.</p><p>In <a href="https://www.ensoanalytics.com/product">Enso</a> it&#8217;s possible to visualize the data flowing through individual nodes as it goes through them and changes over time. It has a textual language that is isomorphic (except for node positioning) to the visual programming side. You can edit the code either visually or as text, it is the same language. Text and graphics are not at odds.</p><p>Game Maker could have worked like that too, drag-and-drop and text being two ways to edit the same language, specially in the older list-oriented design. But in Game Maker&#8217;s case it would mostly be helpful to newbies. The drag-and-drop UI provides no advantages to an expert. Enso does, because of the visualization. This is what I want you to keep in mind: seeing, not drawing.</p><h1>The Power of Nodes</h1><p>Where nodes provide value is at a higher level. In order to provide value they must let you &#8220;see&#8221; something you otherwise would struggle to know, specifically the application&#8217;s inner workings. I don&#8217;t mean visualizing a class or package diagram, that&#8217;s not really helpful unless you&#8217;re completely unfamiliar with the codebase.</p><p>I mean the drawings must reflect the actual logic of the application, they must help you see what it <em>does</em>. For example, a Harel Statechart (a hierarchical form of State Machine) lets you make application states and their transitions explicit, and to see the whole thing in a single screenful. You can then select a particular state and &#8220;dig down&#8221; to see the internal states it can be in, and so on and so forth.</p><p>Harel Statecharts also allow for concurrent state machines, which can send events to each other, so you can program complex concurrent applications with them. If you&#8217;ve already been converted to the Church of Coroutines, it&#8217;s that on steroids.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!5r-k!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc788fa1-d7ad-43c0-a9e6-2bbb60d76b96_1071x541.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!5r-k!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc788fa1-d7ad-43c0-a9e6-2bbb60d76b96_1071x541.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!5r-k!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc788fa1-d7ad-43c0-a9e6-2bbb60d76b96_1071x541.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!5r-k!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc788fa1-d7ad-43c0-a9e6-2bbb60d76b96_1071x541.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!5r-k!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc788fa1-d7ad-43c0-a9e6-2bbb60d76b96_1071x541.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!5r-k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc788fa1-d7ad-43c0-a9e6-2bbb60d76b96_1071x541.jpeg" width="1071" height="541" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bc788fa1-d7ad-43c0-a9e6-2bbb60d76b96_1071x541.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:541,&quot;width&quot;:1071,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Statechart model of the control of the vienna' ferris wheel, modelled with YAKINDU Statechart tools &quot;,&quot;title&quot;:&quot;Statechart model of the control of the vienna' ferris wheel, modelled with YAKINDU Statechart tools &quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Statechart model of the control of the vienna' ferris wheel, modelled with YAKINDU Statechart tools " title="Statechart model of the control of the vienna' ferris wheel, modelled with YAKINDU Statechart tools " srcset="/__u/substackcdn.com/image/fetch/$s_!5r-k!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc788fa1-d7ad-43c0-a9e6-2bbb60d76b96_1071x541.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!5r-k!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc788fa1-d7ad-43c0-a9e6-2bbb60d76b96_1071x541.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!5r-k!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc788fa1-d7ad-43c0-a9e6-2bbb60d76b96_1071x541.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!5r-k!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc788fa1-d7ad-43c0-a9e6-2bbb60d76b96_1071x541.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A statechart in the itemis CREATE tool (formerly YAKINDU). From their blog.</figcaption></figure></div><p>But I&#8217;m not suggesting we should all be programming in hierarchical state machines. There will be many applications where that is not a very natural way of working. The point is thinking in terms of &#8220;seeing&#8221;. What helps programmers &#8220;see&#8221; what they need to see? The UX to edit the application&#8217;s logic should reflect this, such that mapping the results of what was &#8220;seen&#8221; back to the code is as easy as possible.</p><p>In a game engine, that could be a live editor experience, where you can pause the game, click on an enemy character, and then tweak its AI script. Immediacy! The script could be a state machine, a flow chart, an event list, or plain ASCII text. The choice is nearly irrelevant in the face of the live editor experience.</p><p>Thinking in those terms, how to maximize immediacy, is what matters. How quickly can the programmer find out what they need to find out? How quickly can they map their findings back to the code? Optimizing for that is what Visual Programming should be about.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>The visual programming capabilities of Game Maker have only gotten worse with time, with the current version having replaced the simple list-oriented drag-and-drop interface with an extremely clunky node-based editor that is the complete opposite of &#8220;information dense&#8221;. The editor also loves to crash on macOS for whatever reason. But the UI looks like it came out of a sci-fi movie now, so I guess it&#8217;s all worth it?</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>They&#8217;ve found great success in model-based design. Stateflow from MathWorks (makers of MATLAB) is a great example. Extremely powerful.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>For Construct 3 the main issue is that it is basically impossible to abstract low-level actions into high-level actions in its visual programming language. Game Maker has the same issue but makes it worse by using a low-information density node-graph UI these days.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>We have its capabilities to blame for <a href="https://en.wikipedia.org/wiki/Defense_of_the_Ancients">DoTA</a> and, in turn, <a href="https://en.wikipedia.org/wiki/League_of_Legends">League of Legends</a> and the entire <a href="https://en.wikipedia.org/wiki/Multiplayer_online_battle_arena">MOBA</a> genre. This despite Warcraft 3 being a real-time strategy game with minor RPG elements. That&#8217;s how powerful the trigger editor was.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>Mr. <a href="https://gist.github.com/d7samurai">d7samurai</a> likes to call this <em>Visualized</em> Programming instead of <em>Visual</em> Programming because everyone immediately thinks of node-graphs when you say visual and tunes out. The visualization is what is important, not the drawings, but do note that nodes are <em>also a form of visualization</em>.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>Except for newbies I guess? Or some insanely convoluted function you shouldn&#8217;t really be writing that way to begin with.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Implementing Logic Programming]]></title><description><![CDATA[I just think it's neat!]]></description><link>https://btmc.substack.com/p/implementing-logic-programming</link><guid isPermaLink="false">https://btmc.substack.com/p/implementing-logic-programming</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Fri, 13 Jun 2025 21:30:56 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!wyKU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69e6e412-0213-43ca-9fee-082f3f9ec376_658x500.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most of my readers are probably familiar with procedural programming, object-oriented programming (OOP), and functional programming (FP). The majority of top programming languages on all of the language popularity charts (like <a href="https://www.tiobe.com/tiobe-index/">TIOBE</a>) support all three to some extent.</p><p>Even if a programmer avoided one or more of those three paradigms like the plague, they&#8217;re likely at least aware of them and what they&#8217;re about. Or they&#8217;re applying one of the paradigms while denying that they&#8217;re doing so, like Haskell programmers using the IO or State Monads (procedural programming), or C programmers writing structs of function pointers (object-oriented programming), or Java programmers using streams (functional programming).</p><p>The same is sadly not true of <a href="https://en.wikipedia.org/wiki/Logic_programming">logic programming</a><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>. While some programmers are aware of its existence, and might have experienced a little bit of it in university, it&#8217;s not even close to the popularity of the other paradigms. I&#8217;d go so far as to say that the majority of programmers have no idea what it&#8217;s about, and that&#8217;s a shame because logic programming is really good at tackling certain kinds of problems.</p><p>OOP and FP are easy to explain in terms of procedural programming concepts, and it&#8217;s also pretty easy to explain how to implement them. That&#8217;s not really the case for logic programming, but when has that ever stopped me?</p><p>What better way to learn something than to implement it?</p><h1>Why Logic Programming?</h1><p>If you&#8217;ve ever lost your marbles trying to model complex relationships between various concepts as objects with bi-directional pointers to each other and derived properties that need to be cached and all that jazz, then that&#8217;s a great example of a problem where you should have used logic programming instead (looking at you <a href="https://en.wikipedia.org/wiki/Object_Management_Group">OMG</a> and your fancy <a href="https://www.omg.org/spec/SysML">SysML v2</a> standard<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>).</p><p>For a rather big list of examples of what you can do with logic programming, check out this blog post from <a href="https://www.philipzucker.com/notes/Languages/datalog/">Phillip Zucker</a>.</p><p>In logic programming we don&#8217;t program with functions as we do in the other paradigms (procedures and methods are also functions). Functions have a set of inputs and a set of outputs, and mutable inputs can be viewed as just another kind of output.</p><p>Rather, in logic programming we program with <em>relations</em>. They&#8217;re also called predicates in logic programming, but they&#8217;re the same thing really (much like procedures and methods are kinds of functions).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!wyKU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69e6e412-0213-43ca-9fee-082f3f9ec376_658x500.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!wyKU!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69e6e412-0213-43ca-9fee-082f3f9ec376_658x500.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!wyKU!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69e6e412-0213-43ca-9fee-082f3f9ec376_658x500.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!wyKU!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69e6e412-0213-43ca-9fee-082f3f9ec376_658x500.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!wyKU!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69e6e412-0213-43ca-9fee-082f3f9ec376_658x500.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!wyKU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69e6e412-0213-43ca-9fee-082f3f9ec376_658x500.jpeg" width="658" height="500" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/69e6e412-0213-43ca-9fee-082f3f9ec376_658x500.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:500,&quot;width&quot;:658,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!wyKU!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69e6e412-0213-43ca-9fee-082f3f9ec376_658x500.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!wyKU!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69e6e412-0213-43ca-9fee-082f3f9ec376_658x500.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!wyKU!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69e6e412-0213-43ca-9fee-082f3f9ec376_658x500.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!wyKU!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69e6e412-0213-43ca-9fee-082f3f9ec376_658x500.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The difference between a relation and a function is that a relation doesn&#8217;t have a clear distinction between what is an input and what is an output. </p><p>I&#8217;ll use what is probably the most well known logic programming language, <a href="https://en.wikipedia.org/wiki/Prolog">Prolog</a>, to illustrate. Let&#8217;s start with a simple example:</p><pre><code>male(dicky).
male(randy).
male(mike).
male(don).
male(elmer).

female(anne).
female(rosie).
female(esther).
female(mildred).
female(blair).</code></pre><p>In the example above, <code>male</code> and <code>female</code> are what are known as <em>predicates</em> in Prolog. Predicates are defined in terms of <em>clauses</em>, and a clause can be either a <em>rule</em> or a <em>fact</em>. A fact is just a rule that is always true. Every &#8220;statement&#8221; in the example above is a fact.</p><p>By writing <code>male(randy)</code> on its own as above, we&#8217;re saying that &#8220;<em>it is a fact that randy is a male</em>&#8221;. This being an <em>is-a</em> relationship is just our own interpretation of what <code>male(_)</code> means. All Prolog cares about is that there&#8217;s a fact for predicate <code>male</code> that states <code>male(randy)</code>. A predicate/relation means whatever we want it to mean.</p><p>The various names of people above are <em>atoms</em> in Prolog. They&#8217;re basically interned strings (same as <a href="https://en.wikipedia.org/wiki/Symbol_(programming)">symbols</a> in Lisp, Ruby, Julia, Smalltalk, etc.), but atoms can also be integers and even complex structures. Prolog is dynamically typed so we don&#8217;t need to declare any types, but that&#8217;s not the case for logic programming in general<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>.</p><p>All we&#8217;ve done so far is state that a bunch of people are either male or female. Not very interesting, but we&#8217;ll make use of this information soon. Let&#8217;s move on to a more interesting set of facts:</p><pre><code><code>parent(don, randy).
parent(don, mike).
parent(don, anne).

parent(rosie, randy).
parent(rosie, mike).
parent(rosie, anne).

parent(elmer, don).
parent(mildred, don).

parent(esther, rosie).
parent(esther, dicky).</code></code></pre><p>In the example above we&#8217;ve added a new predicate (parent) and some associated facts, this time relating two people. When we write <code>parent(don, randy)</code>, we&#8217;re saying that Don is one of Randy&#8217;s parents. Relations don&#8217;t really have an ordering, this is just how we&#8217;ve chosen to interpret each of the two arguments.</p><h1>The Power of Rules</h1><p>So far, nothing special, but now we can write our first rule:</p><pre><code>father(X, Y) :- male(X), parent(X, Y).</code></pre><p>The rule above says that for any given X and Y (uppercase letters are variables), if X is a male, and X is a parent of Y, then X is the father of Y<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>. The weird <code>:-</code> symbol is a reverse implication, while the comma means &#8220;and&#8221;. In Prolog &#8220;or&#8221; would be a semicolon, or just another rule for the same predicate. You can read the example as:</p><pre><code>father(X, Y) if male(X) and parent(X, Y)</code></pre><p>Anyway, we can now start doing some queries (?- is the query operator)<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>, for example:</p><pre><code>?- father(X, randy).</code></pre><p>This will return X=don. If instead we query:</p><pre><code>?- father(don, X).</code></pre><p>We get X=randy, X=mike, and X=anne.</p><p>As I mentioned previously, rules don&#8217;t have any clear distinction between inputs and outputs. What constitutes an output depends on the query. I can write the query:</p><pre><code><code>?- father(X, Y).</code></code></pre><p>And get every possible pair of father and child in the Prolog interpreter&#8217;s &#8220;database of facts&#8221; (so to speak). The word <strong>database</strong> is quite relevant actually, since as the name <em>relation</em> implies, logic programming is closer to relational programming (e.g., SQL) than it is to the other paradigms.</p><p>But logic programming can be a lot more powerful (and succinct) than crappy SQL. For example, we can keep adding more interesting rules:</p><pre><code>son(X, Y) :- male(X), parent(Y, X).
daughter(X, Y) :- female(X), parent(Y, X).

sister(X, Y) :- daughter(X, P), parent(P, Y), X \= Y.
brother(X, Y) :- son(X, P), parent(P, Y), X \= Y.

aunt(X, Y) :- sister(X, P), parent(P, Y).
uncle(X, Y) :- brother(X, P), parent(P, Y).</code></pre><p>These should hopefully be pretty obvious. Notice how we can introduce new variables in the body of the rules, like P above. Prolog uses <a href="https://en.wikipedia.org/wiki/Unification_(computer_science)">unification</a> to link variables together, the same process used by type inference.</p><p>The real power comes from <em>recursion</em>:</p><pre><code>ancestor(X,Y) :- parent(X,Y).
ancestor(X,Y) :- parent(X,Z), ancestor(Z,Y).</code></pre><p>The above is similar to how you do pattern matching in Haskell, in case you&#8217;re familiar with that. We can also, for example, add some special cases inspired by the creation myth of Abrahamic religions:</p><pre><code>ancestor(adam, X) :- male(X); female(X).
ancestor(eve, X) :- male(X); female(X).</code></pre><p>If we declare adam as male, then we end up with adam as being an ancestor of adam, which is pretty funny (but also pretty easy to fix).</p><h1>Neat, how do I implement it?</h1><p>Well, I could go over how to implement a Prolog interpreter, but honestly I don&#8217;t think you should. Prolog is honestly kind of jank, and I&#8217;m not talking about good vintage jank like C.</p><p>The main issue is that Prolog is not truly declarative; how you write the rules has a major impact on how the interpreter behaves. You might end up with repeated answers for a query or it might enter an infinite loop. Prolog also allows IO, so the order in which things get executed is critical. It&#8217;s <a href="https://en.wikipedia.org/wiki/Turing_completeness">Turing-complete</a>, for better or worse.</p><p>If you really want to implement Prolog specifically, you have to follow its execution semantics, which are based on <a href="https://en.wikipedia.org/wiki/SLD_resolution">SLD resolution</a> using depth-first search and backtracking. Prolog programs depend on the specific order in which rules get executed, so you have to perform the search the same way as all the other Prolog implementations do. Check out <a href="https://github.com/a-yiorgos/wambook">Warren&#8217;s Abstract Machine</a> on how to do it efficiently (also a good source for implementing unification).</p><p>But we don&#8217;t need Turing-complete Prolog. We already have whatever Turing-complete, multi-paradigm programming language we&#8217;re using on a daily basis, we just need to power it up with logic programming.</p><p>Most people would do this by implementing <a href="https://en.wikipedia.org/wiki/MiniKanren">miniKanren</a>, another logic programming language that is specifically designed to be embedded into a host language, originally Scheme. A later developed simplified core, <a href="http://webyrd.net/scheme-2013/papers/HemannMuKanren2013.pdf">microKanren</a>, is so small it can be implemented in 39 lines of Scheme code without any macros.</p><p>But I am not a big fan of the miniKanren family. Its design is very functional, which has its advantages, but to me the &#8220;database&#8221; aspect is important. Unlike Prolog, where facts can be added and removed while the program is running, in miniKanren you set up the universe of facts you care about each time you make a query.</p><p>This results in a very clean implementation<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>, but it leaves a lot of performance on the table. To me, maintaining a stateful database of facts is a core part of the job. That&#8217;s what we&#8217;re really doing: implementing a fancy database. You can of course bolt on a stateful fact database to a miniKanren implementation, but it&#8217;s not how miniKanren is meant to work.</p><p>Instead, we&#8217;ll turn our attention to <a href="https://en.wikipedia.org/wiki/Datalog">Datalog</a>, a subset of prolog that is <em>not</em> Turing-complete. You can&#8217;t use Datalog to develop complete applications, but it sure is great at modeling relationships. In fact, I wish it would replace SQL as the language of choice for databases<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a>. SQL isn&#8217;t even a good <a href="https://en.wikipedia.org/wiki/Relational_model">relational language</a>, and logic programming is just on another level entirely<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-8" href="#footnote-8" target="_self">8</a>.</p><p>Since Datalog is a subset of Prolog, we could just implement the same algorithm we&#8217;d use for a Prolog interpreter and it would work, but the lack of Turing-completeness gives us a lot of room for maneuver. </p><p>For one, techniques from <a href="https://en.wikipedia.org/wiki/Database_engine">databases</a> are applicable: B-trees, query optimization, index selection, etc. Datalog is also very amenable to <a href="https://en.wikipedia.org/wiki/Partial_evaluation">partial evaluation</a>, as employed by the highly optimized <a href="https://souffle-lang.github.io/translate">Souffl&#233;</a> dialect.</p><p>Here we&#8217;ll keep it simple and implement what I think is the most basic algorithm for interpreting Datalog: <a href="https://en.wikipedia.org/wiki/Datalog#Na%C3%AFve_evaluation">Na&#239;ve Evaluation</a>. It&#8217;s a simple bottom-up fixpoint algorithm that repeatedly applies each rule until no new facts can be derived. We&#8217;ll also abuse operator overloading to make the code <em>look</em> sort of like Datalog.</p><h1>Modeling Datalog</h1><p>We&#8217;ll use Python to keep things as simple as possible. The first thing we need is some way to represent variables, values and usages of a predicate (that we&#8217;ll call atoms):</p><pre><code>class Variable:
    def __init__(self, name: str):
        self.name = name
    
    def __repr__(self) -&gt; str:
        return self.name

Value = bool | int | float | str
Term = Variable | Value

class Atom:
    def __init__(self, predicate: str, terms: Tuple[Term, ...]) -&gt; None:
        self.predicate = predicate
        self.terms = terms</code></pre><p>This Atom class is just used to model a predicate usage like <code>father(X, bill</code>) and such, either with a variable or a value in each argument. Not sure what else to call it. Note that this is different from the meaning of an atom in Prolog.</p><p>Also notice that we cannot pass predicates as arguments to other predicates. This is allowed in Prolog but not in Datalog. Datalog has some restrictions to ensure it always terminates (they can be loosened, but we&#8217;ll stick to the basics):</p><ul><li><p>negation is not allowed.</p></li><li><p>complex terms as arguments of predicates, e.g., <code>p(f(x), y)</code>, are not allowed.</p></li><li><p>every variable that appears in the head of a clause must also appear in an atom within the body of the clause.</p></li></ul><p>For values we only allow a few builtin Python types to keep things simple. With variables, values and atoms in place, we move on to predicates:</p><pre><code>Fact = Tuple[Value, ...]

class Rule:
    def __init__(self, head: Atom, body: Tuple[Atom, ...]):
        assert len(body) &gt;= 1
        self.head = head
        self.body = body

class Predicate:
    def __init__(self, name: str, arity: int):
        self.name = name
        self.arity = arity
        self.facts: Set[Fact] = set()
        self.rules: List[Rule] = []</code></pre><p>We split clauses directly into separate facts and rules. A fact is just a row of values belonging to a predicate. A rule has a head (left side of the :- operator), which is just a single atom, and a body with one or more atoms (right side of the :- operator). Arity is the expected number of arguments.</p><p>To manage our &#8220;database&#8221; we&#8217;ll create a class simply called Datalog:</p><pre><code>class Datalog:
    def __init__(self) -&gt; None:
        self.variables: Dict[str, Variable] = {}
        self.predicates: Dict[str, Predicate] = {}

    def variable(self, name: str) -&gt; Variable:
        assert name not in self.variables
        v = Variable(name)
        self.variables[name] = v
        return v

    def predicate(self, name: str, arity: int) -&gt; Predicate:
        assert name not in self.predicates
        c = Predicate(name, arity)
        self.predicates[name] = c
        return c</code></pre><p>We also store the variables here to make the API a bit nicer and to allow us to safely use <a href="https://en.wikipedia.org/wiki/Identity_(object-oriented_programming)">reference equality</a> later on for better performance. We could do the same with string values (turning them into symbols) but I leave that as an exercise for the reader.</p><p>Next, we need a way to create atoms and to add facts and rules to a predicate. We&#8217;ll add a bit of nasty operator overloading to the Predicate class to achieve this:</p><pre><code>def __getitem__(self, terms: Term | Tuple[Term, ...]) -&gt; Atom:
    # make sure we always work with a tuple
    terms = terms if isinstance(terms, tuple) else (terms,)
    if len(terms) != self.arity:
        raise ValueError()
    return Atom(self.name, terms)

def __setitem__(self, 
    terms: Term | Tuple[Term, ...], 
    rhs: Atom | Tuple[Atom, ...]) -&gt; None:
    # make sure we always work with a tuple
    terms = terms if isinstance(terms, tuple) else (terms,)
    # if the rhs is the empty tuple, we're adding a fact
    if rhs == ():
        # NOTE: facts cannot contain variables, add a check!
        self.facts.add(cast(Tuple[Value, ...], terms))
    elif isinstance(rhs, tuple):
        self.rules.append(Rule(Atom(self.name, terms), rhs))
    else:
        self.rules.append(Rule(Atom(self.name, terms), (rhs,)))</code></pre><p>What we&#8217;ve done above allows us to write the following:</p><pre><code>dl = Datalog()

parent = dl.predicate('parent', 2)
ancestor = dl.predicate('ancestor', 2)

X, Y, Z = dl.variable('X'), dl.variable('Y'), dl.variable('Z')

parent['alice', 'bob'] = ()
parent['bob', 'carol'] = ()
ancestor[X, Y] = parent[X, Y]
ancestor[X, Y] = parent[X, Z], ancestor[Z, Y]</code></pre><p>We have square brackets instead of round, = instead of :-, and need to write = () to declare a fact, but it sure looks a lot like Datalog. Even the = () is actually correct because <code>pred(foo, bar).</code> in Prolog and Datalog is just syntax sugar for:</p><pre><code>pred(foo, bar) :- .</code></pre><p>So all we&#8217;re missing is that little bit of syntax sugar.</p><h1>Interpreting Datalog</h1><p>We can model Datalog programs now, but we cannot perform any queries nor infer any new information. First thing we&#8217;ll need is an extra datastructure:</p><pre><code>Substitution = dict[Variable, Value]</code></pre><p>Pretty simple, just a mapping of variables to values. We&#8217;ll make heavy use of this datastructure soon, but first, the most important method &#8212; <strong>infer</strong>:</p><pre><code>def infer(self) -&gt; None:
    while True:
        newly_added_facts: List[Tuple[Predicate, Fact]] = []
        for predicate in self.clauses.values():
            for rule in predicate.rules:
                for sub in self.evaluate(rule.body):
                    fact = tuple(
                        sub[t] if isinstance(t, Variable) 
                        else t for t in rule.head.terms)
                    if fact not in predicate.facts:
                        newly_added_facts.append((predicate, fact))
        if not newly_added_facts:
            break
        for p, f in newly_added_facts:
            p.facts.add(f)</code></pre><p>Method <strong>infer</strong> is the core of our Datalog engine. It implements the fixpoint algorithm that expands rules into new facts. You should call this method each time you manually add a new set of facts and/or rules.</p><p>Every loop iteration, for each rule, we call another method called <strong>evaluate</strong>, which lazily outputs all possible substitutions given the body of the rule. Each substitution is then used to create a new fact by replacing every variable with the associated value in the head of the rule (keeping any values already in the head).</p><p>If that fact was not already in the list of known facts, then we&#8217;ve derived a new fact. Once we&#8217;ve gone over every rule, if we found any new facts, we update our fact database and iterate again. If no new facts were derived, we&#8217;re done.</p><p>Method <strong>evaluate</strong> is just a wrapper around another method called <strong>search</strong>:</p><pre><code>def evaluate(self, atoms: Sequence[Atom]) -&gt; Iterable[Substitution]:
    return self._search(0, atoms, {})

def search(self, i: int, atoms: Sequence[Atom], 
            sub: Substitution) -&gt; Iterable[Substitution]:
    if i == len(atoms):
        yield sub
        return
    atom = atoms[i]
    for fact in self.clauses[atom.predicate].facts:
        new_sub = sub.copy()
        if unify(atom, fact, new_sub):
           yield from self._search(i + 1, atoms, new_sub)</code></pre><p>Method <strong>search</strong> implements a lazy depth-first search. Its goal is to successfully unify every atom in the body of a rule. To do this, it picks the atom at index &#8216;i&#8217;, and for every fact associated with the corresponding predicate, it tries to unify the atom with the fact. If unification succeeds (meaning we&#8217;ve obtained a substitution for every variable in the atom), we recursively call <strong>search</strong> again, only this time starting at the next index and enforcing the current substitution.</p><p>As an example of how it works, consider this rule:</p><pre><code>ancestor[X, Y] = parent[X, Z], ancestor[Z, Y]</code></pre><p>The first atom is parent[X, Z]. These are the known facts of parent:</p><pre><code>parent['bob', 'carol']
parent['alice', 'bob']</code></pre><p>We start with the first fact. We unify X=&#8217;bob&#8217; and Z=&#8217;carol&#8217;. We then call search recursively, moving to the next atom, ancestor[Z, Y], with that substitution in hand.</p><p>From the other rule of ancestor:</p><pre><code><code>ancestor[X, Y] = parent[X, Y]</code></code></pre><p>We probably already derived that every parent is an ancestor, so the current facts of ancestor are also:</p><pre><code><code>ancestor['bob', 'carol']
ancestor['alice', 'bob']</code></code></pre><p>We try to unify with the first fact and fail, because the substitution enforces Z=&#8217;carol&#8217; which cannot unify with &#8216;bob&#8217;. The second fact also fails to unify, so this is a dead end.</p><p>That branch of the recursion dies and we&#8217;re back trying to unify the first atom. Now we unify with the second fact, obtaining the substitution X=&#8217;alice&#8217; and Z=&#8217;bob&#8217;. We recurse again with that substitution.</p><p>We try to unify ancestor[Z, Y] with its first fact, and we succeed, because we can set Z=&#8217;bob&#8217; and Y=&#8217;carol&#8217;. We managed to do a complete substitution, so we yield it.</p><p>The function <strong>unify</strong>, used within <strong>search</strong>, is pretty simple:</p><pre><code>def unify(atom: Atom, fact: Fact, substitution: Substitution) -&gt; bool:
    for t, v in zip(atom.terms, fact):
        if isinstance(t, Variable):
            if t in substitution and substitution[t] != v:
                return False
            substitution[t] = v
        elif t != v:
            return False
    return True</code></pre><p>It takes in an atom, a fact, and a substitution, and pairs up each term in the atom with the corresponding value in the fact at the same position. If the term is a variable, we do the following:</p><ul><li><p>If the variable is in the substitution, then we check if the value associated with it and the value in the fact match. If they don&#8217;t, unification fails.</p></li><li><p>If the variable isn&#8217;t in the substitution, then we update the substitution by mapping the variable to the value in the fact.</p></li></ul><p>If instead of a variable we see a value, then we just compare that value with the value in the fact directly.</p><p>To finish up our little interpreter, we only need one more method, and it&#8217;s a trivial one:</p><pre><code>def query(self, *atoms: Atom) -&gt; Iterable[Substitution]:
    return self.evaluate(atoms)</code></pre><p>Yup, query is just evaluate, but variadic to make the API a bit nicer. With that, we can finally write our tiny Datalog program:</p><pre><code>dl = Datalog()

parent = dl.predicate('parent', 2)
ancestor = dl.predicate('ancestor', 2)

X, Y, Z = dl.variable('X'), dl.variable('Y'), dl.variable('Z')

parent['alice', 'bob'] = ()
parent['bob', 'carol'] = ()
ancestor[X, Y] = parent[X, Y]
ancestor[X, Y] = parent[X, Z], ancestor[Z, Y]

dl.infer()

for result in dl.query(ancestor[X, 'carol']):
    print(result)</code></pre><p>Which outputs (in any order):</p><pre><code>{X: 'alice'}
{X: 'bob'}</code></pre><h1>Conclusion</h1><p>Well that ended up a lot longer than I expected, but the actual implementation is pretty small all things considered. It&#8217;s not efficient, but some small optimizations go a long way, for example switching to semi-na&#239;ve evaluation. </p><p>The main difference is that instead of always going over every fact in each iteration of the loop, we only apply each rule to facts we derived in the previous iteration.</p><p>Another optimization is dynamically sorting the atoms in the body of a rule based on the number of values each atom contains and the number of associated facts, to try and cause search branches to fail as soon as possible.</p><p>We could also add support for arithmetic and composite atoms (like lists), which introduce some challenges if we wish to stay &#8220;Turing-incomplete&#8221;.</p><p>Either way, you now have a new tool in your arsenal. No more horrible object graphs desperately trying and failing to model relations, you can now simply use the best paradigm for the job.</p><p>Update: This article was discussed on <a href="https://news.ycombinator.com/item?id=44272467">hackernews</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Logic programming just hasn&#8217;t had much of an impact. <a href="https://en.wikipedia.org/wiki/Prolog#Impact">Many reasons</a> have been postulated as to why, but to me the real culprit was trying to make it a general purpose programming paradigm like the other big 3. It&#8217;s simply ill-suited for application development. It works much better as a purely declarative auxiliary paradigm, like the relational model (e.g. SQL).</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Disclaimer: my opinions are my own and do not reflect those of my employer.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>The hybrid functional-logic programming language <a href="https://en.wikipedia.org/wiki/Mercury_(programming_language)">Mercury</a> is statically typed. The same is true of the <a href="https://en.wikipedia.org/wiki/Souffl&#233;_(programming_language)">Souffl&#233;</a> dialect of Datalog. More on Datalog later.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>Reminder this is how we chose to interpret the order of the arguments, the relations themselves don&#8217;t have an ordering!</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>Technically we could already do queries, they just wouldn&#8217;t be interesting.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>That is, if you can stand to read (lisp-style (code)) with horribly named functions passing around unnamed closures all over the place. But that&#8217;s a syntax issue, not semantics.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p><a href="https://en.wikipedia.org/wiki/Datomic">Datomic</a> uses Datalog, so there&#8217;s at least one database using it, but they use a non-standard lisp-style syntax that I really don&#8217;t like (it&#8217;s <a href="https://en.wikipedia.org/wiki/Clojure">Clojure</a> based).</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-8" href="#footnote-anchor-8" class="footnote-number" contenteditable="false" target="_self">8</a><div class="footnote-content"><p>For one, <a href="https://en.wikipedia.org/wiki/Database_normalization">database normalization</a> isn&#8217;t really something that has to be kept in mind when doing logic programming. The style of relations one writes in logic programming just naturally cover most normal forms by nature.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Tagged Pointers for Memory Safety]]></title><description><![CDATA[Seriously, the performance hit is not that bad, why not?]]></description><link>https://btmc.substack.com/p/tagged-pointers-for-memory-safety</link><guid isPermaLink="false">https://btmc.substack.com/p/tagged-pointers-for-memory-safety</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Thu, 01 May 2025 20:33:41 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f5f0e6a6-0e4e-4c90-b9be-d72b19e88cd9_3750x3000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Mainstream programming languages use one or more of the following approaches (if any) to achieve memory safety:</p><ul><li><p>Tracing Garbage Collection (GC for short)</p></li><li><p>Reference Counting (RC for short)</p></li><li><p>Linear/Affine Types (<a href="https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization">RAII</a> + <code>std::unique_ptr</code> is similar)</p></li><li><p>&#8220;Borrow Checking&#8221; (i.e., Static Lifetime Analysis)<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></p></li></ul><p>Each solution has different tradeoffs: GC requires a hefty and complex runtime to be both global and efficient; RC can&#8217;t handle cycles and has some performance overhead to track the reference counts; Linear/Affine Types/RAII impose a tree-like structure to memory; Borrow Checking is insufficient by itself and imposes quite a few constraints that can be hard to understand (so called &#8220;fighting the borrow checker&#8221;)</p><p>But there is actually one more approach<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> that could be used: Pointer Tagging. Often discussed in the context of hardware solutions like <a href="https://en.wikipedia.org/wiki/Capability_Hardware_Enhanced_RISC_Instructions">CHERI</a>, pointer tagging can also be implemented in software with a reasonable performance cost.</p><p>The basic idea is to add some extra tag bits to a pointer such that we can determine if the memory address it points to is still valid. If the tag bits in the pointer and the tag bits of the allocation don&#8217;t match, then the data at that memory address has been freed and overwritten, and we have therefore caught a Use-After-Free. </p><p>But how do we produce and manage these tag bits? Read on.</p><h2>Generational Handles</h2><p>The idea of pointer tagging is similar to a common approach to track slot reuse in object pools: generational handles.</p><p>When you add an object to a pool, you get back a handle: an index to the slot where that object is stored. You can use this handle to grab the corresponding object and work with it. But what happens if the object has been freed and a new object was added to the same slot in the pool? How do you know if the handle remains valid?</p><p>The simple solution is to track the &#8220;generation&#8221; of the object in the slot. Whenever an object in a slot is deleted, that slot&#8217;s generation counter is incremented, so the next object placed at that slot has a higher generation count.</p><p>Instead of returning just an index as a handle, the pool returns an index + generation pair. When accessing a slot using a handle, its generation value is compared to the generation value of the slot. If they don&#8217;t match, the handle is no longer valid.</p><p>If generation counts are allowed to wrap around, then &#8220;generation collisions&#8221; become possible, but such collisions are extremely unlikely given enough bits for the generation counter.</p><p>To give some perspective, if you do nothing but increment a 64 bit number every clock cycle on a 3 GHz CPU, it will take 97 years to overflow.</p><h2>Generational References</h2><p>Generational References, a term coined by the developer of the <a href="https://vale.dev">Vale</a> programming language, extend the idea of Generational Handles to arbitrary pointers. They&#8217;re a form of pointer tagging inspired by generational handles, hence the name.</p><p>An implementation similar to the approach used for generational handles requires a custom memory allocator since we need to manage the generation counts separately from the allocations. But there&#8217;s another really clever solution that was implemented in Vale that avoids this issue, remaining compatible with malloc, and it is the one we&#8217;ll use: <a href="https://verdagon.dev/blog/generational-references">random tags</a>.</p><h2>tag_ptr&lt;T&gt;</h2><p>We&#8217;ll implement Tagged Pointers / Generational References in C++ using a smart pointer class we&#8217;ll call <code>tag_ptr</code>. These smart pointers don&#8217;t actually do any memory management themselves, like <code>std::unique_ptr</code> or <code>std::shared_ptr</code>, they only provide memory safety.</p><p>The idea is as follows: A simple thread-local<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> random number generator is used to produce a stream of 64 bit numbers to use as tags.</p><p>A function <code>make_tagged&lt;T&gt;</code> allocates space for a T plus an 8 byte header. A random number is generated and placed in the header as the tag. It then returns a <code>tag_ptr&lt;T&gt;</code> which stores a pair of a raw pointer to the allocated T and the tag.</p><p>When <code>tag_ptr&lt;T&gt;</code> is dereferenced it compares its tag with the tag in the allocation. One of 4 things can happen:</p><ul><li><p>The memory was freed and returned to the OS, causing a segmentation fault.</p></li><li><p>The tags match and the pointer is valid.</p></li><li><p>The tags don&#8217;t match, so we caught a Use-After-Free and abort the program.</p></li><li><p>The tags match but this is actually a collision. For whatever reason the exact same value as the tag was written where the old header should be.</p></li></ul><p>The last situation is <em>extremely</em> unlikely. There&#8217;s around a 1/(2^64) chance of collision which is an absurdly small number (around 0.000000000000000005%).</p><h2>C++ Implementation</h2><p>First, we need the random number generator. We&#8217;ll use xorshift* with 64 bits of state and 64 bits of output because it produces values that are well distributed, it is very efficient, and it never outputs 0.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a></p><p>That last bit, never outputting 0, is typically a downside for a random number generator, but it is good for us, since 0 is by far the most common value for memory to have and we can clear a tag in an allocation header by simply setting it to 0.</p><p>Here is xorshift* (adapted from Wikipedia)<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>:</p><pre><code>#include &lt;cstdint&gt;

using namespace std;

uint64_t generate_random_tag() {
    thread_local static uint64_t x = 1;
    x ^= x &gt;&gt; 12;
    x ^= x &lt;&lt; 25;
    x ^= x &gt;&gt; 27;
    return x * 0x2545F4914F6CDD1DULL;
}</code></pre><p>To tag data we&#8217;ll use the following struct:</p><pre><code>template&lt;class T&gt;
struct tagged {
    uint64_t tag;
    T data;

    template&lt;class... Args&gt;
    tagged(Args&amp;&amp;... args)
    : tag(generate_random_tag())
    , data(std::forward&lt;Args&gt;(args)...) {}

    ~tagged() {
        tag = 0;
    }
};</code></pre><p>This struct forwards arguments to the constructor of whatever type it is tagging and generates and stores a random tag. All the destructor does is zero out the tag.</p><p>This struct could be used directly (to enable tagging of stack allocated objects for example) but it&#8217;s mainly meant to be used by the following function:</p><pre><code>template&lt;class T, class... Args&gt;
tag_ptr&lt;T&gt; make_tagged(Args&amp;&amp;... args) {
    tagged&lt;T&gt;* alloc = new tagged&lt;T&gt;(std::forward&lt;Args&gt;(args)...);
    return tag_ptr(*alloc);
}</code></pre><p>This function is analogous to s<code>td::make_unique</code>/<code>std::make_shared</code>, in that it allocates the data on the heap and returns it wrapped in a smart pointer. In this case a <code>tag_ptr</code>, our tagged pointer / generational reference implementation:</p><pre><code>#include &lt;cstdlib&gt;

template&lt;class T&gt;
class tag_ptr {
  T* ptr;
  uint64_t tag;

  bool is_valid() {
    char* raw_data = reinterpret_cast&lt;char*&gt;(ptr) - sizeof(uint64_t);
    uint64_t source_tag = *reinterpret_cast&lt;uint64_t*&gt;(raw_data);
    return tag == source_tag
  }
public:
  tag_ptr() = default;
  explicit tag_ptr(tagged&lt;T&gt;&amp; t)
  : ptr(&amp;t.data), tag(t.tag) {}

  T&amp; operator*() {
    if (!is_valid()) {
      abort();
    }
    return *ptr;
  }

  void destroy() {
    if (!ptr) {
      return;
    }
    if (!is_valid()) {
      abort();
    }
    char* raw_data = reinterpret_cast&lt;char*&gt;(ptr) - sizeof(uint64_t);
    tagged&lt;T&gt;* tagged_data = reinterpret_cast&lt;tagged&lt;T&gt;*&gt;(raw_data);
    delete tagged_data;
  }
};</code></pre><p>Our <code>tag_ptr</code> class stores a raw pointer to the tagged data and a copy of the tag. The dereference operator compares the stored tag to (what should be) the tag of the allocation and it aborts the program if they don&#8217;t match.</p><p>Note: There is no destructor since the <code>tag_ptr</code> <em>does not own memory</em>! A destroy method must be explicitly called, which means memory leaks are possible.</p><p>A possible solution to avoid memory leaks is to allow <code>tagged&lt;T&gt;</code> to exist independently from <code>tag_ptr&lt;T&gt;</code>, and to have <code>tagged&lt;T&gt;</code> do the memory management using all the standard RAII stuff.</p><p>If you choose this route, get rid of the <code>destroy</code> method and the <code>make_tagged</code> functions, and add a convenience method to <code>tagged&lt;T&gt;</code> to produce <code>tag_ptrs</code>:</p><pre><code><code>template&lt;class T&gt;
struct tagged {
    ...
    
    tag_ptr&lt;T&gt; get_ref() {
        return tag_ptr(*this);
    }
};</code></code></pre><h2>Tradeoffs of tag_ptr&lt;T&gt;</h2><p>Compared to other smart pointers, <code>tag_ptr&lt;T&gt;</code> has advantages and disadvantages. </p><p>The main advantage (and it is a pretty major one), is that it is a &#8220;trivial type&#8221; to use C++ standard terminology. That means it can be bitwise copied freely, e.g., using <code>memcpy</code>. Its copy constructor has no custom logic.</p><p>The second advantage is that they have no problem dealing with arbitrary graphs, unlike<code> std::shared_ptr</code> (which imposes a DAG-like structure) or <code>std::unique_ptr</code> (which imposes a tree-like structure).</p><p>The major disadvantage is the space cost. Each copy of a <code>tag_ptr</code> is twice the size of a regular pointer. You&#8217;ll want to keep the amount of <code>tag_ptrs</code> to a minimum. The 8 byte header isn&#8217;t very substantial unless you&#8217;re heap allocating lots of small structs.</p><p>The second disadvantage is the performance cost. Allocations are slightly slower because of the random number generation, while dereferences require an extra branch to check for the tags. The branch should be predicted correctly every time so the cost is pretty minimal. You should also never pass <code>tag_ptrs</code> as function arguments unless the function might delete the data, you should take a const reference to the data directly instead (so the check happens only once for that call).</p><p>Something that is neither an advantage nor disadvantage is that <code>tag_ptrs</code> don&#8217;t manage memory. They&#8217;re more like a runtime equivalent of lifetime analysis. While this means you need to use another technique to manage memory, it also means they can be used with other techniques!</p><p>They work pretty well with memory arenas for example, the only constraint is that you can&#8217;t just reset the arena pointer, you must clear the memory with 0.</p><h2>Weak Tagged Pointers</h2><p>One thing that is a bit unfortunate about our <code>tag_ptr</code> implementation is that we can&#8217;t safely check if they&#8217;re valid before dereferencing them.</p><p>If the pointer is valid or the data has been overridden, it&#8217;ll work. But if the page was released back to the operating system at some point or its read permission removed, trying to compare the tags will cause a segmentation fault.</p><p>With the standard allocator there isn&#8217;t much we can do about this<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>, but if we&#8217;re in control of the allocator there&#8217;s a simple trick that can be done.</p><p>Instead of unmapping a page when it is no longer in use, you can tell the kernel you don&#8217;t need it anymore. On Linux you can use the following:</p><pre><code>madvise(page_address, page_size, MADV_DONTNEED);</code></pre><p>This releases the page and any attempt to read the page will result in a zero-fill-on-demand page. The address range of the page is still considered &#8220;in use&#8221;, not a big deal with a 64 bit address space but something to consider. The allocator should store the page in a freelist and reuse it at some point instead of just calling <code>mmap</code>.</p><p>Using <code>MADV_FREE</code> instead of <code>MADV_DONTNEED</code> means the page is only released when there is memory pressure.</p><p>The windows equivalent of <code>MADV_FREE</code> is <code>MEM_RESET</code> on <code>VirtualAlloc</code>. To replicate the behavior of <code>MADV_DONTNEED</code> requires decommiting and re-commiting the page.</p><p>With this in place, we can safely call the <code>is_valid</code> method on <code>tag_ptr</code> to check if the data it points to remains valid before dereferencing it.</p><p>Weak tagged pointers also allow us to throw an exception or return a default value instead of segfaulting when dereferencing, if that&#8217;s relevant.</p><h2>Conclusion</h2><p>Tagged Pointers are a pretty simple approach to achieve memory safety<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a> with a relatively minor performance impact. They have advantages and disadvantages compared to shared pointers and unique pointers, but their ability to deal with arbitrary graphs makes them highly complementary.</p><p>Using a custom memory allocator that marks pages as not needed instead of unmapping them allows tagged pointers to become weak tagged pointers, meaning you can check their validity before accessing them. </p><p>Weak tagged pointers are a much more efficient alternative to regular weak pointers (the sort often used in tandem with shared pointers).</p><p>The next time someone complaints about your use of a &#8220;memory unsafe&#8221; language, just ask them if they&#8217;re ok with a few % performance loss in exchange for safety. If calling .clone() in Rust or using Rc is considered acceptable, so should this.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>It&#8217;s not just Rust, C# also uses this to make Span&lt;T&gt; safe.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Well, two, if we count Rapha&#235;l Proust&#8217;s rather bonkers <a href="https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-908.pdf">compile time garbage collection approach</a>. In practice performance <a href="https://nathancorbyn.com/pdf/practical_static_memory_management.pdf">isn&#8217;t where it needs to be</a> but the idea is really clever.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>We&#8217;ll ignore multithreading challenges in this blog post for simplicity, but it&#8217;s not too hard to design a thread-safe variant.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>If you are worried about actual cyber attacks, using xorshift* with 128 bits of state will do the job. However, this does mean 0 is now a possible output, so you need to take that into account when clearing out tags.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>You may want to seed the generator instead of always using 1 as the seed.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>Attempts could be made with a signal handler to change the protections of the page when a SIGSEGV occurs, but whether that is safe to do is highly operating system dependent and very much not recommended.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>Assuming you&#8217;ve separately handled bounds checks of course. Just write your own std::span and std::vector alternatives and index with [] like a normal person.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Implementing Generic Types in C]]></title><description><![CDATA[There are various ways, but some are better than others.]]></description><link>https://btmc.substack.com/p/implementing-generic-types-in-c</link><guid isPermaLink="false">https://btmc.substack.com/p/implementing-generic-types-in-c</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Sun, 16 Mar 2025 10:21:56 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/6eb92ac1-d749-42aa-923d-b7f7f5d83287_1920x1280.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the most annoying things about programming in C is the lack of support for generic types, also known as parametric polymorphism. Not to be confused with the abomination that is <a href="https://en.cppreference.com/w/c/language/generic">_Generic</a><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>.</p><p>I wish C had something like the following:</p><pre><code>struct Vector&lt;T&gt; {
  T* data;
  size_t capacity;
  size_t length;
};

void vector_resize&lt;T&gt;(Vector&lt;T&gt;* v, size_t capacity) {
  v-&gt;data = realloc(v-&gt;data, capacity * sizeof(T));
  v-&gt;capacity = capacity;
}

void vector_append&lt;T&gt;(Vector&lt;T&gt;* v, T element) {
  if (v-&gt;length &gt;= v-&gt;capacity) {
    vector_resize(v, v-&gt;capacity ? v-&gt;capacity * 2 : 8);
  }
  v-&gt;data[v-&gt;length] = element;
  v-&gt;length++;
}

void vector_clear&lt;T&gt;(Vector&lt;T&gt;* v) {
  free(v-&gt;data);
  v-&gt;data = NULL;
  v-&gt;capacity = 0;
  v-&gt;length = 0;
}

Vector&lt;char*&gt; v = {0};
vector_resize(&amp;v, 10);
vector_append(&amp;v, "abc");
vector_clean(&amp;v); </code></pre><p>C++ can easily do the above with <a href="https://en.wikipedia.org/wiki/Template_(C%2B%2B)">templates</a>, and I personally quite enjoy programming in &#8220;C with methods, templates, namespaces and overloading&#8221;, avoiding the more complicated elements of C++ (like move semantics<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>) while still benefiting from it.</p><p>But sometimes you really want to program in pure C for whatever reason. There isn&#8217;t really a perfect way to do the above in C, but there are ways, some better than others.</p><p>I&#8217;m aware of the following four methods:</p><ul><li><p>Template Macros</p></li><li><p>Template Headers</p></li><li><p>Type Erasure with <code>void*</code></p></li><li><p>Inlining Macros</p></li></ul><p>We&#8217;ll go over each of them and discuss their pros and cons.</p><h2>BAD: Template Macros</h2><p>C++ templates are an implementation of generic types based on <a href="https://en.wikipedia.org/wiki/Monomorphization">monomorphization</a>. What does that mean? It&#8217;s basically copy paste + find &amp; replace.</p><p>When you write:</p><pre><code>template&lt;typename T&gt; 
T id(T data) { return data; }
...
int i = id(10);
char* str = id("abc");</code></pre><p>What the compiler spits out is something like:</p><pre><code><code>int id_int(int data) { return data; }
char* id_char(char* data) { return data; }
...
int i = id_int(10);
char* str = id_char("abc");</code></code></pre><p>Though the names of the functions aren&#8217;t nice and obvious like that, they&#8217;re actually unreadable <a href="https://en.wikipedia.org/wiki/Name_mangling#How_different_compilers_mangle_the_same_functions">mangled</a> gibberish. The important point is that the templated code gets &#8220;copy-pasted&#8221; each time you call it with a different type, and everywhere T was used the concrete type is used instead.</p><p>We can implement a similar thing in C using macros for our Vector struct:</p><pre><code>#define _VECTOR_TEMPLATE_INSTANCE(T) \
typedef struct { \
  T* data; \
  size_t capacity; \
  size_t length; \
} Vector_##T; \
\
void vector_resize_##T(Vector_##T* v, size_t capacity) { \
  v-&gt;data = realloc(v-&gt;data, capacity * sizeof(T)); \
  v-&gt;capacity = capacity; \
} \
\
void vector_append_##T(Vector_##T* v, T element) { \
  if (v-&gt;length &gt;= v-&gt;capacity) { \
    vector_resize_##T(v, v-&gt;capacity ? v-&gt;capacity * 2 : 8); \
  } \
  v-&gt;data[v-&gt;length] = element; \
  v-&gt;length++; \
} \
\
void vector_clear_##T(Vector_##T* v) { \
  free(v-&gt;data); \
  v-&gt;data = NULL; \
  v-&gt;capacity = 0; \
  v-&gt;length = 0; \
}
#define VECTOR_TEMPLATE_INSTANCE(T) _VECTOR_TEMPLATE_INSTANCE(T)</code></pre><p>The two levels of macros are necessary for <code>Vector_##T</code> to work. Without them it&#8217;ll just become <code>Vector_T</code>. The preprocessor is weird.</p><p>Anyway, we can then call this macro like so:</p><pre><code>VECTOR_TEMPLATE_INSTANCE(int)</code></pre><p>And all the code in the macro gets copy pasted with T replaced with int.</p><p>There are <em>many</em> issues with this particular solution. The first and most obvious is just how hideous it is. All those backslashes and pound signs&#8230; ugh. You also get no syntax highlighting or autocompletion and errors will only show up once you invoke the macro. But that&#8217;s not all! The following doesn&#8217;t work:</p><pre><code><code>VECTOR_TEMPLATE_INSTANCE(char*)</code></code></pre><p>Because the &#8220;name mangling&#8221; will be borked due to the <code>*</code>. The solution is to typedef pointer types or wrap them in a struct, or to add an extra &#8220;name&#8221; parameter to the macro like:</p><pre><code><code>VECTOR_TEMPLATE_INSTANCE(char*, str)</code></code></pre><p>But we also need to consider <em>where</em> to call this macro, because we need to be careful not to call it with the same type in multiple places, or we might end up with multiple definitions for the same function.</p><p>For an example of this approach in the wild, check out <a href="https://github.com/P-p-H-d/mlib">M*LIB</a>. Personally I find this way of implementing generics in C to be all downsides.</p><h2>GOOD: Template Header</h2><p>The idea of the template header is similar to the template macro, but using a &#8220;parameterized&#8221; header file instead:</p><pre><code>#ifndef T
#error you need to define T before including this header
#else

typedef struct {
  T* data;
  size_t capacity;
  size_t length;
} CAT(Vector,T);

void CAT(vector_resize,T)(CAT(Vector,T)* v, size_t capacity) {
  v-&gt;data = realloc(v-&gt;data, capacity * sizeof(T));
  v-&gt;capacity = capacity;
}

void CAT(vector_append,T)(CAT(Vector,T)* v, T element) {
  if (v-&gt;length &gt;= v-&gt;capacity) {
    CAT(vector_resize,T)(v, v-&gt;capacity ? v-&gt;capacity * 2 : 8);
  }
  v-&gt;data[v-&gt;length] = element;
  v-&gt;length++;
}

void CAT(vector_clear,T)(CAT(Vector,T)* v) {
  free(v-&gt;data);
  v-&gt;data = NULL;
  v-&gt;capacity = 0;
  v-&gt;length = 0;
}

#undef T
#endif</code></pre><p>We can then use this header file like so:</p><pre><code>#define T int
#include "vector_template.h"</code></pre><p>We&#8217;ve gotten rid of the backslashes, we get proper syntax highlighting, and by just adding a <code>#define T int</code> line temporarily we get type checking and autocompletion and all that good stuff.</p><p>The CAT<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> macro is needed to implement the indirection for ##T we used in the macro version. There are ways to make it a little prettier (see below) but I wanted to show the basic solution first.</p><p>We still have the naming issue when using pointer types and the multiple definition issue, but thankfully they can be worked around without too much trouble by doing the following:</p><pre><code>#ifndef T
#error you need to define T before including this header
#else

#ifndef SUFFIX
#define SUFFIX T
#endif

#define G(N) CAT(N, SUFFIX)

typedef struct {
  T* data;
  size_t capacity;
  size_t length;
} G(Vector);

void G(vector_resize)(G(Vector)* v, size_t capacity);
void G(vector_append)(G(Vector)* v, T element);
void G(vector_clear)(G(Vector)* v);

#ifdef VECTOR_IMPLEMENTATION

void G(vector_resize)(G(Vector)* v, size_t capacity) {
  v-&gt;data = realloc(v-&gt;data, capacity * sizeof(T));
  v-&gt;capacity = capacity;
}

void G(vector_append)(G(Vector)* v, T element) {
  if (v-&gt;length &gt;= v-&gt;capacity) {
    G(vector_resize)(v, v-&gt;capacity ? v-&gt;capacity * 2 : 8);
  }
  v-&gt;data[v-&gt;length] = element;
  v-&gt;length++;
}

void G(vector_clear)(G(Vector)* v) {
  free(v-&gt;data);
  v-&gt;data = NULL;
  v-&gt;capacity = 0;
  v-&gt;length = 0;
}

#endif

#undef G
#undef SUFFIX
#undef T
#endif</code></pre><p>We add an extra parameter called SUFFIX (which is mapped to T by default) to allow changing the suffix in the type and function names (could also have been PREFIX or NAME or whatever you want to call it). The CAT macro is wrapped in the G<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a> macro to make things nicer to look at and automatically apply the suffix. Lastly we employ the same approach used by <a href="https://github.com/nothings/stb#how-do-i-use-these-libraries">header-only libraries</a> to separate the function declarations from their implementation, solving the &#8220;careful where you use this&#8221; issue.</p><p>For an example of this approach in the wild, check out <a href="https://github.com/stclib/STC">STC</a>.</p><h2>MEH: Type Erasure using void*</h2><p>I mentioned that C++ generics use <a href="https://en.wikipedia.org/wiki/Monomorphization">monomorphization</a>, but that&#8217;s not the only way to implement generic types. Java, for example, uses <a href="https://en.wikipedia.org/wiki/Type_erasure">type erasure</a>.</p><p>Basically, when you write the following in Java:</p><pre><code><code>class Vector&lt;T&gt; {
  public T[] data;
  public size_t capacity;
  public size_t length;
}

Vector&lt;Integer&gt; v = new Vector&lt;Integer&gt;();</code></code></pre><p>What the compiler is actually doing is:</p><pre><code><code>class Vector {
  public object[] data;
  public size_t capacity;
  public size_t length;
}

Vector v = new Vector();</code></code></pre><p>The actual element type has disappeared. T and int have been <em>erased</em>. This approach to generics has pros and cons, but for our purposes the important part is that we can actually do something pretty similar in C without any macros:</p><pre><code><code>typedef struct {
  void* data;
  size_t capacity;
  size_t length;
} Vector;

void vector_resize(Vector* v, size_t capacity, size_t t_size) {
  v-&gt;data = realloc(v-&gt;data, capacity * t_size);
  v-&gt;capacity = capacity;
}

void vector_append(Vector* v, void* element, size_t t_size) {
  if (v-&gt;length &gt;= v-&gt;capacity) {
    vector_resize(v, v-&gt;capacity ? v-&gt;capacity * 2 : 8, t_size);
  }
  size_t offset = v-&gt;length * t_size;
  memcpy((char*)v-&gt;data + offset, element, t_size);
  v-&gt;length++;
}

void* vector_get(Vector* v, size_t index, size_t t_size) {
  size_t offset = index * t_size;
  return (char*)v-&gt;data + offset;
}

void vector_clear(Vector* v) {
  free(v-&gt;data);
  v-&gt;data = NULL;
  v-&gt;capacity = 0;
  v-&gt;length = 0;
}</code></code></pre><p>We can use it as follows:</p><pre><code><code>Vector v = {0};
vector_resize(&amp;v, 10, sizeof(double));
double element = 4.0,
vector_push(&amp;v, &amp;element, sizeof(double));
vector_clean(&amp;v);</code></code></pre><p>Not a macro in sight, wonderful. But we had to pay a price. Every function now needs an extra parameter so we know the size of the data we are working with. We also can&#8217;t index the vector data directly, we have to treat it as an opaque blob of bytes to write into (using memcpy). We need <code>vector_get</code> for the same reason.</p><p>We also have to pass elements by pointer. That means we need the element to be an <a href="https://en.wikipedia.org/wiki/Value_(computer_science)#lrvalue">lvalue</a> so we can take its address. That&#8217;s why 4.0 is stored in a variable first.</p><p>The biggest issue is the same one Java had before version 5<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>, there&#8217;s no type safety:</p><pre><code>Vector v = {0};
vector_push(&amp;v, &amp;some_int, sizeof(double));
vector_push(&amp;v, &amp;some_double, sizeof(char*));
vector_push(&amp;v, &amp;some_char_ptr, sizeof(int));</code></pre><p>We can improve upon the sizeof issue by storing size information on the vector itself:</p><pre><code>typedef struct {
  void* data;
  size_t capacity;
  size_t length;
  size_t t_size;
} Vector;

Vector vector_init(Vector* v, size_t element_size) {
  return (Vector){.t_size = element_size};
}

void vector_append(Vector* v, void* element) {
  if (v-&gt;length &gt;= v-&gt;capacity) {
    vector_resize(v, v-&gt;capacity ? v-&gt;capacity * 2 : 8);
  }
  size_t offset = v-&gt;length * v-&gt;t_size;
  memcpy((char*)v-&gt;data + offset, element, v-&gt;t_size);
  v-&gt;length++;
}

void vector_resize(Vector* v, size_t capacity) { ... }
void* vector_get(Vector* v, size_t index) { ... }
void vector_clear(Vector* v) { ... }</code></pre><p>Parameter <code>t_size</code> is a specific instance of a more general <code>t_info</code><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a> structure, which stores all the information the container needs from the element type to function. Storing this information in the container, however, does not prevent us from appending a double to a vector of ints.</p><p>The main reason to use this approach is separate compilation. You can compile the code for a vector once and then link to it many time. Useful for dynamic libraries or FFI-related stuff, but it doesn&#8217;t provide much value when programming in C itself.</p><p>For an example of this approach in the wild, check out <a href="https://gitlab.gnome.org/GNOME/glib">GLib</a>.</p><h2>GOOD: Inlining Macros</h2><p>This one is essentially a &#8220;fixed&#8221; version of the template macro solution. The idea is to have individual macros for each operation, none of which declare anything:</p><pre><code>#define Vector(T) struct { \
  T* data; \
  size_t length; \
  size_t capacity; \
}

#define vector_resize(v, capacity) { \
  v-&gt;data = realloc(v-&gt;data, capacity * sizeof(v-&gt;data[0])); \
  v-&gt;capacity = capacity; \
}

#define vector_append(v, element) { \
  if (v-&gt;length &gt;= v-&gt;capacity) { \
    vector_resize(v, v-&gt;capacity ? v-&gt;capacity * 2 : 8); \
  } \
  v-&gt;data[v-&gt;length] = element; \
  v-&gt;length++; \
}

#define vector_clear(v) { \
  free(v-&gt;data); \
  v-&gt;data = NULL; \
  v-&gt;capacity = 0; \
  v-&gt;length = 0; \
}</code></pre><p>We can use it as follows:</p><pre><code>typedef Vector(char*) Vector_str;

Vector_str v = {0};
vector_resize(&amp;v, 10);
vector_append(&amp;v, "abc");
vector_clean(&amp;v);</code></pre><p>Compared to the template macro solution this has massive advantages. There&#8217;s no &#8220;name mangling&#8221; issue because the macros don&#8217;t declare anything. We don&#8217;t need to worry about where these macros are invoked for the same reason. Outside of function arguments we don&#8217;t even need the typedef.</p><p>As with the template macro approach, this approach also suffers from a lack of syntax highlighting and typechecking, plus those damn backslashes again. It also causes a bit of code bloat since the same code is getting inlined all over the place.</p><p>One way to minimize those issues is to shift some of the work to helper functions. In the our Vector example none of the macros is large enough or complicated enough to justify doing this, but for a HashMap you&#8217;ll definitely want to do it.</p><p>Regardless, since the we&#8217;re working with multiple smaller macros rather than one big unwieldy macro, it&#8217;s a lot more bearable.</p><p>For an example of this approach in the wild, check out <a href="https://github.com/JacksonAllan/CC">CC</a> (though CC goes even further with <code>typeof</code> tricks to not require the <code>typedef</code>). There&#8217;s also Sean Barrett&#8217;s <a href="http://nothings.org/stb_ds/">stb_ds</a> library which avoids the typedef by hiding the length and capacity behind the data pointer itself in memory. </p><p>I do not understand their hatred of the <code>typedef</code>, but both libraries are good.</p><h2>Summary</h2><p>I&#8217;ve gone over four approaches to implement Generic types in C using different techniques, each with pros and cons. Well, except the first one, <em>template macros</em>, where I can&#8217;t really find any pro, only cons.</p><p>The <em>type erasure</em> (<code>void*</code>) approach only makes sense if FFI or dynamic linking is involved, or you really must avoid code bloat at all costs.</p><p>The <em>inlining macro</em> approach is the most pleasant to use, but it is a bit annoying to develop since it requires programming inside macros (bleh). For more complex data structures than a dynamic array you&#8217;ll want to shift as much work to helper functions as possible, making it a bit of a hybrid with the <code>void*</code> approach.</p><p>The <em>template header</em> version is somewhat annoying to use but is in my opinion the most pleasant to actually program. It also works ok in an FFI or dynamic linking setting one level higher. For example, a graphics library might expose <code>Vector_texture</code> and <code>Vector_vertex</code> types and such.</p><p>So, to me, there are actually only two choices when all you care about is programming in C itself: template headers or inlining macros. The former is nicer to program while the latter is nicer to use. Try them out, see which one you prefer.</p><p>UPDATE: This article was discussed on <a href="https://news.ycombinator.com/item?id=43377966">hackernews</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>_Generic is more like a form of overloading/ad-hoc polymorphism and has nothing to do with generic types. It&#8217;s horrible to use and extremely limited. Instead of _Generic, what they should have added to C was something like Odin&#8217;s <a href="https://odin-lang.org/docs/overview/#explicit-procedure-overloading">explicit overloading sets</a>. Alas.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>If you want move semantics you&#8217;re better off using a language that implements them in a reasonable manner, like Rust.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><pre><code><code>#define _CAT(A, B) A##_##B
#define CAT(A, B) _CAT(A, B)</code></code></pre></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>The G stands for Generic &#129299;.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>Java 5 added generics to the type system, ensuring you use the types correctly. Before you had to do casts all over the place. Not quite as bad as the memory corruption you might get in C but getting random exceptions thrown in your face wasn&#8217;t much better.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>For example, a field <code>k_info</code> for a HashMap would be a pointer to a structure containing the key type size, a pointer to a hash function, and a pointer to a comparison function.</p><p></p></div></div>]]></content:encoded></item><item><title><![CDATA[Object-Oriented Brain Rot]]></title><description><![CDATA[Is a square a subclass of rectangle or a rectangle a subclass of square?]]></description><link>https://btmc.substack.com/p/oop-brain-rot</link><guid isPermaLink="false">https://btmc.substack.com/p/oop-brain-rot</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Fri, 31 Jan 2025 22:11:35 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/03b3de73-c06b-42ee-88b3-83831feb9813_2000x1500.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Despite my &#8220;pen name&#8221; clearly pointing out my propensity to complain, it&#8217;s not actually something I particularly enjoy doing. I do it because I care. I&#8217;d much rather teach cool stuff, but sometimes I get so annoyed at something that I need to vent and this is one of those blog posts.</p><blockquote><p>Disclaimer 1: Opinions expressed here are solely my own and do not express the views or opinions of my employer.</p></blockquote><p>As part of my job I&#8217;ve been developing an implementation of the <a href="https://www.omg.org/spec/SysML/2.0/Beta2/About-SysML">Systems Modeling Language 2.0</a> standard (typically referred to as SysMLv2). Think UML but for Systems Engineering (aerospace, automotive, etc.) and designed by the same people, <a href="https://www.omg.org">OMG</a>.</p><p>Content-wise I think it&#8217;s a pretty good standard. It has all the stuff I&#8217;d expect to find<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> and while the way you specify some things is a little weird, it could be much worse. It&#8217;s certainly better than UML or its previous iteration (SysMLv1).</p><p>Specification-wise, however, it is abysmal. Implementing this thing is an exercise in frustration. There&#8217;s a whole separate spec (KerML) that has to be understood and implemented first even though maybe 3 people max actually care about the distinction. Every concept is specified as a UML class, some with 10+ levels of inheritance, including multiple-inheritance.</p><p>The associations between concepts are all bidirectional and are overridden multiple times throughout the inheritance chain, e.g., one class might have an association with a multiplicity of 0..*, unique, unordered; a subclass redefines it to have multiplicity 0..1; a subclass of that subclass redefines it again to be multiplicity 1..*, non-unique, ordered. <a href="https://en.wikipedia.org/wiki/Barbara_Liskov">Barbara Liskov</a> and her <a href="https://en.wikipedia.org/wiki/Liskov_substitution_principle">substitution principle</a> be damned.</p><p>But relationships in SysMLv2 are reified as their own objects, so what you technically have are bidirectional associations (in a UML sense) between SysMLv2 elements and SysMLv2 relationships (which are also SysMLv2 elements), and then between the relationships and other elements. You can&#8217;t just get the &#8220;value&#8221; of an AttributeUsage, there&#8217;s a thing in the KerML standard library that attributes must specialize which is how you&#8217;re supposed to access their values, and in order to actually find the data you have to dig through multiple levels of relationships to find a FeatureValue<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> which is the relationship that has an association with the damn value.</p><p>It&#8217;s everything wrong with object oriented design dialed up to 11 until the knob breaks. I despise it. But this post isn&#8217;t actually about SysMLv2. The SysMLv2 specification is a manifestation of a much bigger issue: <em>Object-Oriented Brain-Rot</em><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>.</p><p>I find that doing too much OOP leads to the degradation of mental faculties, intelligence, and common sense. Hence the term brain-rot.</p><blockquote><p>Disclaimer 2: I have nothing against objects and interfaces, I think they&#8217;re nearly fundamental computational constructs. Even a closure can be seen as an object whose interface is a single method. This post is about a type of thinking.</p></blockquote><h1>Is a Square a Rectangle?</h1><p>In geometry a square is a kind of rectangle. All squares are rectangles, but not all rectangles are squares. In OOP, things are a little trickier. Consider:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;5af2c52c-381b-44fd-ad5b-0620c84adee2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">class Rectangle : Shape {   
  double width;   
  double height;    
  
  Rectangle(double width, double height) {     
    this.width = width;     this.height = height;   
  }    
  
  double getArea() {
    return width * height;   
  } 
}  

class Square : Rectangle {   
  Square(double length) {     
    this.width = length;     
    this.height = length;   
  }    
  
  double getArea() {
    return width * width;
  } 
}</code></pre></div><p>The above code has a major issue:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;c83cbc8b-16e1-4304-9542-881d2243c193&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">Rectangle r = Square(10); 
r.height = 20; 
print(r.getArea()); // oops</code></pre></div><p>Square breaks the <a href="https://en.wikipedia.org/wiki/Liskov_substitution_principle">Liskov Substitution Principle</a>. I can do things with a Rectangle (like setting its height separately from its width) which I cannot do with a Square. The typical solutions are to make the objects immutable, or to make both inherit from Shape directly.</p><p>But why do Rectangle and Square need to be modeled as objects in the first place? What&#8217;s their behavior? What is the &#8220;<a href="https://en.wikipedia.org/wiki/Single-responsibility_principle">Single Responsibility</a>&#8221; of a Rectangle or a Square?</p><p>While the exercise is meant as a way to showcase potential issues arising from modeling real world taxonomies as object-oriented class hierarchies<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>, to me the very existence of the exercise is problematic, because it means people are trying to model things as class hierarchies that shouldn&#8217;t be class hierarchies in the first place.</p><h1>What problem are you solving?</h1><p>The issue starts with the very act of modeling Rectangles and Squares as a class hierarchy. Why are you doing this? What purpose do these classes serve? You&#8217;re building some sort of application, what is the purpose of Square in the application?</p><p>Consider as an example a video game. For whatever reason I may need to draw a square onscreen. Do I need an actual Square object that subclasses Shape with a draw method or so? No.</p><p>The square emerges from some game state that requires the appearance of a square onscreen. There&#8217;s no need to store the square itself at any point, it is <em>derived</em> from other data. A &#8220;square&#8221; can be something as simple as:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;e76812f1-5c17-4f4c-9d2b-2a15b494cdb3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">void draw_square(int x, int y, int length) {
  draw_rectangle(x, y, length, length); 
}</code></pre></div><p>While a &#8220;rectangle&#8221; might be:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;ad53ad79-76a2-47e5-a44c-fb0ec84d9713&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">void draw_rectangle(int x, int y, double width, double height) {
  Point points[] = {
    {x, y}, {x+width, y},      
    {x+width, y+height}, {x, y+height}   
  };   
  draw_polygon(points); 
}</code></pre></div><p>The screen gets redrawn every frame so keeping the data for the shapes around is completely unnecessary. Maybe you&#8217;re drawing a square because a unit has been selected in a strategy game and the square serves as the selection border. The square exists while the unit is selected, and does not exist otherwise.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!Uc7m!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0586066c-6063-4f08-9c7d-a6d3dfae9152_960x600.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!Uc7m!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0586066c-6063-4f08-9c7d-a6d3dfae9152_960x600.heic 424w, /__u/substackcdn.com/image/fetch/$s_!Uc7m!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0586066c-6063-4f08-9c7d-a6d3dfae9152_960x600.heic 848w, /__u/substackcdn.com/image/fetch/$s_!Uc7m!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0586066c-6063-4f08-9c7d-a6d3dfae9152_960x600.heic 1272w, /__u/substackcdn.com/image/fetch/$s_!Uc7m!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0586066c-6063-4f08-9c7d-a6d3dfae9152_960x600.heic 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!Uc7m!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0586066c-6063-4f08-9c7d-a6d3dfae9152_960x600.heic" width="960" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0586066c-6063-4f08-9c7d-a6d3dfae9152_960x600.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:154713,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!Uc7m!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0586066c-6063-4f08-9c7d-a6d3dfae9152_960x600.heic 424w, /__u/substackcdn.com/image/fetch/$s_!Uc7m!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0586066c-6063-4f08-9c7d-a6d3dfae9152_960x600.heic 848w, /__u/substackcdn.com/image/fetch/$s_!Uc7m!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0586066c-6063-4f08-9c7d-a6d3dfae9152_960x600.heic 1272w, /__u/substackcdn.com/image/fetch/$s_!Uc7m!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0586066c-6063-4f08-9c7d-a6d3dfae9152_960x600.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A screenshot from the original Warcraft. The selected unit has a green square around it. There&#8217;s no reason for the game to store the &#8220;square&#8221;. The square is derived from the selection and the position and size of the unit.</figcaption></figure></div><p>But maybe you&#8217;re not actually working on a game. Perhaps you&#8217;re working on a vector drawing application where the user can add squares and rectangles and edit them. In this case you actually need to store squares and rectangles as actual entities.</p><p>But even then, do squares and rectangles need to be modeled as separate classes? A square is usually just a rectangle whose width and height are the same. In almost every vector drawing app you only have &#8220;rectangles&#8221;, even if you create a &#8220;square&#8221; you&#8217;re given the option of setting the width and height separately because that is what is most useful in a drawing application.</p><p>What about rounded rectangles? Is &#8220;RoundedRectangle&#8221; a subclass of Rectangle? Just have rounded corners be a property of rectangles. I have separate buttons to add rectangles and rounded rectangles in Pixelmator Pro but all they do is set a different default for the rounding of the corners, the entity produced is the same. There&#8217;s no need to model a separate concept. All a &#8220;rectangle&#8221; really is in such an app, ultimately, is a convenient user interface to manipulate some <a href="https://en.wikipedia.org/wiki/B&#233;zier_curve">B&#233;zier curves</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!6fBO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1e6a9ed-ec85-4a37-8600-cee5c6d210ec_2232x1524.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!6fBO!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1e6a9ed-ec85-4a37-8600-cee5c6d210ec_2232x1524.heic 424w, /__u/substackcdn.com/image/fetch/$s_!6fBO!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1e6a9ed-ec85-4a37-8600-cee5c6d210ec_2232x1524.heic 848w, /__u/substackcdn.com/image/fetch/$s_!6fBO!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1e6a9ed-ec85-4a37-8600-cee5c6d210ec_2232x1524.heic 1272w, /__u/substackcdn.com/image/fetch/$s_!6fBO!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1e6a9ed-ec85-4a37-8600-cee5c6d210ec_2232x1524.heic 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!6fBO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1e6a9ed-ec85-4a37-8600-cee5c6d210ec_2232x1524.heic" width="1456" height="994" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b1e6a9ed-ec85-4a37-8600-cee5c6d210ec_2232x1524.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:994,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:59868,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!6fBO!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1e6a9ed-ec85-4a37-8600-cee5c6d210ec_2232x1524.heic 424w, /__u/substackcdn.com/image/fetch/$s_!6fBO!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1e6a9ed-ec85-4a37-8600-cee5c6d210ec_2232x1524.heic 848w, /__u/substackcdn.com/image/fetch/$s_!6fBO!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1e6a9ed-ec85-4a37-8600-cee5c6d210ec_2232x1524.heic 1272w, /__u/substackcdn.com/image/fetch/$s_!6fBO!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1e6a9ed-ec85-4a37-8600-cee5c6d210ec_2232x1524.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Adding a rectangle in Pixelmator Pro. Note how the rectangle still has a Corner Radius property that just happens to be set to 0. Rounded Rectangle Shape does the exact same thing, only the Corner Radius property is set to a non-zero value to start.</figcaption></figure></div><p>There&#8217;s no need to waste time worrying about how well whatever concepts fit into your class hierarchy. You don&#8217;t need the class hierarchy and you need the real-world relations between the concepts even less. Just write the code that solves the problem.</p><h1>What if the relations are the point?</h1><p>In SysMLv2 the relations between the various concepts are relevant. The taxonomy itself is important information. But imposing that structure onto a codebase is a very strange thing to do. Why does the code need to reflect the taxonomy? If you care about relations, just model them directly. Here&#8217;s how you model the relation between a square and a rectangle in <a href="https://en.wikipedia.org/wiki/Datalog">Datalog</a> or <a href="https://en.wikipedia.org/wiki/Answer_set_programming">Answer set programming</a>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;d90d5f38-967f-4c3c-95c1-88b2ea5e5cc5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">% every square is a rectangle with the same width and height 
rectangle(Id, L, L) :- square(Id, L).  

% every rectangle of equal width and height is a square 
square(Id, L) :- rectangle(Id, L, L).</code></pre></div><p>With the above rules, I can add some facts to the database and have the set of squares and rectangles be kept consistent and up-to-date automatically:</p><pre><code>square(a, 3).
square(b, 5).
rectangle(c, 1, 2).
rectangle(d, 4, 4).
rectangle(c, 8, 7).</code></pre><p>If I query all the squares, then I get <strong>a</strong>, <strong>b</strong> <em>and</em> <strong>d</strong>. Even though I defined d as a rectangle, it is automatically defined as a square as well. Similarly if I query all the rectangles, I get all 5 entities. That&#8217;s how you model bidirectional relationships. Trivial.</p><p>By using the right tool for the job I made my life a lot easier. Had I suffered from OO brain-rot like OMG seems to, I would have made a class hierarchy. But then I could make a rectangle with equal width and height that for whatever reason is <em>not</em> a square.</p><p>It is entirely the wrong tool for the job. You&#8217;ve applied object-oriented design so much that you see it as not only the solution to everything, but a necessary first step.</p><p>Your mental faculties have actually decreased as a result. As a newbie you would just solve the problem directly, you wouldn&#8217;t even waste time on this, and the outcome would be better. What else can this be called besides brain-rot?</p><h1>A paradigm made of straw</h1><p>Some readers may accuse me of making up a <a href="https://en.wikipedia.org/wiki/Straw_man">straw man</a> and that this has nothing to do with OOP, but I disagree. The two are very much intertwined. While you can certainly use objects and interfaces in reasonable ways, that&#8217;s not how OOP is taught and practiced. When I was in university and learning OOP, object-oriented design was always at the forefront.</p><p>From the very beginning we were learning how Cat and Dog are subclasses of Mammal which in turn is a subclass of Animal. Though maybe they should be subclasses of Feline and Canine respectively? What about Carnivore? Do we use multiple-inheritance?</p><p>While the exercise was meant to explain inheritance, I really need to stress out how it was entirely disconnected from solving an actual problem. The same pattern repeated later when we were modeling &#8220;Customers&#8221; and &#8220;Employees&#8221; as subclasses of &#8220;Person&#8221;. Once again, a taxonomy that did not serve any actual purpose in solving a problem. All a customer really was in the end was an ID in a database.</p><p>This is what students are being evaluated on, and it&#8217;s how they learn to think about problems: in terms of object-oriented design. That the first step to creating a software solution is to model a taxonomy of concepts as a class hierarchy. This is a detrimental and unproductive way of thinking. It creates a rigid structure in the codebase that does not reflect the actual needs of the codebase, making it a pain to work with.</p><p>As an example, consider how people turn their noses at &#8220;Utils&#8221; or &#8220;Manager&#8221; classes even though just having some extra functions laying around is often the simplest and most natural approach. What issues are such classes actually causing, beyond going against some nonsensical object-oriented design ideal<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>?</p><p>Later those same students go to a job interview and are questioned on their knowledge of <a href="https://en.wikipedia.org/wiki/Software_design_pattern">design patterns</a>, another entirely wrong way to look at problems. You don&#8217;t need the <a href="https://en.wikipedia.org/wiki/Visitor_pattern">Visitor Pattern</a>, you need <a href="https://en.wikipedia.org/wiki/Tagged_union">sum types</a> (even C can do them).</p><p>So a combo-wombo of an entirely wrong way to think about problems, leading to poor program structure, plus rote memorization of patterns meant to solve the structural issues caused by the paradigm.</p><p>If you&#8217;re worried about maintainability, which is what OOP is supposedly good at<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>, then making a class-hierarchy based on a real-world taxonomy is the worst thing you can possibly do. Even serious real-world taxonomies need to be changed from time to time. Your partial taxonomy is going to have to change and the rigid structure it imposed on your codebase won&#8217;t make it easy.</p><p>Reset your brain and focus on the problem. You have data that needs to be transformed and/or moved around. How to accomplish that in a simple and performant manner? That&#8217;s all you need to care about.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Though it is, notably, missing positioning information for the visual representation. I find this utterly bizarre as it was a major complaint of the first version. Guess they were too busy making the spec as convoluted as they could get away with to worry about such minutiae.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Truly it is a <a href="http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html">Kingdom of Nouns</a>.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>There are many other forms of brain-rot in software engineering, including functional brain-rot where every function needs to be as generic as possible using monad transformers or what have you, even though all you&#8217;re doing is parsing a JSON file. That&#8217;s just as bad.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>Which, as the rest of the article hopefully shows, is a good thing for an exercise to teach. But it&#8217;s focusing on the wrong thing. The issue is not the perils of modeling real-world concepts as a class hierarchy, it&#8217;s that real-world concepts should <em>never</em> be modeled as a class hierarchy. There&#8217;s no reason to.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>Well, the classes shouldn&#8217;t exist in the first place, they&#8217;re just a pile of functions. It&#8217;s the language that&#8217;s imposing they be stored inside a class. More OOP brain-rot, only in this case on the part of the language designer.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>It really isn&#8217;t IMO. It&#8217;s a great way to implement extensible plugin systems, which may sound like it makes the system easy to maintain (if it&#8217;s easy to extend then it must be easy to maintain right?), but plugin systems require a rigid interface. <em>Rigid</em>. As in, hard to change.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Borrow Checking from Scratch]]></title><description><![CDATA[Let us try to make a C-like language memory safe.]]></description><link>https://btmc.substack.com/p/borrow-checking-from-scratch</link><guid isPermaLink="false">https://btmc.substack.com/p/borrow-checking-from-scratch</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Sun, 17 Nov 2024 21:44:50 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/56e4edb3-36ad-4736-a1f9-1821048a2549_3270x2180.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Anyone who has programmed in Rust has at one point or another &#8220;fought the Borrow Checker&#8221;<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>. You write code that appears to be (and possibly is) completely fine but the compiler is unable to statically<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> prove that it is memory safe, so it screams at you.</p><p>The so called &#8220;Borrow Checker&#8221; is a part of Rust whose job is to enforce a pair of rules at compile-time:</p><ol><li><p>Any borrow must last for a scope no greater than that of the owner.</p></li><li><p>You may have any number of immutable borrows to the same data, or a single mutable borrow to that data, but not both at the same time. 1 writer xor N readers.</p></li></ol><p>But what&#8217;s a &#8220;borrow&#8221;? What&#8217;s an &#8220;owner&#8221;? Why those two rules? Is Rule 2 necessary if we don&#8217;t care about multithreading?</p><p><strong>I&#8217;m not going to explain the rust borrow checker.</strong> You can read the <a href="https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html">Rust documentation</a> if you want a very detailed explanation. Rather, the goal of this post is to naturally arrive at a similar solution by tackling the same problem: memory safety through compile-time checks.</p><p>There are two main vulnerabilities we&#8217;re trying to prevent: <a href="https://cwe.mitre.org/data/definitions/415.html">Double-Free</a> (alongside memory leaks) and <a href="https://cwe.mitre.org/data/definitions/416.html">Use-After-Free</a>. The nastiest vulnerability, <a href="https://cwe.mitre.org/data/definitions/125.html">Out-of-Bounds Access</a>, is trivial to prevent with minimal runtime support<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>.</p><p>We&#8217;ll start from a simple C-like language and add compiler checks (our own &#8220;borrow checker&#8221; of sorts) to make it memory safe, and hopefully show you how you could have arrived at a similar solution in the process.</p><p>Did you know C# has something like a &#8220;<a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/structs#16412-safe-context-constraint">borrow checker</a>&#8221; too<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>?</p><h1>Meet Cnile</h1><p>We&#8217;re going to turn a made-up C-like language memory safe. We&#8217;ll call it Cnile<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>. </p><p>Cnile is very similar to C with a few minor differences: In Cnile the declaration syntax puts all relevant information next to the type, e.g. <code>int[] x</code> instead of <code>int x[]</code>. All data is initialized to 0. &#8220;Constness&#8221; is a property of a binding and not of a type, and is transitive.</p><p>The most important change is that it supports slices. Arrays don&#8217;t decay to pointers, they implicitly cast to slices, and slices track their length:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;31d8e998-1134-454c-927e-db76824f6c85&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">int[] x = {1, 2, 3, 4};

int example(int[..] y) {
  // will crash the program
  return y[5];
}

example(x);</code></pre></div><p>Slices are equivalent to the following anonymous struct:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;df1e1927-c274-4445-a927-1567af7e227e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">{ T* data; size_t length; } </code></pre></div><p>The <code>T[..]</code> syntax was proposed for C by Walter Bright <a href="https://digitalmars.com/articles/C-biggest-mistake.html">back in 2009</a>. Works well enough. Note that an array parameter would have a different meaning.</p><p>The above takes care of bounds checking. If the bounds check is too expensive for whatever reason, just grab the internal pointer and index that.</p><h1>Memory Safety at Runtime without GC</h1><p>If we ignore memory leaks, there&#8217;s actually a pretty easy way to also handle both double-free and use-after-free at runtime.</p><p>If all allocations are stored in arenas/regions, and the smallest arena size is at least one memory page in size, you can just use operating system features to protect the relevant pages after an arena is cleared. Any attempt to access that memory will cause a segmentation fault. There&#8217;s barely any performance cost to doing this<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>.</p><p>With a 64-bit address space it&#8217;ll take a very very long time before any page needs to be reused, and the chances that an invalid pointer to such an old page survived without being dereferenced is extremely low.</p><p>This only works for bulk allocations. If you want memory safety with individual malloc/free style memory management, you can do so with memory tagging.</p><p>Instead of a raw pointer, a <code>safe_malloc</code> would return a special reference type, which is equivalent to the following anonymous struct:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;ca7574b4-da81-467a-9baf-7244744c5e8e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">{ size_t tag; void* ptr }</code></pre></div><p>The Vale programming language calls these <a href="https://verdagon.dev/blog/generational-references">generational references</a>. Each allocation generates and stores a non-zero 64-bit random number tag at the top of the allocation. A simple <a href="https://en.wikipedia.org/wiki/Xorshift">xorshift</a> generator works great for this.</p><p>When dereferencing one of these tagged references, the tag stored in the reference and the tag stored in the allocation are compared. If they don&#8217;t match then we&#8217;ve detected a use-after-free.</p><p>There&#8217;s around a 0.0000000000000002% chance of collision. Extremely unlikely. Your odds of winning a lottery that would make you a multi-millionaire are over a billion times higher.</p><p>A <code>safe_free</code> function would only accept references like the above, not pointers, and would zero-out the tag in the allocation.</p><p>As with bounds checks the check for the tag does not need to be done on every access. As soon as you know the reference is valid, you can use the pointer directly until you call a function that might free the reference.</p><p>But we don&#8217;t want <em>any</em> runtime costs, we want to do everything at compile-time.</p><h1>Double-Frees and Memory Leaks</h1><p>Consider the following data structure:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;3fe585c9-1735-41d7-afbe-870b4e63eafd&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">struct DynArray {
  int* data;
  size_t length;
  size_t capacity;
}

void push(DynArray* a, int i) { 
  if (a-&gt;length == a-&gt;capacity) {
    a-&gt;capacity = (a-&gt;capacity + 1) * 2;
    a-&gt;data = unsafe_realloc(...);
  }
  a-&gt;data[a-&gt;length] = i;
  a-&gt;length += 1;
}

void delete(DynArray* a) {
  unsafe_free(a-&gt;data);
  a-&gt;data = null;
  a-&gt;length = 0;
  a-&gt;capacity = 0;
}

DynArray a = {};
push(&amp;a, 1);
delete(&amp;a);</code></pre></div><p>Our first goal is to ensure that <strong>delete</strong> is called one time for each DynArray, thereby preventing memory leaks and double-free-related vulnerabilities.</p><p>Calling delete on &#8220;a&#8221; twice is actually perfectly safe, the problem only occurs if we copy &#8220;a&#8221;. This would result in a shallow copy, as the internal pointer of both arrays would point to the same memory.</p><p><strong>Deleting both &#8220;a&#8221; and a shallow copy of &#8220;a&#8221; results in a Double-Free</strong>. We need to prevent DynArray&#8217;s from being shallow copied<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a>. Automatically performing a deep copy would be an option, but Cnile is a systems programming language, we don&#8217;t want any stinky hidden operations like that.</p><h2>First Attempt: No Implicit Copies</h2><p>Let us add a special annotation to DynArray, which tells the compiler we don&#8217;t want any implicit copies of it happening:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;279f2c78-c49a-4161-97e7-23930e0e4f59&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">[no_copy]
struct DynArray {
  ...
}

DynArray a = {};
push(&amp;a, 1);
DynArray b = a; // error
a = {}; // ok but now we're in trouble
delete(&amp;a) // not freeing the right "a"</code></pre></div><p>We&#8217;ve forbidden the compiler from inserting implicit copies of DynArrays. The only way to pass a DynArray around now is through a pointer. We would have to go out of our way to make a function that shallow copies DynArrays to get into trouble again<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-8" href="#footnote-8" target="_self">8</a>.</p><blockquote><p>Note: dereferencing a DynArray pointer would create a shallow copy so that is not allowed either, only the individual members can be accessed. If we were bothering with access control, the internal pointer would be private.</p></blockquote><p>We designed delete to be safe to call multiple times, so double-free cannot occur without &#8220;cheating&#8221; and messing with the pointer inside the array directly, but there are major problems with this solution:</p><ul><li><p>We can&#8217;t return a DynArray from a function directly, we have to use an out parameter pointer.</p></li><li><p>Nothing actually forces us to call delete, so memory leaks are not solved.</p></li><li><p>We can assign a new value to a DynArray, making the original impossible to delete (another source of memory leaks).</p></li></ul><p>If we forget to call delete, once a DynArray goes out of scope we have a memory leak. We need to ensure delete always gets called.</p><h2>Second Attempt: Single-Use Types</h2><p>Let us change our perspective a little bit. Instead of thinking about preventing copies, we enforce instead that a DynArray must be used (without going through a pointer) exactly 1 time. In Computer Science these single-use types are called <a href="https://en.wikipedia.org/wiki/Substructural_type_system">Linear Types</a>, so we&#8217;ll call the annotation <code>linear</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;8f1ec9eb-6ab9-4ba0-a566-40bf22509356&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">[linear]
struct DynArray {
  ...
}

DynArray a = {};
DynArray b = a; // ok, 1 use
printf("%d\n", a.length) // error, a is used up
// error, we never used (e.g. deleted) b</code></pre></div><p>This might not seem like much of a change, but we&#8217;ve gotten quite a lot from it. The line <code>DynArray b = a</code> is now allowed: it shallow copies &#8220;a&#8221; into &#8220;b&#8221; but because &#8220;a&#8221; cannot be used afterwards, there&#8217;s no issue, we are only able to call delete on &#8220;b&#8221;. And we <em>have</em> to either call delete on b, or assign it to something else.</p><p>In Rust this is called a &#8220;move&#8221; but there&#8217;s no need to think of it as &#8220;moving&#8221;. It&#8217;s just a side-effect of how the single-use rule works.</p><p>To make <code>a.length</code> work without using up &#8220;a&#8221;, it can be treated as if it were actually:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;425c8933-6009-4dbe-8d98-f5886c632698&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">(&amp;a)-&gt;length</code></pre></div><p>Which would be allowed since accessing &#8220;a&#8221; through a pointer doesn&#8217;t consume it.</p><p>To make delete actually consume a DynArray, we just need to change it to take a DynArray by value:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;bf97a0e4-32db-4d68-a4a4-f464e2527b5a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">void delete(DynArray a) {
  unsafe_free(a.data);
  unsafe_drop(a);
}</code></pre></div><p>Passing a DynArray to delete will shallow-copy it, consuming it in the process and making it inaccessible. Inside delete we only need to free the pointer, no need to zero-out other data since it is no longer valid and cannot be accessed.</p><p>The <code>unsafe_drop</code> builtin is used to get rid of &#8220;a&#8221; inside of delete, since here too we must use it once.</p><p><strong>This solution, if we ignore aliasing through pointers, is actually enough to achieve memory safety</strong>. The language <a href="https://www.parasail-lang.org">ParaSail</a>, which lacks pointers, achieves memory safety via a similar approach (though it inserts implicit deep copies and destructor calls).</p><p>A &#8220;Borrow Checker&#8221; is only necessary to retain memory safety in the presence of aliasing. Aliasing is where the challenge is. For example:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;fe2f738c-1f4d-4de9-bf02-d11320469109&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">DynArray a;
push(&amp;a, 1);
int* x = &amp;a.data[0];
delete(a);
printf("%d\n", *x) // Use-After-Free</code></pre></div><p>We need to prevent the above somehow.</p><h1>Use-After-Free and Aliasing</h1><p>We&#8217;ve already partially solved Use-After-Free with the previous solution. As long as we don&#8217;t actually store a pointer to a DynArray anywhere, there is no way to use it after it has been deleted. The single-use rule takes care of that.</p><p>Aliasing due to pointers in function parameters also does not introduce any problems. The pointers are scoped to the function so they&#8217;ll be gone by the time the function returns. Returning a pointer from a function is also ok as long as it is immediately passed as an argument to another function, not stored in a variable.</p><p>Pointers that are restricted to being used in this manner are called &#8220;second-class references&#8221; and are a perfectly reasonable and simple solution to the problem. The <a href="https://www.hylo-lang.org">Hylo</a> language works like this for example.</p><p>This is not the approach we will use, we want a bit more control. Before that though there&#8217;s still one problem that we need to tackle. Remember how Rust had that weird Rule 2 enforcing 1 writer xor N reader &#8220;borrows&#8221; (note: borrow=reference)? It&#8217;s important regardless of multithreading. Consider:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;0d35f8b2-5760-4533-991f-d13dce5db913&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">void concat(DynArray* a, int[..] b) {
  for (int i = 0; i &lt; b.length; i++) {
    push(&amp;a, b[i]);
  }
}

DynArray x = ...;
concat(&amp;x, x.data[0..2]); // Kaboom</code></pre></div><p>If you&#8217;ve done serious C or C++ programming the above should immediately trigger PTSD. The <code>v[i..j]</code> syntax is how we create a slice from existing data in Cnile: we&#8217;re creating a slice of the first 3 elements of x. So the idea is for the concat call above to append the first 3 elements of x to its end.</p><p>The problem is that the push call inside concat might need to reallocate, which may then perform a new allocation, copy the data to it, and free the old allocation. If this happens, the slice is now pointing to freed memory, causing a use-after-free.</p><p>Forbidding internal pointers could be an option for a more higher level language design. If all pointers point to the &#8220;source&#8221;, which is only ever invalidated by a call to a function like delete, then we avoid the issue. Many languages (e.g. Java) lack internal pointers. Not an option for us since we&#8217;re Cnile.</p><p>There is one more advantage to the 1 writer xor N reader rule (outside of multithreading): it prevents <a href="https://wiki.c2.com/?IteratorInvalidationProblem">iterator invalidation</a> bugs. Without it we must either allow iterator invalidation bugs (like C++), or catch them at runtime (like Java).</p><p>We&#8217;ll go with 1 writer xor N readers since it prevents lots of bugs, but this is an interesting design space and I encourage you to think about it.</p><h2>Safe References</h2><p>We&#8217;ll keep pointers as actual pointers and leave them as &#8220;unsafe&#8221; constructs in Cnile. As a safe alternative, we&#8217;ll provide references that enforce the &#8220;borrow checking&#8221; rules. Slices will be made safe references as well.</p><p>Let us consider concat above, and make a safe version of it with references:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;bbde43ce-5b87-4f05-8368-047069a01f17&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">void concat(DynArray&amp; a, int[..] b) {
  for (int i = 0; i &lt; b.length; i++) {
    push(&amp;a, b[i]);
  }
}

DynArray x = ...;
concat(&amp;x, x.data[0..2]); // error</code></pre></div><p>All we&#8217;ve done is change * (pointer) to &amp; (safe reference). But now the concat line fails because of the 1 writer xor N reader rule. How do we check this though? Well, internally the compiler is tracking extra information:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;a0a87d2b-1280-4d73-9054-b5a8ee354a1c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">void concat(DynArray&amp; &lt;?p1&gt; a, int[..] &lt;?p2&gt; b) {
  for (int i = 0; i &lt; b.length; i++) {
    push(&amp;a, b[i]);
  }
}

DynArray x = ...;
concat(&amp;x &lt;x&gt;, x.data[0..2] &lt;x.data&gt;); // error</code></pre></div><p>Every reference carries alongside it a hidden &#8220;path&#8221; to the data it refers to. Unlike Rust, but like C#, the user never writes these out themselves, they have no syntax. </p><p>The compiler checks the hidden reference paths for overlap. Overlapping paths that have more than 1 writer or a writer and a reader are rejected.</p><p>Function parameters have &#8220;variables&#8221; (marked with ?) for the paths, that are assigned when the function is called. Within concat the two inputs are assumed to be independent, the check happens on each callsite.</p><p>The same paths can be used to detect use-after-free:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;7cb07b96-2d94-45b4-91d4-1cf726303095&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">DynArray  a = {}; // available: {a}
DynArray&amp; x = &amp;a; // available: {a, x &lt;a&gt;}
DynArray  b = a;  // available: {x &lt;a&gt;, b}
concat(x &lt;a&gt;, {1, 2, 3});  // error, a no longer available</code></pre></div><p>Things get trickier when we want to return references from functions, what path should the output reference have in the following example:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;7e8102ca-487d-4b3d-b4ca-0da6e66ab739&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">int[..] &lt;?&gt; shortest(int[..] &lt;?p1&gt; a, int[..] &lt;?p2&gt; b);</code></pre></div><p>The shortest function returns the slice with the smallest length from the two inputs, meaning the return reference path &lt;?&gt; could end up being either p1 or p2.</p><p>In these situations all we can do is a safe approximation, and record <em>both</em> paths:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;0603ad4d-2f51-4725-be90-f9e7b8b3b062&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">int[..] &lt;?p1 &amp; ?p2&gt; 
shortest(int[..] a &lt;?p1&gt;, int[..] &lt;?p2&gt; b);</code></pre></div><p>When using this function, the output will only last for as long as either of the inputs lasts, regardless of the actual result:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;4209f6b3-b34c-4c8f-8b87-8773d1ac356b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">int[..] x = a[0..2]; // x &lt;a&gt;
int[..] y = b[0..3]; // y &lt;b&gt;
int[..] z = shortest(x, y); // z is x, but has path &lt;a &amp; b&gt;
delete(b);
printf("%d/n", z[0])  // error, b is no longer available</code></pre></div><p>This is the unavoidable limitation with compile-time checking, some things will only be known at runtime, so we need to consider all situations and assume the worst.</p><h2>Path Precision</h2><p>For the example above the most specific we can be is the intersection of the two input paths, but the compiler will do this even for cases where it isn&#8217;t necessary:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;72852cb1-5d8b-4b5f-9a44-4ff9b90722b7&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">int[..] &lt;?p1 &amp; ?p2&gt; 
find_subslice(int[..] a &lt;?p1&gt;, int[..] &lt;?p2&gt; b);</code></pre></div><p>This function returns a sub-slice of &#8220;a&#8221; that is equal to &#8220;b&#8221; (if any), or just returns an empty slice. The result will never point to &#8220;b&#8221; but the compiler has no way to tell this from the function&#8217;s declaration. So we need to provide more information.</p><p>In Rust this would be done with <em>lifetime annotations</em>. In C# this is done by explicitly saying which inputs <em>cannot</em> be part of the output. We&#8217;ll follow the C# solution:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;7b5ce413-e4ad-4d34-9626-97bea7b5e0b4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">int[..] find_subslice(int[..] a, [in] int[..] b);</code></pre></div><p>The <code>[in]</code> annotation means that &#8220;b&#8221; will not be part of the output. In C# the keyword <code>scoped</code> is used instead, but the meaning is the same.</p><p>This gets us most of the power of Rust without introducing lifetime annotations. There are two major capabilities that we lose by not having them:</p><ul><li><p>We have no way to state in a function or struct declaration that two references must have overlapping paths. Paths are always allowed to be disjoint.</p></li><li><p>We have no way to state in a function declaration with a struct that stores multiple references as input, which reference paths will be part of the output.</p></li></ul><p>To make the second case clear, let us talk about <em>reference structs</em>.</p><h2>Reference Structs</h2><p>In C#, a struct that contains a &#8220;ref&#8221; type (like Span) must itself be a &#8220;ref&#8221; type. In Cnile, we have the same constraint:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;7466e825-3109-4d51-86bd-762a9bfd0062&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">[reference]
struct SlicePair {
  int[..] a;
  int[..] b;
}</code></pre></div><p>Structs that contain reference types must themselves be annotated as being references, so the compiler knows it needs to track paths for them.</p><p>The struct SlicePair above stores two slices together. What is its path?</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;70e0921b-feea-4026-8489-bc30b6f20516&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">int[..] x   = ... // x has path &lt;foo&gt;
int[..] y   = ... // y has path &lt;bar&gt;
SlicePair p = {x, y} // p has path &lt;?&gt;</code></pre></div><p>If we treat a SlicePair as a unit, then &#8220;p&#8221; must have path <code>&lt;foo &amp; bar&gt;</code>. The fields <code>p.a</code> and <code>p.b</code>, however, could each track individual paths.</p><p>Field-level tracking depends on how much information the compiler carries around for the type. For a rather messy example:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;fc527927-2c5b-4129-8eb1-e7c4beb4a7b1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">int[..] get_x(SlicePair&amp; p);
int[..] get_y(SlicePair&amp; p);</code></pre></div><p>What are the paths inferred by the compiler for <code>get_x</code> and <code>get_y</code> above? It&#8217;s a bit weird at first glance:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;33af1689-c670-4278-a1f7-ce9137e1119f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">int[..] &lt;?p1 &amp; ?p2&gt; get_x(SlicePair &lt;?p2&gt; &amp; &lt;?p1&gt; p);
int[..] &lt;?p1 &amp; ?p2&gt; get_y(SlicePair &lt;?p2&gt; &amp; &lt;?p1&gt; p);</code></pre></div><p>There are two paths to consider for &#8220;p&#8221;. The path to the SlicePair <em>itself</em> (p1) and the path that the SlicePair <em>references</em> (p2). </p><p>There&#8217;s no way for the compiler to know which of the two paths matters for the output, so if the SlicePair goes out of scope, the output of get_x and get_y is no longer valid even if the actual slices stored in the SlicePair are perfectly valid. </p><p>If we had explicit lifetime annotations, the user could be fully specific in this case:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;399c6fe4-e14d-40fc-ab26-813a1e88fac4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">int[..]&lt;'a&gt; get_x(SlicePair&lt;'a,'b&gt; &amp;&lt;'c&gt; p);
int[..]&lt;'b&gt; get_y(SlicePair&lt;'a,'b&gt; &amp;&lt;'c&gt; p);</code></pre></div><p>This is how it works in Rust. Many times the compiler can figure out the lifetime annotations for you, but not always. When it can&#8217;t, as in the example above, you better understand how lifetime annotations work. You also always need to write them out by hand for struct declarations, which gets pretty annoying.</p><p>By lacking any syntax for lifetime annotations we avoid functions with complex declarations like the above, but are also more limited in the programs we can express. Personally I prefer to avoid the complexity that lifetime annotations introduce.</p><h1>Conclusion</h1><p>Well, there you have it, a memory safe C-like language through the use of compile-time checks (except for bounds checks and escape hatches like raw pointers of course).</p><p>Note that we didn&#8217;t really mention &#8220;ownership&#8221;, &#8220;moves&#8221; and &#8220;borrowing&#8221; except when contrasting with Rust terminology. We have no destructors<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-9" href="#footnote-9" target="_self">9</a> nor lifetime annotations. There&#8217;s quite a bit of design space to work with.</p><p>Single-use (linear) types alongside scoped references with implicit path tracking (as done in C#) provide a simpler alternative to Rust&#8217;s borrow checker implementation.</p><p>Hopefully this clarifies how and why a &#8220;borrow checker&#8221; works the way it does and the options one has when designing one. I find Vale&#8217;s idea of mixing single-use (linear) types with generational references to be a very compelling alternative to borrow checking with minimal runtime overhead.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>This used to be explicitly called out in the <a href="https://doc.rust-lang.org/1.8.0/book/references-and-borrowing.html">official rust documentation</a>.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p><em>Statically</em> means the check occurs entirely at compile-time. If checks happen at runtime, we say they performed <em>dynamically</em>.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>C and C++ are the only languages that refuse to deal with bounds checking properly. Their respective committees should be sued for gross negligence.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>It needs one for types like &#8220;Span&#8221; (a slice type) which introduce so called &#8220;internal pointers&#8221;. Internal pointers are a total pain in the ass to track in a garbage collector, so C# went with a compile-time solution. There is also a &#8220;Memory&#8221; type which is similar to Span but instead of storing a pointer + length pair, it stores a GC-friendly reference + offset + length triple, and doesn&#8217;t need to be &#8220;borrow checked&#8221; as a result.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>A term of endearment for C developers, it&#8217;s not meant to be derogatory. The C equivalent of Rustacean or Gopher, only funnier.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>Clearing an arena is no longer just resetting a pointer, it requires obtaining new pages. Since clearing is not done very often, this is not a major cost.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>For this sort of datastructure in particular, the chances of someone accidentally making a shallow copy are very very low. But remember we&#8217;re trying to solve the general problem, and a more complicated example would make the this blog post harder to follow.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-8" href="#footnote-anchor-8" class="footnote-number" contenteditable="false" target="_self">8</a><div class="footnote-content"><p>So just don&#8217;t do that.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-9" href="#footnote-anchor-9" class="footnote-number" contenteditable="false" target="_self">9</a><div class="footnote-content"><p>Rust needs destructors because rather than Linear Types it uses Affine Types (i.e., <em>at-most-one-use</em> types). If a variable is not used by the end of its scope, the Rust compiler inserts an implicit destructor call. We could have supported the same if we wanted to.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Avoid Null Objects for Error Handling]]></title><description><![CDATA[They have their uses, but can easily lead to a mess if used naively.]]></description><link>https://btmc.substack.com/p/dont-use-null-objects-for-error-handling</link><guid isPermaLink="false">https://btmc.substack.com/p/dont-use-null-objects-for-error-handling</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Sat, 27 Jul 2024 12:28:37 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9eba8a5c-1f2e-404d-bce7-ecaa51a0750d_3840x2400.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There&#8217;s this idea in lower level programming circles that the best way to &#8220;handle&#8221; errors is to just return a harmless default value when any operation fails, and then just let the program do a whole lot of nothing with those values. The value is typically &#8220;zero&#8221; but that&#8217;s mostly due to the languages being used (like C) zeroing structs by default. This is often called Zero Is Initialization (ZII) as a pun on Resource Acquisition is Initialization (RAII) from C++. </p><p>Note that a default constructor achieves the same purpose as ZII<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> (albeit less efficiently), the idea of returning a safe default on error is the bit I&#8217;m concerned with here, the zeroing itself is not relevant.</p><p>I think this is a silly way to handle errors, mostly stemming from C-language deficiencies. Zero being a useful default is a good idea, specially in C, that&#8217;s not what I have issue with. My problem is specifically with returning a default, &#8220;harmless&#8221; value (that just happens to be &#8220;zero&#8221; in this case) as the output of every fallible function, and continuing to do useless work with that value.</p><p>In Object-Oriented languages this would be an instance of the NullObjectPattern. Same idea, different coat of paint. <strong>I don&#8217;t like it</strong>.</p><p>Dealing with errors doesn&#8217;t require checking for them everywhere, nor to keep doing useless computations with a &#8220;harmless default&#8221; when an error occurs, even in C. There are situations where &#8220;null objects&#8221; are the most convenient solution to the problem, but much like exceptions using them everywhere just leads to a mess.</p><h2>Zero being Useful != Errors being Useful</h2><p>To make things a bit clearer I&#8217;ll use an example from the <a href="https://ruby0x1.github.io/machinery_blog_archive/post/defaulting-to-zero/index.html">Our Machinery blog</a>, which is where I first saw the idea of applying ZII to error handling. A lot of the article is about how much nicer using 0 as a default is in C, which I 100% agree with, so let us skip to the part I have a beef with. The example uses the following function:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;398fbf51-1747-49fe-a38a-638e5e35722d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">uint32_t find_bone(const char *name);</code></pre></div><p>This function returns the index of a data structure with information on a particular human bone, stored in a global array, given that bone&#8217;s name. So passing in &#8220;femur&#8221; would give you the index where the information on femur is stored.</p><p>If I pass in the name &#8220;fenur&#8221;, which is not a valid name for a human bone, the function has to do something about it. The suggestion in the blog is to return index 0, which happens to contain a special &#8220;no bone&#8221; case. That &#8220;no bone&#8221; is a &#8220;null object&#8221; to use OOP terminology. That means I can do:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;a52a1556-d699-4045-a37d-28a23d1355bb&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">uint32_t femur_id = find_bone("fenur");
float femur_length = bone_length(femur_id);</code></pre></div><p>Getting the bone length will always work no matter what the output of find_bone is. In this case it&#8217;ll return 0, which is the length of &#8220;no bone&#8221;. Program doesn&#8217;t crash, control flow is kept simple, job&#8217;s done right? <strong>Not really no</strong>.</p><h2>Garbage In, Garbage Out</h2><p>Lets assume that <code>find_bone</code> does nothing but return 0 (i.e., &#8220;no bone&#8221;) when a bone is missing, because if it does anything else (like record the error somewhere) then that&#8217;s the actual error handling part. We&#8217;ll cover that case later. For now, assume <code>find_bone</code> does nothing besides return index 0, aka &#8220;no bone&#8221;, in case of an error.</p><p>This special &#8220;no bone&#8221; (a null object) is a valid bone in the sense that every operation on bones works on &#8220;no bone&#8221;. I can get the length of a &#8220;no bone&#8221;, I can get the position of a &#8220;no bone&#8221;, I can get the name of a &#8220;no bone&#8221;, I can render a &#8220;no bone&#8221;, whatever.</p><p>But I wanted the femur. The <code>find_bone</code> function &#8220;handled&#8221; the error by returning garbage, and the rest of the program kept on going like nothing happened.</p><blockquote><p>Garbage! From King Garbage! Of the Garbage Dynasty! Stupid dog, always bringing garbage into the house.</p><p>&#8212; Eustace Bagge</p></blockquote><p>If I get the length of a &#8220;no bone&#8221;, which is not the length of a femur, that length is also garbage. Any calculation involving that length is also garbage. And so on and so forth. Some people think that because &#8220;no bone&#8221; is a valid bone, this somehow is not garbage data. I don&#8217;t understand their reasoning. NaNs are valid floats, but they&#8217;re unwanted garbage nearly all of the time.</p><p>If we assume that the job of the application was to render a human skeleton, then the resulting display would have oddly short and deformed legs. Some people consider this an acceptable outcome, they&#8217;ll talk about displaying a giant red ERROR<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> texture or similar instead but it&#8217;s the same thing really.</p><p>You didn&#8217;t handle the error, you <em>ignored it</em>. Your program &#8220;works&#8221; in the presence of an error by producing results the user didn&#8217;t ask for. Sometimes a half-working state can be beneficial, a lot of the time it is not.</p><h2>Deal with Errors, but Later.</h2><p>While some people will unironically claim &#8220;null objects&#8221; as the be-all-end-all solution to error handling, most people are using them as part of a larger strategy. For an example of the latter see this article by Ryan Fleury: <a href="https://www.rfleury.com/p/the-easiest-way-to-handle-errors">The Easiest Way To Handle Errors Is To Not Have Them</a><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>.</p><p>I mentioned before that the <code>find_bone</code> function could have recorded the error somehow. Instead of just returning &#8220;no bone&#8221;, <code>find_bone</code> will additionally store the information that &#8220;fenur&#8221; doesn&#8217;t exist in some error log.</p><p>Some other part of the program will later go through this log and display the error to the user. This allows you to write your whole program without adding error checks everywhere, and still deal with the error. The result is a program with much simpler control flow, which in turn makes the code a lot easier to follow.</p><p><strong>For C in particular, because all the code always executes, that means also all the resource management code executes, which is extremely valuable</strong><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a><strong>. </strong></p><p>I can definitely accept that reasoning, but that is a workaround for a language deficiency. What you wanted to do is abort the &#8220;task&#8221; that caused the error, reverting the program to the point right before the task started.</p><p>The &#8220;work&#8221; the program did meanwhile didn&#8217;t really achieve anything useful for the user. Best case scenario it amounted to a lot of &#8220;NOP&#8221;-ing around, worst case there is now garbage state all over the place the user will have to cleanup somehow.</p><p>Avoiding the latter takes <em>discipline</em>. It&#8217;s not enough to just return &#8220;null objects&#8221; everywhere and pray it all works out, that results in a bad user experience.</p><p>My suggestion if you do this is to record the error right where it occurs, let the null object propagate until the end of the function, then check if an error occurred. Don&#8217;t let it propagate further than that. You can extend &#8220;a function&#8221; to a larger task but make sure no null objects escape that task by getting stored somewhere.</p><p>Additionally, make sure you add checks or asserts for these null objects in every IO function. You don&#8217;t want to be sending them over the network and such. Remember, we&#8217;re talking about the case where they&#8217;re &#8220;errors&#8221;, not just a nice default.</p><h2>What are &#8220;errors&#8221; to begin with?</h2><p>There are two major sources of &#8220;errors&#8221;: bugs (unexpected errors) and partial functions (expected errors). You can consider program state as an additional input to a function for the latter case, to keep things simple.</p><p>I have another article on the fault (bug) &#8594; error &#8594; failure pipeline<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>, but the basic idea is that a bug is one or more erroneous lines in the program. You can&#8217;t do anything about a bug after you&#8217;ve already launched the program. Those lines, when executed, will cause an error, which is some invalid data or state. You can&#8217;t do anything about that directly either, since you didn&#8217;t expect it. That data or state will propagate and transform until it manifests as some user visible glitch or trigger some assertion in the code, resulting in a failure.</p><p>Can&#8217;t do anything about glitches (the user will have to report them). The only thing you can do with a failure is a generic &#8220;crash handler&#8221; that attempts to restore the program to a previous valid state and records as much information as it can to help debug the source of the failure (i.e., to figure out the actual bug that led to it)<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>.</p><p>Unexpected Errors &#8594; Crash Handler. Moving on.</p><h2>Partial Functions</h2><p>A partial function is a function that is only defined for a subset of its input set. Division is the classic example, its result is undefined when 0 is given as the divisor.</p><p>On a computer we can&#8217;t just leave things &#8220;undefined&#8221; like that, we need to decide what happens when 0 is passed in. CPUs do different things for integer and floating point division, and as programmers we too have different options when dealing with any partial function:</p><ol><li><p>We can restrict the function&#8217;s input set, for example by only allowing non-zero integers as input, and relying on the type system to prevent compilation if 0 is passed in<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a>. Non-nullable pointers are a great example of this approach.</p></li><li><p>We can check if the input is within the subset for which the function is defined as a precondition or assertion, triggering a &#8220;panic&#8221; if a value outside of that subset is given. We&#8217;re treating a particular input as being a bug in this case, meaning the actual error handling will be done as with any other bug, with a crash handler.</p></li><li><p>We can expand the output set of the function and map the inputs for which the function is not normally defined to this larger set. This is your <code>Optional&lt;T&gt;</code> or <code>Result&lt;T, E&gt;</code> style solution, or multiple return values as in Go<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-8" href="#footnote-8" target="_self">8</a>.</p></li><li><p>We can map inputs for which the function is not normally defined to one or more existing members of its output set, preferably unused ones. Division by 0 could return 0 for example<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-9" href="#footnote-9" target="_self">9</a>, I think Elm does this.</p></li></ol><p>Continuing with division as an example, these are the options in pseudo-code:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;d783e417-9372-4fb8-bca4-7f5d6975f96c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">1. int div(int a, nz_int b) { ... }
2. int div(int a, int b) { requires(b != 0); ... }
3. int? div(int a, int b) { if (b == 0) return NONE; else ... }
4. int div(int a, int b) { if (b == 0) return 0; else ... }</code></pre></div><p>All options are valid, there is no option that&#8217;s universally better, but some options are better than others in different situations.</p><p>Depending on the language you&#8217;re using some options might be rather cumbersome. Options 1 and 3 are pretty terrible in C for example. I guess that&#8217;s a good reason to go for 4, but that doesn&#8217;t make it a good thing, it just makes C bad.</p><h2>Error Handling the Whinesalot Way</h2><p>My suggestion is to do the following: Pick Option 1 (restrict the input set) if it&#8217;s easy enough to do. The typechecker is your friend, use it. If that just isn&#8217;t practical, pick between Options 2 (assertion) and 3 (extended output set) depending on the case. Division by 0 is invariably a bug (so 2), but I might pass a non-existent key to a hash-table to check if it is stored there (so 3). Option 4 should only be used for languages that can&#8217;t do 3 properly (like C), and it should be used <em>as if</em> it were 3. Meaning &#8220;no bone&#8221; is an <strong>error case</strong> and it should be handled <strong>immediately</strong>.</p><p>What do I mean by dealing with an error &#8220;immediately&#8221;? Does that mean showing an error message right away? No, it means aborting the erroneous task.</p><p>First, should <code>find_bone</code> follow option 2 (assertion) or 3 (expanded output set)? It depends. Where are its inputs coming from? Is it only called in one place? Or in multiple places for different purposes?</p><p>Consider for example that it&#8217;s used to get data for some fixed set of bones in a help page. The bones are hardcoded and written by the programmer, if one is missing it&#8217;s almost certainly a bug. In this situation it&#8217;s more convenient for <code>find_bone</code> to do option 2 since needing to check if a bone I know is there is actually there is just noise.</p><p>But what if the name of the bone is given in a search box by the user? They can make a typo, or pass in a real bone name that just so happens to not be stored in the application&#8217;s &#8220;bone database&#8221;. In this situation it&#8217;s more convenient for <code>find_bone</code> to do option 3, so I&#8217;m reminded that I need to handle the situation of the bone being missing and provide the user with an appropriate message (thanks typechecker!).</p><p>You see this duality in many APIs, for example Python has both <code>dict[key]</code>, which throws an exception if the key is missing (option 2) and <code>dict.get(key)</code>, which returns <code>None</code> if the key is missing (option 3).</p><p>Since Option 2 is always dealt with the same way, what matters is how to program with partial functions that use Option 3.</p><h2>I (know/don&#8217;t care/don&#8217;t know) if I&#8217;m right</h2><p>Sometimes an API only provides Option 3, but you &#8220;know&#8221; you&#8217;re passing in a valid value. In that case use whatever solution the language provides to assert this. You can use <code>.unwrap()</code> in Rust for example, or <code>try!</code> in Swift. I&#8217;m not a fan of these noisy methods/operators, I much rather have two separate functions, one that uses Option 2 (meaning crash on wrong input) and one for Option 3, which returns an error value.</p><p>Sometimes if something is missing, you don&#8217;t really care, as it can be safely replaced by a default. An example would be filling in a default avatar if a user doesn&#8217;t provide one. Hopefully you&#8217;re using a language that makes this easy to do, like C#:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;bdc06e53-de76-47e8-8f79-9c4c7495634b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">var avatar = user.GetAvatar() ?? new DefaultAvatar();</code></pre></div><p>Note that the default avatar might be a generated image, rather than the same image for every user. In other cases what makes a good &#8220;default&#8221; might be different, even for the same function, when used in different parts of the program. Having &#8220;zero&#8221; be the default is not necessarily the answer here.</p><p>The big red &#8220;ERROR&#8221; texture can also apply in this case, but only if that is an acceptable default texture when a texture is missing, it is <strong>not</strong> ok as a result if the texture was actually necessary for the task, as in the bone example.</p><p>The last case is where you are <em>hoping</em> the function succeeds, but you can&#8217;t know if it will <em>a priori</em>. This is the interesting one, sorry it took so long to get here!</p><h2>Error Values vs Null Objects</h2><p>If a function may return an error result, and that result cannot simply be replaced with a <em>useful</em> default (not a useless &#8220;null object&#8221;), then you need to do the following:</p><ol><li><p>Abort the &#8220;task&#8221; being attempted, whatever that may be. A &#8220;task&#8221; may or may not be a larger unit than a single function. This doesn&#8217;t mean crashing the program, it means returning to the last working state.</p></li><li><p>Inform the user of the error, either immediately, or by recording it in some error set that will later be displayed to the user <em>in bulk</em>.</p></li></ol><p>As an example, consider a type checker, which is actually one of the best fit problems for the &#8220;null object&#8221; solution<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-10" href="#footnote-10" target="_self">10</a>, since we typically want to collect multiple errors rather than just stopping at the first one (at least in an IDE setting).</p><p>To make the difference clear between my suggested error handling approach and the &#8220;null object&#8221; approach, consider typechecking the add operator with implicit conversions between floats and integers. We start with the &#8220;null object&#8221; version:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;e625eea6-7132-45a1-84bf-c077947d01a8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">Type top(Ctx* c, Type a, Type b) {
  // needed to prevent recording a mismatched type error for ERRORs
  if (a == ERROR || b == ERROR) { return ERROR; }
  if (a == b) { return a; }
  if (a == INT &amp;&amp; b == FLOAT) { return b; }
  if (a == FLOAT &amp;&amp; b == INT) { return a; }
  record_mismatched_type_error(c, a, b);
  return ERROR;
}

Type typecheck_add(Ctx* c, ASTNode* l, ASTNode* r) {
  Type left_t = typecheck(c, l);
  Type right_t = typecheck(c, r);
  Type result_t = top(c, left_t, right_t);
  // this check is needed to avoid redundant error messages
  if (!result_t) { return ERROR; }
  if (!(result_t == INT || result_t == FLOAT)) {
    record_expected_type_error(c, result_t, "a numeric type");
  }
  return result_t;
}</code></pre></div><p>Note how we must check anyway for <code>ERROR</code> to ensure we don&#8217;t keep recording error messages that <code>ERROR</code> is not a numeric type or whatever. That check could be done on the various &#8220;record&#8221; functions as well (and should be there instead), but it has to happen somewhere, and we need to remember to do so.</p><p>Either way, if we naively use null objects without any checks, the compiler output will be full of useless errors! With error values, we&#8217;d do the following:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;b6550c76-b61b-43c6-a028-cdb4792aa3bf&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">OptionalType top(Ctx* c, Type a, Type b) {
  if (a == b) { return some(a); }
  if (a == INT &amp;&amp; b == FLOAT) { return some(b); }
  if (a == FLOAT &amp;&amp; b == INT) { return some(a); }
  record_mismatched_type_error(c, a, b);
  return error();
}

OptionalType typecheck_add(Ctx* c, ASTNode* l, ASTNode* r) {
  OptionalType left_t = typecheck(c, l);
  OptionalType right_t = typecheck(c, r);
  return_if_error(left_t, right_t);
  OptionalType result_t = top(c, left_t.value, right_t.value);
  return_if_error(result_t);
  if (!(result_t.value == INT || result_t.value == FLOAT)) {
    record_expected_type_error(c, result_t.value, "a numeric type");
  }
  return result_t;
}</code></pre></div><p>In this case we never do computation with <code>OptionalType</code>, all functions take <code>Type</code> as input, <code>OptionalType</code> is only ever used as a return type. Because of this we need to turn <code>OptionalType</code> into <code>Type</code> before using it. The typechecker makes sure we never forget to do this. If not for needing to record multiple errors, I wouldn&#8217;t even bother with <code>OptionalType</code>, I&#8217;d just <code>exit()</code> the moment I hit an error.</p><p>Additionally, no unnecessary computation happens. The <code>top</code> function is never called if either the left or the right node failed to typecheck. In this small example it makes no difference, but it can have an impact in a larger application. Imagine instead of a typechecker we were writing a JSON parser, what use is there to keep going?</p><p>The important point is that we got additional type safety for little additional effort, and most of it is due to C not helping what so ever. Consider Rust instead:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;5ff9bd8f-479f-40bd-acc0-bd69c4b0098a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">fn top(c: &amp;Ctx, a: Type, b: Type) -&gt; Option&lt;Type&gt; {
  if a == b { return Some(a); }
  if a == INT &amp;&amp; b == FLOAT { return Some(b); }
  if a == FLOAT &amp;&amp; b == INT { return Some(a); }
  record_mismatched_type_error(c, a, b);
  return None;
}

fn typecheck_add(c: &amp;Ctx, a: &amp;ASTNode, b: &amp;ASTNode) -&gt; Option&lt;Type&gt; {
  let left_t = typecheck(c, l);
  let right_t = typecheck(c, r);
  let result_t = top(c, left_t?, right_t?);
  if (!(result_t? == INT || result_t? == FLOAT)) {
    record_expected_type_error(c, result_t?, "a numeric type");
  }
  return result_t;
}</code></pre></div><p>Other than some extra noise with <code>Some</code> and <code>?</code>, this is just as simple as the &#8220;null object&#8221; case where the check for <code>ERROR</code> is hidden in the record functions (except here we don&#8217;t even need to do so). We&#8217;ll never end up with a &#8220;mismatched types: ERROR and INT&#8221;, we&#8217;ve structured the code in a way that makes it impossible! We cannot forget to handle the error, and neither can the intern.</p><p>If the language allowed implicit conversions between <code>Type</code> and <code>Option&lt;Type&gt;</code>, then we could get rid of those noisy and unnecessary <code>Some(x)</code>, and if it implicitly added the &#8220;?&#8221; operator when attempting to convert <code>Option&lt;Type&gt;</code> to <code>Type</code>, then the code would be completely clean<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-11" href="#footnote-11" target="_self">11</a>.</p><p>The &#8220;null object&#8221; solution is not some novel discovery, it&#8217;s a workaround for poor error handling in your language of choice. If you use it, use it properly.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Default constructors can handle situations where zero is not a useful value. Pointers are a good example, you rarely want null, you want the pointer to point to the default of whatever type the pointer points to.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>This is an example of the above footnote. A giant red error texture is not zero. A pointer to such a texture is not zero. You can only take &#8220;zero&#8221; = &#8220;default&#8221; so far.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>I have many issues with the title of this article. Errors don&#8217;t disappear because your program can continue to function in their presence. Just because adding &#8220;42&#8221; + 0 in Javascript gives &#8220;420&#8221; instead of throwing an exception doesn&#8217;t make the program any better (I&#8217;d say it makes it worse because garbage data is worse than no data). That said, the issue is with the <em>title</em> of the article, not the article itself, which is much more nuanced.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>Thanks for reminding me Boostibot.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>Don&#8217;t start arguing about the names! They don&#8217;t matter! The idea matters!</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>Some people think you can handle all errors this way, they&#8217;re crazy and you shouldn&#8217;t listen to them. It&#8217;s meant as a last line of defense, not as the only line of defense.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>Dependent Types and Refinement Types can encode this sort of constraint in the type system but they&#8217;re more trouble than they are worth in my opinion.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-8" href="#footnote-anchor-8" class="footnote-number" contenteditable="false" target="_self">8</a><div class="footnote-content"><p>Exceptions straddle the line between options 2 and 3, which is why they&#8217;re a bad error handling mechanism. Panics as in Go or Rust are like exceptions but exclusively meant for option 2, which is why they&#8217;re not an issue in those languages.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-9" href="#footnote-anchor-9" class="footnote-number" contenteditable="false" target="_self">9</a><div class="footnote-content"><p>I find it funny that some people recoil at this idea but have no issue with &#8220;no bone&#8221;. They&#8217;re the same thing to me.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-10" href="#footnote-anchor-10" class="footnote-number" contenteditable="false" target="_self">10</a><div class="footnote-content"><p>I actually do this for my toy compiler, sometimes it&#8217;s simply the best option, treat everything I said in this article as being prefaced with &#8220;in general&#8221;. Just because I don&#8217;t find null objects a good default choice for error handling, doesn&#8217;t mean they aren&#8217;t useful!</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-11" href="#footnote-anchor-11" class="footnote-number" contenteditable="false" target="_self">11</a><div class="footnote-content"><p>Noting this idea down for my own little programming language &#129488;.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Tracing Garbage Collection for Arenas]]></title><description><![CDATA[Tracing GC is to Arenas what jelly is to peanut butter.]]></description><link>https://btmc.substack.com/p/tracing-garbage-collection-for-arenas</link><guid isPermaLink="false">https://btmc.substack.com/p/tracing-garbage-collection-for-arenas</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Mon, 24 Jun 2024 10:03:35 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f4a4bca1-1e60-4f30-8efa-fb162ce716f8_3271x2180.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Certain things just don&#8217;t mix: oil and water, me and productivity, garbage collection and systems programming<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>.</p><p>Well, the latter is not exactly true. C++, Rust and Ada all have <a href="https://en.wikipedia.org/wiki/Reference_counting">reference counted</a> pointers in their standard library, and reference counting is a type of garbage collection. Both Swift and Nim use automatic reference counting as their default memory management strategy.</p><p>Interestingly Swift&#8217;s predecessor Objective-C, older versions of Nim, and early versions of Rust all supported tracing garbage collection! Yet they all abandoned it.</p><p>There are many reasons as to why, but I think it boils down to a few key points:</p><ul><li><p>Reference counting, at least in its simplest form, is deterministic and plays well with scope-based resource management. Objects are freed immediately after the last reference to them is dropped. A typical tracing garbage collector is non-deterministic, you don&#8217;t know when it will kick in and free things.</p></li><li><p>Reference counting, at least in its simplest form, is entirely local. The less you use of it, the less of an impact it has in your program. A typical tracing GC needs a hefty runtime, safepoints inserted into functions, etc., even if it is barely used.</p></li><li><p>Reference counting, at least in its simplest form, plays well with C, and therefore with anything that talks to C (i.e., it is good for FFI). A typical tracing GC needs its runtime to be set up by the host, or for a copy of it to be bundled with every FFI-exported library.</p></li></ul><p>Note that I specifically mentioned reference counting in <em>its simplest form</em>, which happens to be the reference counting implementation used by all of these systems programming languages. They all have plenty of compile-time optimizations, sure, but the runtime component is bog standard reference counting. No fancy concurrent, coalesced, deferred reference counting or anything like that, because it would have the same issues as a tracing GC.</p><p>According to <a href="https://dl.acm.org/doi/10.1145/1028976.1028982">A Unified Theory of Garbage Collection</a>, reference counting and tracing garbage collection are just two ends of a spectrum. The more you optimize one or the other the more they meet in the middle. As far as &#8220;systems programming languages&#8221; are concerned, those optimizations have an unacceptable cost.</p><p>But that got me thinking. If modern tracing garbage collectors are &#8220;in the middle of the spectrum&#8221;, and that is bad, and simple reference counting is &#8220;on one end of the spectrum&#8221;, and it is ok&#8230; what about the other end? Can we make &#8220;the simplest form&#8221; of tracing garbage collection work for systems programming?</p><h2>Back to Basics</h2><p>With how fancy modern tracing garbage collectors are, we tend to forget that the original algorithms were extremely simple.</p><p><a href="https://en.wikipedia.org/wiki/Tracing_garbage_collection#Na&#239;ve_mark-and-sweep">Mark-and-Sweep</a> is just graph reachability. Starting from a set of roots, find all reachable nodes and mark them. Go through the graph again and free every node that isn&#8217;t marked.</p><p>It&#8217;s not particularly efficient, but it&#8217;s extremely simple to implement. So simple in fact that you could just implement your own mark-and-sweep garbage collector for whatever graph-like data structures you use.</p><p>The <a href="https://yices.csl.sri.com/doc/misc-operations.html#garbage-collection">Yices2</a> SMT solver, written in C, does this for example. It has a mark-and-sweep garbage collector you can invoke at any time to free no longer used expression nodes. Note that the collector is never implicitly called, there&#8217;s no runtime, it&#8217;s just a function that Yices makes available to the host.</p><p>For the set of roots it doesn&#8217;t need to do any fancy stack scanning. Any expression stored in a model is a root, and any root expressions not stored in a model can be passed in as an extra argument.</p><p>We&#8217;re so blinded by these fancy incremental, concurrent, generational garbage collectors that we forget just how simple tracing garbage collection really is.</p><p>Yices2 proves that tracing garbage collection can be used in a systems programming context. The host calls the GC if and when it wants to, no runtime.</p><p>But can we generalize this? Having each library implement their own custom mark-and-sweep tracing GC is fine and all, but I&#8217;m wondering how this could work as a general memory management strategy. </p><p>Just taking an existing, modern garbage collector and making it cleanup only when explicitly called does not work very well, they&#8217;re incremental for a reason (in fact pretty much every tracing GC-ed language tells you not to do this). But we don&#8217;t need any of that fancy-schmancy stuff, we&#8217;re going back to 1970.</p><h2>Garbage Collected Arenas</h2><p>While the languages mentioned previously all use a mix of scope-based resource management and reference counting, in C that&#8217;s actually not so common since it has to be done manually. Needing to manually increment and decrement reference counts is not only pretty annoying it is also very error prone.</p><p>The more common memory management approach is to use Arenas, also known as Bump Allocators. You throw a bunch of allocations into a big bucket, and then you free them all in one go. Allocation and deallocation are both extremely fast, but deallocation is delayed to when it is convenient for the program, rather than immediately when an object becomes inaccessible.</p><p>You know, a bit like how a tracing GC works.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!89a_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a594295-e92b-4efe-ba36-36388c75cb01_323x263.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!89a_!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a594295-e92b-4efe-ba36-36388c75cb01_323x263.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!89a_!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a594295-e92b-4efe-ba36-36388c75cb01_323x263.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!89a_!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a594295-e92b-4efe-ba36-36388c75cb01_323x263.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!89a_!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a594295-e92b-4efe-ba36-36388c75cb01_323x263.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!89a_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a594295-e92b-4efe-ba36-36388c75cb01_323x263.jpeg" width="323" height="263" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8a594295-e92b-4efe-ba36-36388c75cb01_323x263.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:263,&quot;width&quot;:323,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!89a_!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a594295-e92b-4efe-ba36-36388c75cb01_323x263.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!89a_!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a594295-e92b-4efe-ba36-36388c75cb01_323x263.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!89a_!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a594295-e92b-4efe-ba36-36388c75cb01_323x263.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!89a_!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a594295-e92b-4efe-ba36-36388c75cb01_323x263.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Yup, that&#8217;s what we&#8217;re doing. Alongside <code>free(&amp;arena)</code> we&#8217;re adding a new <code>collect(&amp;arena, &amp;roots)</code> function in the style of Yices. Collect does the same thing as free, but it copies all data accessible through the roots to a new bucket (adjusting the pointers in the process). This is called a <a href="https://en.wikipedia.org/wiki/Stop_and_copy#Moving_vs._non-moving">moving collector</a>, which has various advantages compared to mark-and-sweep:</p><ul><li><p>Allocating is just a pointer bump on the arena, as fast as can be.</p></li><li><p>Freeing is just resetting the arena pointer. That means inaccessible data, no matter how much or how big, has zero impact on the garbage collector<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>.</p></li><li><p>Data also gets compacted when copied, avoiding fragmentation and improving memory locality.</p></li><li><p>The particular algorithm we&#8217;ll use, <a href="https://en.wikipedia.org/wiki/Cheney's_algorithm">Cheney&#8217;s Algorithm</a>, is iterative. It uses no recursion what so ever.</p></li></ul><p>So what&#8217;s the catch? Well, copying can be slow, but beyond that not much. Having to pass the roots manually is a bit annoying but Yices shows that it can work. Not an issue for a custom language that uses this as its memory management strategy, since it can easily compute the roots.</p><p>There&#8217;s an extra cost per allocation, however, a small header has to be prepended with the following information:</p><ul><li><p>The size of the allocation.</p></li><li><p>A pointer to a &#8220;tracing/moving&#8221; function or to a data structure with information about the pointers contained in the allocation.</p></li><li><p>A forwarding pointer for the case where the same data is reachable from multiple roots but has already been copied.</p></li></ul><p>The forwarding pointer is only needed during collection, so with some bit fiddling we can get away with an 8 byte header. Reference counting needs 8 bytes for the reference count, another 8 if there are weak references, plus however much is used by the malloc header.</p><p>The tracing/moving function is equivalent to the destructor in the reference counting case, with the advantage that it is not recursive.</p><p>Lastly, the set of roots has to be stored somewhere to be passed to the collect function. In a custom language, this can be implemented very easily following the approach outlined in this paper: <a href="https://dl.acm.org/doi/10.1145/512429.512449">Accurate garbage collection in an uncooperative environment</a><strong>.</strong></p><p>Basically, you keep a shadow stack of pointers to garbage collected data. If the language has an effect system, you can restrict this shadow stack to be manipulated only by functions that directly or indirectly call collect. You could, for example, only allow applications (not libraries) to ever call collect<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>.</p><h2>Putting it all together</h2><p>Start from a language like <a href="https://ziglang.org/documentation/master/#Memory">Zig</a> or <a href="https://odin-lang.org/docs/overview/#allocators">Odin</a> where every function that allocates takes an allocator as input (implicitly or explicitly). From the function&#8217;s point of view, this allocator is just a bog standard arena that asks for some weird metadata. You could just as well pass in a normal, non-garbage collected arena to this function if desired, and you can have as many distinct arenas as makes sense for your application.</p><p>But, if you don&#8217;t need that level of control, you can also just pass in the default arena. This arena acts just like a regular arena, except it stores some extra metadata alongside each allocation. Again, from the function&#8217;s point of view, there&#8217;s no difference beyond needing to pass in a pointer to the metadata (can be compiler generated or written by hand, doesn&#8217;t matter).</p><p>Lastly, the application (not libraries!) can call collect at any point to clean up the default GC arena. The compiler needs to maintain a shadow-stack for every function in collect&#8217;s call stack. This can be easily tracked with a simple effect system. In the case of another language like C serving as the host, it can just pass the root set explicitly to collect.</p><p>And we&#8217;re done. The only cost is the extra header 8 byte header per allocation and a tracing function per type that stores GC pointers. Otherwise the application can choose to never call collect at all and just call free directly and everything will still work. The garbage collection is (almost) entirely optional<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>.</p><p>This honestly sounds too good to be true, so I&#8217;m wondering why it hasn&#8217;t been done before. The closest attempt I can think of is <a href="https://cone.jondgoodwin.com/memory.html">Cone</a>. Maybe I&#8217;ll have to be the one to put it to the test in my own programming language. For now, here&#8217;s a <a href="https://github.com/sirwhinesalot/gc-arena">GC Arena implementation in C</a> (still needs improvement).</p><p>Update: This article was discussed on <a href="https://news.ycombinator.com/item?id=40774748#40808609">hackernews</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>In case you don&#8217;t know what it means, application programming is meant to serve end-users directly, whereas systems programming is meant to serve <em>other software</em>. Think low-level libraries, game engines, operating systems, etc.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Copying large amounts of memory can be slow, but that can be mitigated by storing very large allocations (i.e., multiple megabytes) in their own separate buckets. These don&#8217;t need to be copied, they can just by attached to the new &#8220;bucket&#8221; that is left after collection. The only real issue are massive amounts of live, small allocations. Use a different approach if you need that many (e.g., pools), or split them up into different arenas.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>Libraries can call collect on GC arenas they themselves create of course, the issue is only calling collect on the default GC arena. That should only ever be done by the application.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>I haven&#8217;t covered multithreaded applications here, but they&#8217;re an important concern. We can&#8217;t add safepoints to functions or we&#8217;re back where we started with too-fancy GC. For reference counting the solution is to use atomic updates or keep data per-thread, moving it as necessary (like Nim does). For this approach I would follow Nim and make each thread have its own Arena, with data needing to be explicitly made available across threads through specialized shared-memory arenas.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Implementing scoped defer in C]]></title><description><![CDATA[Existing library implementations aren't quite what I want.]]></description><link>https://btmc.substack.com/p/implementing-scoped-defer-in-c</link><guid isPermaLink="false">https://btmc.substack.com/p/implementing-scoped-defer-in-c</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Sat, 09 Mar 2024 22:44:11 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/80931070-27a7-4a21-bc96-5489761b21ec_6000x4000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Note: Before reading this post, you should read my post on <a href="/__u/btmc.substack.com/p/implementing-generators-yield-in">implementing generators in C</a> because I&#8217;ll be relying on similar techniques here.</em></p><p>Error handling and resource management in C can be a total pain in the butt. Consider for example a program that needs to split a log file into three separate files for &#8220;info&#8221;, &#8220;warning&#8221; and &#8220;error&#8221; logs respectively:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;5426b587-b005-4e66-bece-c956fb69b494&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">FILE* log_file = fopen("log.txt", "r");
if (!log_file) {
  return -1;
}

FILE* info_log_file = fopen("info_log.txt", "w");
if (!info_log_file) {
  fclose(log_file);
  return -1;
}

FILE* warning_log_file = fopen("warning_log.txt", "w");
if (!warning_log_file) {
  fclose(info_log_file);
  fclose(log_file);
  return -1;
}

FILE* error_log_file = fopen("error_log.txt", "w");
if (!error_log_file) {
  fclose(warning_log_file);
  fclose(info_log_file);
  fclose(log_file);
  return -1;
}

// ...
// code to actually read and write the files goes here.
// if any operation fails we also need to "cleanup" there.
// ...

fclose(error_log_file);
fclose(warning_log_file);
fclose(info_log_file);
fclose(log_file);
return 0;</code></pre></div><p>Copy-pasting those <code>fclose</code> calls around everywhere is not only annoying but extremely error prone. The above is the <em>simplest</em> example. Imagine we only want to create the various output files if one of the corresponding log type lines appears in the original file, or only create and write to one of the files if a certain configuration option is set. There might be memory management (malloc/free) happening in between all of this for whatever reason.</p><p>In this post I&#8217;m going to show you a really neat macro trick that makes dealing with <em>resource</em> management as pleasant as it can get in <em>standard</em> C<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>.</p><h2>What do other languages do?</h2><p>Most programming languages provide a built-in mechanism for resource management.</p><p>C++ and Rust have <a href="https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization">scope-based resource management</a>, where each type may have an associated &#8220;destructor&#8221; (called <code>drop</code> in rust) that is implicitly inserted by the compiler at the end of the scope where the variable storing the resource resides. When the scope ends resources are cleaned up, no matter if it happens &#8220;naturally&#8221;, due to an exception, or a return statement.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a></p><p>This solution is particularly nice because there is nothing for the consumer of the API to worry about, it just works, but it requires that object lifetimes be tied to scopes.</p><p>Languages like C#, Java and Python have special resource handling blocks: <code>using</code> statements in C#, <code>try-with-resource</code> statements in Java, and <code>with</code> statements in python<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>, which implicitly call a cleanup function at the end of their scope. In C# it&#8217;s also possible to have <code>using</code> statements not introduce a new scope, tying the resource to the <em>current</em> scope instead.</p><p>Instead of calling the destructor, a specific method from the language&#8217;s &#8220;cleanup protocol&#8221; is called:  <code>Dispose()</code> in C#, <code>close()</code> in Java and <code>__exit__()</code> in Python.</p><p>This solution has the disadvantage that the consumer of the API must remember to use the resource handling statement for cleanup to occur, but it allows regular objects to be managed in a non-scope oriented manner.</p><p>Languages like Go, Zig, Odin, and C3 support a <code>defer</code><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a> statement that allows to specify the cleanup action, rather than implicitly invoking some standard cleanup protocol method or destructors:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;go&quot;,&quot;nodeId&quot;:&quot;000a0512-cf8e-497f-8c11-10a4ca6483c3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-go">// Go
log_file, err := os.Open("log.txt")
if err != nil {
  return nil, err
}
defer log_file.Close()

info_log_file, err := os.Open("info_log.txt", "w")
if err != nil {
  return err
}
defer info_log_file.Close()

warning_log_file, err := os.Open("warning_log.txt", "w")
if err != nil {
  return err
}
defer warning_log_file.Close()

error_log_file, err := os.Open("error_log.txt", "w")
if err != nil {
  return err
}
defer error_log_file.Close()

// ...

return nil</code></pre></div><p>This solution has the disadvantage that the user of the API must not only remember to use defer, but also to know which specific function to call. </p><div><hr></div><p>An important thing to note is that Go&#8217;s defer works differently from the other languages mentioned. The defer of the other languages is very simple: it can be understood as &#8220;copy-pasting&#8221; the cleanup code right before the scope ends (including if it is exited by a return statement).</p><p>Go&#8217;s defer is more of an imperative command. The block of code it will execute works like a closure, capturing the values of the referenced variables at the time of the defer statement&#8217;s execution (not at cleanup!). It also runs the cleanup code at function end rather than scope end, so it requires extra storage. For example, a defer statement executed in a loop 10 times will add 10 cleanup instructions to the &#8220;cleanup stack&#8221;.</p><p>Go&#8217;s style of defer is less efficient and its behavior can be surprising<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>.</p><div><hr></div><p>The advantage of defer is that it is fully explicit and gives the user the most control.</p><p>Fully explicit? Most control? Sounds like C to me. There are already some existing implementations of defer for C but I&#8217;m not happy with any of them:</p><ul><li><p>The <a href="https://gustedt.gitlabpages.inria.fr/defer/">reference implementation</a> of the <a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2895.htm">N2895 defer proposal</a> is needlessly complicated, to the point of requiring an extra preprocessor. It relies on GCC extensions and setjmp + longjmp. It needs all this complexity to support features nobody asked for, IMHO. It is &#8220;Go style&#8221;.</p></li><li><p>ceraii&#8217;s <a href="https://github.com/seleznevae/ceraii/blob/master/src/ceraii.h">implementation</a> is also quite complicated (though not to the same extent) and relies on setjmp + longjmp. It is &#8220;Go style&#8221;.</p></li><li><p>moon-chilled&#8217;s <a href="https://github.com/moon-chilled/Defer/blob/master/defer.h">Defer macro</a> is the simplest but also requires either GCC extensions or setjmp + longjmp, plus a 32 element buffer at the start of each function to store the defers. It is &#8220;Go style&#8221;.</p></li></ul><p>I don&#8217;t want any of this, I want a simple and efficient scoped defer like Zig, Odin, C3 and D have, but in pure standard C. I want the scoped defer of the <a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3199.htm">N3199 proposal</a>.</p><p>So until that gets accepted (i.e. never), it is time to get our hands dirty.</p><h2>Goto Fail</h2><p>The classic solution to do resource management in C is to (ab)use goto statements to organize things a little<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;6cab114d-e8c1-426d-9649-c3f53a8f1917&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">int result = 0;
FILE* log_file = fopen("log.txt", "r");
if (!log_file) {
  result = -1;
  goto DEFER_0;
}

FILE* info_log_file = fopen("info_log.txt", "w");
if (!info_log_file) {
  result = -1;
  goto DEFER_1;
}

FILE* warning_log_file = fopen("warning_log.txt", "w");
if (!warning_log_file) {
  result = -1;
  goto DEFER_2;
}

FILE* error_log_file = fopen("error_log.txt", "w");
if (!error_log_file) {
  result = -1;
  goto DEFER_3;
}

// ...

DEFER_4:
  fclose(error_log_file);
DEFER_3:
  fclose(warning_log_file);
DEFER_2:
  fclose(info_log_file);
DEFER_1:
  fclose(log_file);
DEFER_0:
  return result;</code></pre></div><p>We avoid the copy-paste problem, but this has its own issues. The resource cleanup has to be explicitly written in last-in-first-out order at the end of the function. Instead of using return, it&#8217;s now necessary to use goto to jump to the correct cleanup point. Things gets even more complicated when certain resources are allocated and released within inner scopes. This technique is also partially responsible for <a href="https://www.imperialviolet.org/2014/02/22/applebug.html">goto fail</a>.</p><h2>A first attempt</h2><p>Having only one resource cleanup call and the ability to jump around opens up some options. Lets reorganize the code a little:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;b07b1ae2-6464-43d1-9527-044859517923&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">int result = 0;
FILE* log_file = fopen("log.txt", "r");
if (!log_file) {
  result = -1;
  goto DEFER_0;
}
if (0) {
  DEFER_1:
    fclose(log_file);
    goto DEFER_0;
}

FILE* info_log_file = fopen("info_log.txt", "w");
if (!info_log_file) {
  result = -1;
  goto DEFER_1;
}
if (0) {
  DEFER_2:
    fclose(info_log_file);
    goto DEFER_1;
}

FILE* warning_log_file = fopen("warning_log.txt", "w");
if (!warning_log_file) {
  result = -1;
  goto DEFER_2;
}
if (0) {
  DEFER_3:
    fclose(warning_log_file);
    goto DEFER_2;
}

FILE* error_log_file = fopen("error_log.txt", "w");
if (!error_log_file) {
  result = -1;
  goto DEFER_3;
}
if (0) {
  DEFER_4:
    fclose(error_log_file);
    goto DEFER_3;
}

goto DEFER_4;
DEFER_0:
  return result;</code></pre></div><p>Yikes that looks even worse! But this was an important change, now the cleanup code is right next to the initialization code, right where defer would be.</p><p>Lets try making some macros:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;b9835b76-188f-44e0-8136-3010b15ed2ae&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#define CAT(A, B) A##B
#define defer_return(I, V) do {goto CAT(DEFER_, I)} while(0)
#define defer(I, F, P) if(0){CAT(DEFER_, I): F; goto CAT(DEFER_, P)}
#define run_deferred(I) goto CAT(DEFER_, I); DEFER_0: return result;

int result = 0;
FILE* log_file = fopen("log.txt", "r");
if (!log_file) {
  defer_return(0, -1);
}
defer(1, fclose(log_file), 0);

FILE* info_log_file = fopen("info_log.txt", "w");
if (!info_log_file) {
  defer_return(1, -1);
}
defer(2, fclose(info_log_file), 1);

FILE* warning_log_file = fopen("warning_log.txt", "w");
if (!warning_log_file) {
  defer_return(2, -1);
}
defer(3, fclose(warning_log_file), 2);

FILE* error_log_file = fopen("error_log.txt", "w");
if (!error_log_file) {
  defer_return(3, -1);
}
defer(4, fclose(error_log_file), 3);

run_deferred(4);</code></pre></div><p>Better, but not good enough. Having to manually count the defers like this is really annoying. We need to get fancier.</p><h2>Duff&#8217;s device to the rescue</h2><p>There&#8217;s no good way to &#8220;count&#8221; in the C preprocessor to avoid the issue above. The closest thing we have is the __LINE__ macro which stores the current line, but there&#8217;s no way to refer to the &#8220;last line&#8221; in which we deferred. </p><p>Rather than trying to do everything at the preprocessor level, we&#8217;ll shift some of the work to runtime<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a>. Let&#8217;s rework the example above to use a duff&#8217;s device-like switch statement instead of straight gotos:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;f1520f15-3c12-41f0-89ae-817568b16720&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#define defer_return do {goto _d_start;} while(0)
#define defer(I, F) {_d=I; if(0){case I: _d--; F; goto _d_start;}}
#define defer_block int _d = -1; _d_start: switch(_d){case -1:
#define run_deferred() goto _d_start;}

int result = 0;
defer_block {
  FILE* log_file = fopen("log.txt", "r");
  if (!log_file) {
    result = -1; defer_return;
  }
  defer(1, fclose(log_file));

  FILE* info_log_file = fopen("info_log.txt", "w");
  if (!info_log_file) {
    result = -1; defer_return;
  }
  defer(2, fclose(info_log_file));

  FILE* warning_log_file = fopen("warning_log.txt", "w");
  if (!warning_log_file) {
    result = -1; defer_return;
  }
  defer(3, fclose(warning_log_file));

  FILE* error_log_file = fopen("error_log.txt", "w");
  if (!error_log_file) {
    result = -1; defer_return;
  }
  defer(4, fclose(error_log_file));
} run_deferred();
return result;</code></pre></div><p>Much better, now we only need 1 count in the defers. The basic idea is that each call to defer sets the defer count (<code>_d</code>) equal to I. It also adds an <code>if(0)</code> block that has the corresponding switch case label in its body. Inside the body it executes the action, decrements <code>_d</code>, and jumps to the switch again.</p><p>So once the first jump back to the switch occurs, the defer blocks get executed in reverse order, until <code>_d</code> becomes 0 which isn&#8217;t a valid case and the switch stops.</p><p>The syntax:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;80a9700b-4d38-4832-b24f-58efb3d581bb&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">defer_block {...} run_deferred();</code></pre></div><p>is modeled after:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;04da10cb-3a32-4613-a675-ef9cf8094be6&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">do {...} while(...);</code></pre></div><p>Which I personally find makes it quite easy to understand. But this is still not good enough! If a user writes the wrong value in the defer count, all hell breaks loose. We&#8217;re also missing inner scope support for proper scoped defer.</p><p>Rather than track the current defer by incrementing and decrementing, we&#8217;ll track where the defers occur by storing their <code>__LINE__</code>, and we&#8217;ll track the previous defer through an auxiliary variable called <code>_d_p</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;dfccdc8c-3cf6-4b1f-9795-40f8a60e58d9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#define defer(F) {int _d_p = _d; \ 
  _d = __LINE__; if(0){case __LINE__: _d = _d_p; F; goto _d_start;}}</code></pre></div><p>The <code>_d_p</code> (defer previous) variable is declared inside a new scope, such that it shadows any previous usage of the variable. It remembers the last value of <code>_d</code>, before <code>_d</code> is set to the current line. Now we can use the line as the case constant, avoiding explicit defer counting. Instead of decrementing, we set <code>_d</code> back to its previous value through <code>_d_p</code>. The last thing we need to ensure is that when the first <code>_d_p</code> is set, it points to &#8220;nowhere&#8221; so the switch ends, we do this by setting <code>_d</code> right after the switch starts to an impossible line:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;d23b9625-4561-4bfa-b003-74817d7aed4c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#define defer_block int _d = 0; \ 
  _d_start: switch(_d){case 0: _d = -1;</code></pre></div><p>We&#8217;ve now gotten a single level of defer working with no manual counting.</p><h2>Scoped defer</h2><p>The last thing we want to support are nested scopes, where you can execute a subset of defers for a particular scope without running the whole thing. We also need <code>defer_return</code> to run every defer from the current scope all the way to the top level defer block.</p><p>The first thing to do is introduce a way to open a new defer scope. We&#8217;ll do this with the most complicated trick so far:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;b3d7734f-7880-4df4-acf4-dfc6d17a167d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#define _DEFER_SNAPSHOT(A) {int _d_p = _d; \ 
  _d = __LINE__; if(0){case __LINE__: _d = _d_p; A;}}
#define defer(F) _DEFER_SNAPSHOT(F; goto _d_start)
#define defer_scope
  do { _DEFER_SNAPSHOT(if(_d_r){goto _d_start;} else {continue;})</code></pre></div><p>We factor out the logic of defer into a <code>_DEFER_SNAPSHOT</code> macro which is shared with <code>defer_scope</code>. The idea is that <code>defer_scope</code> behaves like defer, in that it creates a &#8220;jump point&#8221;, but rather than executing an action, it makes a decision based on a new <code>_d_r</code> (defer return) variable:</p><ul><li><p>If <code>_d_r</code> is true, it continues the &#8220;unwinding&#8221; process.</p></li><li><p>If <code>_d_r</code> is false, it jumps out of its <code>do {} while(0)</code> loop with <code>continue</code>.</p></li></ul><p>This variable is set by the updated <code>defer_return</code>. We also add a <code>defer_break</code> alternative which is used to end only the current scope:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;1e9c70f1-a282-4007-bd7d-069a5810b23f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#define defer_break { goto _d_start; }
#define defer_return { _d_r = 1; goto _d_start; }</code></pre></div><p>We make a small change to run_deferred, such that it works for both the top level defer block and the inner defer scopes:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;e967919e-741a-41d3-bac1-e1fa0aa25571&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#define run_deferred() goto _d_start; } while (0)</code></pre></div><p>For the inner scopes, the <code>while(0)</code> ends the <code>do</code> loop. For the top level block, it&#8217;s just an extra <code>while(0)</code> that does nothing.</p><p>Lastly, we need to initialize the <code>_d_r</code> variable when we start the defer_block:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;2ca79de1-e402-4d5f-9369-e70a258082c3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#define defer_block int _d_r = 0; int _d = 0; \ 
  _d_start: switch(_d){case 0: _d = -1;</code></pre></div><p>And we&#8217;re done, scoped defer:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;5e1f84dd-6d7b-4c9c-9bda-a116564ba2ba&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">int result = 0;
defer_block {
  FILE* log_file = fopen("log.txt", "r");
  if (!log_file) {
    result = -1; defer_return;
  }
  defer(fclose(log_file));

  defer_scope {
    FILE* info_log_file = fopen("info_log.txt", "w");
    if (!info_log_file) {
      result = -1; defer_return;
    }
    defer(fclose(info_log_file));
    
    FILE* warning_log_file = fopen("warning_log.txt", "w");
    if (!warning_log_file) {
      result = -1; defer_return;
    }
    defer(fclose(warning_log_file));

  } run_deferred();

  FILE* error_log_file = fopen("error_log.txt", "w");
  if (!error_log_file) {
    result = -1; defer_return;
  }
  defer(fclose(error_log_file));
} run_deferred();
return result;</code></pre></div><h2>Conclusion</h2><p>Unlike the little macro I used to implement generators in C, this one is a bit&#8230; much. But it&#8217;s also substantially simpler and more efficient than the longjmp based approaches, so if you want to have defer in C, try this out.</p><p>If you are ok with sticking to GCC (or clang), then <a href="https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-cleanup-variable-attribute">__attribute__((cleanup))</a> is the way to go, it just works, even if it only allows calling a function with a specific prototype rather than an arbitrary block of code.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>I&#8217;m explicitly saying &#8220;resource&#8221; management here rather than &#8220;memory&#8221; management because managing memory in C should be done with Arenas and Pools. I&#8217;m explicitly saying &#8220;standard&#8221; C because there are non-standard extensions that are far superior to the crazy implementation presented in this article.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>It is a bit more complicated than this because objects can be stored in other objects, in which case the container is supposed to call the destructors of its contents. Objects may also be moved out of a scope in which case they should no longer be freed at the end of it. Rust uses a scope-local 1 bit reference count to track this while in C++ moving an object sets the source in a &#8220;moved-from&#8221; state that its destructor knows to ignore.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>C# and Python also have &#8220;destructors&#8221; but their usage is frowned upon, they are only meant as a last line of defense. Non-memory resources need to be freed as soon as they are no longer necessary, but due to tracing garbage collection (or cycle collection in Python&#8217;s case) the destructor is not called in a predictable manner.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>D also supports &#8220;defer&#8221; statements but calls them <a href="https://tour.dlang.org/tour/en/gems/scope-guards">scope guards</a>.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>This Go code deadlocks:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;go&quot;,&quot;nodeId&quot;:&quot;601c45ad-4333-47cc-9cc7-a6894112e8c9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-go">for i := 0; i &lt; 100; i++ { 
  mutex.Lock()   
  defer mutex.Unlock()
  ...
}</code></pre></div></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>While the example is the &#8220;typical&#8221; way, it&#8217;s possible to simplify it a lot by having a single goto target label at the end that first checks if each resource was acquired and frees it if so:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;996259b7-1a9d-4ecb-a286-1e0ad123f908&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">FILE* f1 = fopen(...);
if (!f1) {
  goto fail;
}
FILE* f2 = fopen(...);
if (!f2) {
  goto fail;
}
fail:
  if (f2) fclose(f2);
  if (f1) fclose(f1);</code></pre></div></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>Don&#8217;t worry, it&#8217;ll be much more efficient than the longjmp based approaches.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Memory Unsafety is an Attitude Problem]]></title><description><![CDATA[No amount of technical improvements can make up for jagoffs.]]></description><link>https://btmc.substack.com/p/memory-unsafety-is-an-attitude-problem</link><guid isPermaLink="false">https://btmc.substack.com/p/memory-unsafety-is-an-attitude-problem</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Sun, 03 Mar 2024 10:19:19 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3eeba583-a4b0-436d-8b39-98315811e11c_1528x1019.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>On February 26th the White House released a <a href="https://www.whitehouse.gov/oncd/briefing-room/2024/02/26/press-release-technical-report/">press statement</a> calling for software developers to take measures to stop introducing so many security vulnerabilities.</p><blockquote><p>We, as a nation, have the ability &#8211; and the responsibility &#8211; to reduce the attack surface in cyberspace and prevent entire classes of security bugs from entering the digital ecosystem but that means we need to tackle the hard problem of moving to memory safe programming languages.</p><p>&#8212; National Cyber Director Harry Coker.</p></blockquote><p>I&#8217;m honestly surprised it took <em>this long</em> for government to start getting involved, considering how long memory safety issues and the resulting security vulnerabilities have made a mess of things.</p><blockquote><p>Some of the most infamous cyber events in history &#8211; the Morris worm of 1988, the Slammer worm of 2003, the Heartbleed vulnerability in 2014, the Trident exploit of 2016, the Blastpass exploit of 2023 &#8211; were headline-grabbing cyberattacks that caused real-world damage to the systems that society relies on every day. Underlying all of them is a common root cause: memory safety vulnerabilities.</p><p>&#8212; Anjana Rajan, Assistant National Cyber Director for Technology Security.</p></blockquote><p>The online discourse quickly devolved into &#8220;just rewrite it in Rust bro!&#8221;<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> which I find a bit unfortunate because it is a relevant report and the use of memory safe languages is just the main recommendation, other possibilities are given. I urge everyone to actually read the report and not just immediately jump to conclusions.</p><p>One of the most important statements in the report, I feel, is the following:</p><blockquote><p>However, even if every known vulnerability were to be fixed, the prevalence of undiscovered vulnerabilities across the software ecosystem would still present additional risk. A proactive approach that focuses on eliminating entire classes of vulnerabilities reduces the potential attack surface and results in more reliable code, less downtime, and more predictable systems.</p></blockquote><p>Extensive testing is not going to cut it, we need to build our software and hardware systems such that certain subsets of security vulnerabilities <em>simply cannot happen</em>.</p><p>But ultimately it is <strong>we</strong> (meaning software developers at large) that need to do this. It&#8217;s not better tools that will do it, it is <strong>we</strong>. The report suggests tools that can help us achieve this goal, but it is all for nothing if the tools are misused or ignored.</p><h2>It all comes down to convenience</h2><p>Humans are lazy creatures, we tend to take the path of least resistance. Most memory safe languages (including every language recommended in the report) have escape hatches that allow you to make as many memory unsafe horrors as you want. But that way of doing things is <em>less convenient</em> than the memory safe way of doing things in those languages, and as such is rarely an issue.</p><p>You can have &#128293; blazing &#128640; fast memory vulnerabilities in <a href="https://github.com/Speykious/cve-rs">100% safe rust</a> &#129408;.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a></p><p>Here, have a double free in C# without even using the unsafe keyword:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;cad449fa-f4b1-4379-a7ad-f031e5c5504b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">static void Main() {
    IntPtr ptr = Marshal.AllocHGlobal(4096);
    Marshal.FreeHGlobal(ptr);
    Marshal.FreeHGlobal(ptr);
}</code></pre></div><p>Nobody in their right mind would do this, <em>but they can</em>.</p><p>So it&#8217;s not so much the availability of memory unsafe features that is the problem, but how convenient they are compared to safe alternatives. The safe route can sometimes get painful in Rust due to the borrow checker, but Rust&#8217;s community has a very strong culture of avoiding unsafe code unless absolutely necessary, <a href="https://github.com/actix/actix-web/issues/289">going so far as pestering library developers about it on github</a>. </p><p>That culture is what keeps Rust developers from taking the path of least resistance and reaching for <code>unsafe</code><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> after losing a single &#8220;fight&#8221; to the borrow checker.</p><p>C and C++ are not &#8220;memory unsafe&#8221; languages, they&#8217;re languages that lack <em>built-in</em> memory safe ways of doing the same job in a more <em>convenient </em>manner.</p><p>There&#8217;s not really much of a reason for this to be the case, beyond not enough people pushing for it versus the number of people that not only don&#8217;t care about security but are actively hostile towards even small safety-related improvements.</p><h2>Do you even C bro?</h2><p>All the way back in the prehistoric times of 2009, Walter Bright, creator of the D programming language and the Digital Mars C and C++ compilers, wrote an excellent article titled <a href="https://digitalmars.com/articles/C-biggest-mistake.html">C&#8217;s Biggest Mistake</a>.</p><p>That mistake, he argues, was conflating arrays with pointers, or more specifically arrays decaying into pointers when passed to functions, losing their size information in the process. <a href="https://www.cvedetails.com/vulnerability-list/opov-1/vulnerabilities.html?q=Overflow+vulnerability">And oh boy was he right</a>.</p><p>Buffer Overflows are one of the most common exploits out there, and C and C++ are pretty much the only languages actually vulnerable to them in any serious capacity. C++ has taken steps to mitigate this problem with the introduction of <a href="https://en.cppreference.com/w/cpp/string/basic_string_view">std::string_view</a> (C++17) and <a href="https://en.cppreference.com/w/cpp/container/span">std::span</a> (C++20), but these should have really been part of C++11<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>. </p><p>Better late then never I suppose&#8230; Except that std::span&#8217;s index operator (<code>[]</code>) is unsafe, and its <code>.at()</code> method (which has been in std::vector and std::string since forever) is only coming in <a href="https://en.cppreference.com/w/cpp/container/span/at">C++26</a>. How did the entire committee forget the <code>.at()</code> method? It boggles the mind. So many talks about safety at cppcon and then they do this.</p><p>What about C then? Well, all you really need to protect against buffer overflows is something like the following:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;dc99ddea-4222-4642-9c66-e1978f38bfcd&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">typedef struct slice_i {
  int* data;
  size_t len;
} slice_i;

#define idx(s, i) (i &gt;= 0 &amp;&amp; i &lt; s.len ? s.data : (abort(), s.data))[i]</code></pre></div><p>Just copy paste slice_i around and change the _i and the type of the pointer as needed. Wouldn&#8217;t it be nice if you didn&#8217;t have to write these structs out by hand or use that hideous macro? If only a very smart gentlemen had suggested a minor syntactical addition to C all the way back in 2009 that could be used to make slices:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;5a0fe003-80c8-41b7-871d-cf831986de82&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">int a[..]</code></pre></div><p>Simple no? If you try to index one of these things you get bounds checking. If you need extra performance you can always just cast it to a pointer. So what did C devs think of Walter&#8217;s suggestion? Lets check out a reddit thread from <a href="https://www.reddit.com/r/C_Programming/comments/irljf1/cs_biggest_mistake/">2020</a>:</p><blockquote><p>I would have said overloading the 'break' keyword.</p><p>All other complaints about C are just, "Why do we need to breath oxygen?" It's just part of the landscape. </p><p>&#8212; <strong>which_spartacus</strong></p></blockquote><p>Ah yes, needing break in switch statements is definitely not just part of the landscape.</p><blockquote><p>... *rolls eyes*</p><p>Fat pointers are pointless. If you want a fat pointer.. *gasp* make a struct of an integer and a pointer! </p><p>&#8212; <strong>okovko</strong></p></blockquote><p>The point flew so hard over this person&#8217;s head that Walter got a home run.</p><blockquote><p>Another problem that can only be solved by writing good code.</p><p>&#8212; <strong>p0k3t0</strong></p></blockquote><p>It was so simple all along.</p><blockquote><p>have to disagree. Since C has no implicit bounds checking (for performance reasons), there's no point in having the compiler know the size of an array / pointer. If you, the programmer, need that information, you can just pass the length explicitly. </p><p>&#8212; <strong>BioHackedGamerGirl</strong></p></blockquote><p>If only there was a convenient way to choose between having bounds checks or no bounds checks depending on the performance requirements of a particular piece of code. Many Buffer Overflows happen on non-performance-critical codepaths.</p><blockquote><p>Next up ... Assembly Language's Biggest Mistake! </p><p>&#8212; <strong>nahnah2017</strong></p></blockquote><p>I think you get the point. 11 years after that article came out, this is the attitude. What do you think will happen if these developers are forced to use Rust? They&#8217;ll just use unsafe everywhere, completely defeating the point.</p><p>These people are why we&#8217;ve had like 5 attempts at a &#8220;strxcpy&#8221; function over the years instead of proper string handling functions in the standard library.</p><p>Even Linux kernel developers, whom you&#8217;d think would mainly consist of the absolute top tier of C developers, and would be expected to be highly security conscious, apparently <a href="https://lwn.net/Articles/948408/">don&#8217;t know how to implement a string buffer.</a><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a></p><h2>What about Use-After-Free?</h2><p>Safety and security are a spectrum, one where reaching 100% is sadly next to impossible. Preventing buffer overflows in C and C++ is almost trivial and would have avoided ~20000 known vulnerabilities.</p><p>Before worrying about trickier memory management concerns, don&#8217;t you think that should be priorities 1 through 20?<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a></p><p>Sadly, after that White House report, we&#8217;re already seeing discussions about adding substructural typing a la Rust to C. One of the silliest cases of <a href="https://en.wikipedia.org/wiki/Cart_before_the_horse">putting the cart before the horse</a> in recent years.</p><p>I&#8217;m not saying adding an owner pointer annotation to C wouldn&#8217;t be a good thing (I&#8217;d love that actually), I&#8217;m saying that&#8217;s not what you should be worrying about for now. If you can&#8217;t even add a slice type to the language how do you expect substructural typing to get accepted by the security unconscious jagoffs exemplified above?</p><p>There are also other effective ways of defending against Use-After-Free. Hardened memory allocators like <a href="https://ssrg-vt.github.io/SlimGuard/">SlimGuard</a> are a thing. They leave a lot of performance on the table to <em>also</em> protect against buffer overflows, which they wouldn&#8217;t need to do if buffer overflows weren&#8217;t such a major issue in the first place!</p><p>Would hardened allocators like SlimGuard be more popular if the performance penalty was reduced from no longer needing overflow protection? Perhaps.</p><h2>Safe Arenas and Pools</h2><p>Thing is, if you&#8217;re coding in C, you shouldn&#8217;t be mallocing and freeing that much in the first place. You should be using <a href="https://www.rfleury.com/p/untangling-lifetimes-the-arena-allocator">Arenas</a> to group multiple lifetimes together and freeing everything in one go. The less frees in your codebase, the less chances of a use-after-free. Is it 100% safe and applicable in all cases? No, but would you rather have <a href="https://www.cvedetails.com/vulnerability-list/opmemc-1/memory-corruption.html">~20000 vulnerabilities</a> or ~2000?</p><p>You can make Arenas nearly 100% safe by taking advantage of memory page protection features and only reusing old pages after exhausting the entire 64-bit address space. The chance of an attacker being able to exploit one of those page reuses is basically 0%.</p><p>On macOS, for example, you can do:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;bc9fb558-dcab-4454-a912-9ecfa258dcab&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">madvise(arena_ptr, size, MADV_FREE_REUSABLE);
mprotect(arena_ptr, size, PROT_NONE);</code></pre></div><p>On Linux you&#8217;d probably use <code>MADV_REMOVE</code> and on Windows you&#8217;d use:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;3383e233-3061-4e27-b6be-9c60716c38cb&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">VirtualFree(arena_ptr, size, MEM_DECOMMIT);</code></pre></div><p>Unlike with malloc the performance penalty of doing this on an arena is minuscule.</p><p>You can also use memory pools with <a href="https://floooh.github.io/2018/06/17/handles-vs-pointers.html">generational handles</a> if you need to allocate and free various objects of the same type with varying lifetimes in a 100% safe manner. Is it applicable in all cases? No, but would you rather have ~2000 vulnerabilities or ~200?</p><p>You see the point? Why are arenas and pools not part of the standard library if they would already help so much with the problem?</p><p>If you had slices, hardened arenas and generational pools, you could make C almost as &#8220;safe&#8221; as Rust<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a>  through linter rules forbidding various unsafe features like pointer arithmetic, &#8220;decayed&#8221; arrays and direct usage of malloc/free without first setting <code>#pragma unsafe</code> or whatever.</p><h2>Memory Safety != Security</h2><p>PHP is a memory safe language. For many years PHP-based websites were a security nightmare due to <a href="https://en.wikipedia.org/wiki/SQL_injection">SQL injection</a> vulnerabilities because developers were concatenating SQL strings with unsanitized user input.</p><p>No amount of language safety features can protect against that, what can is providing safe alternatives that ensure the SQL query is built in such a way that user input cannot possibly affect it. That&#8217;s what every language (<a href="https://www.php.net/manual/en/security.database.sql-injection.php">including PHP</a>) has these days.</p><p>Java, another memory safe language, was a security nightmare in the <a href="https://en.wikipedia.org/wiki/Java_applet#Security">Java Applet</a> days. Recently there was the <a href="https://en.wikipedia.org/wiki/Log4Shell">Log4Shell vulnerability</a>, which involved a combination of very bad design decisions resulting in (quoting wikipedia):</p><blockquote><p>The vulnerability's disclosure received strong reactions from cybersecurity experts. Cybersecurity company <a href="https://en.wikipedia.org/wiki/Tenable,_Inc.">Tenable</a> said the exploit was "the single biggest, most critical vulnerability ever,"<a href="https://en.wikipedia.org/wiki/Log4Shell#cite_note-:4-18"><sup>[18]</sup></a> <em><a href="https://en.wikipedia.org/wiki/Ars_Technica">Ars Technica</a></em> called it "arguably the most severe vulnerability ever"<a href="https://en.wikipedia.org/wiki/Log4Shell#cite_note-:1-19"><sup>[19]</sup></a> and <em><a href="https://en.wikipedia.org/wiki/The_Washington_Post">The Washington Post</a></em> said that descriptions by security professionals "border on the apocalyptic."<a href="https://en.wikipedia.org/wiki/Log4Shell#cite_note-:7-8"><sup>[8]</sup></a></p></blockquote><p>Nothing can protect against a logging framework connecting to remote servers based on a format string beyond a culture of everyone and their mother screaming their lungs out against the addition of such a feature.</p><p>That&#8217;s not to say memory safety isn&#8217;t important, it is. But lacking security is ultimately an <em>attitude</em> problem. If developers don&#8217;t care about it, forcing onto them a memory safe language will not accomplish anything. They&#8217;ll screw up some other way. </p><p>First make them care, then make it easy for them to do the right thing.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>It&#8217;s certainly not a bad idea to use Rust if it&#8217;s a good fit for your project, the issue is the rewrite part. Complete rewrites (specially those in a whole new language you lack experience with) aren&#8217;t cheap and can easily introduce new problems of their own.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>At the time of writing, pretty sure this will be patched in the compiler eventually. Still, you could just use unsafe to introduce all the vulnerabilities you could possibly want.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>As in, the keyword that allows dereferencing raw pointers in Rust. It&#8217;s the easiest way to dodge the borrow checker.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>C++ has long since had std::vector and std::string which cover some, but not all, use cases.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>If they know how to implement them, why are they using garbage unsafe functions to copy string bytes around? Every language with mutable strings implements them the same way: capacity + length + data. Seq_buf was close but has a pointless tracing field, just get rid of that. Why is there still ongoing discussion? This article is from October 26, 2023&#8230;</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>Apparently not, according to some reddit <a href="https://www.reddit.com/r/C_Programming/comments/90uq7c/comment/e2tjx1d/">users</a>.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>Using arenas and pools is a common approach in Rust to work around the tree-like structure imposed onto memory by the borrow checker. <br>I&#8217;m not suggesting anything crazy here.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Implementing Generators (yield) in C]]></title><description><![CDATA[The end result works better than you might expect.]]></description><link>https://btmc.substack.com/p/implementing-generators-yield-in</link><guid isPermaLink="false">https://btmc.substack.com/p/implementing-generators-yield-in</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Mon, 19 Feb 2024 17:43:15 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/013a3bdd-c6f3-4a48-aa89-df005e056322_3270x2180.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://en.wikipedia.org/wiki/Generator_(computer_programming)">Generators</a> are a subset of <a href="https://en.wikipedia.org/wiki/Coroutine#Generators">coroutines</a> that can be used to implement iterators, streams and state machines in a super convenient manner. I love them in <a href="https://wiki.python.org/moin/Generators">Python</a>, <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/yield">C#</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator">JavaScript</a>, where you&#8217;ll recognize them as functions that use the <code>yield</code> keyword instead of regular <code>return</code>.</p><p>While C does not natively support generators, we can implement them in a somewhat clever way by abusing a quirk of how the switch statement works<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>.</p><p>Why would you want to implement generators in C? I can think of a few reasons:</p><ul><li><p>You want to have a better understanding of how generators (or coroutines in general) work.</p></li><li><p>You use C as a compilation target for your programming language and want to add support for yield.</p></li><li><p>They can be really convenient!</p></li></ul><h2>Fibonacci Numbers</h2><p>As a running example we&#8217;ll be turning a function that prints the fibonacci numbers into a generator function that yields the fibonacci numbers on demand. Here&#8217;s our starting point:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;ba649eee-2f79-4bb0-b3c9-e83bf2a80d0b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">void fib(void) {
  int a = 0;
  int b = 1;
  int n = 1;
  while (1) {
    printf("%d\n", a);
    a = b;
    b = n;
    n = a + b;
  }
}</code></pre></div><p>This is a simple iterative fibonacci implementation that prints out an &#8220;infinite&#8221;<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> stream of the fibonacci numbers. Hopefully nothing too surprising.</p><p>Instead of having fib print the numbers out directly (or store them in a list or whatever else), we want it to lazily produce the next number in the sequence on demand, with the caller then doing whatever they want with it.</p><p>To achieve this we&#8217;ll turn fib into a coroutine, meaning a function that can be suspended and resumed<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>.</p><h2>Stack Frames</h2><p>First thing we need to do is store fib&#8217;s local variables outside of it. We can&#8217;t have fib&#8217;s local variables in registers or the regular C stack because when we suspend it we&#8217;re returning back to the caller, which will trample all over those registers and the stack before we get to resume fib.</p><p>The way to do this in C is to manually put all those local variables into a separate struct, reifying fib&#8217;s stack frame, and storing it elsewhere<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;debd9cfc-724a-429c-8515-6e1dda0788d5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">typedef struct {
  int a;
  int b;
  int n;
} fib_frame;

void fib(fib_frame* f) {
  f-&gt;a = 0;
  f-&gt;b = 1;
  f-&gt;n = 1;
  while (1) {
    printf("%d\n", f-&gt;a);
    f-&gt;a = f-&gt;b;
    f-&gt;b = f-&gt;n;
    f-&gt;n = f-&gt;a + f-&gt;b;
  }
}

// usage
fib_frame f = {};
fib(&amp;f);</code></pre></div><p>All we&#8217;ve managed to do so far is make fib less efficient and more annoying to call, but now we have fib&#8217;s stack frame stored outside of the machine&#8217;s stack, which we will need to implement suspension.</p><h2>Suspending a Function</h2><p>In order to suspend fib, we need to store some additional information in its stack frame: the equivalent of the <a href="https://en.wikipedia.org/wiki/Program_counter">program counter</a>. We need to know in which &#8220;instruction&#8221; fib stopped at after the yield:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;0809dc10-2807-4b83-8ca0-8fd19835cc1b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">typedef struct {
  // program counter (sort of)
  int _pc;
  int a;
  int b;
  int n;
} fib_frame;</code></pre></div><p>We&#8217;ll also change fib so that instead of printing numbers it returns them:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;3116eae7-e0e7-47ef-81bc-d1124c78c3be&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">int fib(fib_frame *f) {
  f-&gt;a = 0;
  f-&gt;b = 1;
  f-&gt;n = 1;
  while (1) {
    return f-&gt;a;
    // we want to resume here somehow
    f-&gt;a = f-&gt;b;
    f-&gt;b = f-&gt;n;
    f-&gt;n = f-&gt;a + f-&gt;b;
  }
  return -1; // we'll never get here
}</code></pre></div><p>We need to store the address of the instruction after the return, and jump directly there the next time we call fib. If you&#8217;re thinking <code>goto</code>, you&#8217;re on the right track. For example, we can do the following:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;11b3ecb6-ba73-40ac-8f5b-bb805b18b5ca&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">int fib(fib_frame *f) {
  if (f-&gt;_pc == 1) { goto resume_1; }
  f-&gt;a = 0;
  f-&gt;b = 1;
  f-&gt;n = 1;
  while (1) {
    // store the resumption point before returning
    f-&gt;_pc = 1;
    return f-&gt;a;
  resume_1:
    f-&gt;a = f-&gt;b;
    f-&gt;b = f-&gt;n;
    f-&gt;n = f-&gt;a + f-&gt;b;
  }
  return -1; // we'll never get here
}</code></pre></div><p>And then we can use fib as follows:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;42b24497-8904-44d3-91d7-4dc5d5675e05&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">int main() {
  fib_frame f = {};
  for (int i = 0; i &lt; 10; i++) {
    printf("%d\n", fib(&amp;f));
  }
  return 0;
}</code></pre></div><p>The fib function just keeps producing the next number in the sequence each time we call it, so if we call it 10 times, we get the first 10 fibonacci numbers.</p><p>Labels in C aren&#8217;t first class, so there is no way to store <code>resume_1</code> directly in <code>_pc</code>, and goto in C cannot be used to jump to a numerical address<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>, hence the conditional at the start translating the number 1 to the appropriate jump.</p><p>If we had more yield points, the same pattern would repeat:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;e01ace6b-86b1-43a2-b803-74d4b5fa88ca&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">if (f-&gt;_pc == X) { goto resume_X; }
if (f-&gt;_pc == Y) { goto resume_Y; }
...
f-&gt;_pc = X; return something; resume_X:  // yield
...
f-&gt;_pc = Y; return whatever;  resume_Y:  // yield</code></pre></div><p>Those ifs could be a switch statement instead:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;3f2b6f4b-1394-4063-a68c-6a6d3252c124&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">switch (f-&gt;_pc) { 
  case X: goto resume_X;
  case Y: goto resume_Y;
}</code></pre></div><p>And those yields are asking for a macro:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;5a78aa5b-353f-49c9-b4e3-591a869f9a1a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#define yield(PC, X, R) PC = X; return R; resume_##X:

// usage
while (1) {
  yield(f-&gt;_pc, 1, f-&gt;a);
  ...</code></pre></div><p>If we standardize on the name of the frame parameter (f) and its program counter field (_pc), we can remove the first argument:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;98415e9a-3a5f-4c44-b3ef-c137615374b4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#define yield(X, R) f-&gt;_pc = X; return R; resume_##X:</code></pre></div><p>Much better, here&#8217;s the full example now:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;a976ff1a-43bd-47cd-9418-0bf9baefc6f7&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">int fib(fib_frame *f) {
  switch (f-&gt;_pc) { 
    case 1: goto resume_1;
  }
  f-&gt;a = 0;
  f-&gt;b = 1;
  f-&gt;n = 1;
  while (1) {
    yield(1, f-&gt;a);
    f-&gt;a = f-&gt;b;
    f-&gt;b = f-&gt;n;
    f-&gt;n = f-&gt;a + f-&gt;b;
  }
  return -1; // we'll never get here
}</code></pre></div><p>Still, needing to spell out the jumps at the start of the function and explicitly numbering the yield points is a bit annoying. Good enough for a compiler backend, but annoying for direct usage in C, the explicit stack frame is bad enough&#8230; However, we can do better by taking advantage of a feature that&#8217;s usually considered a wart in C.</p><h2>Switch Fall-Through</h2><p>Most languages, including newer members of the C-family, have their equivalent of a switch statement work like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;e6500534-a96a-4ccf-be51-5180db66bd2f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">switch (x) {
  case 1 {
    // do this for case 1
  }
  case 2 {
    // do this for case 2
  }
  case 3 {
    // do this for case 3
  }
}</code></pre></div><p>C&#8217;s switch statement doesn&#8217;t work like this, instead it works like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;8f723dd2-676b-4489-be1c-989516360884&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">switch (x) {
  case 1:
    // do this for case 1
  case 2:
    // do this also for case 1
    // do this for case 2
  case 3:
    // do this also for case 1
    // do this also for case 2
    // do this for case 3
}</code></pre></div><p>This is called <a href="https://en.wikipedia.org/wiki/Switch_statement#Fallthrough">fall-through</a> and is a common beginner trap and source of bugs for sleep deprived programmers that forget to put a <code>break</code> at the end of each case. C# retains the same syntax but always requires an explicit break to avoid this issue.</p><p>Basically, those <code>cases</code> work like our <code>resume_X</code> label from before. You can even put a <code>case</code> inside a block from another <code>case</code> and C won&#8217;t bat an eye:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;a0f33646-f16d-4317-bd84-7f7bef374ba8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">// this works, what the hell C!
switch (x) {
  case 1: while (1) {
    case 2: if (0) {
      case 3: 
        ..
    }
  }
}</code></pre></div><p>Nasty, but we can take advantage of this. Instead of the switch at the start of the function mapping a number to a goto statement, we can use it to jump directly:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;e5144640-babf-4662-80d9-5f93515fa93b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">int fib(fib_frame *f) {
  switch (f-&gt;_pc) { 
  case 0:
    f-&gt;a = 0;
    f-&gt;b = 1;
    f-&gt;n = 1;
    while (1) {
      f-&gt;_pc = 1;
      return f-&gt;a;
    case 1:
      f-&gt;a = f-&gt;b;
      f-&gt;b = f-&gt;n;
      f-&gt;n = f-&gt;a + f-&gt;b;
    }
  }
  return -1; // we'll never get here
}</code></pre></div><p>Might seem like a small change, but the start of the function is now always the same, regardless of the number of yields:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;7e9679dc-6e39-40ae-8011-b0cb6dd9592c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">switch (f-&gt;_pc) { 
  case 0:</code></pre></div><p>This not only reduces the amount of boilerplate, but it also enables us to handle the numbering of the yield points automatically<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>. Add another pair of convenience macros and we&#8217;re done<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;db8c1030-b03a-4b34-8ad2-68984fd31154&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#define generator_init() switch (f-&gt;_pc) { case 0:
#define generator_done(R) } return R
#define yield(R) f-&gt;_pc = __LINE__; return R; case __LINE__:

typedef struct {
  int _pc;
  int a;
  int b;
  int n;
} fib_frame;

int fib(fib_frame *f) {
  generator_init();
  f-&gt;a = 0;
  f-&gt;b = 1;
  f-&gt;n = 1;
  while (1) {
    yield(f-&gt;a);
    f-&gt;a = f-&gt;b;
    f-&gt;b = f-&gt;n;
    f-&gt;n = f-&gt;a + f-&gt;b;
  }
  generator_done(-1);
}</code></pre></div><p>This blog post could stop here but sadly I have no self control.</p><h2>Bonus: Iterators</h2><p>The above is a perfectly serviceable generator function but it doesn&#8217;t work quite the same way as generator functions in Python or C# do. If our generator function has any input arguments, we need to initialize them directly in the frame structure, which is a bit unusual to say the least. Easy fix, split the function in two, and lets rename frame to iterator while we&#8217;re at it:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;545860d1-e6aa-47f1-8135-afeaa2d9a19e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">typedef struct {
  // same fields as before, new name
} fib_iterator;

int fib_next(fib_iterator *f) {
  // same as our old fib
}

fib_iterator fib(void) {
  // no arguments so zero initialized
  // should have used a different example...
  return (fib_iterator){};
}

// no need to initialize the struct manually anymore
fib_iterator f = fib();
for(int i = 0; i &lt; 10; i++) {
  printf("%d\n", fib_next(&amp;f));
}</code></pre></div><p>Now it looks more familiar, but it&#8217;s the same thing really.</p><h2>Bonus: Foreach</h2><p>Other languages also have foreach loops and various ways of combining iterators. Right now we don&#8217;t have a standardized way for an iterator to indicate that it has stopped, and our running example (fib) never terminates, so we need a new one:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;900d1f96-977f-4c40-847a-c8e3cf32b316&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#define generator_init() switch (i-&gt;_pc) { case 0:
#define generator_done() } return false
#define yield(R) i-&gt;_pc = __LINE__; *r = R; return true; case __LINE__:

typedef struct {
  int *array;
  int len;
  int idx;
  int _pc;
} rev_iterator;

rev_iterator rev(int *array, int len) {
  return (rev_iterator){array, len};
}

bool rev_next(rev_iterator *i, int *r) {
  generator_init();
  for (i-&gt;idx = i-&gt;len-1; i-&gt;idx &gt;= 0; i-&gt;idx--) {
    yield(i-&gt;array[i-&gt;idx]);
  }
  generator_done();
}</code></pre></div><p>The new example is a simple reverse iterator, yielding the elements of an array starting from the end and going backwards.</p><p>The next function now returns a boolean indicating if the iterator produced a value, while storing the actual output in an out parameter. We also adjust the macros accordingly. Now we can make a &#8220;foreach&#8221; loop:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;f0068c83-76e7-4f9e-bcff-eb2c02ec48a0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">rev_iterator i = rev((int[]){1, 2, 3}, 3);
for(int v; rev_next(&amp;i, &amp;v);) {
  printf("%d\n", v);
}</code></pre></div><h2>Bonus: Itertools</h2><p>Ok ok, I swear this is the last one. Other languages let us combine iterators with fancy generic functions like zip, take_while, etc. We can also do those in C&#8230; somewhat.</p><p>First change is that we need to have a shared interface for our iterators:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;d5e5d7c0-62e1-4c77-843c-79a7eb45faf0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">typedef struct iterator iterator;
struct iterator {
  int _pc;
  bool (*next)(iterator*, void*);
};

typedef struct {
  iterator base;
  int *array;
  int len;
  int idx;
} rev_iterator;</code></pre></div><p>Yup, we&#8217;re making objects.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!JJrJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb04b3546-b9cb-45d3-868c-ec14a6c455b2_888x499.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!JJrJ!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb04b3546-b9cb-45d3-868c-ec14a6c455b2_888x499.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!JJrJ!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb04b3546-b9cb-45d3-868c-ec14a6c455b2_888x499.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!JJrJ!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb04b3546-b9cb-45d3-868c-ec14a6c455b2_888x499.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!JJrJ!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb04b3546-b9cb-45d3-868c-ec14a6c455b2_888x499.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!JJrJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb04b3546-b9cb-45d3-868c-ec14a6c455b2_888x499.jpeg" width="888" height="499" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b04b3546-b9cb-45d3-868c-ec14a6c455b2_888x499.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:499,&quot;width&quot;:888,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:89324,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!JJrJ!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb04b3546-b9cb-45d3-868c-ec14a6c455b2_888x499.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!JJrJ!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb04b3546-b9cb-45d3-868c-ec14a6c455b2_888x499.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!JJrJ!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb04b3546-b9cb-45d3-868c-ec14a6c455b2_888x499.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!JJrJ!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb04b3546-b9cb-45d3-868c-ec14a6c455b2_888x499.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We also need to update the constructor function<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-8" href="#footnote-8" target="_self">8</a> and the macros. The rev_next function from before stays the same:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;b1e8209c-325f-45d4-a702-99222d6b76e6&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#define generator_init() switch (i-&gt;base._pc) { case 0:
#define generator_done() } return 0
#define yield(R) i-&gt;base._pc = __LINE__; *r = R; return 1; case __LINE__:

rev_iterator rev(int *array, int len) {
  return (rev_iterator){
    .base.next = (bool (*)(iterator*, void*))rev_next,
    .array = array, 
    .len = len,
  };
}</code></pre></div><p>This shared interface allows us to implement a yield_from macro:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;90f5e70b-1cdf-403e-9711-9c70e75f6321&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#define iter_next(I, R) ((iterator*)(I))-&gt;next((iterator*)(I), (R))
#define yield_from(I) while(iter_next((I), r)) \ 
  { i-&gt;base._pc = __LINE__; return 1; case __LINE__:; }</code></pre></div><p>And with it, we can implement a sequence iterator:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;de975dde-7b3d-416c-b854-1a2e6e540d86&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">typedef struct {
  iterator base;
  iterator* fst;
  iterator* snd;
} seq_iterator;

bool seq_next(seq_iterator* i, void* r) {
  generator_init();
  yield_from(i-&gt;fst); 
  yield_from(i-&gt;snd);
  generator_done();
}

seq_iterator seq(iterator* fst, iterator* snd) {
  return (seq_iterator) {
    .base.next = (bool (*)(iterator*, void*))seq_next,
    .fst = fst,
    .snd = snd,
  }
}</code></pre></div><p>Now we can pretend we&#8217;re a modern programming language with streams:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;8e909ba6-cf05-43a2-9860-48f2959e9f86&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">rev_iterator r1 = rev(int[]{1, 2, 3}, 3);
rev_iterator r2 = rev(int[]{4, 5, 6}, 3);
seq_iterator s = seq((iterator*)&amp;r2, (iterator*)&amp;r1);
for(int v; seq_next(&amp;s, &amp;v)) {
  printf("%d\n", v);
}</code></pre></div><p>If we change the generator constructor functions to heap allocate and return opaque pointers<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-9" href="#footnote-9" target="_self">9</a>, we&#8217;ve reached parity with all these new language whippersnappers:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;53b4351c-4462-48fb-8b00-3c960e287366&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">iterator* r1 = rev(int[]{1, 2, 3}, 3);
iterator* r2 = rev(int[]{4, 5, 6}, 3);
iterator* s = seq(r2, r1);
for(int v; iter_next(s, &amp;v)) {
  printf("%d\n", v);
}</code></pre></div><p>But in all seriousness this is getting out of hand &#128556;.</p><h1>Conclusion</h1><p>Generators in C with only mild macro abuse. This was quite a fun post to write. I don&#8217;t think I&#8217;d bother with generators in C most of the time (certainly not with fancy combinators), but it is nice the capability is there if I need it. </p><p>I&#8217;ll definitely use this for my toy language backend though, generators are awesome.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>The same quirk used in <a href="https://en.wikipedia.org/wiki/Duff%27s_device#Mechanism">Duff&#8217;s device</a>.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>There will eventually be an overflow and the function will start printing out garbage.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>There are two main kinds of coroutine implementations: <a href="https://en.wikipedia.org/wiki/Coroutine#Definition_and_Types">stackless and stackful</a>. I won&#8217;t go into the distinction here because it gets confusing and would probably deserve an own post, but for generators stackless works well.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>In Python, C# and JavaScript the local variables are stored as private fields of the iterator object returned by the function.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>Storing the address of labels and jumping to them, known as a <a href="https://en.wikipedia.org/wiki/Goto#Computed_GOTO_and_Assigned_GOTO">computed goto</a>, is available as a GCC extension but it is not part of the C standard.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>Only one yield per line is a small price to pay.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>The two extra macros are really not necessary. I wrote them just in case you find the &#8220;switch (x) { case 0:&#8221; unbearably ugly for some reason.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-8" href="#footnote-anchor-8" class="footnote-number" contenteditable="false" target="_self">8</a><div class="footnote-content"><p>If that function pointer cast makes you feel icky, add an additional wrapper function instead.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-9" href="#footnote-anchor-9" class="footnote-number" contenteditable="false" target="_self">9</a><div class="footnote-content"><pre><code>iterator* seq(iterator* fst, iterator* snd) {
  seq_iterator* i = malloc(sizeof(seq_iterator));
  *i = (seq_iterator){
    .base.next = (bool (*)(iterator*, void*))seq_next,
    .fst = fst,
    .snd = snd,
  };
  return (iterator*)i;
}</code></pre></div></div>]]></content:encoded></item><item><title><![CDATA[Faults, Errors and Failures]]></title><description><![CDATA[You can't handle errors directly. It's complicated.]]></description><link>https://btmc.substack.com/p/you-cant-handle-errors</link><guid isPermaLink="false">https://btmc.substack.com/p/you-cant-handle-errors</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Tue, 13 Feb 2024 23:47:28 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a232afc0-3d1d-406f-940e-f58c958a52b5_640x400.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Having somewhat recently become a father (one of the reasons my writing has slowed down to a crawl), I&#8217;ve had to deal with a very particular kind of error output: <em>crying</em>.</p><p>From a computational perspective, it&#8217;s about equivalent to the following:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!eBhP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F315b15c8-6cce-4223-89d3-144029f78235_246x209.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!eBhP!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F315b15c8-6cce-4223-89d3-144029f78235_246x209.png 424w, /__u/substackcdn.com/image/fetch/$s_!eBhP!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F315b15c8-6cce-4223-89d3-144029f78235_246x209.png 848w, /__u/substackcdn.com/image/fetch/$s_!eBhP!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F315b15c8-6cce-4223-89d3-144029f78235_246x209.png 1272w, /__u/substackcdn.com/image/fetch/$s_!eBhP!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F315b15c8-6cce-4223-89d3-144029f78235_246x209.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!eBhP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F315b15c8-6cce-4223-89d3-144029f78235_246x209.png" width="246" height="209" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/315b15c8-6cce-4223-89d3-144029f78235_246x209.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:209,&quot;width&quot;:246,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:5454,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!eBhP!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F315b15c8-6cce-4223-89d3-144029f78235_246x209.png 424w, /__u/substackcdn.com/image/fetch/$s_!eBhP!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F315b15c8-6cce-4223-89d3-144029f78235_246x209.png 848w, /__u/substackcdn.com/image/fetch/$s_!eBhP!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F315b15c8-6cce-4223-89d3-144029f78235_246x209.png 1272w, /__u/substackcdn.com/image/fetch/$s_!eBhP!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F315b15c8-6cce-4223-89d3-144029f78235_246x209.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>Loud and unspecific. Well, at least with a baby there&#8217;s a limited set of reasons for crying, so fixing the issue amounts to tackling each possible cause until something works. Good luck debugging the error message above though.</p><p>That got me thinking, how does one end up with a useless error message like the above? What facilities should programming languages provide to make it easy for developers to handle errors properly and conveniently?</p><p>Originally this post was going to be a survey on error handling approaches, similar to my post on memory management approaches, but I decided against it in the end because I think looking at this problem from &#8220;outside the box&#8221; is important. Maybe someone can come up with a better way to &#8220;handle errors&#8221;.</p><h2>Faults, Errors and Failures.</h2><p>Lets starts from the beginning. What do we mean by <em>error</em>? Unfortunately the term &#8220;error&#8221; is not used consistently in the literature, but we can use 3 related, commonly used terms from the study of fault tolerance to distinguish different meanings for the word error<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a><strong>:</strong></p><ul><li><p>A <strong>fault</strong> (known colloquially as a <em>bug</em>), is static defect in software, meaning there&#8217;s some line (or lines) of code that are incorrect and will likely result in&#8230;</p></li><li><p>An <strong>error</strong>, which is an <em>unobserved</em>, incorrect internal state that will likely result in&#8230;</p></li><li><p>A <strong>failure</strong>, which is <em>observed</em>, incorrect behavior with respect to the software&#8217;s expected behavior.</p></li></ul><p>For example, consider the following piece of C# code:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;5c15143c-df7b-4811-a76c-05bddf1012dc&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">var a = new int[]{1,2,3,4,5};
for (int i = 0; i &lt;= 5; i++) {
  Console.WriteLine(a[i]);
}</code></pre></div><p>There&#8217;s a <em>fault</em> in the loop stop condition (it should be <code>i &lt; 5</code>), which will result in an <em>error</em> state of <code>i = 5</code>, which will trigger a <em>failure</em> when used to index the vector (throwing an IndexOutOfRange exception).</p><p><strong>The error is not the exception (failure) nor is it the incorrect loop condition (fault).</strong> </p><p>It is critical to understand this distinction for the rest of the article to make sense. Before tackling error <em>handling</em>, I&#8217;ll need to talk about error <em>prevention</em>, as the two often get mixed up, and it&#8217;ll help clarify what I mean.</p><h2>Error (Fault) Prevention</h2><p>Testing, static type systems, model checking, sanitizers, fuzzers, and even certain language features like <code>foreach</code> loops (which would trivially avoid the problem above) are ways to indirectly <em>prevent</em> errors by either detecting or preventing <em>faults</em>.</p><p>I&#8217;m 100% pro all of the above, they&#8217;re all great. If you&#8217;re not using them and you could, then you should. Your users will thank you. Your colleagues will thank you. Your future self will thank you.</p><p>Errors are almost always the result of faults. Barring cosmic rays, hardware issues or really unusual race conditions between the application and the operating system, if an error occurs it is because <em>the programmer screwed up</em> and introduced a bug.</p><p>Some languages like Rust and Haskell have a reputation that &#8220;if the code compiles, it works&#8221;. They get this reputation because they excel at preventing common faults through a combination of a powerful static type system plus a culture of modeling function domains and codomains as accurately as possible.</p><p>Consider the following function declaration (in Rust syntax):</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;eb397af4-a8a2-4422-8c16-30db47495108&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">fn head(v: Vec&lt;i64&gt;) -&gt; i64</code></pre></div><p>The head function takes as input a vector<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> of signed 64-bit integers and returns the first integer in that vector.</p><p>This function&#8217;s domain is the set of all vectors of signed 64-bit integers and its codomain is the set of signed 64-bit integers. But this function declaration is &#8220;lying&#8221;, either about its domain, or about its codomain, depending on the point of view.</p><p>There is a hidden pre-condition that <code>v</code> is not empty. If <code>v</code> is empty, the function will panic<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>.</p><p>A &#8220;truthful&#8221; head function would have a different domain or codomain:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;b9a9216f-e68b-4ef7-a80a-9cf726da69c2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">fn head(v: NonEmptyVec&lt;i64&gt;) -&gt; i64
fn head(v: Vec&lt;i64&gt;) -&gt; Option&lt;i64&gt;</code></pre></div><p>In the first case, we tighten the domain, in the second case we loosen the codomain. Either way, we&#8217;ve made the function less likely to result in faults &#8212; by reminding the programmer of special cases they must take into account &#8212; at the cost of making it more annoying to use.</p><p>In the first case the caller must prove they have a NonEmptyVec by calling some explicit conversion function, while in the latter case the caller must always handle the &#8220;None&#8221; case even if they know for a fact that the vector is not empty.</p><p>If multiple properties are desired at the same time (e.g., expecting a non-empty even-length vector) the &#8220;truthful domain&#8221; approach quickly collapses without access to much more powerful type system features like <a href="https://en.wikipedia.org/wiki/Dependent_type">dependent types</a> or <a href="https://en.wikipedia.org/wiki/Refinement_type">refinement types</a><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>, which in turn add a massive amount of complexity to the language and are, IMO, not worth it<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>.</p><p>The codomain can always be loosened with a single additional &#8220;invalid input&#8221; case, and various language facilities can be added to conveniently deal with this extra case, making it the better solution most of the time. For example, in C# nullable types have a great deal of features to make them as convenient to use as possible:</p><ul><li><p>flow typing, which propagates the result of null checks (i.e. an <code>int?</code> variable becomes <code>int</code> for as long as the result of the check is valid)</p></li><li><p>null-coalescing (<code>??</code> and <code>??=</code>) operators that make it convenient to replace a null value with an alternative value from the non-nullable type.</p></li><li><p>null-conditional (<code>?.</code> and <code>?[]</code>) operators that allow &#8220;<a href="https://en.wikipedia.org/wiki/Monad_(functional_programming)#Definition">flat-mapping</a>&#8221; operations from the non-nullable type into the nullable type.</p></li></ul><p>The idea is to keep the advantage of truthful codomains (reminding the programmer to handle special cases) while mitigating the disadvantages (inconvenience).</p><h2>Error (Failure) Handling</h2><p>Even with as many fault prevention measures in place as possible, errors will always happen. Fault prevention amounts to ensuring known pre-and-post-conditions are properly handled, it can&#8217;t help with unexpected logic errors or lacking requirements.</p><p>Unfortunately once an error occurs, it can&#8217;t be handled directly. Remember, an error is an <em>unobserved</em> incorrect state. The moment you <em>observe</em> an error, it has already turned into a <em>failure</em>.</p><p>Consider for example a set of additions and subtractions applied to a variable where intermediate computations result in overflow but the final result does not. The intermediate overflowed values are <em>errors</em>, but there is no resulting <em>failure</em>. </p><p>If you actually checked for overflow each operation, you&#8217;d detect the error, triggering a <em>failure</em> and causing a panic or equivalent. And if, instead, you checked all the inputs to make sure they wouldn&#8217;t ever overflow, you&#8217;d be preventing a <em>fault</em>. Conflating faults and failures, I believe, has led to some really shitty language features that cause more faults and failures than they solve (e.g., <strong>exceptions</strong>, more on those in a bit)</p><p>So errors cannot be handled, only failures can, and a failure is an observed incorrect behavior of the program. If the program is behaving incorrectly, what can you do about it?</p><p>First, how does the program know it is behaving incorrectly? If the program can know it is behaving incorrectly, couldn&#8217;t the fault be prevented in the first place? <em><strong>Yes</strong></em>. But for various reasons the cost of doing so may be too high.</p><p>An example would be needing to check after every arithmetic operation for overflow. I don&#8217;t mean the compiler inserting checks and triggering a failure, I mean the programmer explicitly checking for overflow after every arithmetic operation and handling that &#8220;special case&#8221; each and every time. Extremely annoying.</p><p>Alternatively, the overflow-related faults could be prevented by using <a href="https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic">arbitrary-precision arithmetic</a>, which would instead have a computational cost and may result in a different kind of failure (out-of-memory).</p><p>Compiler (or programmer) inserted checks are a means of computationally observing failures caused by unlikely (but otherwise expected) errors, in turn caused by faults that would be excessively costly to prevent. The most common example is array bounds checking.</p><p>Note that these checks are <strong>not</strong> failure handling, they are a necessary step to detect the failure but actually handling it comes afterwards.</p><h2>Exceptions Suck</h2><p>The most common response to observing a failure is to throw an exception. Exceptions unwind the stack until they hit a programmer-specified handler (i.e., a <code>try/catch</code> block) or a default handler that crashes the program and usually prints out a <a href="https://en.wikipedia.org/wiki/Stack_trace">stack trace</a> to help debug the problem.</p><p>The second most common approach is to abort the program, by sending it a signal that more often than not is caught by a default signal-handler, which will terminate the program and output a &#8220;useful&#8221; message like <a href="https://en.wikipedia.org/wiki/Segmentation_fault">Segmentation Fault</a> (which is really a type of failure as defined above, don&#8217;t you hate inconsistent naming conventions?)</p><p>Rust panics can be set to use an exception-like mechanism, to terminate the current thread, or to abort the program (sending it a signal as above).</p><p>You&#8217;ll notice that all of the possibilities just crash the program by default. Before I discuss why, I need to point out that the following is <em>not</em> failure handling:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;8ccf7807-3f2a-4e67-8e4e-ee0f7d2a5598&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">try {
  File f = new File("filename.txt");
  ...
}
catch (FileNotFoundException e) {
  ...
}</code></pre></div><p>In my opinion this is just a weird looking conditional for one particular &#8220;output value&#8221; of the File constructor. The &#8220;truthful&#8221; codomain of the File constructor includes additional cases that are &#8220;returned&#8221; as exceptions.</p><p>Would you ever write code like the above to handle an array out of bounds situation? Right after you tried to index an array? What about division by zero? No, right<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>? </p><p>Forgetting to handle a missing file is a fault, and catching that &#8220;output&#8221; is <em>not having that fault</em>. Using exceptions instead of properly modeling the function&#8217;s codomain has increased the likelihood of a fault and its corresponding failure by not reminding the programmer to handle a common failure case.</p><p>I really dislike exceptions for mixing up these unrelated concerns<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a>.</p><p>If you allow encoding different &#8220;error types&#8221; into your failure handling feature you&#8217;ve probably already screwed up. Let me explain.</p><h2>How to handle Errors (i.e., Failures)</h2><p>Let me put it nice and clear:</p><div class="pullquote"><p>Handling a failure means returning the program to a known, correct state.</p></div><p>Remember the pipeline: Fault &#8594; Error &#8594; Failure. A fault is a bug in the program&#8217;s source code which causes the program to enter an incorrect unobserved state, which can then lead to a failure, which is observed incorrect behavior.</p><p>The job of a failure handler is to <em>get rid of the error</em>. Note that it&#8217;s not handling the error, what is being handled is the failure, the error that caused it is unknown. But the goal is, nonetheless, to get rid of the error, somehow.</p><p>Consider an IndexOutOfBounds exception. If one happens, it&#8217;s because there is a bug in the program that resulted in some variable being set to an incorrect value, which then resulted in the observed incorrect behavior of trying to index an array out of bounds. What should the failure handler do?</p><p>First, a true failure handler won&#8217;t be anywhere near the actual index out of bounds situation, because if it was, you were just handling one of the possible &#8220;return values&#8221; of the indexing operation (preventing a fault), not actually handling a failure.</p><p>In a language with <a href="https://en.wikipedia.org/wiki/Effect_system">algebraic effect handlers</a>, the handler for the index out of bounds situation could &#8220;fix&#8221; the failure by resuming the program with a made up value for that index. Terrible idea, essentially replacing a failure with a new error. With exceptions you can&#8217;t even do that.</p><p>No, a true failure handler is a piece of code that a programmer hopes never actually has to run! It&#8217;s the last line of defense. All that the failure handler knows is that some variable (no idea which) got set to a wrong value at some point (no idea where) that was then used to wrongly index an array (knows where, but can&#8217;t do anything about it).</p><p>Given the above, IndexOutOfBounds might as well have been PoopExplosion9000<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-8" href="#footnote-8" target="_self">8</a> as far as the failure handler is concerned. The information is useful for the programmer, as is the stack trace, but it could just as well have been a text string in the assertion error message. The actual type of the exception is utterly useless for the purpose of failure handling (not for ghetto codomains, but you shouldn&#8217;t use exceptions for that in the first place).</p><p>While you can only handle failures (because only the failure is observed) getting rid of the failure by itself doesn&#8217;t do much good, there&#8217;s still the unobserved invalid state that led to it in the first place. But you can&#8217;t do anything about that invalid state directly, since you have no idea what it is or where it originated from. </p><p>This is why the default handler for any sort of panic mechanism (exceptions, signals, etc.) is a full-on crash that spits out as much information as it can for the programmer to debug with. There&#8217;s nothing else it can do.</p><p>The only thing you can do is turn it off and on again.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!r57f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fb61bc-562b-4212-b5eb-19182a345144_1280x719.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!r57f!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fb61bc-562b-4212-b5eb-19182a345144_1280x719.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!r57f!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fb61bc-562b-4212-b5eb-19182a345144_1280x719.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!r57f!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fb61bc-562b-4212-b5eb-19182a345144_1280x719.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!r57f!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fb61bc-562b-4212-b5eb-19182a345144_1280x719.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!r57f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fb61bc-562b-4212-b5eb-19182a345144_1280x719.jpeg" width="1280" height="719" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/16fb61bc-562b-4212-b5eb-19182a345144_1280x719.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:719,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:156942,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!r57f!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fb61bc-562b-4212-b5eb-19182a345144_1280x719.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!r57f!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fb61bc-562b-4212-b5eb-19182a345144_1280x719.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!r57f!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fb61bc-562b-4212-b5eb-19182a345144_1280x719.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!r57f!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fb61bc-562b-4212-b5eb-19182a345144_1280x719.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Every failure handler that&#8217;s any better than the default is ultimately just decreasing the scope of what&#8217;s getting restarted or improving the usefulness of the info-dump. </p><h2>Restarting for the Greater Good</h2><p>The choice of panic mechanism (exceptions, signals, terminating threads) essentially dictates the scope of what can be restarted. If restarting wasn&#8217;t the goal, then there would be no need for any mechanism beyond terminating the program outright.</p><p>Exceptions let you restart at an arbitrary point in a function. This is less useful than it sounds because the error state may have occurred outside the handler&#8217;s restart point. You can only safely restart pure functions or those that work like <a href="https://en.wikipedia.org/wiki/Transaction_processing">transactions</a>.</p><p>Restarting threads (particularly worker threads) is one of the best approaches if the threads don&#8217;t share mutable state. Erlang (and by extension Elixir) is built entirely around this idea, using <a href="https://erlang.org/documentation/doc-4.9.1/doc/design_principles/sup_princ.html">supervision trees</a>. Because Erlang was designed with failure handling in mind, it is an excellent fit for high-reliability systems.</p><p>The last case, sending a signal to a program, may sound like it doesn&#8217;t leave much room for &#8220;restarting&#8221; but that depends heavily on how the software works. If the program backs-up the users work every second to disk, then you can go back to a &#8220;known, correct state&#8221; by restarting the whole program and instructing it to load the user&#8217;s backed up work. You can also use this approach in a multi-process architecture.</p><p>Another software architecture that works well for restarting is the <a href="https://guide.elm-lang.org/architecture/">Elm Architecture</a>, since each &#8220;update&#8221; step can be cancelled. Alas, Elm itself lacks a nice failure handling mechanism to take advantage of this. Trying to avoid failures at all costs results in <a href="https://github.com/elm/core/issues/1072">this sort of nonsense</a>.</p><p>In all cases the hope is that the triggering of the fault is an uncommon occurrence, otherwise the program will end up in a pointless restart loop.</p><p>Unfortunately errors due to logic bugs may not result in a computationally observable failure. Think glitched out physics simulations for example. Just gotta wait for the user complaints to show up.</p><h2>Wrapping Up</h2><p>Faults are bugs in the source code that lead to errors (unobserved invalid states) that wreck havoc until they trigger a failure (observed invalid behavior).</p><p>Many faults are easily preventable mistakes while others are too costly or annoying to prevent. Languages and tools that avoid or detect such mistakes are good.</p><p>You can&#8217;t handle errors directly, you can only handle their corresponding failures.</p><p>Handling a failure always means restarting (part of) the program to get rid of the error, otherwise it&#8217;s not really handling a failure, it&#8217;s just working with an extra known &#8220;return value&#8221; of a function.</p><p>You should architect your software in a way that allows for proper failure handling. Some languages (like Erlang) have excellent support for this. Others like Elm think it isn&#8217;t necessary (I disagree).</p><p>Exceptions are used as both a way to model extra &#8220;return values&#8221; of functions and as a failure handling mechanism, leading them to be lousy at both. Exceptions suck.</p><div><hr></div><p><strong>Side-Note: </strong>I didn&#8217;t mention it within the text because it&#8217;s already too big and an incoherent mess, but there&#8217;s another nice approach to preventing faults than expanding a codomain with a &#8220;must handle invalid result&#8221; case to remind the programmer of the potential fault.</p><p>You can also expand the codomain with an extra &#8220;harmless&#8221; result. A terrible idea for a library since it can&#8217;t possible know what such a harmless result would look like for the caller, but a perfectly valid approach for an application. </p><p>For example, lets say your program loads up a bunch of textures at the start. Instead of the texture loading function returning an <code>Option&lt;Texture&gt;</code> that you then need to deal with everywhere, it could return just <code>Texture</code>, but use a special &#8220;Missing Texture&#8221; for textures that failed to load.</p><p>The function would log somewhere which textures were missing, and just let the program proceed as normal, still letting the user do some work (if they didn&#8217;t need those textures). The program can check that errors were logged at some point and display them to the user in a separate codepath, while fixing the missing textures can be done by the user some other time. Check <a href="https://www.rfleury.com/p/the-easiest-way-to-handle-errors">this post by Ryan Fleury</a> on the subject.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>In the original version of this article I mentioned the terms came from <strong>IEEE 610.12-1990</strong>. That&#8217;s only partially true, the terms were indeed standardized there, but the meaning of error in particular was different (it referred to the discrepancy between the expected output and the actual output). I was originally taught the terms with these meanings in a course on fault tolerance, and I saw that standard as the source of the terms, but I never actually read the standard. I apologize for this mistake. Regardless, the source of the terms matters less than the concepts they refer to in post, feel free to replace the 3 terms with your preferred ones for each.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>In other languages a vector is usually known as a dynamic array or an array list.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>Kind of the rust equivalent of an exception but not really as they can be configured to behave differently. A bit more on panics later in the post.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>Pre-and-post conditions in function signatures are similar to refinement types that only apply to the type of the function itself (rather than its individual arguments or return type), but if they aren&#8217;t statically checked (or model checked) they&#8217;re just the &#8220;crash&#8221; approach.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>Having refinement types (or pre-and-post conditions) available makes model checking both more efficient and precise, so they&#8217;re valuable. I just don&#8217;t think that level of type checking strictness makes sense for the programming language itself.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>Right?&#8230; I haven&#8217;t gone insane have I?</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>My complaints only apply in the context of statically typed languages. In a dynamically typed language, everything has codomain &#8220;Any&#8221;, so it&#8217;s already as loose as it can be and it&#8217;s better to crash loudly than let some garbage error value spread through the program.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-8" href="#footnote-anchor-8" class="footnote-number" contenteditable="false" target="_self">8</a><div class="footnote-content"><p>The joys of being a father &#128169;&#8230;</p></div></div>]]></content:encoded></item><item><title><![CDATA[How to store types after Semantic Analysis]]></title><description><![CDATA[There are many approaches with no clear "best way".]]></description><link>https://btmc.substack.com/p/how-to-store-types-after-semantic</link><guid isPermaLink="false">https://btmc.substack.com/p/how-to-store-types-after-semantic</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Sun, 17 Dec 2023 21:47:04 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/cf13a0be-4cd3-4149-be29-3e76b49ef229_3272x2177.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have a tendency to run around in circles a lot when working on code that doesn&#8217;t have an obvious &#8220;right way&#8221; to do things. While working on my toy compiler I had this issue while implementing the semantic analysis phase (i.e., the type checker):</p><p><strong>Where do you store the types?</strong></p><p>Some CS professor is probably already screaming &#8220;in the symbol table dummy!&#8221;, but that&#8217;s not what I&#8217;m talking about. The job of the symbol table is to map identifiers in the various scopes of the program to their types or other relevant information, but it&#8217;s usually an <em>ephemeral</em> thing. Once the semantic analysis phase is over, the symbol table is gone. Parts of it don&#8217;t even last more than the scope where they are needed.</p><p>But what if you need to know the types of various expressions and such in the later phases? For example, what if you need to go from implicit casts to explicit casts? What what if you have type inference and need to remember what the actual computed type was? What if you need to know which overloaded function got selected? How do you store this information? Well, there are 4 ways I can think of:</p><ul><li><p>The Mutable AST</p></li><li><p>The Typed AST</p></li><li><p>The Generic AST</p></li><li><p>The Relational AST</p></li></ul><h2>The Mutable AST</h2><p>This one is the easiest, but mutation can get awkward depending on the language that you are using to implement the compiler. I&#8217;ll use Python here to keep things simple. I&#8217;m also not going to show a full blown AST, just little bits to give the idea:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;8da99aa3-ed7f-46f1-8061-05fbe19c079c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">@dataclass
class Expr:
  span: Span
  e_type: Optional[Type] = field(default=None, init=False)

@dataclass
class Binary(Expr):
  left: Expr
  op: Token
  right Expr</code></pre></div><p>Lets say we&#8217;re parsing an expression like <code>3 + 4.0</code>. The parser has no idea about types, so it just does the following<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;c807bcdd-bee1-4c9b-9fbe-f5a94211a086&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def parse_binary(self, left: Expr) -&gt; Expr:
  op = self.parse_token()
  right = self.parse_expression()
  span = left.span + right.span
  return Binary(span, left, op, right)</code></pre></div><p>The span represents the area of the source code that the expression covers, important to show some red squiggles later &#128521;. Then in the typechecker we fill in the e_type:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;81ba0044-21ec-4611-bf2d-f136d0a3a939&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def check_binary(self, b: Binary):
  self.check_expr(b.left)
  self.check_expr(b.right)
  if b.op.id in ("+", "-", "*", "/"):
    self.check_if_numeric(b.left)
    self.check_if_numeric(b.right)
    b.e_type = self.common_supertype(b.left, b.right)
  elif b.op.id in ("==", "!="):
    ...

def check_if_numeric(self, e: Expr):
  assert(e.e_type is not None)
  if not isinstance(e.e_type, (IntType, FloatType)):
    raise TypeCheckError(
      f"expected a numeric type, got {e.e_type}"),
      e.span)</code></pre></div><p>Something like this. We fill in e_type as we recursively go through the AST in the typechecker, then we see if the e_types of the sub-expressions are of the type we want.</p><p>Note that you want your checking function to return &#8220;nothing&#8221;, because this forces you to grab the e_type in the caller, in turn making sure you don&#8217;t forget to set it.</p><ul><li><p><strong>The Good</strong>: As simple as it gets. You use the same AST you already created during parsing so no extra allocations necessary, nor any duplication in the source code.</p></li><li><p><strong>The Bad</strong>: Depending on the language that you are using, that <code>Optional[T]</code> can get annoying. In Rust it would mean a lot of <code>.unwrap()</code> for example. Additionally, the structure of the AST classes/structs that makes the most sense for parsing may be awkward to work with in later stages<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>.</p></li><li><p><strong>The Ugly</strong>: Your AST classes/structs reference &#8220;types&#8221;, which technically come from a later stage in the compilation. Not a major issue by any stretch of the imagination but it is a bit awkward from a code-organization standpoint.</p></li></ul><h2>The Typed AST</h2><p>This one is a lot more work. Here your type checker produces an entirely new structure from the AST, lets call it a Typed-AST or TAST for short. For example:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;81f58f21-520a-4b4a-b851-ace482d9cb4b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># AST
@dataclass
class Expr:
  span: Span

@dataclass
class Binary(Expr):
  left: Expr
  op: Token
  right Expr

# TAST
@dataclass
class TExpr:
  e_type: Type

@dataclass
class TBinary(TExpr):
  left: TExpr
  op: BinaryOp
  right TExpr</code></pre></div><p>Your typechecking functions would take ASTs as input and spit out TASTs as output.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;659adb31-6c2e-4496-9352-e63f88cd46c4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def check_binary(self, b: Binary) -&gt; TBinary:
  left = self.check_expr(b.left)
  right = self.check_expr(b.right)
  ...
  return TBinary(e_type, left, op, right)</code></pre></div><ul><li><p><strong>The Good</strong>: This approach has the most freedom. You can have the AST be as convenient as possible for parsing/typechecking and the TAST be as convenient as possible for typechecking/code-generation. You can&#8217;t forget to set anything, the act of building the TAST forces you to fill in the types. No Optional in sight.</p></li><li><p><strong>The Bad</strong>: You need a whole separate class/struct hierarchy! That&#8217;s quite a bit of extra work compared to reusing the same AST. You need to maintain both as you add or change features. This approach also requires more memory allocations.</p></li><li><p><strong>The Ugly</strong>: &#8220;TExpr&#8221;, &#8220;TBinary&#8221;, etc. &#129314;. I ended up calling the AST ones &#8220;ExprNode&#8221; and the TAST ones &#8220;Expr&#8221; instead.</p></li></ul><h2>The Generic AST</h2><p>This one is sort of a middle ground between the two approaches above. Instead of storing an Optional type and mutating the AST, we store a generic metadata field:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;c376eb17-65b7-45d6-b206-da2335fbfc6a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># AST
@dataclass
class Expr(Generic[T]):
  metadata: T

@dataclass
class Binary(Expr[T]):
  left: Expr[T]
  op: Token
  right Expr[T]</code></pre></div><p>The parser outputs <code>Expr[Span]</code> while the type checker outputs <code>Expr[Type]</code>, for example. You can make the metadata as rich as you&#8217;d like between stages.</p><ul><li><p><strong>The Good</strong>: Like the mutable AST there is only one class/struct hierarchy, easy to maintain. Like the typed AST there are no annoying Optionals relating to the type information.</p></li><li><p><strong>The Bad</strong>: Like the mutable AST the structure may not be the best fit for every compilation stage. Like the typed AST this needs extra memory allocations.</p></li><li><p><strong>The Ugly</strong>: The moment you need more than 1 generic parameter you&#8217;ll regret ever picking this option.</p></li></ul><h2>The Relational AST</h2><p>The last option is to not store the extra type information in the AST at all, but instead to do things the relational way: each AST node has a unique ID that serves as the primary key for tables of whatever extra data you need.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;3d1ef6b7-8927-4aaa-8b90-90b5f6d7610a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">@dataclass
class CompilerDB:
  expr_types: Dict[int, Type]
  expr_spans: Dict[int, Span]
  ...

@dataclass
class Expr:
  id: int</code></pre></div><p>You can improve type-safety by creating distinct &#8220;ID&#8221; types for different things, instead of using <code>int</code> everywhere. You can also take the relational model further<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;f7dbae36-1692-4225-8286-324361d56da5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">@dataclass
class CompilerDB:
  expressions: List[Expr]
  expr_types: Dict[Id[Expr], Type]
  expr_spans: Dict[Id[Expr], Span]
  ...

@dataclass
class Expr:
  id: Id[Expr]

@dataclass
class Binary:
  left: Id[Expr]
  op: Token
  right: Id[Expr]</code></pre></div><p>In Python this approach doesn&#8217;t make a lot of sense, but in a more high-performance language like C++, Rust or even C# this can lead to some nice performance gains. Not only can the IDs be smaller than a pointer, saving memory, but if your parser builds the list of expressions in the CompilerDB bottom-up, then in the typechecker you can navigate the list linearly instead of using recursion<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>.</p><p>So what&#8217;s the catch? Unfortunately mainstream programming languages don&#8217;t support relational programming in any meaningful way, so you need to do everything in a rather cumbersome manner:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;01a6f075-b5cd-4df4-b918-5c718a33d7ac&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def check_binary(self, b: Binary):
  b_span = self.db.expr_spans[b.id]
  l_type = self.db.expr_types[b.left]
  l_span = self.db.expr_spans[b.left]
  r_type = self.db.expr_types[b.right]
  r_span = self.db.expr_spans[b.right]
  self.check_numeric(l_type, l_span)
  self.check_numeric(r_type, r_span)
  e_type = self.compute_super_type(l_type, r_type, b_span)
  self.db.expr_types[b.id] = e_type</code></pre></div><ul><li><p><strong>The Good</strong>: Very efficient in both speed and memory usage. Extremely adaptable. Basically all the benefits of the relational model.</p></li><li><p><strong>The Bad</strong>: The CompilerDB needs to be dragged around everywhere. You can&#8217;t implement <code>.str()</code> in the AST classes themselves, the function needs access to the CompilerDB. Boilerplate-heavy. Easy to make mistakes.</p></li><li><p><strong>The Ugly</strong>: It reminded me of how cool the <a href="https://en.wikipedia.org/wiki/Relational_model">relational model</a> is and how mainstream programming languages don&#8217;t even know it exists beyond lousy <a href="https://en.wikipedia.org/wiki/Object&#8211;relational_mapping">ORM</a> libraries for SQL databases. I don&#8217;t like SQL and I don&#8217;t like ORMs.</p></li></ul><h1>Conclusion</h1><p>Ok, four options, which one should you use? If you&#8217;re building a small, simple compiler that doesn&#8217;t need a lot of extra processing, use the Mutable AST. If your compiler is single pass then it&#8217;s a no-brainer since the &#8220;annoying optional&#8221; problem doesn&#8217;t even exist, just fill the type in right there and then.</p><p>If you&#8217;re building a more complicated compiler, you may want to use either the Typed AST or the Relational AST, depending on your needs.</p><p>Most computer-science papers use some variant of the Typed AST approach, usually translating between multiple different intermediate languages. It&#8217;s the nicer approach if you want to do lots of analyses and you want to make sure you <a href="https://fsharpforfunandprofit.com/posts/designing-for-correctness/">got them right</a>. </p><p>If you want the best performance then the Relational AST is the way to go. You can avoid recursion, you save memory by using IDs instead of pointers, you can move data to different tables depending on usage patterns. It can be very <a href="https://www.dataorienteddesign.com/dodbook/">data-oriented</a>.</p><p>The Generic AST is not worth it, the other approaches are just better. Any other approach I don&#8217;t know about? Let me know in the comments below.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Check out my article on <a href="/__u/btmc.substack.com/p/how-to-parse-expressions-easy">how to parse expressions</a>, it&#8217;s really easy :)</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>An example would be the class representing variable declarations. If you have type inference then the &#8220;declared_type&#8221; field may not get filled in during parsing, and that means an optional getting dragged around to the later stages.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>If every node has a type, you don&#8217;t need a dictionary, you can use an array of the same size as the main node array instead. Come to think of it, if even half the nodes need the info, the array version is the way to go. Don&#8217;t use &#8220;Optionals&#8221; though, make a special &#8220;Error&#8221; type for the others. Accessing them is a bug in your code.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>The construction of the list by the parser guarantees that child nodes in the AST are visited before their parents, so in the parent you just grab the already-computed type of the child node in the relevant table.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Machine Reasoning: The forgotten side of AI]]></title><description><![CDATA[Even "Machine Reasoning" is a made up term.]]></description><link>https://btmc.substack.com/p/machine-reasoning-the-forgotten-side</link><guid isPermaLink="false">https://btmc.substack.com/p/machine-reasoning-the-forgotten-side</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Mon, 04 Dec 2023 08:06:19 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/7360446e-082c-4df0-8107-ef53c4d00176_2604x2040.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://en.wikipedia.org/wiki/Artificial_intelligence">Artificial Intelligence</a> (AI) is seemingly everywhere these days, with massive amounts of money being thrown around at projects that are little more than API calls to <a href="https://en.wikipedia.org/wiki/ChatGPT">ChatGPT</a>. <em>Sigh</em>&#8230; I really should scam some investor during one of these silly gold rushes, I&#8217;m poor only because I want to be it seems. AI on the <a href="https://en.wikipedia.org/wiki/Blockchain">blockchain</a> anyone?</p><p>Anyway, that&#8217;s not to undermine the incredible achievement that is ChatGPT or other <a href="https://en.wikipedia.org/wiki/Large_language_model">Large Language Models</a> (LLMs), nor other neural network-based solutions for image or audio generation and such. They&#8217;re game changing pieces of tech, no doubt about it.</p><p>But I cannot help but be sad that the focus is <em>all in</em> on neural networks, they&#8217;re not the only approach to AI. Not only are there many other forms of <strong>Machine Learning</strong> (ML)<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>, but there is also <strong>Machine Reasoning</strong> (MR), the focus of this post.</p><h2>Machine Reasoning? What&#8217;s that?</h2><p>If you&#8217;ve never heard the term Machine Reasoning, I don&#8217;t blame you, I&#8217;m convinced it&#8217;s something a former manager of mine came up with to distinguish what we were doing from <a href="https://en.wikipedia.org/wiki/Machine_learning">Machine Learning</a> (which was already getting hyped at the time).</p><p>You won&#8217;t find the term on Wikipedia, instead you&#8217;ll find the idea split across many different pages like &#8220;automated reasoning&#8221;, &#8220;knowledge representation and reasoning&#8221;, &#8220;constraint satisfaction problem&#8221;, and more. A complete mess to follow.</p><p>To make it clear what I mean: a Machine Learning system is a piece of software that derives rules from facts (an inductive process). In contrast, a Machine Reasoning system is a piece of software that derives facts from rules (a deductive process).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!bujp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b67841-7fe9-4f25-8721-0c0cad9d8956_480x480.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!bujp!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b67841-7fe9-4f25-8721-0c0cad9d8956_480x480.gif 424w, /__u/substackcdn.com/image/fetch/$s_!bujp!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b67841-7fe9-4f25-8721-0c0cad9d8956_480x480.gif 848w, /__u/substackcdn.com/image/fetch/$s_!bujp!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b67841-7fe9-4f25-8721-0c0cad9d8956_480x480.gif 1272w, /__u/substackcdn.com/image/fetch/$s_!bujp!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b67841-7fe9-4f25-8721-0c0cad9d8956_480x480.gif 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!bujp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b67841-7fe9-4f25-8721-0c0cad9d8956_480x480.gif" width="480" height="480" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/42b67841-7fe9-4f25-8721-0c0cad9d8956_480x480.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:480,&quot;width&quot;:480,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3384357,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!bujp!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b67841-7fe9-4f25-8721-0c0cad9d8956_480x480.gif 424w, /__u/substackcdn.com/image/fetch/$s_!bujp!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b67841-7fe9-4f25-8721-0c0cad9d8956_480x480.gif 848w, /__u/substackcdn.com/image/fetch/$s_!bujp!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b67841-7fe9-4f25-8721-0c0cad9d8956_480x480.gif 1272w, /__u/substackcdn.com/image/fetch/$s_!bujp!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b67841-7fe9-4f25-8721-0c0cad9d8956_480x480.gif 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://github.com/mxgmn/WaveFunctionCollapse">Wave function collapse</a> is a Machine Reasoning-based approach to texture synthesis. More concretely it is based in part on the AC-3 algorithm from Constraint Programming (CP). We&#8217;ll discuss CP below. Note: The image above is intentionally slowed down for illustrative purposes.</figcaption></figure></div><h2>Expert Systems you mean?</h2><p>When I mention Machine Reasoning to people, the first thing that usually comes to mind for them are <a href="https://en.wikipedia.org/wiki/Expert_system">Expert Systems</a> &#8212; piles of &#8220;IF-THEN&#8221; rules painstakingly hand-written by domain experts. It&#8217;s no surprise those things fell out of favor against machine learning approaches that just figure things out from a big pile of data.</p><p>Expert systems are a form of machine reasoning yes but one of the least interesting IMO. Just as linear regression is not the first thing that comes to mind when talking about Machine Learning, neither should expert systems be the first thing that comes to mind when talking about Machine Reasoning.</p><div class="pullquote"><p>If a path-finding algorithm like A* is what first came to your mind, great, that&#8217;s more like it!</p></div><p>For me the truly interesting Machine Reasoning systems are model generators for which the <a href="https://en.wikipedia.org/wiki/Constraint_satisfaction_problem">constraint satisfaction problem</a> page is the closest but incomplete.</p><p>What&#8217;s a model generator you ask? It&#8217;s a piece of software that given a set of variables and a set of constraints on those variables, produces an assignment of those variables (a model) that is consistent with the constraints.</p><p>On top of that basic idea you can add:</p><ul><li><p>Objective functions &#8212; where we&#8217;re interested in finding an <em>optimal</em> model (in regards to the objective) rather than any arbitrary model.</p></li><li><p>Soft constraints &#8212; where we&#8217;re interested in finding a model that satisfies the most constraints simultaneously.</p></li></ul><p>When the hard constraints have one or more conflicts, it&#8217;s also possible to compute Minimum Unsatisfiable Subsets (MUS) &#8212; meaning the minimal set of constraints that if removed would make the problem have a solution.</p><p>The search for an optimal solution can be done incrementally depending on the algorithm used (producing better and better solutions over time), and we can also ask some systems to produce <em>all</em> possible models for the problem, rather than just one.</p><h2>That&#8217;s nice and all, but why not ML instead?</h2><p>Machine Learning (ML) and Machine Reasoning (MR) overlap for some problems but you&#8217;d normally use them for different purposes.</p><p>There are two properties of ML that make it a poor fit for some domains:</p><ul><li><p>ML requires a massive amount of training data to work well. Some domains don&#8217;t have such a quantity of data available.</p></li><li><p>ML is statistical in nature and ultimately a stochastic process. The results aren&#8217;t very predictable, hence &#8220;<a href="https://en.wikipedia.org/wiki/Hallucination_(artificial_intelligence)">hallucinations</a>&#8221; and hands with 7 fingers.</p></li></ul><p>ML excels at problems without a clear set of rules and where the solution does not have to be 100% accurate. For example, I have no idea what rules I would give to some system for it to generate a picture of a dog, and it is usually ok if the picture looks more like a drawing or 3D render than a perfectly lit photo of a dog.</p><p>MR, on the other hand, is a better choice for well defined problems that require precise solutions. Examples include: <a href="https://en.wikipedia.org/wiki/Electronic_design_automation">electronic design automation</a> (EDA), software verification, test generation, network optimization, routing, resource allocation, scheduling, supply chain management, etc.</p><p>One example of what MR can do is <a href="https://plm.sw.siemens.com/en-US/simcenter/integration-solutions/studio/">Simcenter Studio</a>, which is able to generate all topological variants of a physical system, simulate them, and then rank them, given simple high-level specification of the system&#8217;s components, their ports, and any relevant connection constraints.</p><p>It was used, for example, to find all 4-speed 2-stage transmission designs that can possibly exist (turns out there are only 12). <em><a href="https://kuleuven.limo.libis.be/discovery/fulldisplay?docid=lirias3035971&amp;context=SearchWebhook&amp;vid=32KUL_KUL:Lirias&amp;lang=en&amp;search_scope=lirias_profile&amp;adaptor=SearchWebhook&amp;tab=LIRIAS&amp;query=any,contains,LIRIAS3035971&amp;offset=0">It proved there are no other solutions</a> </em>in around 8 hours. It took decades for the first 4-speed 2-stage transmission design to be patented after 2-stage transmissions were first introduced.</p><p>An ML system trained on pre-existing 3-speed 2-stage transmission designs might be able to accidentally produce a 4-speed 2-stage transmission design, but there&#8217;s no guarantee. It certainly cannot prove there are only 12 solutions.</p><p>Many package managers use MR tech to handle <a href="https://hal.science/hal-00870846/file/W5_PX_Le_Berre_On_SAT_technologies_for_dependency_management_and_beyond.pdf">package dependency resolution</a>. A friend of mine is using MR for planning wedding seats.</p><h2>Machine Reasoning Technologies</h2><p>There are many different technologies that fit the umbrella of &#8220;Machine Reasoning&#8221; or model generators as I&#8217;ve defined it above, here I&#8217;ll briefly cover the ones I find the most interesting or relevant: <a href="https://en.wikipedia.org/wiki/Boolean_satisfiability_problem">Boolean Satisfiability</a> (SAT), <a href="https://en.wikipedia.org/wiki/Satisfiability_modulo_theories">Satisfiability Modulo Theories</a> (SMT), <a href="https://en.wikipedia.org/wiki/Constraint_programming">Constraint Programming</a> (CP), <a href="https://en.wikipedia.org/wiki/Linear_programming#Integer_unknowns">(Mixed) Integer Programming</a> (MIP) and <a href="https://en.wikipedia.org/wiki/Answer_set_programming">Answer Set Programming</a> (ASP).</p><h3>Boolean Satisfiability (SAT)</h3><p>Boolean Satisfiability (SAT), is sort of the assembly language of model generation. SAT is restricted to boolean variables, and boolean constraints (clauses) in <a href="https://en.wikipedia.org/wiki/Conjunctive_normal_form">Conjunctive Normal Form</a> (CNF). This may sound limiting at first but then you remember that computers work in 0s and 1s. You can model <a href="https://en.wikipedia.org/wiki/Logic_gate">logic gates</a> in CNF using a <a href="https://en.wikipedia.org/wiki/Tseytin_transformation">Tseytin transformation</a>, and with logic gates you can model arithmetic.</p><p>The algorithm at the heart of nearly every SAT solver is called <a href="https://en.wikipedia.org/wiki/Conflict-driven_clause_learning">Conflict-Driven Clause Learning</a> (CDCL). CDCL is a backtracking algorithm that records extra conflict clauses every time it hits a dead-end branch in the search space, preventing similar paths from being explored in the future.</p><p>CDCL (plus many fancy heuristics and preprocessing tricks) makes SAT solvers absurdly efficient. Modern SAT solvers can handle problems with millions of variables and millions of constraints. They&#8217;re a core piece of tech in the EDA domain. </p><p>There&#8217;s a yearly <a href="http://www.satcompetition.org">SAT competition</a> where many different SAT solvers compete to solve as many problems as possible in as little time as possible. The standard input format for SAT solvers is <a href="http://www.satcompetition.org/2009/format-benchmarks2009.html">DIMACS</a>. Example SAT solvers include <a href="https://fmv.jku.at/kissat/">Kissat</a> and <a href="https://github.com/msoos/cryptominisat">CryptoMiniSAT</a>.</p><h3>Satisfiability Modulo Theories (SMT)</h3><p>Satisfiability Modulo Theories (SMT), builds on top of SAT by combining a SAT solver with one or more <a href="https://en.wikipedia.org/wiki/Theory_(mathematical_logic)">theory</a> solvers. Examples of theories include integer arithmetic, real arithmetic, uninterpreted functions, strings, etc. SMT solvers are an indispensable piece of technology in the domains of software verification and formal proofs. </p><p>Most SMT solvers use the CDCL(T) algorithm, but there are other approaches like translating the problem entirely into SAT (only possible for some theories like the theory of bit-vectors or floating point numbers).</p><p>Like with SAT there&#8217;s a <a href="https://smt-comp.github.io/">yearly competition</a> where many different SMT solvers compete to solve as many problems as possible in as little time as possible. The standard input format for SMT solvers is <a href="http://smtlib.cs.uiowa.edu">SMTLIB2</a>. SMT solvers include: <a href="https://cvc5.github.io">cvc5</a>, <a href="https://www.microsoft.com/en-us/research/project/z3-3/">Z3</a>, <a href="https://yices.csl.sri.com">Yices 2</a>, <a href="https://mathsat.fbk.eu">MathSAT 5</a>, <a href="https://bitwuzla.github.io">Bitwuzla</a>, etc.</p><h3>Constraint Programming (CP)</h3><p>Unlike SMT which is a direct descendant of SAT, Constraint Programming (CP) developed in parallel, though these days many CP solvers employ SAT solvers internally and work in a similar manner to SMT solvers.</p><p>In CP the focus is on finite-domain integer variables<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> and <a href="https://en.wikipedia.org/wiki/Constraint_(mathematics)#Global_constraints">global constraints</a>: powerful high-level constraints that can, through specialized algorithms, effectively and efficiently reduce the domains of the variables involved in the constraint. The most famous global constraint is <code>alldifferent</code>, which constrains every variable in its input array to take a different value. </p><p>The naive encoding for <code>alldifferent</code> would require pairwise inequalities between every constrained variable, which is inefficient to solve. There are substantially more efficient ways of decomposing or propagating <code>alldifferent</code>. The global constraint has more information than a set of equivalent local constraints, and because of this can be solved more efficiently. That&#8217;s the basic idea of CP.</p><p>CP solvers are an indispensable piece of technology in operations research, excelling at solving complex scheduling and routing problems.</p><p>There are two main algorithms used these days for solving CP problems: Maintaining Arc-Consistency (MAC) and <a href="https://link.springer.com/chapter/10.1007/978-3-642-04244-7_29">Lazy Clause Generation</a> (LCG). MAC works by employing specialized algorithms for each global constraint alongside an arc-consistency algorithm like <a href="https://en.wikipedia.org/wiki/AC-3_algorithm">AC-3</a>, whereas LCG works similarly to an SMT-solver, with each global constraint behaving as a sort of theory solver that produces SAT clauses representing reductions in the domain of the variables.</p><p>Like SAT and SMT, CP has yearly competitions, one for each of the two standard input formats: <a href="https://www.minizinc.org">MiniZinc</a> and <a href="https://www.xcsp.org">XCSP3</a>. CP solvers include: <a href="https://developers.google.com/optimization/cp/cp_solver">CP-SAT</a>, <a href="https://www.gecode.org">Gecode</a>, <a href="https://choco-solver.org">Choco</a>, etc.</p><h3>(Mixed) Integer Programming (MIP)</h3><p>This section is technically encompassing many related technologies.</p><p>Linear programming (LP) focuses on optimization problems involving continuous variables and linear inequality constraints on those variables, plus an objective function. Integer linear programming (ILP) is the same but with integer variables. If the solver supports both continuous and integer variables, it&#8217;s called Mixed Integer Programming (MIP). If the variables are integer and the constraints non-linear, it&#8217;s called Integer Programming (IP). If the variables are mixed and the constraints non-linear, it&#8217;s called Mixed-Integer Non-Linear Programming (MINLP). And then there are special cases of non-linear programming like Quadratic Programming (QP) that have dedicated algorithms. <em>Phew</em>&#8230; got all that?</p><p>(Mixed) Integer Programming (MIP) in particular has the same &#8220;computational power&#8221; as the other approaches above. They can all handle <a href="https://en.wikipedia.org/wiki/NP-completeness">NP-complete</a> problems.</p><p>The focus here is clearly on optimization. If you need to find the best solution to a problem that is mostly continuous plus some discrete variables, this is the tech to go for. Some SMT solvers and most CP solvers can also handle optimization, but MIP solvers tend to be much better at it. So much better in fact that commercial MIP solvers can sell for thousands of dollars.</p><p>The main algorithm used by most modern MIP solvers is called <a href="https://en.wikipedia.org/wiki/Branch_and_cut">Branch &amp; Cut</a>, which combines the Simplex algorithm from linear programming with backtracking search and cutting planes to reduce the search space when the search hits a dead-end.</p><p>Unlike SAT, SMT and CP, I&#8217;m not aware of any official competition for MIP, but there is a <a href="https://plato.asu.edu/ftp/milp.html">benchmark</a> that compares some open source and commercial solvers. There isn&#8217;t a standard input format for MIP, but <a href="https://en.wikipedia.org/wiki/Nl_(format)">nl</a>, <a href="https://www.gurobi.com/documentation/current/refman/lp_format.html">LP</a> and <a href="https://en.wikipedia.org/wiki/MPS_(format)">MPS</a> are the most commonly supported formats. MIP solvers include: <a href="https://github.com/coin-or/Cbc">CBC</a>, <a href="https://www.scipopt.org">SCIP</a>, <a href="https://highs.dev">HiGHS</a>, <a href="https://www.gurobi.com">Gurobi</a>, <a href="https://www.ibm.com/products/ilog-cplex-optimization-studio">CPLEX</a>, etc.</p><h3>Answer Set Programming (ASP)</h3><p>One way to think of Answer Set Programming (ASP) is as a middle ground between <a href="https://en.wikipedia.org/wiki/Datalog">Datalog</a> and <a href="https://en.wikipedia.org/wiki/Prolog">Prolog</a>. Not turing-complete like Prolog, but more expressive than Datalog (Datalog cannot handle NP-complete problems, but ASP can).</p><p>Constraints in ASP (unlike the other approaches) are mainly universally quantified rather than existentially quantified. That is:</p><pre><code>ancestor(A, B) :- parent(A, B). 
ancestor(A, C) :- parent(A, B), ancestor(B, C).</code></pre><p>Is equivalent to the first order logic formula</p><pre><code>&#8704;a, b. Parent(a, b) &#8594; Ancestor(a, b)
&#8704;a, b, c. Parent(a, b) &#8743; Ancestor(b, c) &#8594; Ancestor(a, c)</code></pre><p>ASP solvers cannot handle universally quantified formulas like that directly, they first need to be <a href="https://en.wikipedia.org/wiki/Ground_expression">grounded</a> (i.e., every variable expanded with every possible value). That&#8217;s handled by a separate component called a <em>grounder</em>. ASP software is usually a pair of a solver and a grounder.</p><p>Modern ASP solvers use an adaptation of CDCL from SAT as their core algorithm and can often invoke the grounder component incrementally.</p><p>Like with SAT, SMT and CP, <a href="https://sites.google.com/view/aspcomp2019/">there is a competition</a>, but it does not happen as often. The standard input formats for ASP solvers are <a href="https://en.wikipedia.org/wiki/Answer_set_programming#Answer_set_programming_language_AnsProlog">AnsProlog</a> and <a href="https://arxiv.org/abs/1911.04326">ASP-Core-2</a>. ASP solvers include: <a href="https://potassco.org/clingo/">clingo</a> (clasp + gringo) and <a href="https://dlv.demacs.unical.it">DLV</a> (wasp + I-DLV)</p><h2>Concluding Remarks</h2><p>That was a long but very incomplete look at a field that is dear to my heart. It saddens me to see it relegated to the sidelines while deep-learning steals the spotlight. There is so much more to cover, I didn&#8217;t even cover local-search-based approaches.</p><p>It&#8217;s not like Machine Reasoning and Machine Learning are at odds either, they can be combined! You can, for example, train a <a href="https://en.wikipedia.org/wiki/Rectifier_(neural_networks)">ReLU</a>-based neural network on the operational data of some real-world hardware system to create a simulation model, translate that neural-network to a set of linear equations, and then use a MIP Solver to optimize some aspect of that system.</p><p>Or you could have an LLM generate constraint satisfaction problems and invoke an existing solver instead of trying to solve arithmetic problems directly. Or combine the texture synthesis capabilities of wave function collapse with the image generation capabilities of diffusion models. </p><p>Don&#8217;t leave everything to megacorps with massive datasets.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Decision Trees, Support Vector Machines, Inductive Logic Programming, Bayesian Networks, Genetic algorithms&#8230; I recommend reading Pedro Domingos&#8217; book &#8220;<a href="https://en.wikipedia.org/wiki/The_Master_Algorithm">The Master Algorithm</a>&#8221; for an overview.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Some CP solvers support real variables using <a href="https://en.wikipedia.org/wiki/Interval_arithmetic">interval arithmetic</a>.</p></div></div>]]></content:encoded></item><item><title><![CDATA[A Survey on Memory Management Approaches]]></title><description><![CDATA[Tracing, Reference Counting, Substructural Types, Regions...]]></description><link>https://btmc.substack.com/p/a-survey-on-memory-management-approaches</link><guid isPermaLink="false">https://btmc.substack.com/p/a-survey-on-memory-management-approaches</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Mon, 20 Nov 2023 23:09:35 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5067d266-47ab-4f47-aa80-094b50ace2d3_853x650.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>How to manage memory? The simplest possible solution (from the perspective of a language designer) would be to simply do nothing and leave it to programmers to explicitly acquire and release memory pages from the operating system. That&#8217;s a bit too cumbersome for most day to day programming, so even the lowest-level languages like C provide functions like malloc and free to manage memory on the heap.</p><p>A function like malloc may appear simple at first glance but most modern malloc implementations, like <a href="https://jemalloc.net">jemalloc</a> or <a href="https://github.com/google/tcmalloc">tcmalloc</a>, are actually incredibly sophisticated. From avoiding memory fragmentation to scalable concurrency, there&#8217;s a lot of hardcore engineering work going on under the hood.</p><p>But while malloc implementations automatically manage memory pages, they&#8217;re considered a form of &#8220;manual memory management&#8221; because it is still up to the programmer to use malloc and free correctly. Managing memory manually, with unstructured usage of malloc and free, is highly error prone, with <a href="https://cwe.mitre.org/data/definitions/415.html">double-free</a> and <a href="https://cwe.mitre.org/data/definitions/416.html">use-after-free</a> bugs possibly resulting in <a href="https://www.cisa.gov/news-events/news/urgent-need-memory-safety-software-products">severe security vulnerabilities</a>.</p><p>Less severe, but also problematic, are <a href="https://en.wikipedia.org/wiki/Memory_leak">memory leaks</a> resulting from forgetting to free some allocations. A long running program with recurring memory leaks will keep increasing its memory usage until the process has to be terminated.</p><p>All three issues stem from &#8220;free&#8221;. If you forget to call it, you get a leak. If you call it more than once, you get a double-free. If you call it while some part of the program is still using that memory, you get a use-after-free. Ouch &#128556;.</p><p>This post is a survey on the various approaches that exist to avoid these issues, from semi-automatic solutions that greatly reduce the memory management effort to fully-automatic solutions that remove the need for &#8220;freeing memory&#8221; entirely.</p><p>I&#8217;ll cover the following in this blog post:</p><ul><li><p>Regions (aka Arenas, aka Bump Allocators)</p></li><li><p>Memory Pools</p></li><li><p>Reference Counting</p></li><li><p>Tracing Garbage Collection</p></li><li><p>Substructural Type Systems</p></li><li><p>Lifetime Annotations</p></li></ul><h2>Regions (aka Arenas, aka Bump Allocators)</h2><p>I mentioned previously that modern malloc and free implementations are highly sophisticated, so it may come as a surprise to you that one of the best ways to manage memory in C is to avoid malloc and use the simplest allocator imaginable instead: a bump allocator (also known as a <a href="https://en.wikipedia.org/wiki/Region-based_memory_management">region</a>, or arena).</p><p>A bump allocator is called that because it just &#8220;bumps&#8221; a pointer forward on each allocation. The simplest bump allocator does little more than the following:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;63c37c26-76ae-426b-befc-044e104e5c6a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">void* alloc(bump_allocator* a, size_t size) {
  if (a-&gt;pointer + size &gt;= a-&gt;end) {
    return null;
  }
  void* result = a-&gt;pointer;
  a-&gt;pointer += size;
  return result;
}</code></pre></div><p>Instead of returning null, the bump allocator may also request additional pages from the operating system, switching the pointer over to the new location.</p><p>If the bump allocator is only tracking a single pointer, how does the program free individual allocations? Well, the secret is that <em>it doesn&#8217;t</em>. You can only reset the allocator&#8217;s pointer back to the beginning<em><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></em>.</p><p>The standard malloc has to be as general as possible to cover all imaginable use cases. The bump allocator on the other hand can only be used to allocate blocks of memory that share a lifetime. Thankfully having many blocks of memory sharing a lifetime is rather common.</p><p>Consider a web server: each request can use its own bump allocator, allocating freely, followed by a single free when the request is complete. Or a video game: any data that only lasts for a frame can be allocated freely and then freed in bulk.</p><p>While a bump allocator cannot handle all memory allocation patterns, the patterns it can handle become almost trivial and therefore substantially less error prone. Going from 1 free per allocation to 1 free per frame makes a huge difference! It&#8217;s a lot easier to get that single free right. As bonus, bump allocation is also ridiculously efficient, so you get a massive performance improvement as the cherry on top.</p><p>The <a href="https://ziglang.org">Zig</a> and <a href="https://odin-lang.org">Odin</a> programming languages are designed with the usage of bump allocators for temporary allocations in mind.</p><p>Having said that, while double-frees are extremely unlikely with bump allocation, use-after-free can still happen. It&#8217;s possible to defend against use after-free by combining regions with the various approaches below. For example, <a href="https://c3-lang.org">C3</a> uses scoped regions for temporary allocations.</p><p>Despite their simplicity, going in depth about regions would require a blog post on its own, as each combination with the other approaches creates a unique approach to memory management.</p><h2>Memory Pools</h2><p>While a bump allocator manages many allocations of different sizes all sharing the same lifetime, a <a href="https://en.wikipedia.org/wiki/Memory_pool">pool allocator</a> manages many allocations of the same size but with different lifetimes. The basic idea of a pool allocator is an array that tracks if the data at a particular index has changed.</p><p>A pool allocator does not return a raw pointer to each allocation, but instead it returns a handle with a mix of a generation ID and the index into the array where the allocation resides:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;4bd336ff-aaf6-4b70-ab12-4a57830642e8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">struct PoolHandle {
  uint32_t generation;
  uint32_t index;
}</code></pre></div><p>The number of bits dedicated to the generation or the index can be changed as needed. The generation ID is used to track when a particular index in the pool is freed and/or reused. If the generation ID of a handle and the generation ID of the current allocation at that index don&#8217;t match, you have detected a use-after-free. Double-free and leaks are not possible with pools, beyond the &#8220;leak&#8221; of keeping a certain number of slots in the pool always available even if they are not in use. You can theoretically still have use-after-free and double-free bugs with the pool pointer itself, but since it is only 1 pointer for many allocations, the likelihood of error is greatly reduced.</p><p>Pools are somewhat inconvenient to use because to access their data you need both a pointer to the pool and the respective handle, you cannot access the data without having access to the pool. You also need different pools for different datatypes or allocation sizes at least. Much like bump allocators, they cover certain common memory usage patterns, but not all.</p><p>Note that unlike bump allocation which is semi-automatic (1 free for many allocations) pool allocation is manual, data is explicitly allocated and freed. The advantage comes from the added safety.</p><p>The <a href="https://vale.dev">Vale</a> language extends the idea of generational handles to generational references, safeguarding every memory allocation with the same idea.</p><p>The two approaches above are handled by the programmer and are language independent: you can use bump allocation in any language with raw pointers, and pool allocation in every language with mutable arrays. The remaining approaches require the compiler to get involved.</p><h2>Scope-Based Resource Management (aka RAII)</h2><p>One of the main improvements C++ brought to the table over C was scope-based resource management, typically called by the awful name <a href="https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization">RAII</a><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> (Resource Acquisition is Initialization), as that was the name originally given to the approach by Bjarne Stroustrup, the creator of C++. He&#8217;s apologized for the name ever since<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>.</p><p>The core idea of scope-based resource management comes from the realization that nobody ever complains about managing memory on the stack, only on the heap. Variables on the stack are automatically pushed and popped alongside the scopes in which they reside, so everything just works.</p><p>So why not tether heap memory to the stack variable that &#8220;owns&#8221;<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a> it, allocating it when the variable is initialized, and freeing it when the variable&#8217;s scope ends? Well, that&#8217;s exactly how it works in C++:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;1cb1f7ca-d6aa-41a8-ad5b-d59c3129db10&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">void example() {
  string s("Hello World");
  vector&lt;string&gt; v;
  // v's destructor is implicitly called here
  // s's destructor is implicitly called here
}</code></pre></div><p>In the case of containers, like vectors, memory is freed recursively: first every element of the vector, then the vector itself. In C++ this is handled by giving every type a constructor member function and a destructor member function (sometimes even automatically generated by the compiler), and implicitly inserting calls to the type&#8217;s destructor at the end of the scope.</p><p>This approach of tying resources to stack variables is useful for more than just memory, so many languages provide scoped lifetimes (e.g. <a href="https://docs.python.org/3/reference/compound_stmts.html#with">with</a> in python) or a <a href="https://go.dev/tour/flowcontrol/12">defer</a> statement to execute some cleanup code at the end of the scope or function, even if they use a different approach to memory management.</p><p>If this was all that C++ supported, it would be a memory safe language (not counting features it inherited from C of course), but things are a bit more complicated.</p><p>One major issue with this approach is the pervasive copying. Just as normal stack allocation copies data freely, so must this emulation of the stack with heap memory. Copying small integers on the stack is no big deal, but copying large arrays is:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;64598b64-1102-4de7-84bb-df1fe0909846&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">void example() {
  vector v1{...}; // some huge vector
  vector v2 = v1; // copy :(
}</code></pre></div><p>To work around this issue, C++ has references and move semantics. References are just like pointers except they can&#8217;t be null (plus some other confusing and mostly irrelevant constraints). Move semantics use a special kind of reference (an rvalue reference) to indicate that the previous location of the value won&#8217;t be used anymore:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;bfd286e8-1fbe-452f-87d5-7eae242bd763&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">void example() {
  vector v1{...};
  vector v2 = move(v1); // no copy :)
  // v1 is in an undefined state after the move
  std::cout &lt;&lt; v1.at(0) &lt;&lt; "\n"; // *boom*
}</code></pre></div><p>The move function doesn&#8217;t actually do anything, it is just a cast to an rvalue (&amp;&amp;) reference, the actual &#8220;move&#8221; occurs in the vector&#8217;s <a href="https://en.wikipedia.org/wiki/Move_assignment_operator">move assignment operator</a>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;cpp&quot;,&quot;nodeId&quot;:&quot;27605f8d-dc63-436e-a2fe-1c0b53d88756&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-cpp">vector&lt;T&gt;&amp; operator=(vector&lt;T&gt;&amp;&amp; other)</code></pre></div><p>The operator is responsible for &#8220;consuming&#8221; the data from &#8220;other&#8221; (v1 above) and storing it in the vector (v2 above). After the assignment, &#8220;other&#8221; (meaning v1) is left in a safe but &#8220;unspecified&#8221; state, and should not be used anymore.</p><p>C++ does not provide any safety mechanisms for references and only minimal safety for moved-from values. You can&#8217;t get a double-free (because the compiler inserts the destructor for you, though the destructor could be buggy) but you can easily have use-after-free bugs with references.</p><p>Rust took this idea (including references and moves), and made it 100% safe. We&#8217;ll cover the approaches Rust uses to ensure safety later. For now, the important thing to keep in mind is that Scope-Based Resource Management (without references on top) enforces a tree-shaped structure on memory.</p><p><a href="https://en.wikipedia.org/wiki/C%2B%2B">C++</a>, <a href="https://www.rust-lang.org">Rust</a>, and <a href="https://en.wikipedia.org/wiki/Ada_(programming_language)">Ada</a> all use Scope-Based Resource Management.</p><h2>Reference Counting</h2><p>The idea of <a href="https://en.wikipedia.org/wiki/Reference_counting">reference counting</a> is simple and effective. When you allocate some data on the heap, a &#8220;reference count&#8221; is stored alongside it. When a new pointer to that data is created, the reference count is increased. When a pointer&#8217;s lifetime ends (e.g. it was stored in a variable that&#8217;s now out of scope), the reference count is decreased. When the reference count reaches 0, the data gets freed.</p><p>Reference Counting is the simplest form of garbage collection and combines very well with Scope-Based Resource Management, since the latter can handle the increments and decrements of the reference count in the constructors and destructors of the pointers. Because of this languages like C++ and Rust can provide reference counted pointers as a library (std::shared_ptr in C++ and Rc and Arc in Rust).</p><p>There are a few issues with reference counting: </p><ul><li><p>The first is that there are a lot of increment and decrement operations happening, and they need to hit main memory to update the reference count, which negatively affects cache usage. This can be mitigated by detecting &#8220;moves&#8221; which are an increment immediately followed by a decrement, and just skipping them.</p></li><li><p>The second is that in a multithreaded environment, these increment and decrement operations need to be atomic, which further slows things down. This can be mitigated by detecting when reference counted pointers cross thread boundaries.</p></li><li><p>The last is that reference counting cannot handle cycles. If A stores a reference to B and B stores a reference to A, the reference count of both is 1 and they&#8217;ll never get freed, causing a leak. This must be worked around by either forbidding cycles (constraint on the memory layout), using <a href="https://en.wikipedia.org/wiki/Weak_reference">weak references</a> to break cycles (manual labor and error prone), or applying some form of cycle collection (i.e. tracing).</p></li></ul><p><a href="https://www.python.org">Python</a>, <a href="https://developer.apple.com/swift/">Swift</a> and <a href="https://nim-lang.org">Nim</a> all use reference counting. Python has cycle collection, Swift does not, and Nim&#8217;s is optional. Without a workaround for the cycle issue, reference counting imposes a <a href="https://en.wikipedia.org/wiki/Directed_acyclic_graph">DAG</a>-like structure on memory.</p><h2>Tracing Garbage Collection</h2><p>Of the approaches we&#8217;ve seen, one requires that all allocations share a lifetime, one is still manual but protects against use-after-free and double-free, one enforces a tree-like memory layout, and one enforces a DAG-like memory layout.</p><p>What if we don&#8217;t want to worry about memory management <em>at all</em><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>? That&#8217;s where <a href="https://en.wikipedia.org/wiki/Tracing_garbage_collection">tracing garbage collection</a> comes in. Unlike every other approach covered so far, tracing garbage collection can handle arbitrary graphs of references.</p><p>The concept is simple: First, determine all root pointers, i.e., pointers stored in registers, stack variables and global variables that point to the heap. Mark every allocation pointed to by a root pointer. For every pointer stored in a marked allocation, recursively mark those allocations. Once every reachable allocation is marked, free all others as they are unreachable and therefore garbage data.</p><p>There are different ways to implement the above idea. The most direct is a mark-and-sweep garbage collector which marks and then sweeps (frees) exactly as described above. But another way to achieve the same idea is with a copying semi-space collector. The basic idea is as follows:</p><p>Set up two bump allocators, the &#8220;from&#8221; space and the &#8220;to&#8221; space. Allocate everything in the from space until it fills up. Once full, go through all the pointers as in a mark-and-sweep collection, but also copy each marked allocation to the &#8220;to&#8221; space. Once finished, &#8220;sweep&#8221; by simply resetting the &#8220;from&#8221; allocator pointer. The &#8220;to&#8221; space becomes the new &#8220;from&#8221; space and the old &#8220;from&#8221; space becomes the new &#8220;to&#8221; space.</p><p>Neat stuff. So if we have these algorithms that can handle arbitrary graph-like structures, why did I even bother going through the other more limited approaches above? Everything has tradeoffs:</p><ul><li><p>The first issue is that it is mostly unpredictable when a tracing garbage collector will get triggered. The algorithm is not cheap so you don&#8217;t want to run it often.</p></li><li><p>The second, is that to make matters worse, when the garbage collector is triggered, it needs to stop the whole program while it does its job. This is terrible for applications that need to remain highly responsive like video games.</p></li><li><p>The third issue is that to work around the above issues, modern tracing garbage collectors are extremely complex, and any solution to reduce the length of the pauses inevitably requires doing more work, and therefore lowers throughput.</p></li><li><p>The fourth and final issue, is that there is a lot of infrastructure that is necessary for the tracing garbage collector to work (i.e., how does it know about all the pointers in the program? how does it stop every thread in the program?), I go over that in my article <a href="/__u/btmc.substack.com/p/the-hidden-cost-of-tracing-garbage">The Hidden Costs of Tracing Garbage Collection</a>.</p></li></ul><p>Java, Javascript, C#, Go, Haskell, etc. all use tracing garbage collection, it is by far the most popular approach despite the drawbacks.</p><h2>Substructural Type Systems</h2><p>I mentioned before that I&#8217;d cover the approaches used by Rust to ensure complete safety for scope-based resource management with moves and references. First lets have a look at &#8220;moving&#8221;.</p><p>&#8220;Moving&#8221; in C++ happens when a move constructor or move assignment operator is executed, it is otherwise not tracked by the type system. The moved-from data must be set by the move operation to some safe but unspecified value such that the destructor can later check for that value to prevent a double-free.</p><p>Rust tracks ownership of values at the type system level with <em>affine types</em>, a kind of <a href="https://en.wikipedia.org/wiki/Substructural_type_system">substructural type system</a>. A variable of an affine type must be used <em>at most once</em>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;aaaa6861-226b-4cac-b231-ce58b186f6fb&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">fn example() {
  let x = vec![...];
  let y = x; // move
  println!("{}", x); // compile error, x was moved
  // destructor of y implicitly called here
}</code></pre></div><p>The &#8220;x&#8221; variable was &#8220;used up&#8221; in the second line of the function, and as such cannot be used anymore. It&#8217;s not so much that rust tracks &#8220;moves&#8221; but more so &#8220;uses&#8221;. If a variable is never used, rust will implicitly insert a destructor for it at the end of the scope, but from the type system&#8217;s perspective the destructor does not matter.</p><p>As an alternative to affine types, there are also <em>linear types</em> that enforce each variable is used <em>exactly once</em>. In a linear type system the programmer would be expected to explicitly call a destructor function (which is just any function that consumes the value and does not return a new one).</p><p>Using affine or linear types can get quite cumbersome, as any transformation requires storing the result in a new variable:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;e1c399a7-647e-4790-a194-c448429681cf&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">let x = f();
let x1 = g(x);
let x2 = h(x1);
...</code></pre></div><p>To make substructural types a bit more palatable there&#8217;s the idea of &#8220;borrowing&#8221;. In Rust that is handled with references, which complicates things, so the example below uses an explicit made up &#8220;borrow&#8221; keyword:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;b298ebc4-5f89-49d0-ac36-d5262c7ab05e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">fn increment(borrow x: Vec&lt;i64&gt;) {
  // increment all values of x
}
let y = vec![...];
increment(y);</code></pre></div><p>Is theoretically equivalent to:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;2b7f125e-974c-4926-bacf-7727d8a82db3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">fn increment(x: Vec&lt;i64&gt;) -&gt; Vec&lt;i64&gt; {
  let x1 = x;
  // increment all values of x1
  return x1;
}
let y = vec![...];
let y = increment(y) // new variable also called y</code></pre></div><p>Though of course the compiler can just do an in-place mutation.</p><p><a href="https://www.rust-lang.org">Rust</a>, <a href="https://en.wikipedia.org/wiki/Clean_(programming_language)">Clean</a>, <a href="https://mercurylang.org">Mercury</a> and <a href="https://austral-lang.org">Austral</a> all use substructural types, but only Rust and Austral use them for memory management. An added benefit of substructural types is that they also prevent data races at compile time.</p><h2>Lifetime Annotations</h2><p>Finally we get to how references can be made safe. The easiest solution is to just remove them from the language entirely, as <a href="http://www.parasail-lang.org">ParaSail</a> and <a href="https://www.hylo-lang.org">Hylo</a> do. You can get surprisingly far without first-class references.</p><p>But what if we really want references plus safety, what can be done? Rust solves this issue with <em>lifetime annotations</em>. The idea of tracking lifetimes came from attempts at making region-based memory management safe to use, but in Rust they&#8217;re generalized to work with any allocation.</p><p>The basic idea is simple, when you create a reference to some allocation, add an extra tag to the type of the reference with the lifetime of the allocation, for example:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;cfe56534-0542-4c3d-8484-e0a4490a03f9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">let x = vec![...];
let y = &amp;x;</code></pre></div><p>The types in the example above are inferred, but if we write them out, we see that there&#8217;s an extra type parameter to the reference, a lifetime annotation:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;b64e3591-51e5-4f21-85c0-dd7f0450e3e1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">let x: Vec&lt;i64&gt; = vec![...];
let y: &amp;'a Vec&lt;i64&gt; = &amp;x;</code></pre></div><p>That little &#8216;a is tracking y&#8217;s lifetime. It doesn&#8217;t actually store anything, it&#8217;s just a type variable that gets unified in the type system. For example, a function like:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;8f4fe447-fd42-4c7e-83fa-446762049ecc&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">fn get&lt;'a&gt;(array: &amp;'a [Stuff], index: usize) -&gt; &amp;'a Stuff {
  ...
}</code></pre></div><p>The type variable &#8216;a is tracking the lifetime of the slice, and records in the type of the returned Stuff reference that it has the same lifetime. This is achieved through unification, the same algorithm used to handle generic type variables.</p><p>Rust&#8217;s syntax is not the easiest to follow when it comes to lifetimes, the above function wouldn&#8217;t normally even have that &#8216;a explicitly written out since Rust can figure out the annotations on its own for cases like the above with only 1 reference. </p><p>So instead we can look at Austral since its references are always explicit:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;1bec74f1-c46e-4151-894b-12f5faced7ad&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">let buf: ByteBuffer := allocateBuffer(100, 'a');
let len: Index := length(&amp;buf);
destroyBuffer(buf);</code></pre></div><p>The above looks very similar to Rust and works the same except for the explicit destructor. But if we look at the length function, things become a bit clearer:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;9fce44f3-c5f0-4d08-9cad-b7e1ad4d5183&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">generic [R: Region]
function length(buf: &amp;[ByteBuffer, R]): Index is
  return !(buf-&gt;size);
end;</code></pre></div><p>The length function is generic over a region called R, and every reference in Austral must have an associated region<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>. Inside of it we use the path operator (&#8594;) to get a reference to the size field of the buffer (of type &amp;[Index, R]) which we then convert into an Index with the dereference operator (!).</p><p>But what if we want to store a reference to the ByteBuffer in another variable?</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;2eefeb42-bd8d-404b-a2fe-60ae10f7cff9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">let bufref: &amp;[ByteBuffer, ?] := &amp;buf;</code></pre></div><p>What do we fill in for the &#8220;?&#8221; there? In Rust, an implicit &#8220;region&#8221; is created (so to say) to fill that in, but in Austral we have to explicitly borrow buf:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;1e5e9347-f4b2-4461-9cfc-9cfe77fc4963&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">borrow buf as bufref in R1 do
  -- bufref is of type &amp;[ByteBuffer, R1]
end borrow;</code></pre></div><p>The region is only valid in that scope, if we tried to store the reference outside of the scope, we&#8217;ll get an error:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;c691179f-3c94-4ce0-87f2-e0e386670cb0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">let outerref: &amp;[ByteBuffer, R1]; -- error: unknown region R1
borrow buf as bufref in R1 do
  outerref := bufref -- impossible since they cannot have the same type
end borrow;</code></pre></div><p>What rust is doing is effectively the same as the above, only with a lot more syntax sugar to avoid needing to explicitly write out the lifetimes.</p><h2>Conclusion</h2><p>Ouff, that was a long article, but hopefully I covered all the main approaches to memory management. If there are any that I missed do let me know in the comments. I&#8217;ve heard of <a href="https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-908.pdf">ASAP</a> which is a sort of compile-time inlined tracing garbage collector but unfortunately the one <a href="https://github.com/doctorn/micro-mitten">implementation</a> of the idea showed mediocre performance (which makes sense, a tracing GC is not something you want to run often, even in little spread out bits).</p><p>Personally, I think if we just gave up on first class references we&#8217;d have a really good memory management story that doesn&#8217;t require the massive sledgehammer that is a tracing garbage collector or nasty lifetime annotations.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Some bump allocators may allow you to &#8220;undo&#8221; the last allocation, or &#8220;pop&#8221; allocations like a stack, or let you record &#8220;snapshots&#8221; you can revert to, but the core idea remains to free everything in one go.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>RAII is a much catchier name than SBRM, so it continues to be used as a shorthand, despite the actual words being effectively gibberish as a description of the approach.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>It&#8217;s ok Bjarne, the idea is what counts, not what it&#8217;s called. And it&#8217;s a pretty neat idea.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>This approach is also known as ownership semantics, because it&#8217;s tracking what memory owns what other memory, where &#8220;owner&#8221; means &#8220;responsible for freeing&#8221;.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>No such thing, there are always considerations, but the closest we can get.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>These are not the same concept of regions as in region-based memory management, where a region exists at runtime as an allocator. In Austral these are the same as lifetimes in Rust, a purely compile-time construct.</p><p></p></div></div>]]></content:encoded></item><item><title><![CDATA[Trying to make sense of Web Components]]></title><description><![CDATA[I don't like this standard, not one bit.]]></description><link>https://btmc.substack.com/p/making-sense-of-web-components</link><guid isPermaLink="false">https://btmc.substack.com/p/making-sense-of-web-components</guid><dc:creator><![CDATA[Sir Whinesalot]]></dc:creator><pubDate>Wed, 15 Nov 2023 22:23:13 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/91700a03-a24d-4b15-8089-c0cfc2026edd_3174x2381.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let me preface this post by saying I&#8217;m not against web frameworks like <a href="https://angular.io">Angular</a> or <a href="https://react.dev">React</a> (and yes React is a framework no matter its devs say). If you&#8217;re developing a complex web application, using a web framework is probably way to go<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>.</p><p>If you&#8217;re developing a component library, to be used by other people developing web applications, then your choice of framework will limit your users to that framework (my favorite component library is <a href="https://mantine.dev">Mantine</a>, but it is only available for React). Most frameworks don&#8217;t play nice with each other, <a href="https://en.wikipedia.org/wiki/Web_Components">web component</a>-based libraries being the sole exception<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>.</p><p>But many web component libraries (like <a href="https://shoelace.style">Shoelace</a>) depend on <a href="https://lit.dev">Lit</a>, which is a framework, which means that if the app developer is not using Lit, then their app has two frameworks now. </p><p>So this post is an attempt at making sense of the features natively provided by the browser, and how well and how far one can take them in order to develop a component library, without needing to rely on a framework of some kind.</p><h1>Web Components</h1><p>The browser-native approach for developing component libraries are <a href="https://en.wikipedia.org/wiki/Web_Components">web components</a>. However, technically, web components aren&#8217;t their own <em>thing</em>, rather they are a mix of three related but ultimately separate standards: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements">Custom Elements</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots">HTML Templates</a>, and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM">Shadow DOM</a>.</p><p>When I first heard about web components, I was expecting them to be something like the following:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:&quot;85aaa304-ea2e-42c6-81d7-8b8e0ab17ccb&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;template name="my-split-panel"&gt;
  &lt;style&gt;...&lt;/style&gt;
  &lt;script&gt;...&lt;/script&gt;
  &lt;div&gt;
    &lt;slot name="left-panel"&gt;...&lt;/slot&gt;
    &lt;div id="splitter"&gt;...&lt;/div&gt;
    &lt;slot name="right-panel"&gt;...&lt;/slot&gt;
  &lt;/div&gt;
&lt;/template&gt;</code></pre></div><p>Which you&#8217;d then use (assuming they were written in a separate file) like the following:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:&quot;3933b133-2547-4c08-b747-3d7d12a1ec95&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;html&gt;
  &lt;head&gt;
    &lt;link rel="import" href="/__u/btmc.substack.com/components/my-split-panel.html"&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;my-split-panel&gt;
      &lt;div slot="left-panel"&gt;...&lt;/div&gt;
      &lt;div slot="right-panel"&gt;...&lt;/div&gt;
    &lt;/my-split-panel&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre></div><p>Pretty simple right?</p><p>Right?&#8230;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!D_ZG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd16a6fa-789e-4b94-8ac8-e5581a4a5041_478x298.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!D_ZG!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd16a6fa-789e-4b94-8ac8-e5581a4a5041_478x298.gif 424w, /__u/substackcdn.com/image/fetch/$s_!D_ZG!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd16a6fa-789e-4b94-8ac8-e5581a4a5041_478x298.gif 848w, /__u/substackcdn.com/image/fetch/$s_!D_ZG!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd16a6fa-789e-4b94-8ac8-e5581a4a5041_478x298.gif 1272w, /__u/substackcdn.com/image/fetch/$s_!D_ZG!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd16a6fa-789e-4b94-8ac8-e5581a4a5041_478x298.gif 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!D_ZG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd16a6fa-789e-4b94-8ac8-e5581a4a5041_478x298.gif" width="478" height="298" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cd16a6fa-789e-4b94-8ac8-e5581a4a5041_478x298.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:298,&quot;width&quot;:478,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3158208,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!D_ZG!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd16a6fa-789e-4b94-8ac8-e5581a4a5041_478x298.gif 424w, /__u/substackcdn.com/image/fetch/$s_!D_ZG!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd16a6fa-789e-4b94-8ac8-e5581a4a5041_478x298.gif 848w, /__u/substackcdn.com/image/fetch/$s_!D_ZG!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd16a6fa-789e-4b94-8ac8-e5581a4a5041_478x298.gif 1272w, /__u/substackcdn.com/image/fetch/$s_!D_ZG!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd16a6fa-789e-4b94-8ac8-e5581a4a5041_478x298.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Literally me looking at the web component specs.</figcaption></figure></div><h2><strong>Boy was I wrong! Oh so wrong&#8230;</strong></h2><p>Unfortunately things are quite a bit different in reality. The HTML import spec never got accepted so that&#8217;s out. Not a major issue, you could just use some sort of bundler to join the files into one.</p><p>The bigger issue are the templates: You can&#8217;t instantiate HTML templates from within HTML. You can&#8217;t actually name them. You can give them an ID, and find them and instantiate them from JavaScript, but not from HTML.</p><p>So if I wanted to make something like a reusable &#8220;card&#8221; or &#8220;avatar&#8221;, or &#8220;navigation&#8221; component, which has no associated behavior but merely styles some divs a certain way, I gotta use JavaScript. A &#8220;split button&#8221; component that reuses builtin browser behavior? Sorry, JavaScript it is. Forget &#8220;server side rendering&#8221; too.</p><div class="pullquote"><p>Why did they do it this way? I&#8217;m sure there was a reason, but it can&#8217;t have been a good one.</p></div><p>Ok so we need to create a &#8220;Custom Element&#8221; in JavaScript to achieve that custom &lt;<code>my-split-panel/&gt;</code> tag. What does that look like?</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:&quot;e312359e-6280-482d-8d06-39471f0cf67d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">const template = document.createElement("template");
template.innerHTML = `
  &lt;template&gt;
    &lt;style&gt;...&lt;/style&gt;
    &lt;div&gt;
      &lt;slot name="left-panel"&gt;&lt;/slot&gt;
      &lt;div id="splitter"&gt;...&lt;/div&gt;
      &lt;slot name="right-panel"&gt;&lt;/slot&gt;
    &lt;/div&gt;
  &lt;/template&gt;
`;

class MySplitPanel extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: "open" });
    shadowRoot.appendChild(template.content.cloneNode(true));
    ...
  }
  connectedCallback() {
    // got attached to the DOM
    ...
  }
}

customElements.define("my-split-panel", MySplitPanel);</code></pre></div><p>We could get the template out of the HTML file using <code>getElementById</code>, but actually putting the template into an HTML file wouldn&#8217;t make any sense for a reusable component library (can&#8217;t import HTML remember?), so we need to put it into a string in the JS file. Nobody thought this was dumb? Just me?</p><p>So Custom Elements, other than being a pure JS thing instead of the HTML+CSS+JS hybrid they should be, are pretty reasonable. They don&#8217;t handle any kind of reactivity as you&#8217;d expect from a modern javascript framework, and they need some annoying boilerplate, but otherwise they&#8217;re fine. </p><p>Or they would be, if not for that &#8220;shadowRoot&#8221; thing, which is part of the Shadow DOM spec.</p><h2>What&#8217;s a Shadow DOM?</h2><p>Shadow DOM is a way to encapsulate the internals of a component from the rest of the page, meaning CSS cannot directly affect a web component&#8217;s internal nodes and any selector queries won&#8217;t find those nodes either. Quoting <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#shadow_dom_and_custom_elements">mdn</a>:</p><blockquote><p>Without the encapsulation provided by shadow DOM, <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements">custom elements</a> would be impossibly fragile. It would be too easy for a page to accidentally break a custom element's behavior or layout by running some page JavaScript or CSS. As a custom element developer, you'd never know whether the selectors applicable inside your custom element conflicted with those that applied in a page that chose to use your custom element.</p></blockquote><p>Funny, somehow this was never a problem for Angular, or React, or Vue, or Svelte, or any other framework, but Custom Elements without shadow DOM would be impossibly fragile? I&#8217;m sorry but I don&#8217;t see how. Even without frameworks and browser support, web &#8220;components&#8221; have been a thing since forever!</p><p>I can plop a CodeMirror instance into my webpage and not worry about blowing up their styles. Why would I? They&#8217;re all namespaced as &#8220;cm-&#8221;. Perhaps they should have used a longer namespace with less of a chance of collision, sure, but that&#8217;s neither here nor there. <a href="https://github.com/css-modules/css-modules">CSS modules</a> work via name mangling and are redundant with this whole shadow DOM approach. CSS variables? Better remember to namespace them as shadow DOM or not they can conflict. Selectors? Just don&#8217;t write lousy selectors! When has that ever been a problem?</p><p>Shadow DOM is a <a href="https://www.matuzo.at/blog/2023/pros-and-cons-of-shadow-dom/">misguided idea</a>. The ability for JavaScript and CSS to dig down and modify components made by other people is a <em>feature</em>, not a bug. The author of a web component has to put a lot of effort into making it possible to style their component from the outside. Shadow DOM does not play well at all with external stylesheets (e.g. <a href="https://getbootstrap.com">Bootstrap</a>) or even <a href="https://tailwindcss.com">Tailwind</a>. It sucks IMO, and <a href="http://blog.namangoel.com/shadow-dom-considered-harmful">I&#8217;m not alone in this sentiment</a>.</p><p>Well, Shadow DOM is technically a separate spec right? We can simply not use it, right? &#8230; Right? Unfortunately template slots require a shadow DOM.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!QoyC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad76c7c6-8144-463f-900d-bb9a45d30e71_461x250.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!QoyC!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad76c7c6-8144-463f-900d-bb9a45d30e71_461x250.gif 424w, /__u/substackcdn.com/image/fetch/$s_!QoyC!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad76c7c6-8144-463f-900d-bb9a45d30e71_461x250.gif 848w, /__u/substackcdn.com/image/fetch/$s_!QoyC!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad76c7c6-8144-463f-900d-bb9a45d30e71_461x250.gif 1272w, /__u/substackcdn.com/image/fetch/$s_!QoyC!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_webp, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad76c7c6-8144-463f-900d-bb9a45d30e71_461x250.gif 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!QoyC!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad76c7c6-8144-463f-900d-bb9a45d30e71_461x250.gif" width="461" height="250" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ad76c7c6-8144-463f-900d-bb9a45d30e71_461x250.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:250,&quot;width&quot;:461,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:9720091,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!QoyC!, /__u/btmc.substack.com/w_424, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad76c7c6-8144-463f-900d-bb9a45d30e71_461x250.gif 424w, /__u/substackcdn.com/image/fetch/$s_!QoyC!, /__u/btmc.substack.com/w_848, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad76c7c6-8144-463f-900d-bb9a45d30e71_461x250.gif 848w, /__u/substackcdn.com/image/fetch/$s_!QoyC!, /__u/btmc.substack.com/w_1272, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad76c7c6-8144-463f-900d-bb9a45d30e71_461x250.gif 1272w, /__u/substackcdn.com/image/fetch/$s_!QoyC!, /__u/btmc.substack.com/w_1456, /__u/btmc.substack.com/c_limit, /__u/btmc.substack.com/f_auto, /__u/btmc.substack.com/q_auto:good, /__u/btmc.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad76c7c6-8144-463f-900d-bb9a45d30e71_461x250.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">My reaction when I first found that out.</figcaption></figure></div><p>So we can avoid this shadow DOM business so long as we don&#8217;t use slots, meaning we can&#8217;t place sub-elements provided by the consumer of our component in the right place without using a shadow DOM (we can&#8217;t use an internal <code>&lt;style/&gt;</code> block either but CSS modules are a much better solution for that anyway so it doesn&#8217;t matter).</p><div class="pullquote"><p>Why did they do it this way? I&#8217;m sure there was a reason, but it can&#8217;t have been a good one.</p></div><p>Well, technically we can place the elements in the right place, just not using slots. There&#8217;s nothing preventing us from doing the following to set the components in the right place:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:&quot;a853c6a6-0725-40f5-a60c-ac22db1cc1ca&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">const splitPanelTemplate = document.createElement("template");
splitPanelTemplate.innerHTML = `
  &lt;div&gt;
    &lt;template id="my-split-panel-left"&gt;&lt;/template&gt;
    &lt;div id="my-split-panel-splitter"&gt;...&lt;/div&gt;
    &lt;template id="my-split-panel-right"&gt;&lt;/template&gt;
  &lt;/div&gt;
`;

class MySplitPanel extends HTMLElement {
  constructor() {
    super();
    this.template = splitPanelTemplate.content.cloneNode(true);
    this.left = this.template.getElementById("my-split-panel-left");
    this.right = this.template.getElementById("my-split-panel-right");
    ...
  }

  connectedCallback() {
    let left = this.querySelector("[data-slot='left']");
    let right = this.querySelector("[data-slot='right']");
    this.left.replaceWith(left);
    this.right.replaceWith(right);
    this.innerHTML = this.template.firstElementChild.innerHTML;
  }
}

// make sure to initialize the component after its children
window.onload = () =&gt; {
  customElements.define("my-split-panel", MySplitPanel);
}</code></pre></div><p>There are some important things to note here. First, we&#8217;re emulating slots with data attributes, by replacing the child nodes of the cloned template node with the child nodes with the corresponding data-slot annotation. </p><p>Second, The <code>connectedCallback()</code> method is not necessarily called when the component&#8217;s children have already been created, but when the browser knows about the component and sees its tag. We work around that by only defining the custom element on <code>window.onload</code>, guaranteeing the child elements are in place first.</p><p>Lastly, this does not handle the case where an element in a slot is changed programmatically. For that, we need to set up a <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver">MutationObserver</a>. Not a big deal, but it would be a lot nicer to have the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/slotchange_event">slotchanged</a> event available.</p><p>My point with the above is not to tell you this is the right way to do things, it&#8217;s to show you that I can emulate shadow DOM slots without a lot of ceremony, meaning there&#8217;s no reason the browser couldn&#8217;t do it too. It&#8217;s an unnecessary restriction.</p><h2>Conclusion</h2><p>Boy what a mess. I do not like this standard, not one bit. That said, the Custom Element part of it can be salvaged, and also templates up to a point. For the example above I wouldn&#8217;t bother with a template at all, I&#8217;d just ask the user to provide the div for the splitter as well. One extra line of code from the user for more control on their part and a much simpler component on my part? Win win. These web components that just add behavior to a set of user-defined light DOM elements are being called &#8220;<a href="https://blog.jim-nielsen.com/2023/html-web-components/">HTML Web Components</a>&#8221; by some other light DOM loving folk out there.</p><p>I hate that name, but I can&#8217;t come up with a better one. I love the idea though, they work great, are very efficient, and fit in well with the existing web. The only issue is that they require the user to provide all the internal divs themselves, which depending on the component may defeat the whole point. Maybe one day I&#8217;ll write a micro compiler from my imaginary HTML+CSS+JS Web Component standard to a light DOM-based Custom Element as the one I showed above. Sort of like a micro <a href="https://svelte.dev">Svelte</a> (without reactivity or any other fancy features).</p><p>Do let me know in the comments if I&#8217;m an idiot for not understanding the brilliance of the shadow DOM. As for me, I prefer the light.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://btmc.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Burning the Midnight Coffee! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>If you&#8217;re making a blog or some news site, you should <em>not</em> be using a frontend framework, you should be using some sort of static site generation or server side rendering, barely any JavaScript should be arriving at your user&#8217;s computer. Be kind to your users.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Technically they don&#8217;t play well with React&#8230; which is why Lit has a React compatibility library of some sort, and why Shoelace wraps all of its components as React components.</p></div></div>]]></content:encoded></item></channel></rss>