<?xml version='1.0' encoding='UTF-8'?>
<?xml-stylesheet href="https://lukeplant.me.uk/assets/xml/atom.xsl" type="text/xsl media="all"?>
<feed xml:lang="en" xmlns="http://www.w3.org/2005/Atom">
  <title>Luke Plant's home page (Posts about Python)</title>
  <id>https://lukeplant.me.uk/blog/categories/python.xml</id>
  <updated>2026-05-02T11:46:16Z</updated>
  <author>
    <name>Luke Plant</name>
  </author>
  <link rel="self" type="application/atom+xml" href="https://lukeplant.me.uk/blog/categories/python.xml"/>
  <link rel="alternate" type="text/html" href="https://lukeplant.me.uk/blog/categories/python/"/>
  <generator uri="https://getnikola.com/">Nikola</generator>
  <entry>
    <title>Inverse Sapir-Whorf and programming languages</title>
    <id>https://lukeplant.me.uk/blog/posts/inverse-sapir-whorf-and-programming-languages/</id>
    <updated>2026-05-01T09:40:36+01:00</updated>
    <published>2026-05-01T09:40:36+01:00</published>
    <author>
      <name>Luke Plant</name>
    </author>
    <link rel="alternate" type="text/html" href="https://lukeplant.me.uk/blog/posts/inverse-sapir-whorf-and-programming-languages/"/>
    <summary type="html">&lt;p&gt;A discussion on how features of programming languages can make it hard to avoid expressing or talking about things you may or may not care about as a programmer.&lt;/p&gt;</summary>
    <content type="html">&lt;p&gt;The &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Linguistic_relativity"&gt;Sapir-Whorf hypothesis&lt;/a&gt;, in its simplest form, is the idea that the language you speak influences the thoughts you think. This post is about a twist on this idea, that I’m calling “Inverse Sapir-Whorf” (for want of a better term), and how we see it in computer programming languages.&lt;/p&gt;
&lt;p&gt;Sapir-Whorf is one of those ideas that has been popularised in general culture in a rather misrepresented and &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Arrival_(film)"&gt;exaggerated form&lt;/a&gt;. In the field of linguistics, not many people today take seriously the “strong” forms of Sapir-Whorf, such as “linguistic determinism” – the idea that a language controls your thoughts or limits what you can think, or that you even need certain languages to think certain thoughts.&lt;/p&gt;
&lt;p&gt;For example, just because a language might lack grammatical tenses, it doesn’t at all follow that the speakers will be more limited in how they think about time – there are always other ways you can express time.&lt;/p&gt;
&lt;p&gt;There is a fair amount of evidence that spoken languages can affect perception, skill and attitudes in certain areas, but it’s usually hard to demonstrate a large direct effect.&lt;/p&gt;
&lt;p&gt;Inverse Sapir-Whorf is a bit different. I haven’t been able to track down where I first came across the idea, but it goes like this: if classic Sapir-Whorf says your language limits what you can say or think, or makes it hard to say some things, inverse Sapir-Whorf says your language limits what you &lt;strong&gt;can’t&lt;/strong&gt; say, or makes it hard &lt;strong&gt;not&lt;/strong&gt; to say some things, or even hard not to think about some things. Some examples might clear things up.&lt;/p&gt;
&lt;section id="examples-in-natural-language"&gt;
&lt;h2&gt;Examples in natural language&lt;/h2&gt;
&lt;p&gt;There are many examples to choose from, but they are not always obvious to native speakers of a language. I’ll pick just a few.&lt;/p&gt;
&lt;section id="english-temporary-or-permanent-present-tense"&gt;
&lt;h3&gt;English: temporary or permanent present tense&lt;/h3&gt;
&lt;p&gt;What’s the difference between someone saying “I’m living in London” and “I live in London”? A non-native speaker may not pick this up at all, and a native speaker may pick it up only subconsciously, but “I’m living in London” reveals that the arrangement is temporary.&lt;/p&gt;
&lt;p&gt;Now, this might not even be to do with the actual length of time you have been living there, because “temporary” is pretty relative. It might be more about how much you &lt;em&gt;like&lt;/em&gt; London. You have to choose a tense, and because you typically do so subconsciously, the language is forcing you to reveal things – either the period of time you’ve been living somewhere, or how you feel about it.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="english-turkish-french-gendered-pronouns-and-nouns"&gt;
&lt;h3&gt;English/Turkish/French: gendered pronouns and nouns&lt;/h3&gt;
&lt;p&gt;In English, in normal speech you are going to use “he” or “she” when referring to a specific person. “Singular they” does exist, but it’s very unnatural if you are talking about a specific person of known or assumed sex.&lt;/p&gt;
&lt;p&gt;You can compare this to another language which doesn’t have gendered pronouns, such as Turkish, which just has “o” for he/she/it. The lack of gendered pronouns in Turkish doesn’t stop you from thinking or talking about a person’s sex, or produce a “less gendered society”, or anywhere close, so it would be difficult to find support for normal Sapir-Whorf here. But the inverse Sapir-Whorf is obvious – English pronouns push you to talk about it whether you want to or not. If you are trying to talk about someone you know, but do so anonymously, it can be very hard to avoid making their identification easier by revealing their sex with an inadvertent “him” or “her”.&lt;/p&gt;
&lt;p&gt;Different again is French, in which &lt;em&gt;nouns&lt;/em&gt; are gendered, which in some cases can force you to reveal information. If you translate “my friend” into French, you have to choose between “mon ami” (male friend) and “mon amie” (female friend), which are distinct, at least in written form, or “mon copain” vs “ma copine”. Possessive pronouns are also interesting – they are gendered in both English and French (his/her, son/sa), but refer to the gender of the possessor and possessee respectively, and so reveal different information.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="turkish-mis-tense"&gt;
&lt;h3&gt;Turkish: “mış” tense&lt;/h3&gt;
&lt;p&gt;With some simplifications, Turkish has two main past tenses: there is the normal one that is similar to “simple past” in English, and then there is the “mış” form (you can pronounce that “mish” if you want).&lt;/p&gt;
&lt;p&gt;This has &lt;a class="reference external" href="https://www.turkishtextbook.com/beginner-mis-forms/"&gt;various functions&lt;/a&gt;, but when describing a past event, this form is used when you have second hand or unreliable information. If someone asks you “Did Fred come to work on Monday?”, then if you saw him you would use the normal past tense “geldi” (he came), but if you only &lt;em&gt;heard&lt;/em&gt; that he came you would instead say “gelmiş” (he came, but second hand information).&lt;/p&gt;
&lt;p&gt;The interesting thing to me as a non-native speaker was the effect of having these options, in contrast to English where you can just use simple past tense without any specific indication of reliability or where the information came from. In certain circumstances, Turkish forces you to include information about your level of certainty or whether you witnessed something –  the simple past form is not neutral, because the existence of the “mış” form makes it an unnatural choice if it is not the most appropriate of the two.&lt;/p&gt;
&lt;p&gt;Interestingly, having learned to think that way, my wife and I have noticed an effect on our English. Often in Turkish the “mış” suffix would come at the end of the last word in a sentence, so now quite frequently we get to the end of an English sentence and notice that we haven’t put in any marker for “this-is-second-hand-info-I-didn’t-actually-witness-it”, and so we tack “mış” on the end.&lt;/p&gt;
&lt;p&gt;Of course, you can easily express the same thing in English, using words like “apparently” and other means, but English doesn’t &lt;strong&gt;force&lt;/strong&gt; you to specify, while Turkish pretty much does.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="comments"&gt;
&lt;h3&gt;Comments&lt;/h3&gt;
&lt;p&gt;You often don’t notice these things until you learn another language, or attempt to teach your language to a foreigner. You kind of just understand them subconsciously. The vast majority of times you choose simple present over present continuous, for example, you won’t be &lt;strong&gt;consciously&lt;/strong&gt; thinking about what that implies.&lt;/p&gt;
&lt;p&gt;I should also note that when a language forces you express something, it might not be in the form of something &lt;em&gt;included&lt;/em&gt;, but in something &lt;em&gt;omitted&lt;/em&gt;. For example, I might say “I love cake” or “I love the cake”. In the first case, I’m talking about cake generally, in the second about a specific cake. It is the absence of the word “the” in the first case that makes it unambiguous that I’m referring to all cake, because if I’m referring to a specific cake, I &lt;strong&gt;must&lt;/strong&gt; use the word “the” or some other marker like “this”. In another language, there might not be a direct equivalent to this distinction.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="examples-in-programming"&gt;
&lt;h2&gt;Examples in programming&lt;/h2&gt;
&lt;p&gt;When it comes to programming languages, I think that the “straight” version of  Sapir-Whorf is closer to being true - in some programming languages it is simply hard to express certain concepts. For example, in a language like Python or Haskell it’s hard (though not impossible) to talk about memory allocations. We often talk about the limitations of a language in terms of “things that are hard to express” in that language. Hillel Wayne has some more discussion of this in his post &lt;a class="reference external" href="https://buttondown.com/hillelwayne/archive/sapir-whorf-does-not-apply-to-programming/"&gt;Sapir-Whorf does not apply to Programming Languages&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But I want to talk more about Inverse Sapir-Whorf. What is the language forcing you to talk about, even if you don’t actually care about it?&lt;/p&gt;
&lt;p&gt;I think there are actually many, many examples of this, but seeing them can be quite hard, and often requires the “foreigner perspective” that comes from learning multiple languages.&lt;/p&gt;
&lt;p&gt;Here are a few:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Most languages force you to express the order in which computation should be done. For example, in Python:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_11fb4ed97b504dcf83b998b745f49865-1" name="rest_code_11fb4ed97b504dcf83b998b745f49865-1" href="https://lukeplant.me.uk/blog/posts/inverse-sapir-whorf-and-programming-languages/#rest_code_11fb4ed97b504dcf83b998b745f49865-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;some_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here you are saying:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;first compute &lt;code class="docutils literal"&gt;y + 1&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;then compute &lt;code class="docutils literal"&gt;z + 2&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;then pass these two values as arguments to &lt;code class="docutils literal"&gt;some_func&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You might not be very conscious of specifying this ordering, but you are doing it, and in Python, there isn’t a way to express the above computation which doesn’t also specify order. Most languages are similar, although in some &lt;a class="reference external" href="https://en.cppreference.com/w/c/language/eval_order.html"&gt;it gets very complicated&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A few languages are very different, however. In Haskell, in an equivalent expression like &lt;code class="docutils literal"&gt;some_func (y + 1) (z + 2)&lt;/code&gt;, due to &lt;a class="reference external" href="https://wiki.haskell.org/Non-strict_semantics"&gt;non-strict semantics&lt;/a&gt; you are not specifying an order of evaluation at all. This enables some clever tricks, like referring to values you haven’t defined yet (see &lt;a class="reference external" href="https://wiki.haskell.org/Tying_the_Knot"&gt;Tying the knot&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/"&gt;Function colouring for async&lt;/a&gt; is another good example. In languages like Javascript or Python with an explicit &lt;code class="docutils literal"&gt;async&lt;/code&gt; keyword, you have to talk about whether code is sync or async.&lt;/p&gt;
&lt;p&gt;In the case of “sync” functions, you do it by omission of the &lt;code class="docutils literal"&gt;async&lt;/code&gt; keyword, but you are still choosing between the two options, and there is no way to write code that is ambivalent on the subject.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Most languages without &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)"&gt;garbage collection&lt;/a&gt; force you to talk about memory allocation and de-allocation.&lt;/p&gt;
&lt;p&gt;For languages like C, you normally do this fairly explicitly – or implicitly use stack allocation, but you’ve still got to make that choice.  In other languages, it can become more hidden, but doesn’t really go away. In Rust, for example, in gets converted into talk about lifetimes or explicit reference counting. Saying “I just don’t care about when the memory for this gets allocated or de-allocated, please deal with it” is not really one of your options.&lt;/p&gt;
&lt;p&gt;Of course, not talking about memory allocation also has a cost. In that case, the language will almost certainly need to put a lot of things on &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Memory_management#HEAP"&gt;the heap&lt;/a&gt; and have a runtime garbage collector. However it may also have significant freedom to choose for you – Haskell will often be able to do this using &lt;a class="reference external" href="https://wiki.haskell.org/Performance/Strictness"&gt;strictness analysis&lt;/a&gt;, for example.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;All modern languages I’m aware of force you to think about “scope”. In many cases, scope is expressed by the physical place in which you put a variable, with some additional syntax if you want something different (like &lt;a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#the-global-statement"&gt;global&lt;/a&gt; or &lt;a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#the-nonlocal-statement"&gt;nonlocal&lt;/a&gt; in Python). If you never want to think about scope, you probably have to drop to assembly and live with a single global address space.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Statically typed languages often force you to think about and talk about the type of every variable. This is lessened somewhat by type inference, as the “conversation” involves a more “intelligent” listener who can pick up more things from context, but it’s still there.&lt;/p&gt;
&lt;p&gt;Pure dynamically typed languages still allow you to talk about types – for example, using things like &lt;code class="docutils literal"&gt;isinstance&lt;/code&gt; checks in Python, but it is more unnatural (and technically it’s a different thing anyway).&lt;/p&gt;
&lt;p&gt;In contrast to both of them, one of the attractions of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Gradual_typing"&gt;gradually typed languages&lt;/a&gt; is that they genuinely avoid the inverse Sapir-Whorf problem, and allow you the freedom to talk about types, or not, at your preference. I’m not sure how well this works in practice - the existing code base conventions and the linters in use always put pressure in some direction.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I suspect that many of the features of more “approachable” or “readable” programming languages could be analysed in these terms – they have a low inverse Sapir-Whorf barrier, and don’t force you to talk about things you don’t have an opinion on, and may not even understand yet.&lt;/p&gt;
&lt;p&gt;Are there more examples of this that you’ve come across? How do they affect the programming languages we use, or how we perceive them?&lt;/p&gt;
&lt;/section&gt;
&lt;section id="links"&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://lobste.rs/s/hb9tdr/inverse_sapir_whorf_programming"&gt;Discussion of this post on Lobsters&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;</content>
    <category term="haskell" label="Haskell"/>
    <category term="python" label="Python"/>
    <category term="software-development" label="Software development"/>
  </entry>
  <entry>
    <title>Announcing fluent-codegen</title>
    <id>https://lukeplant.me.uk/blog/posts/announcing-fluent-codegen/</id>
    <updated>2026-03-10T12:33:49Z</updated>
    <published>2026-03-10T12:33:49Z</published>
    <author>
      <name>Luke Plant</name>
    </author>
    <link rel="alternate" type="text/html" href="https://lukeplant.me.uk/blog/posts/announcing-fluent-codegen/"/>
    <summary type="html"></summary>
    <content type="html">&lt;p&gt;This post announces &lt;a class="reference external" href="https://github.com/spookylukey/fluent-codegen"&gt;fluent-codegen&lt;/a&gt;, a Python library for generating Python code. Not everyone needs this, but when you need it, you’ll probably know, and you’ll want probably want a decent solution that is not based on string concatenation.&lt;/p&gt;
&lt;p&gt;The history of this project is that it started out as a &lt;code class="docutils literal"&gt;codegen&lt;/code&gt; module in &lt;a class="reference external" href="https://github.com/django-ftl/fluent-compiler"&gt;fluent-compiler&lt;/a&gt;, which is an implementation of &lt;a class="reference external" href="https://projectfluent.org/"&gt;Project Fluent&lt;/a&gt;, Mozilla’s internationalisation solution. So the word “fluent” referred to that originally, but now it refers to a set of nice APIs for building up Python expressions.&lt;/p&gt;
&lt;p&gt;As I needed a code generation library for another purpose, I pulled out this library and fleshed it out into a relatively complete, standalone project. It has also evolved quite a bit since its earlier form, and is a pretty well rounded library now. You can check out &lt;a class="reference external" href="https://fluent-codegen.readthedocs.io/en/latest/"&gt;the nice docs&lt;/a&gt;, which include a nice little toy example of a “SVG to Python Turtle” compiler. Enjoy!&lt;/p&gt;</content>
    <category term="python" label="Python"/>
  </entry>
  <entry>
    <title>Help my website is too small</title>
    <id>https://lukeplant.me.uk/blog/posts/help-my-website-is-too-small/</id>
    <updated>2025-12-19T13:45:33Z</updated>
    <published>2025-12-19T13:45:33Z</published>
    <author>
      <name>Luke Plant</name>
    </author>
    <link rel="alternate" type="text/html" href="https://lukeplant.me.uk/blog/posts/help-my-website-is-too-small/"/>
    <summary type="html">&lt;p&gt;How can it be a real website if it’s less than 7k?&lt;/p&gt;</summary>
    <content type="html">&lt;p&gt;A jobs web site I belong to just emailed me, telling me that some of the links in my public profile on their site are “broken” and “thus have been removed”.&lt;/p&gt;
&lt;p&gt;The evidence that these sites are broken? They are too small:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.djangoproject.com/"&gt;https://www.djangoproject.com/&lt;/a&gt;: response body too small (6220 bytes)&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.cciw.co.uk/"&gt;https://www.cciw.co.uk/&lt;/a&gt;: response body too small (3033 bytes)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The first is the home page of the Django web framework, and is, unsurprisingly, implemented using Django (see the &lt;a class="reference external" href="https://github.com/django/djangoproject.com"&gt;djangoproject.com source code&lt;/a&gt;). The second is one of my own projects, and also implemented using Django (source &lt;a class="reference external" href="https://github.com/cciw-uk/cciw.co.uk/"&gt;also available&lt;/a&gt; for anyone who cares).&lt;/p&gt;
&lt;p&gt;Checking in webdev tools on these sites gives very similar numbers to the above for the over-the-wire size of the initial HTML (though I get slightly higher figures), so this wasn’t a blip caused by downtime, as far as I can see.&lt;/p&gt;
&lt;p&gt;Apparently, if your HTML is less than 7k, that obviously can’t be a real website, let alone something as ridiculously small as 3k. Even with compression turned up all the way, it’s clearly impossible to return more than an error message with less than &lt;a class="reference external" href="https://minime.stephan-brumme.com/react/18.0.0/"&gt;at least 4k&lt;/a&gt;, right?&lt;/p&gt;
&lt;p&gt;So please can Django get it sorted and add some bloat to their home page, and to their framework, and can someone also send me tips on bloating my own sites, so that my profile links can be counted as real websites? Thanks!&lt;/p&gt;
&lt;section id="links"&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://lobste.rs/s/3vdhci/help_my_website_is_too_small"&gt;Discussion of this post on Lobsters&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://news.ycombinator.com/item?id=46373559"&gt;Discussion of this post on Hacker News&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;</content>
    <category term="django" label="Django"/>
    <category term="python" label="Python"/>
    <category term="web-development" label="Web development"/>
  </entry>
  <entry>
    <title>Breaking “provably correct” Leftpad</title>
    <id>https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/</id>
    <updated>2025-10-03T21:00:00+01:00</updated>
    <published>2025-10-03T21:00:00+01:00</published>
    <author>
      <name>Luke Plant</name>
    </author>
    <link rel="alternate" type="text/html" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/"/>
    <summary type="html">&lt;p&gt;Why? Because it’s fun.&lt;/p&gt;</summary>
    <content type="html">&lt;p&gt;I don’t know much about about formal methods, so a while back I read Hillel Wayne’s post &lt;a class="reference external" href="https://www.hillelwayne.com/post/lpl/"&gt;Let’s prove Leftpad&lt;/a&gt; with interest. However:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;I know Donald Knuth’s famous quote: “Beware of bugs in the above code; I have only proved it correct, not tried it”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I also know how it turned out that &lt;a class="reference external" href="https://research.google/blog/extra-extra-read-all-about-it-nearly-all-binary-searches-and-mergesorts-are-broken/"&gt;code that had been proved correct harboured a bug not found for decades&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I thought I’d take a peek and do some testing on these &lt;a class="reference external" href="https://github.com/hwayne/lets-prove-leftpad"&gt;Leftpad implementations&lt;/a&gt; that are all “provably correct”.&lt;/p&gt;
&lt;nav class="contents" id="contents" role="doc-toc"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#top"&gt;Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#methodology" id="toc-entry-1"&gt;Methodology&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#implementations" id="toc-entry-2"&gt;Implementations&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#liquid-haskell" id="toc-entry-3"&gt;Liquid Haskell&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#java" id="toc-entry-4"&gt;Java&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#lean4" id="toc-entry-5"&gt;Lean4&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#rust" id="toc-entry-6"&gt;Rust&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#some-competition" id="toc-entry-7"&gt;Some competition!&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#results" id="toc-entry-8"&gt;Results&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#comments" id="toc-entry-9"&gt;Comments&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#explanation" id="toc-entry-10"&gt;Explanation&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#what-is-a-character" id="toc-entry-11"&gt;What is a character?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#how-does-a-programming-language-handle-strings" id="toc-entry-12"&gt;How does a programming language handle strings?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#putting-them-together" id="toc-entry-13"&gt;Putting them together&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#what-went-wrong" id="toc-entry-14"&gt;What went wrong?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#lies-damned-lies-and-natural-language" id="toc-entry-15"&gt;Lies, damned lies and natural language&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#what-is-correct" id="toc-entry-16"&gt;What is correct?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#confessions-and-corrections" id="toc-entry-17"&gt;Confessions and corrections&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#response" id="toc-entry-18"&gt;Response&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;section id="methodology"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-1" role="doc-backlink"&gt;Methodology&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I’ll pick a few, simple, perfectly ordinary inputs at random, and work out what
I think the output should be. This is a pretty trivial problem so I’m expecting
that all the implementations will match my output. &lt;em&gt;[narrator: He is is expecting no such thing]&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I’m also expecting that, even if for some reason I’ve made a mistake, all the implementations will at least match each other. &lt;em&gt;[narrator: More lies]&lt;/em&gt;  They’ve all been proved correct, right?&lt;/p&gt;
&lt;p&gt;Here are my inputs and expected outputs. I’m going to pad to a length of 10, and use &lt;code class="docutils literal"&gt;-&lt;/code&gt; as padding so it can be seen and counted more easily than spaces.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr class="header"&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Length&lt;/th&gt;
&lt;th&gt;Expected padding&lt;/th&gt;
&lt;th&gt;Expected Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;𝄞&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;code&gt;---------𝄞&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Å&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;code&gt;---------Å&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;𓀾&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;code&gt;---------𓀾&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;אֳֽ֑&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;code&gt;---------אֳֽ֑&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;résumé&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;----résumé&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;résumé&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;----résumé&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;[“ordinary”, “random” - I think my work here is done…]&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I’ve used a monospace font so that the right hand side of the outputs all line up as you’d expect.&lt;/p&gt;
&lt;p&gt;Entry 6 is not a mistake, by the way, it just does “e acute” in a different way to entry 5. Nothing to see here, move along…&lt;/p&gt;
&lt;/section&gt;
&lt;section id="implementations"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-2" role="doc-backlink"&gt;Implementations&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Not all of the &lt;a class="reference external" href="https://github.com/hwayne/lets-prove-leftpad/tree/master"&gt;implementations&lt;/a&gt; were that easy to run. In fact, many of them didn’t take any kind of “string” as input, but vectors or lists or such things, and it wasn’t obvious to me how to pass strings in. So I discounted them.&lt;/p&gt;
&lt;p&gt;For the ones I could run, I attempted to do so by embedding the test inputs directly in the program, if possible.&lt;/p&gt;
&lt;section id="liquid-haskell"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-3" role="doc-backlink"&gt;Liquid Haskell&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Embedding the characters directly in Haskell source code kept getting me “lexical error in string/character literal”, so I wrote a small driver program that read from a file.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="java"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-4" role="doc-backlink"&gt;Java&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://github.com/hwayne/lets-prove-leftpad/blob/ea9c0f09a2d3e981d82118497c307844fc7b1f49/java/LeftPad.java#L7"&gt;leftpad function provided&lt;/a&gt; didn’t take a string, but a &lt;code class="docutils literal"&gt;char[]&lt;/code&gt;. Thankfully, it’s easy to convert from &lt;code class="docutils literal"&gt;String&lt;/code&gt; objects, using the &lt;code class="docutils literal"&gt;.toCharArray()&lt;/code&gt; function. So I did that.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="lean4"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-5" role="doc-backlink"&gt;Lean4&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There is a handy &lt;a class="reference external" href="https://live.lean-lang.org/"&gt;online playground&lt;/a&gt;, and the implementation had a helpful &lt;code class="docutils literal"&gt;#eval&lt;/code&gt; block that I could modify to get output. You can &lt;a class="reference external" href="https://live.lean-lang.org/#codez=PQWgBAsg9gTgpmAzgSwLYAcA2zEBdkDGYAhgHYAmSuMxyA5gBa4BmsA7sTJQG5wwpRSYKMzC4GcAFBgZmOM1zpilROjgFkzQsXyCSFMOhhQRiMMiG42UML37JBiAFzSZrGCRgAjZNU4BPMGw8MwswOjhSPmJMfUp3KhgLOjNVdU1tTEx/aRBgSUluTmRiLzkwAG9ARuAwJzAAFX81AF8C8nkg+UVlMAAKITqAOR0ASj7iWrAqsd7YuoAZHFwpsYWlqdqAXldFvAA6eCxtXAR+sHBMPblSOnExiYBqB6CC8ThYOFROhSVyAH1rrcGH0BmBhrgZhM6tM+nMwLtljCXDIZLMur8wEIJpgRldIkCwJswKhiAAPTGwvE3O5bMBeHIyFAYMAAbTkP2UABowTo9ogAK5eP7Kf5wACOfxJpIAugVJG8Pl92d1/kZ5MhySyAEIAUTFU2lrPmxDYzH5mF1+qqhrOQ1G40mMNmkwRK1qrlRCIOcCOBB0pyEFypQPuuJwAAV4FpSQB5URojmULFBVZE+muJnoVnK36y14SRXfFV/AXMaOsy0Go0ms0WvVV208iEO6EzOGupEeoJ7HAAZX5ZY1cdh6J6yZxtPTjLQWbZo/IeckpGIqDgqmIBAQveoyUQbQ6OZ6jfBkMmAGEGJwZmY6tukjdVmA78ktq4AERvvbofmIBjJs7gIgwY0k8SAFAAxHARSxIelAAIwAAxgAA5CAyFgG+gAsG4AeLtvvm7zwEq84Avi4ggnaza9MQdQXlefTOE+O4Pl2dQJiqFITIguKAmRRJShSvRATxDCpq49JIDO2bzty4J8oKwrkKKEpSnmCqEYx950Hs5A6MQfzfr+QiCbeTF0DMBA0ZeMAzKQFEjCx9Ffj+f5gEQpC4jpuATESQGeY8zy9F6hzYH6JwUgQqZ0gyYD8qQrCYJQz43E5hmuFGFgIKQ3oEDGQgAPwAF5gHle4yH6iAIEVmwAHwScyLKyYccA6LKZXEBVSCEtVXYWNQNhGcgDCsY1PrNcsvQAO0pcCEUUlx2m6YSSALV5YCgUFPohf64X2SidWziNag6Nyg2tSicCkhuyx6ny/ioF8G2+v6Jb8gQBDIfhhawfp6Xkv0dnjJZdHGRpyS7SirGPVtYUActwmhmAAA8DysbBHFIB5i1OGm0WxfFlCwRmkksklWl+cK6BqBQ3KkytekGX+qkFup32luW/1NpCQPWfRJmaeDMh1L5i2I04AVo8m81+ZOuNxVACVFr8RP1bT5PEJTkTkDTpl0/pzmkNKQA"&gt;play with it here&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="rust"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-6" role="doc-backlink"&gt;Rust&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/hwayne/lets-prove-leftpad/blob/ea9c0f09a2d3e981d82118497c307844fc7b1f49/creusot%20(rust)/src/lib.rs"&gt;The code here&lt;/a&gt; had loads of extra lines regarding specs etc. which I stripped so I could easily run it, which worked fine.&lt;/p&gt;
&lt;p&gt;More tricky was that the code didn’t take a string, but some &lt;code class="docutils literal"&gt;Vec&amp;lt;Thingy&amp;gt;&lt;/code&gt;. As I know nothing about Rust, I got ChatGPT to tell me how to convert from a string to that. It gave me two options, I picked the one that looked simpler and less &amp;lt;&amp;lt;angry&amp;gt;&amp;gt;. I didn’t deliberately pick the one which made Rust look even worse than all the others, out of peevish resentment for every time someone has rewritten some Python code (my go-to language) in Rust and made it a million times faster – that’s a ridiculous suggestion.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="some-competition"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-7" role="doc-backlink"&gt;Some competition!&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To make things interesting, let’s compare these provably correct implementations with one vibe-coded by ChatGPT, in some random language, like, say, um, Swift. It gave me this code:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code swift"&gt;&lt;a id="rest_code_01436f3585ee41f18b92225a7131a680-1" name="rest_code_01436f3585ee41f18b92225a7131a680-1" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#rest_code_01436f3585ee41f18b92225a7131a680-1"&gt;&lt;/a&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;Foundation&lt;/span&gt;
&lt;a id="rest_code_01436f3585ee41f18b92225a7131a680-2" name="rest_code_01436f3585ee41f18b92225a7131a680-2" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#rest_code_01436f3585ee41f18b92225a7131a680-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_01436f3585ee41f18b92225a7131a680-3" name="rest_code_01436f3585ee41f18b92225a7131a680-3" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#rest_code_01436f3585ee41f18b92225a7131a680-3"&gt;&lt;/a&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;leftPad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Character&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_01436f3585ee41f18b92225a7131a680-4" name="rest_code_01436f3585ee41f18b92225a7131a680-4" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#rest_code_01436f3585ee41f18b92225a7131a680-4"&gt;&lt;/a&gt;    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;paddingCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_01436f3585ee41f18b92225a7131a680-5" name="rest_code_01436f3585ee41f18b92225a7131a680-5" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#rest_code_01436f3585ee41f18b92225a7131a680-5"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repeating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;paddingCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
&lt;a id="rest_code_01436f3585ee41f18b92225a7131a680-6" name="rest_code_01436f3585ee41f18b92225a7131a680-6" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#rest_code_01436f3585ee41f18b92225a7131a680-6"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can &lt;a class="reference external" href="https://swiftfiddle.com/6x45qsd74jasbj4buruikqivne"&gt;play with it online here&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="results"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-8" role="doc-backlink"&gt;Results&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here are the results, green for correct and red for … less correct.&lt;/p&gt;
&lt;style&gt;
  table.results {
      font-family: monospace;
  }
  td.result-pass {
      background-color: light-dark(#00ff00, #004000);
      color: light-dark(black, white);
  }
  td.result-fail {
      background-color: light-dark(#ff8080, #400000);
      color: light-dark(black, white);
  }
&lt;/style&gt;&lt;table class="results"&gt;
  &lt;tr&gt;
    &lt;th&gt;Input&lt;/th&gt;
    &lt;th&gt;Reference&lt;/th&gt;
    &lt;th&gt;Java&lt;/th&gt;
    &lt;th&gt;Haskell&lt;/th&gt;
    &lt;th&gt;Lean&lt;/th&gt;
    &lt;th&gt;Rust&lt;/th&gt;
    &lt;th&gt;Swift&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;𝄞&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𝄞&lt;/td&gt;
    &lt;td class="result-fail"&gt;--------𝄞&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𝄞&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𝄞&lt;/td&gt;
    &lt;td class="result-fail"&gt;------𝄞&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𝄞&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Å&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------Å&lt;/td&gt;
    &lt;td class="result-fail"&gt;--------Å&lt;/td&gt;
    &lt;td class="result-fail"&gt;--------Å&lt;/td&gt;
    &lt;td class="result-fail"&gt;--------Å&lt;/td&gt;
    &lt;td class="result-fail"&gt;-------Å&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------Å&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;𓀾&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𓀾&lt;/td&gt;
    &lt;td class="result-fail"&gt;--------𓀾&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𓀾&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𓀾&lt;/td&gt;
    &lt;td class="result-fail"&gt;------𓀾&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𓀾&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;אֳֽ֑&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------אֳֽ֑&lt;/td&gt;
    &lt;td class="result-fail"&gt;------אֳֽ֑&lt;/td&gt;
    &lt;td class="result-fail"&gt;------אֳֽ֑&lt;/td&gt;
    &lt;td class="result-fail"&gt;------אֳֽ֑&lt;/td&gt;
    &lt;td class="result-fail"&gt;--אֳֽ֑&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------אֳֽ֑&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;résumé&lt;/td&gt;
    &lt;td class="result-pass"&gt;----résumé&lt;/td&gt;
    &lt;td class="result-pass"&gt;----résumé&lt;/td&gt;
    &lt;td class="result-pass"&gt;----résumé&lt;/td&gt;
    &lt;td class="result-pass"&gt;----résumé&lt;/td&gt;
    &lt;td class="result-fail"&gt;--résumé&lt;/td&gt;
    &lt;td class="result-pass"&gt;----résumé&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;résumé&lt;/td&gt;
    &lt;td class="result-pass"&gt;----résumé&lt;/td&gt;
    &lt;td class="result-fail"&gt;--résumé&lt;/td&gt;
    &lt;td class="result-fail"&gt;--résumé&lt;/td&gt;
    &lt;td class="result-fail"&gt;--résumé&lt;/td&gt;
    &lt;td class="result-fail"&gt;résumé&lt;/td&gt;
    &lt;td class="result-pass"&gt;----résumé&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;&lt;p&gt;And pivoted the other way around so you can compare individual inputs more easily:&lt;/p&gt;
&lt;table class="results"&gt;
  &lt;tr&gt;
    &lt;th&gt;Language&lt;/th&gt;
    &lt;th&gt;𝄞&lt;/th&gt;
    &lt;th&gt;Å&lt;/th&gt;
    &lt;th&gt;𓀾&lt;/th&gt;
    &lt;th&gt;אֳֽ֑&lt;/th&gt;
    &lt;th&gt;résumé&lt;/th&gt;
    &lt;th&gt;résumé&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Reference&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𝄞&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------Å&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𓀾&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------אֳֽ֑&lt;/td&gt;
    &lt;td class="result-pass"&gt;----résumé&lt;/td&gt;
    &lt;td class="result-pass"&gt;----résumé&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Java&lt;/td&gt;
    &lt;td class="result-fail"&gt;--------𝄞&lt;/td&gt;
    &lt;td class="result-fail"&gt;--------Å&lt;/td&gt;
    &lt;td class="result-fail"&gt;--------𓀾&lt;/td&gt;
    &lt;td class="result-fail"&gt;------אֳֽ֑&lt;/td&gt;
    &lt;td class="result-pass"&gt;----résumé&lt;/td&gt;
    &lt;td class="result-fail"&gt;--résumé&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Haskell&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𝄞&lt;/td&gt;
    &lt;td class="result-fail"&gt;--------Å&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𓀾&lt;/td&gt;
    &lt;td class="result-fail"&gt;------אֳֽ֑&lt;/td&gt;
    &lt;td class="result-pass"&gt;----résumé&lt;/td&gt;
    &lt;td class="result-fail"&gt;--résumé&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Lean&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𝄞&lt;/td&gt;
    &lt;td class="result-fail"&gt;--------Å&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𓀾&lt;/td&gt;
    &lt;td class="result-fail"&gt;------אֳֽ֑&lt;/td&gt;
    &lt;td class="result-pass"&gt;----résumé&lt;/td&gt;
    &lt;td class="result-fail"&gt;--résumé&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Rust&lt;/td&gt;
    &lt;td class="result-fail"&gt;------𝄞&lt;/td&gt;
    &lt;td class="result-fail"&gt;-------Å&lt;/td&gt;
    &lt;td class="result-fail"&gt;------𓀾&lt;/td&gt;
    &lt;td class="result-fail"&gt;--אֳֽ֑&lt;/td&gt;
    &lt;td class="result-fail"&gt;--résumé&lt;/td&gt;
    &lt;td class="result-fail"&gt;résumé&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Swift&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𝄞&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------Å&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------𓀾&lt;/td&gt;
    &lt;td class="result-pass"&gt;---------אֳֽ֑&lt;/td&gt;
    &lt;td class="result-pass"&gt;----résumé&lt;/td&gt;
    &lt;td class="result-pass"&gt;----résumé&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;&lt;/section&gt;
&lt;section id="comments"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-9" role="doc-backlink"&gt;Comments&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Rust, as expected, gets &lt;a class="reference external" href="https://en.wiktionary.org/wiki/nul_points"&gt;nul points&lt;/a&gt;. What can I say?&lt;/p&gt;
&lt;p&gt;Vibe-coding with Swift: 💯&lt;/p&gt;
&lt;p&gt;Other that, we can see:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;The only item that all implementations (apart from Rust) get correct is entry 5, the first of the two &lt;code class="docutils literal"&gt;résumé&lt;/code&gt; options.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Java is mostly consistent with the others, but it appears it doesn’t like musical notation, or Egyptian hieroglyphics (item 3 is “standing mummy”), which seems a little rude.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The score so far:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fancy-pants languages and formal verification: 0&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vibe-coding it with ChatGPT: 1&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="explanation"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-10" role="doc-backlink"&gt;Explanation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OK, I’ve had my fun now :-)&lt;/p&gt;
&lt;p&gt;(The original “Let’s Prove Leftpad” project was done “because it is funny”, and this post is in the same spirit. I want to be especially clear that &lt;a class="reference external" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/"&gt;I’m not actually a fan of vibe-coding&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;What’s actually going on here? There are two main issues, both tied up with the concept of “the length of a string”.&lt;/p&gt;
&lt;p&gt;(If you already know enough about Unicode, or don’t care about the details, you can skip to the “What went wrong?” section to continue discussion regarding formal verification).&lt;/p&gt;
&lt;p&gt;First:&lt;/p&gt;
&lt;section id="what-is-a-character"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-11" role="doc-backlink"&gt;What is a character?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Strings are composed of “characters”, but what are they?&lt;/p&gt;
&lt;p&gt;Most modern computer languages, and all the ones I included above, use Unicode as the basis for answering this. Unicode, at its heart, is a list of “code points” (although it is bit more than this). A code point, however, is not exactly a character.&lt;/p&gt;
&lt;p&gt;Many of the code points you use most often, like &lt;a class="reference external" href="https://unicodeplus.com/U+0041"&gt;Latin Capital Letter A U+0041&lt;/a&gt;, are exactly what you think of as a character. But many are not. These include:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Combining_character"&gt;combining characters&lt;/a&gt;, which are used to add accents and marks to other characters. These have some complexity:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First, whether you regard the accent as part of the character or not can be a matter of debate. For example, in &lt;code class="docutils literal"&gt;é&lt;/code&gt;, “e acute”, you might think of this as a different letter to &lt;code class="docutils literal"&gt;e&lt;/code&gt;, or an &lt;code class="docutils literal"&gt;e&lt;/code&gt; plus an acute accent. In some languages, this distinction is critical e.g. in Turkish &lt;code class="docutils literal"&gt;ç&lt;/code&gt; is not just “c with some decoration”, it’s an entirely distinct letter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Second, Unicode often has multiple ways of generating these accented characters, either “pre-composed” as a single code point, or as a combination of multiple code points. So, there is both:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;é: &lt;code class="docutils literal"&gt;Latin Small Letter E With Acute U+00E9&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;é: &lt;code class="docutils literal"&gt;Latin Small Letter E U+0065&lt;/code&gt; + &lt;code class="docutils literal"&gt;Combining Acute Accent U+0301&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://unicode-explorer.com/articles/space-characters"&gt;various spacing characters&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Unicode_control_characters"&gt;control characters&lt;/a&gt; such as bidi isolation characters.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and probably more&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, Unicode has another concept, called the “extended grapheme cluster”, or “user-perceived character”, which more closely maps to what you might think of as a “character”. That’s the concept I’m implicitly using for my claim of what leftpad should output.&lt;/p&gt;
&lt;p&gt;Secondly, there is the question of:&lt;/p&gt;
&lt;/section&gt;
&lt;section id="how-does-a-programming-language-handle-strings"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-12" role="doc-backlink"&gt;How does a programming language handle strings?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Different languages have different fundamental answers to the question of “what is a string?”, different internal representations of them (how the data is actually stored), and different ways of exposing strings to the programmer.&lt;/p&gt;
&lt;p&gt;Some languages, especially performance oriented ones, provide little to zero insulation from the internal representation, while others provide a fairly strong abstraction. Some languages, like Haskell, provide &lt;a class="reference external" href="https://hasufell.github.io/posts/2024-05-07-ultimate-string-guide.html#string-types"&gt;multiple string types&lt;/a&gt; (which can be used with string literals in your code with the &lt;a class="reference external" href="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_strings.html"&gt;OverloadedStrings extension&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;At this point, as well as “code points”, we’ve got to consider “encodings”. If you have a code point, even a “simple” one like &lt;code class="docutils literal"&gt;U+0041&lt;/code&gt; (&lt;code class="docutils literal"&gt;A&lt;/code&gt;), you have said nothing about how you are going to store or transmit that data. An encoding is a system for doing that. Two relevant ones here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/UTF-8"&gt;UTF-8&lt;/a&gt; is probably the most common/popular one. It uses anything from 1 to 4 bytes to express code points. It has lots of useful properties, but an important one is backwards compatibility with ASCII - if you happen to be limited to the 127 characters found in ASCII, then UTF-8 encoded Unicode text is one-byte-per-codepoint, and is byte-for-byte the the same as ASCII.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/UTF-16"&gt;UTF-16&lt;/a&gt; is an encoding where most code points (specifically those in the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane"&gt;Basic Multilingual Plane or BMP&lt;/a&gt;) take up 2 bytes, and the remainder can be specified using 4 bytes and a system called “surrogate pairs”.&lt;/p&gt;
&lt;p&gt;UTF-16 exists because originally Unicode had less than 65,536 code points, meaning you could represent all points with just two bytes, and it was thought we would never need more.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In terms of languages today, with some simplification we can say the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In Haskell, Lean, Python, and many other languages, &lt;strong&gt;strings are sequences of Unicode code points&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;There is little attempt to hide the idea of code points from you, although in some, like Python, the internal representation itself might be hidden (see &lt;a class="reference external" href="https://peps.python.org/pep-0393/"&gt;PEP 393&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In Java and Javascript, &lt;strong&gt;strings are sequences of UTF-16 items&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Originally strings were intended to be sequences of code points, but when Unicode grew, to avoid breaking all existing Java code, it morphed into the UTF-16 compromise.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In Rust, &lt;strong&gt;strings are UTF-8 encoded Unicode code points&lt;/strong&gt; (see &lt;a class="reference external" href="https://doc.rust-lang.org/std/string/struct.String.html"&gt;String&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Rust does also have an easily accessible &lt;a class="reference external" href="https://doc.rust-lang.org/std/primitive.str.html#method.chars"&gt;chars&lt;/a&gt; method/concept, which corresponds to a Unicode code point. I didn’t use this above - Rust would have behaved the same as Haskell/Lean if I had.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In Swift, &lt;strong&gt;strings are sequences of user-perceived characters&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I’m not a user of Swift, but &lt;a class="reference external" href="https://developer.apple.com/documentation/swift/string"&gt;from the docs&lt;/a&gt; it appears to do a pretty good job of abstracting away from the “sequence of code point” paradigm. For example, iterating over a string gets you the “characters” (i.e. extended grapheme clusters) one by one, and the &lt;code class="docutils literal"&gt;.count&lt;/code&gt; property gives you the number of such characters. It does also have a &lt;code class="docutils literal"&gt;.length&lt;/code&gt; property that gives you the number of code points.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="putting-them-together"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-13" role="doc-backlink"&gt;Putting them together&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;These differences, between them, explain the differences in output above. In more detail:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;the treble clef &lt;code class="docutils literal"&gt;𝄞&lt;/code&gt; is a single code point, but it is outside the BMP and requires 2 UTF-16 items. So Java considers the length to be 2, and only 8 padding characters were added, in contrast to other languages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I created the &lt;code class="docutils literal"&gt;Å&lt;/code&gt; using two code points (although there is a pre-composed version of this character).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;אֳֽ֑&lt;/span&gt;&lt;/code&gt; is Hebrew, and is composed an Aleph followed by multiple vowel marks and a cantillation mark, bringing it up to 4 code points.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal"&gt;résumé&lt;/code&gt; was spelled in two different ways, one with precomposed &lt;code class="docutils literal"&gt;é&lt;/code&gt; which is one code point, the other with combining characters where each &lt;code class="docutils literal"&gt;é&lt;/code&gt; requires two code points.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;None of the inputs was wholly in the ASCII range, so encoding them as UTF-8 requires more bytes, which is why Rust (as I used it) behaved as it did.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="what-went-wrong"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-14" role="doc-backlink"&gt;What went wrong?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For me, the biggest issue is not the “code points” vs “characters” debate, which is responsible for most of the variation shown, but the issue that resulted in the difference in the Java output i.e. UTF-16. All of the others (if I hadn’t stitched up Rust) would have resulted in the same output at least.&lt;/p&gt;
&lt;p&gt;Apparently, nothing in the process of doing the formal verification forced the implementations to converge, and I think it is pretty fair to conclude that that at least one of the implementations must be faulty, given that they produce different output.&lt;/p&gt;
&lt;p&gt;So what went wrong?&lt;/p&gt;
&lt;/section&gt;
&lt;section id="lies-damned-lies-and-natural-language"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-15" role="doc-backlink"&gt;Lies, damned lies and natural language&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;English (or any natural language) is at the heart of the problem here. We should start with the phrase “provably correct”. It has a technical definition, but I’m not convinced those English words help us. The post accompanying the &lt;a class="reference external" href="https://ucsd-progsys.github.io/liquidhaskell-blog/2018/05/17/hillel-verifier-rodeo-I-leftpad.lhs/"&gt;LiquidHaskell entry for Leftpad&lt;/a&gt; puts it this way:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My eyes roll whenever I read the phrase “proved X (a function, a program) correct”.&lt;/p&gt;
&lt;p&gt;There is no such thing as “correct”.&lt;/p&gt;
&lt;p&gt;There are only “specifications” or “properties”, and proofs that ensures that your code matches those specifications or properties.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For these reasons, I think I’d prefer talking about “formally verified” functions – it at least prompts you to ask “what does that mean”, and maybe suggests that you should be thinking about what, specifically, has been verified.&lt;/p&gt;
&lt;p&gt;The next bit of English to trip us up is “the length of a string”. It’s extremely easy to imagine this is an easy concept, but in the presence of Unicode it really isn’t.&lt;/p&gt;
&lt;p&gt;Hillel’s original informal requirements don’t actually use that phrase, instead they use the pseudo-code &lt;code class="docutils literal"&gt;max(n, len(str))&lt;/code&gt;. Looking at the implementations, it appears people have subconsciously interpreted this as “the length of the string”, and then assumed that the functions like &lt;code class="docutils literal"&gt;length&lt;/code&gt; or &lt;code class="docutils literal"&gt;size&lt;/code&gt; that their language provides do “the right thing”.&lt;/p&gt;
&lt;p&gt;We could conclude this is in fact a problem with informal requirements – it was at the level of interpreting those requirements that this went wrong. Therefore, we need &lt;strong&gt;more&lt;/strong&gt; formal specifications and verification, &lt;strong&gt;not less&lt;/strong&gt;. But I don’t think this gets round the fact that we’ve got to translate at some point, and at that point you’ve got trouble.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-is-correct"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-16" role="doc-backlink"&gt;What is correct?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The issue I haven’t addressed so far is whether my reference output and the Swift implementation are actually “correct”. The reality is that you can make arguments for different things.&lt;/p&gt;
&lt;p&gt;Implicitly, I’m arguing that &lt;strong&gt;left pad should be used for visual alignment in a fixed width context&lt;/strong&gt;, and the implementation that does the best at that is the best one. I think that is a pretty reasonable case. But I’m sure you could make a case for other output – there isn’t actually anything that says &lt;strong&gt;what&lt;/strong&gt; left pad should be used for. It’s possible that there are use cases where “the language’s underlying concept of the length of a string, whatever that may be” is the most important thing.&lt;/p&gt;
&lt;p&gt;In addition, I was hiding the fact that “fixed width” is yet another lie:&lt;/p&gt;
&lt;p&gt;I was originally going to use a flag character like 🏴󠁧󠁢󠁥󠁮󠁧󠁿 as one of my inputs, which is a single “extended grapheme cluster” that uses no less then &lt;strong&gt;7 code points&lt;/strong&gt;. It also results in 14 UTF-16 units in Java. The problem was that this character, like most emojis and many other characters from wide scripts like Chinese, takes up a &lt;strong&gt;double width&lt;/strong&gt; even with a monospace font.&lt;/p&gt;
&lt;p&gt;To maintain the subterfuge of “look how these all line up neatly in the correct output”, I was forced to use other examples. In other words, the example use case I was relying on to prove that these leftpad implementations were broken, is itself a broken concept. But I would still maintain that my reference output is closer to what you would expect leftpad to do.&lt;/p&gt;
&lt;p&gt;A big point is this: even if we argue that a give implementation is “correct” (in that it does what its specifications say it does), that doesn’t mean you are &lt;strong&gt;using&lt;/strong&gt; it correctly. Are you using it for its intended purpose and context? That seems like a really hard question to answer even for leftpad, and many other real world functions are similar.&lt;/p&gt;
&lt;p&gt;So, I’m not sure what my final conclusion is, other than “programming is hard, &lt;span class="strike"&gt;let’s go shopping&lt;/span&gt;  let’s eat chocolate” (alternative suggested by my wife, that’s my plan for the evening then).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="confessions-and-corrections"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-17" role="doc-backlink"&gt;Confessions and corrections&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Swift implementation was indeed written by ChatGPT, and it got it right first time, with just the prompt “Implement leftpad in Swift”. However:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Swift is the only language I know where an implementation that does what I wanted it to do is that simple.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I followed up by getting ChatGPT to produce a Python version, and it had all the same problems as Haskell/Lean and similar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I noticed that Swift doesn’t calculate strings lengths the way I would need for my use case for some characters, such as &lt;a class="reference external" href="https://unicodeplus.com/U+200B"&gt;Zero Width Space U+200B&lt;/a&gt; and  &lt;a class="reference external" href="https://unicodeplus.com/U+2067"&gt;Right-To-Left Isolate U+2067&lt;/a&gt;, which I would need to count for zero length.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As mentioned, the other way to use the Rust version has the same behaviour as the Haskell/Lean/etc versions. ChatGPT did actually point out to me that this way was better, and the other way (the one I used) was adequate only if the input was limited to ASCII.&lt;/p&gt;
&lt;p&gt;I do feel &lt;strong&gt;slightly&lt;/strong&gt; justified in my treatment of this solution however - &lt;a class="reference external" href="https://github.com/hwayne/lets-prove-leftpad/blob/ea9c0f09a2d3e981d82118497c307844fc7b1f49/creusot%20(rust)/src/lib.rs#L8"&gt;the provided code&lt;/a&gt; didn’t give a function that actually took a string, nor did it force you to use it in a specific way. It dodged the hard part, so I punished it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="response"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/breaking-provably-correct-leftpad/#toc-entry-18" role="doc-backlink"&gt;Response&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hillel was kind enough to look at this post, and had this response to add:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In general, formally verified code can “go wrong” in two ways: the proven properties that don’t match what we need, or the proof depends on assumptions that are not true in practice. This is a good example of the former. An example of the latter would be the assumption that the output string is storable in memory. None of the formally verified functions will correctly render &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;leftpad(“-“,&lt;/span&gt; 1e300, “foo”)&lt;/code&gt;. This is why we always need to be careful when talking about “proving correctness”. In formal methods, “correct” &lt;strong&gt;always&lt;/strong&gt; means “conforms to a certain specification under certain assumptions”, which is very different from the colloquial use of “correct” (does what you want and doesn’t have bugs).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He also pointed out that padding/alignment functionality available in standard libraries, like Python’s &lt;a class="reference external" href="https://docs.python.org/3/library/string.html#formatspec"&gt;Format Specification Mini-Language&lt;/a&gt; and Javascript’s &lt;a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart"&gt;padStart&lt;/a&gt;, have similar issues.&lt;/p&gt;
&lt;/section&gt;</content>
    <category term="haskell" label="Haskell"/>
    <category term="python" label="Python"/>
    <category term="software-development" label="Software development"/>
  </entry>
  <entry>
    <title>Why I’m not letting the juniors use GenAI for coding</title>
    <id>https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/</id>
    <updated>2025-07-26T21:30:00+01:00</updated>
    <published>2025-07-26T21:30:00+01:00</published>
    <author>
      <name>Luke Plant</name>
    </author>
    <link rel="alternate" type="text/html" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/"/>
    <summary type="html">&lt;p&gt;TLDR: because I want them to become seniors one day, and I want them to enjoy being developers&lt;/p&gt;</summary>
    <content type="html">&lt;p&gt;In my current project, I am training some junior developers — some of them pretty much brand new developers — and one of the first rules I gave them was “ensure that &lt;a class="reference external" href="https://code.visualstudio.com/docs/copilot/overview"&gt;Copilot&lt;/a&gt; (or any other &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Generative_artificial_intelligence"&gt;Generative AI&lt;/a&gt; assistant that will write code for you in your editor) is turned off”. This post explains why. The long and short of it is this: it’s because I want the junior developers to become senior developers, and I want them to enjoy programming as well.&lt;/p&gt;
&lt;p&gt;Other people might also be interested in my reasons, so I’m writing this as a blog post.&lt;/p&gt;
&lt;p&gt;I’ve read many, many other posts that are &lt;a class="reference external" href="https://lucumr.pocoo.org/2025/6/4/changes/"&gt;for&lt;/a&gt; and &lt;a class="reference external" href="https://blog.glyph.im/2025/06/i-think-im-done-thinking-about-genai-for-now.html"&gt;against&lt;/a&gt;, or just plain &lt;a class="reference external" href="https://nolanlawson.com/2025/04/02/ai-ambivalence/"&gt;depressed&lt;/a&gt;, and some of this is about personal preference, but as I’m making rules for other people, I feel I ought to justify those rules just a little.&lt;/p&gt;
&lt;p&gt;I’m also attempting to write this up in a way that hopefully non-programmers can understand. I don’t want to write a whole load of posts about this increasingly tedious subject, so I’m making one slightly broader one that I can just link to if anyone asks my opinion.&lt;/p&gt;
&lt;p&gt;Rather than talk generalities, I’ll build my case using a single very concrete example of real output from an &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Large_language_model"&gt;LLM&lt;/a&gt; on a real task.&lt;/p&gt;
&lt;nav class="contents" id="contents" role="doc-toc"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#top"&gt;Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#the-problem" id="toc-entry-1"&gt;The problem&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#what-i-did" id="toc-entry-2"&gt;What I did&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#how-it-went" id="toc-entry-3"&gt;How it went&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#the-main-point" id="toc-entry-4"&gt;The main point&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#so-what" id="toc-entry-5"&gt;So what?&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#scenario-1" id="toc-entry-6"&gt;Scenario 1&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#scenario-2" id="toc-entry-7"&gt;Scenario 2&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#scenario-3" id="toc-entry-8"&gt;Scenario 3&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#what-about-the-mid-level-programmer" id="toc-entry-9"&gt;What about the mid-level programmer?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#and-what-about-the-senior-programmer" id="toc-entry-10"&gt;And what about the senior programmer?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#updates" id="toc-entry-11"&gt;Updates&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;section id="the-problem"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#toc-entry-1" role="doc-backlink"&gt;The problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This example comes from a one-off problem I created when I accidentally ended up with data in two separate &lt;a class="reference external" href="https://www.sqlite.org/"&gt;SQLite&lt;/a&gt; databases, when I wanted it one. The data wasn’t &lt;strong&gt;that&lt;/strong&gt; important – it was all just test data for the project I’m currently working on – so I could have just thrown one of these databases away. But the data had &lt;strong&gt;some&lt;/strong&gt; value to me, so I decided to see if I could use an LLM to quickly create a one-off script to merge the two databases.&lt;/p&gt;
&lt;p&gt;Merging databases sounds like something so common there would be a generic tool to do it, but in reality the devil is in the details, and every &lt;a class="reference external" href="https://www.ibm.com/think/topics/database-schema"&gt;database schema&lt;/a&gt; has specifics that mean you need a custom solution.&lt;/p&gt;
&lt;p&gt;The specific databases in question were pretty small, with a pretty simple schema. The only big problem with the schema, common to many databases schemas, is how you deal with &lt;strong&gt;ID&lt;/strong&gt; fields:&lt;/p&gt;
&lt;p&gt;This database schema has multiple tables, and records in one table are related to records in another table using an ID column, which in my case was just a unique number starting at one: 1, 2, 3 etc. To make it concrete, let’s say my database stored a list of houses, and a list of rooms in each house (it didn’t, but it works as an example).&lt;/p&gt;
&lt;p&gt;So you have a &lt;code class="docutils literal"&gt;houses&lt;/code&gt; table:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th class="head"&gt;&lt;p&gt;id&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;address&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;1&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;45 Main St&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;2&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;67 Mayfair&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;3&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;2 Bag End&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;…&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;…&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And a &lt;code class="docutils literal"&gt;rooms&lt;/code&gt; table:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th class="head"&gt;&lt;p&gt;id&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;house_id&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;name&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;1&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;1&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Dining room&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;2&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;1&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Living room&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;3&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;2&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;The Library&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;…&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;…&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;…&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;house_id&lt;/code&gt; column above links each room with a specific house from the &lt;code class="docutils literal"&gt;houses&lt;/code&gt; table. In the example above, the rooms with ids &lt;code class="docutils literal"&gt;1&lt;/code&gt; and &lt;code class="docutils literal"&gt;2&lt;/code&gt; both belong to house with ID &lt;code class="docutils literal"&gt;1&lt;/code&gt;, aka “45 Main St”.&lt;/p&gt;
&lt;p&gt;Due to the database merge, we’ve got multiple instances of each of these tables, and they could very easily be using the same ID values to refer to different things. When I come to merge the tables:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;I’ve got to assign new values in the &lt;code class="docutils literal"&gt;id&lt;/code&gt; columns for each table&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;for the &lt;code class="docutils literal"&gt;rooms&lt;/code&gt; table, I’ve got to remember the mapping of old-to-new IDs from the &lt;code class="docutils literal"&gt;houses&lt;/code&gt; table and apply the same mapping to the &lt;code class="docutils literal"&gt;house_id&lt;/code&gt; column.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If I get this wrong, the data will be horribly confused and corrupted.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-i-did"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#toc-entry-2" role="doc-backlink"&gt;What I did&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I used &lt;a class="reference external" href="https://aider.chat/"&gt;aider.chat&lt;/a&gt;, which is a pretty good project with a good reputation, and one that I’m used to, to the point of being reasonably competent (although I can’t claim I use any of these tools a massive amount). This was a while back, and I can’t remember which LLM model I was using, but I think it was one of the best ones from Claude, or maybe DeepSeek-R1, both of which are (or were) well regarded.&lt;/p&gt;
&lt;p&gt;I fed it the database schema as a SQL file, then prompted it similar to below:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Write a script that takes a list of db files as command line args, and merges the contents. The output will have a single ’metadata’ table copied from one of the input files. The ’rooms’ and ’houses’ tables will be merged, being careful to update ’id’ and ’house_id’ columns correctly, so that they refer to new values.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’m not going to spend time arguing about whether this was the best possible tool/model/prompt, that’s an endless time sink – I’m happy that I did a decent enough job on all fronts for my comments to be fair.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="how-it-went"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#toc-entry-3" role="doc-backlink"&gt;How it went&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The results were basically great in most ways that most people would care about: the LLM created a mostly working script of a few hundred lines of code in a few minutes. If I remember correctly it was pretty much there on the first attempt.&lt;/p&gt;
&lt;p&gt;I did actually care about the data, so I carefully checked the script, especially the code that mapped the IDs.&lt;/p&gt;
&lt;p&gt;There was one bit of code that created the mapping, and a second bit of code that then used it. For this second part, correct code would have looked something like this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_054cacf7909c4f1998a27db4ff51291a-1" name="rest_code_054cacf7909c4f1998a27db4ff51291a-1" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#rest_code_054cacf7909c4f1998a27db4ff51291a-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;new_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id_mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;old_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal"&gt;id_mapping&lt;/code&gt; is a mapping (dictionary) that contains the “old ID” as keys, and the “new ID” as values&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal"&gt;old_id&lt;/code&gt; is a variable containing an old ID value.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the brackets syntax &lt;code class="docutils literal"&gt;[old_id]&lt;/code&gt; does a dictionary lookup and returns the value found – in other words, given the old ID it returns the new ID.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is a crucial question with mappings like this: what happens if, for some reason, the mapping doesn’t contain the &lt;code class="docutils literal"&gt;old_id&lt;/code&gt; value?  With the code as written above, the dictionary lookup will raise an exception, which will cause the code to abort at this point. &lt;strong&gt;This is a good thing&lt;/strong&gt; – since somehow we are missing a value, there is nothing we can do with the current data, and obviously there is some serious problem with our code which means that aborting loudly is the best of our options, or at least the most sensible default.&lt;/p&gt;
&lt;p&gt;However, what the LLM actually wrote was like this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_a2ed15c86abe4ebdaa1d21e4ee059d7d-1" name="rest_code_a2ed15c86abe4ebdaa1d21e4ee059d7d-1" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#rest_code_a2ed15c86abe4ebdaa1d21e4ee059d7d-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;new_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id_mapping&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This code also does a dictionary lookup, but it uses the &lt;a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#dict.get"&gt;get method&lt;/a&gt; to supply a default value. If the lookup fails because the value is missing, the default is returned instead of raising an exception. The default given here is &lt;code class="docutils literal"&gt;old_id&lt;/code&gt;, which is &lt;strong&gt;the original value&lt;/strong&gt;.  This is, of course, &lt;strong&gt;disastrously wrong&lt;/strong&gt;.  If, for whatever reason, the new ID value is missing, the old ID value is not a good enough fallback – that’s just going to create horribly corrupted data. Worst of all, it will do so absolutely silently – it could just fill the output data with essentially garbage, with no warning that something had gone wrong.&lt;/p&gt;
&lt;p&gt;We might ask where this idea came from — the LLM has written extra code to produce a much worse result, why? The answer is most likely found, in part, in the way the LLM was trained – that is, on mediocre code.&lt;/p&gt;
&lt;p&gt;A better answer to “why” the AI wrote this is much more troubling, but I’ll come back to that.&lt;/p&gt;
&lt;p&gt;We might also ask, “does it matter?”&lt;/p&gt;
&lt;p&gt;Part of the answer to that is context. For the project I’m currently working on, “silently wrong output” is one of the very worst things we can do. There are projects with different priorities, and there are even a few where quality barely matters at all, for which we really wouldn’t care about things like this. There are also lots of projects where you might expect people &lt;strong&gt;would&lt;/strong&gt; care about this, but they actually don’t, which is fairly sad. Anyway, I’m glad not to be currently working on any projects like that – correct output matters, and I like that.&lt;/p&gt;
&lt;p&gt;In this case, there is a second reason you might say it doesn’t matter: the rest of the code was actually correct. This meant that the mapping dictionary was complete, so the incorrect behaviour would never get triggered – the problem I’ve found is actually hypothetical. So what am I worrying about?&lt;/p&gt;
&lt;p&gt;The problem is that in real code, the hypothetical could become reality very quickly. For example:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Some change to the code could introduce a bug which means that the mapping is now missing entries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A refactoring means the code gets used in a slightly different situation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There is some change to the context in which the code is used. For example, if other processes were writing to one of these databases while the merge operation was happening, it would be entirely possible for the mapping dictionary to be missing entries.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So we find ourselves in an interesting situation:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;The code, as written by the LLM, appears on the one hand to be perfectly adequate, if measured according to the metric of “does it work right now”.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the other hand, the code is disastrously inadequate if measured by the standard of letting it go anywhere near important data, or anything you would want to live long term in your code base.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="the-main-point"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#toc-entry-4" role="doc-backlink"&gt;The main point&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Writing correct code is hard. The difference between correct and disastrously bad can be extremely subtle. These differences are easily missed by the untrained eye, and you might not even be able to come up with an example where the bad code fails, because in its initial context it does not.&lt;/p&gt;
&lt;p&gt;There are many, many other examples of this in programming, and there are many examples of LLMs tripping up like this. Often it is not what is present that is even the problem, but what is absent.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="so-what"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#toc-entry-5" role="doc-backlink"&gt;So what?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OK, so LLMs sometimes write horribly flawed code that appears to work. Couldn’t we say the same about junior programmers?&lt;/p&gt;
&lt;p&gt;Yes, we could. I think the big difference comes when you think about what happens next, after this bad code is written. So, I’ll think about this under 3 scenarios.&lt;/p&gt;
&lt;section id="scenario-1"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#toc-entry-6" role="doc-backlink"&gt;Scenario 1&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In this scenario, I, as a senior developer, am the person who got the LLM to write the code, and I’m now tasked with code review in order to find potential flaws and time-bombs like the above.&lt;/p&gt;
&lt;p&gt;First, in this scenario, the temptation to not check carefully is &lt;strong&gt;very strong&lt;/strong&gt;. The whole reason I’m tempted to use an LLM in the first place is that I don’t want to devote much time to the task. For me this happens when:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;I can’t &lt;strong&gt;justify&lt;/strong&gt; much time, because I consider it’s not that important - it’s just something I need to do to get back to the main thing I’m doing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I don’t think I will &lt;strong&gt;enjoy&lt;/strong&gt; spending that time.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For both of these cases, the “must go faster” mindset means it’s psychologically very hard to slow down and do the careful review needed.&lt;/p&gt;
&lt;p&gt;So, I’m unlikely to review this code as carefully as I should. For me, assuming that the code matters at all, this is a killer problem.&lt;/p&gt;
&lt;p&gt;Maybe someone else would review it and catch this? That’s not good enough for me – &lt;strong&gt;I don’t rely on other people reviewing my code&lt;/strong&gt;. I’m a professional software developer who works on important projects. Sometimes I work alone, with no-one else doing effective review. My clients expect and deserve that I write code that actually works.&lt;/p&gt;
&lt;p&gt;Of course I also know that I’m far from perfect, and welcome any code review I can get. But even when I think there is review going on, I treat it as a backup, an extra safety measure, and not a reason to justify being sloppy and careless.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="scenario-2"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#toc-entry-7" role="doc-backlink"&gt;Scenario 2&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In this scenario, the code was written by a junior developer. In contrast to the previous section, I don’t expect junior developers to produce code that works. But I do expect them to &lt;strong&gt;learn&lt;/strong&gt; to do so. And I hope that that I will be rightly suspicious and do a thorough review.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://blog.glyph.im/2025/06/i-think-im-done-thinking-about-genai-for-now.html"&gt;Like Glyph&lt;/a&gt;, I actually quite enjoy doing code review for junior developers, as long as they actually have both the willingness and capacity to improve (which they usually do, but not always). Code review can be a great opportunity to actually train someone.&lt;/p&gt;
&lt;p&gt;So what happens in this scenario when I, hopefully, after careful review spot the flaw and point it out?&lt;/p&gt;
&lt;p&gt;If I have time to properly mentor a developer, the process would be a conversation (preferably in person or a video call) that starts something like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When you wrote &lt;code class="docutils literal"&gt;new_id = old_id_to_new_id.get(old_id, old_id)&lt;/code&gt;, can you explain &lt;strong&gt;what was going through your mind&lt;/strong&gt;? Why did you choose to write &lt;code class="docutils literal"&gt;.get(old_id, old_id)&lt;/code&gt;, rather than the simpler and shorter &lt;code class="docutils literal"&gt;[old_id]&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It’s possible the reason was them thinking “doing something is better than crashing”. We can then address that disastrously wrong (but quite common) mindset. This will hopefully be a very fruitful discussion, because it will actually correct the issue &lt;strong&gt;at the root&lt;/strong&gt;, and so stop it happening again, or at least make it much less likely.&lt;/p&gt;
&lt;p&gt;In some cases, there isn’t a reason “why” they wrote the code they did –  sometimes junior developers just don’t understand a lot of the code they are writing, they are just guessing until something seems to work. In that case, we can address that problem:&lt;/p&gt;
&lt;p&gt;The developer needs to go back to basics of things like assignments and function calls etc. The developer must reach the point where they can explain and justify every last jot and tittle of code they produce. It seems pretty obvious to me that the best way to achieve that is to make them write pretty much all their code, at least at the level of choosing each “token” that they add (e.g. choosing from a list of methods with auto-complete is OK, but not more than that). If I want them to be able to justify each token choice, it is essential to make them engage their brain and choose each token.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="scenario-3"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#toc-entry-8" role="doc-backlink"&gt;Scenario 3&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The third scenario is again that the junior developer “produced” the code, and I’m now reviewing it, but it turns out they just prompted some LLM assistant.&lt;/p&gt;
&lt;p&gt;At this point, the first step in the root cause analysis – the question “what was your thought process in producing this code” – fails immediately.&lt;/p&gt;
&lt;p&gt;There is no real answer to the question “why” the LLM wrote the bad code in the first place. You can’t ask it “what were you were thinking” and get a meaningful answer – &lt;a class="reference external" href="https://lukeplant.me.uk/blog/posts/chatgpt-no-inner-monologue-or-meta-cognition/"&gt;it’s pointless to ask so don’t even try&lt;/a&gt;. It lacks the meta-cognition needed for self-improvement, and much of the time &lt;a class="reference external" href="https://machinelearning.apple.com/research/illusion-of-thinking"&gt;when it looks like it is reasoning, it is actually just pattern matching&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The next problem is that the LLM basically can’t learn, at least not deeply enough to make a significant difference. You can tell it “don’t do that again”, and it might partly work, but you can’t really change how it thinks, just what the current instructions are.&lt;/p&gt;
&lt;p&gt;So we can’t address the root cause.&lt;/p&gt;
&lt;p&gt;What about the junior developer learning from this, in this scenario – is there something they could take away which would prevent the mistake in the future?&lt;/p&gt;
&lt;p&gt;If their own mind wasn’t engaged in making the mistake, I think it is quite unlikely that they will be able to effectively learn from the LLM’s mistakes. For the junior dev, the possible take-aways about changes to &lt;strong&gt;their own behaviour&lt;/strong&gt; are:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Check the LLM’s output more carefully. But I’m not convinced they are equipped at this point to really do checking – they first need practice in the careful thinking about what correct code looks like, to gain the trained eye that can spot subtle issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Don’t use LLMs to write code (which is what I’m arguing here)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="what-about-the-mid-level-programmer"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#toc-entry-9" role="doc-backlink"&gt;What about the mid-level programmer?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At what point do I suggest the junior developers should start using LLMs for some of the more boring tasks? Once they are “mid-level” (whatever that means), is it appropriate?&lt;/p&gt;
&lt;p&gt;There is obviously a point at which you let people make their own decisions.&lt;/p&gt;
&lt;p&gt;For myself, I’m pretty firmly convinced that the software I’ve just recently
created with 25+ years’ professional experience, I simply couldn’t have created
with only 15 years experience. Not because I’m a poor programmer – I think I’ve got good reason to believe I’m significantly above average (for example, I taught myself machine code and assembly as a young teenager, and I’ve been part of the core team of &lt;a class="reference external" href="https://www.djangoproject.com/"&gt;top open-source projects&lt;/a&gt;). There is just a lot to learn, and always a
lot further you could go.&lt;/p&gt;
&lt;p&gt;In this most recent project, we’ve seen a lot of success because of certain key architectural decisions that I made right at the beginning. Some of these used advanced patterns that 10 years I would not have instinctively reached for, or wouldn’t even have known well enough to attempt them.&lt;/p&gt;
&lt;p&gt;For example, due to difficulties with manual testing of the output in my
current project, we depend critically on regression tests that make heavy use of
a version of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Command_pattern"&gt;the Command pattern&lt;/a&gt; or &lt;a class="reference external" href="https://mmapped.blog/posts/29-plan-execute"&gt;plan-execute pattern&lt;/a&gt;. In fact we use multiple
levels of it, one level being essentially an embedded DSL that has both
an evaluator and compiler. This code is not trivial, and it’s also quite far
from the simplest thing that you would think of, but it has proven critical to
success.&lt;/p&gt;
&lt;p&gt;How did I know I needed these? Well, I can tell you one thing for sure: I would never have got the experience and judgement necessary if I hadn’t been doing a lot of the coding myself over the past 25 years.&lt;/p&gt;
&lt;p&gt;In his video &lt;a class="reference external" href="https://www.youtube.com/watch?v=ltLUadnCyi0"&gt;A tale of two problem solvers&lt;/a&gt;, youtuber &lt;a class="reference external" href="https://www.youtube.com/@3blue1brown"&gt;3Blue1Brown&lt;/a&gt; has a section that is hugely relevant here, especially from 36 minutes onwards. In it, he describes how practising mathematicians will often do hundreds of concrete examples in order to build up intuition and sharpen their skills so that they can tackle more general and harder problems. What particularly struck me was that famous mathematicians throughout history have done this – “they all have this seemingly infinite patience for doing tedious calculations”.&lt;/p&gt;
&lt;p&gt;Computer programming may seem different, in that we deliberately don’t do the tedious calculations, but teach the computer to do that. However, there are huge similarities. Programming, like mathematics, involves a &lt;strong&gt;formal notation&lt;/strong&gt; and &lt;strong&gt;problem solving&lt;/strong&gt;. In the case pf programming, the formal notation is aimed at a machine that will mechanically interpret it to produce a desired behaviour.&lt;/p&gt;
&lt;p&gt;Obviously we do avoid doing lots of long multiplication once we’ve taught the computer to do that. However, given the similarities of the mental processes involved in maths and programming, when it comes to any of the higher level things about how to structure programs, I think we are absolutely fooling ourselves if we think we can avoid doing all the “grunt work” of writing code, organising code bases, slowly improving code structure, etc. and still end up magically knowing all the patterns that we need to use, understanding their trade-offs, and all the reasons why certain architectures or patterns would or wouldn’t be appropriate.&lt;/p&gt;
&lt;p&gt;If you ever want to progress beyond mid-level, I strongly suspect that offloading significant parts of programming to an LLM will greatly reduce your growth. You may be much &lt;strong&gt;faster&lt;/strong&gt; at outputting things of a similar level to what you can currently handle, but I doubt you’ll be able to tackle fundamentally harder projects in the future.&lt;/p&gt;
&lt;p&gt;While I’m talking about youtubers, the video &lt;a class="reference external" href="https://www.youtube.com/watch?v=5eW6Eagr9XA"&gt;The Expert Myth&lt;/a&gt; by &lt;a class="reference external" href="https://www.youtube.com/@veritasium"&gt;Veritasium&lt;/a&gt; is also really helpful here. He describes how expertise requires the following ingredients:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Valid environment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Many repetitions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Timely feedback&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deliberate practice&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As far as I can see, heavy use of LLMs to write code for you will destroy points 2 and 3 – neither the repetitions nor the feedback are really happening if you don’t actually write the code yourself. I doubt that the “deliberate practice and study, in an uncomfortable zone” is going to happen either if you never get happy with the manual bits of coding.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="and-what-about-the-senior-programmer"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#toc-entry-10" role="doc-backlink"&gt;And what about the senior programmer?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To complete this post, we of course want to ask, should the “senior programmer” use LLMs to do a lot of the grunt programming? By senior, I do mean significantly more than the 5 years that many “seniors” have. Does there come a point where you have “arrived” and the arguments in the previous section no longer apply? A time when you basically don’t need to do much more learning and sharpening of skills?&lt;/p&gt;
&lt;p&gt;My answer to that is “I hope not”. Like others, I’m still hoping that by the end of my career I could be at least &lt;a class="reference external" href="https://www.scattered-thoughts.net/writing/speed-matters/"&gt;10x faster&lt;/a&gt; than I am now (without an LLM writing the code for me), maybe even more. Computers are mental levers, so I don’t think that’s ridiculous. I’m hoping not just to be 10x faster, but 10x better in other ways – in terms of reliability, or in terms of being able to tackle problems that would just stump me today, or to which my solutions today would be massively inferior.&lt;/p&gt;
&lt;p&gt;Everything I know about learning says that outsourcing my actual thinking to LLMs is unlikely to produce that result.&lt;/p&gt;
&lt;p&gt;In addition, there are at least some people who, after actively using LLMs integrated into their editor (like Copilot and Cursor), &lt;a class="reference external" href="https://lucianonooijen.com/blog/why-i-stopped-using-ai-code-editors/"&gt;have now stopped doing so because they noticed their skills were rusting&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These arguments, plus the small amount of evidence I have, are enough that I don’t feel the need to make a guinea pig out of myself to collect more data.&lt;/p&gt;
&lt;p&gt;So, for myself, I choose to use LLMs &lt;strong&gt;very rarely&lt;/strong&gt; for actually writing code. Rarely enough that if they were to disappear completely, it would make little difference to me.&lt;/p&gt;
&lt;p&gt;But, aside from the practical arguments regarding becoming a better programmer, one of the big reasons for this is that I &lt;strong&gt;simply enjoy programming&lt;/strong&gt;.  I enjoy the challenge and the process of getting the computer to do exactly what I want it to do, in a reasonable amount of time, expressing myself both precisely and concisely.&lt;/p&gt;
&lt;p&gt;When you are programming at the optimal level of abstraction, it is actually &lt;strong&gt;much nicer&lt;/strong&gt; to express yourself in code compared to English, and often much faster too. Natural language is truly horrible for times when you want precision. And in computer programming, you usually are able to &lt;strong&gt;create the abstractions you need&lt;/strong&gt;, so you can often get close to that optimal level of abstraction.&lt;/p&gt;
&lt;p&gt;Obviously there are times when there is a bad fit between the abstractions you want and the abstractions you have, resulting in a lot of redundancy. You can’t always rewrite everything to make it all as ideal as you want. But if I’m writing large amounts of code at the wrong abstraction level, that’s bad, and I don’t really want a system that helps me to write more and more code like that.&lt;/p&gt;
&lt;p&gt;The point of this section is really for the benefit of the junior developers that I’m forcing to do things “the hard way”. What I’m saying is this: I’m not withholding some special treat that I reserve just for myself. I willingly code in exactly the same way as you, and I really enjoy it. I believe that I’m sparing you the miserable existence of never becoming good at programming, while you keep trying to cajole something that doesn’t understand what it’s doing into producing something you don’t understand either. That just doesn’t sound like my idea of fun.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="updates"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/why-im-not-letting-the-juniors-use-genai-for-coding/#toc-entry-11" role="doc-backlink"&gt;Updates&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;March 2026:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It looks like Carson Gross, of whom I’m a big fan, &lt;a class="reference external" href="https://htmx.org/essays/yes-and/"&gt;agrees with this stance when it comes to his students&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Since writing the above, I have successfully used coding agents to produce &lt;a class="reference external" href="https://github.com/spookylukey/planegcs/"&gt;significant, high quality projects&lt;/a&gt; where I have done extremely little of the actual coding. However, I stand by what I’ve written above: while the agent was coding away, I never relegated myself to merely managing it. I was coding away just as hard, but on a different, related bit of work. I still don’t use AI integrated into my editor.&lt;/p&gt;
&lt;p&gt;I’m not letting my skills stagnate, and if I get an agent to work outside my area of competency, I do it consciously. For example, in the planegcs library linked above, I was quite happy to not improve my understanding of C++ and its build tools. On the other hand, I think I’m going to need to do a Rust project, and I’m intending to do all the coding myself, even though I know an agent could make much more rapid progress, because I actually want to get good at Rust.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;</content>
    <category term="artificial-intelligence" label="Artificial Intelligence"/>
    <category term="python" label="Python"/>
    <category term="software-development" label="Software development"/>
  </entry>
  <entry>
    <title>Statically checking Python dicts for completeness</title>
    <id>https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/</id>
    <updated>2025-06-27T11:09:03+01:00</updated>
    <published>2025-06-27T11:09:03+01:00</published>
    <author>
      <name>Luke Plant</name>
    </author>
    <link rel="alternate" type="text/html" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/"/>
    <summary type="html">&lt;p&gt;A Pythonic way to ensure that your statically-defined dicts are complete, with full source code.&lt;/p&gt;</summary>
    <content type="html">&lt;p&gt;In Python, I often have the situation where I create a dictionary, and want to ensure that it is &lt;strong&gt;complete&lt;/strong&gt; – it has an entry for every valid key.&lt;/p&gt;
&lt;p&gt;Let’s say for my (currently hypothetical) automatic squirrel-deterring water gun system, I have a number of different states the water tank can be in, defined using an enum:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_e3b92173a5f0474b89aa0802cdf31855-1" name="rest_code_e3b92173a5f0474b89aa0802cdf31855-1" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_e3b92173a5f0474b89aa0802cdf31855-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StrEnum&lt;/span&gt;
&lt;a id="rest_code_e3b92173a5f0474b89aa0802cdf31855-2" name="rest_code_e3b92173a5f0474b89aa0802cdf31855-2" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_e3b92173a5f0474b89aa0802cdf31855-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_e3b92173a5f0474b89aa0802cdf31855-3" name="rest_code_e3b92173a5f0474b89aa0802cdf31855-3" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_e3b92173a5f0474b89aa0802cdf31855-3"&gt;&lt;/a&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TankState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StrEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_e3b92173a5f0474b89aa0802cdf31855-4" name="rest_code_e3b92173a5f0474b89aa0802cdf31855-4" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_e3b92173a5f0474b89aa0802cdf31855-4"&gt;&lt;/a&gt;    &lt;span class="n"&gt;FULL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"FULL"&lt;/span&gt;
&lt;a id="rest_code_e3b92173a5f0474b89aa0802cdf31855-5" name="rest_code_e3b92173a5f0474b89aa0802cdf31855-5" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_e3b92173a5f0474b89aa0802cdf31855-5"&gt;&lt;/a&gt;    &lt;span class="n"&gt;HALF_FULL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HALF_FULL"&lt;/span&gt;
&lt;a id="rest_code_e3b92173a5f0474b89aa0802cdf31855-6" name="rest_code_e3b92173a5f0474b89aa0802cdf31855-6" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_e3b92173a5f0474b89aa0802cdf31855-6"&gt;&lt;/a&gt;    &lt;span class="n"&gt;NEARLY_EMPTY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"NEARLY_EMPTY"&lt;/span&gt;
&lt;a id="rest_code_e3b92173a5f0474b89aa0802cdf31855-7" name="rest_code_e3b92173a5f0474b89aa0802cdf31855-7" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_e3b92173a5f0474b89aa0802cdf31855-7"&gt;&lt;/a&gt;    &lt;span class="n"&gt;EMPTY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EMPTY"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In a separate bit of code, I define an RGB colour for each of these states, using a simple dict.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_b8cf6f255be5405da20a3f8426f0e320-1" name="rest_code_b8cf6f255be5405da20a3f8426f0e320-1" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_b8cf6f255be5405da20a3f8426f0e320-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;TANK_STATE_COLORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_b8cf6f255be5405da20a3f8426f0e320-2" name="rest_code_b8cf6f255be5405da20a3f8426f0e320-2" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_b8cf6f255be5405da20a3f8426f0e320-2"&gt;&lt;/a&gt;    &lt;span class="n"&gt;TankState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FULL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x00FF00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_b8cf6f255be5405da20a3f8426f0e320-3" name="rest_code_b8cf6f255be5405da20a3f8426f0e320-3" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_b8cf6f255be5405da20a3f8426f0e320-3"&gt;&lt;/a&gt;    &lt;span class="n"&gt;TankState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HALF_FULL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x28D728&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_b8cf6f255be5405da20a3f8426f0e320-4" name="rest_code_b8cf6f255be5405da20a3f8426f0e320-4" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_b8cf6f255be5405da20a3f8426f0e320-4"&gt;&lt;/a&gt;    &lt;span class="n"&gt;TankState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NEARLY_EMPTY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0xFF9900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_b8cf6f255be5405da20a3f8426f0e320-5" name="rest_code_b8cf6f255be5405da20a3f8426f0e320-5" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_b8cf6f255be5405da20a3f8426f0e320-5"&gt;&lt;/a&gt;    &lt;span class="n"&gt;TankState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EMPTY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0xFF0000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_b8cf6f255be5405da20a3f8426f0e320-6" name="rest_code_b8cf6f255be5405da20a3f8426f0e320-6" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_b8cf6f255be5405da20a3f8426f0e320-6"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is deliberately distinct from my &lt;code class="docutils literal"&gt;TankState&lt;/code&gt; code and related definitions, because it relates to a different part of the project - the user interface. The UI concerns shouldn’t be mixed up with the core logic.&lt;/p&gt;
&lt;p&gt;This dict is fine, and currently complete. But I’d like to ensure that if I add a new item to &lt;code class="docutils literal"&gt;TankState&lt;/code&gt;, I don’t forget to update the &lt;code class="docutils literal"&gt;TANK_STATE_COLORS&lt;/code&gt; dict.&lt;/p&gt;
&lt;p&gt;With a growing ability to do static type checks in Python, &lt;a class="reference external" href="https://stackoverflow.com/questions/72022403/type-hint-for-an-exhaustive-dictionary-with-enum-literal-keys"&gt;some people have asked how we can ensure this using static type checks&lt;/a&gt;. The short answer is, we can’t (at least at the moment).&lt;/p&gt;
&lt;p&gt;But the better question is “how can we (somehow) ensure we don’t forget?” It doesn’t have to be a static type check, as long as it’s very hard to forget, and if it preferably runs as early as possible.&lt;/p&gt;
&lt;p&gt;Instead of shoe-horning everything into static type checks, let’s just make use of the fact that this is Python and we can write any code we want at module level. All we need to do is this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_ebc769b710ff414a828735cf76d69018-1" name="rest_code_ebc769b710ff414a828735cf76d69018-1" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_ebc769b710ff414a828735cf76d69018-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;TANK_STATE_COLORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_ebc769b710ff414a828735cf76d69018-2" name="rest_code_ebc769b710ff414a828735cf76d69018-2" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_ebc769b710ff414a828735cf76d69018-2"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# …&lt;/span&gt;
&lt;a id="rest_code_ebc769b710ff414a828735cf76d69018-3" name="rest_code_ebc769b710ff414a828735cf76d69018-3" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_ebc769b710ff414a828735cf76d69018-3"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_ebc769b710ff414a828735cf76d69018-4" name="rest_code_ebc769b710ff414a828735cf76d69018-4" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_ebc769b710ff414a828735cf76d69018-4"&gt;&lt;/a&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TankState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_ebc769b710ff414a828735cf76d69018-5" name="rest_code_ebc769b710ff414a828735cf76d69018-5" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_ebc769b710ff414a828735cf76d69018-5"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TANK_STATE_COLORS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"TANK_STATE_COLORS is missing an entry for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That’s it, that’s the whole technique. I’d argue that this is a pretty much optimal, Pythonic solution to the problem. No clever type tricks to debug later, just 2 lines of plain simple code, and it’s impossible to import your code until you fix the problem, which means you get the early checking you want.
Plus you get &lt;strong&gt;exactly the error message you want&lt;/strong&gt;, not some obscure compiler output, which is also really important.&lt;/p&gt;
&lt;p&gt;It can also be extended if you want to do something more fancy (e.g. allow some values of the enum to be missing), and if it does get in your way, you can turn it off temporarily by just commenting out a couple of lines.&lt;/p&gt;
&lt;section id="thats-not-quite-it"&gt;
&lt;h2&gt;That’s not quite it&lt;/h2&gt;
&lt;p&gt;OK, in a project where I’m using this &lt;strong&gt;a lot&lt;/strong&gt;, I did eventually get bored of this small bit of boilerplate. So, as a Pythonic extension of this Pythonic solution, I now do this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_92e7c9499aba47fbac2310608a002230-1" name="rest_code_92e7c9499aba47fbac2310608a002230-1" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_92e7c9499aba47fbac2310608a002230-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;TANK_STATE_COLORS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TankState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_92e7c9499aba47fbac2310608a002230-2" name="rest_code_92e7c9499aba47fbac2310608a002230-2" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_92e7c9499aba47fbac2310608a002230-2"&gt;&lt;/a&gt;    &lt;span class="n"&gt;TankState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FULL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x00FF00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_92e7c9499aba47fbac2310608a002230-3" name="rest_code_92e7c9499aba47fbac2310608a002230-3" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_92e7c9499aba47fbac2310608a002230-3"&gt;&lt;/a&gt;    &lt;span class="n"&gt;TankState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HALF_FULL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x28D728&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_92e7c9499aba47fbac2310608a002230-4" name="rest_code_92e7c9499aba47fbac2310608a002230-4" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_92e7c9499aba47fbac2310608a002230-4"&gt;&lt;/a&gt;    &lt;span class="n"&gt;TankState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NEARLY_EMPTY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0xFF9900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_92e7c9499aba47fbac2310608a002230-5" name="rest_code_92e7c9499aba47fbac2310608a002230-5" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_92e7c9499aba47fbac2310608a002230-5"&gt;&lt;/a&gt;    &lt;span class="n"&gt;TankState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EMPTY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0xFF0000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_92e7c9499aba47fbac2310608a002230-6" name="rest_code_92e7c9499aba47fbac2310608a002230-6" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_92e7c9499aba47fbac2310608a002230-6"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_92e7c9499aba47fbac2310608a002230-7" name="rest_code_92e7c9499aba47fbac2310608a002230-7" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_92e7c9499aba47fbac2310608a002230-7"&gt;&lt;/a&gt;&lt;span class="n"&gt;assert_complete_enumerations_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TANK_STATE_COLORS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Specifically, I’m adding:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;a type hint on the constant&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a call to a clever utility function that does just the right amount of Python magic.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This function needs to be “magical” because we want it to produce good error messages, like we had before. This means it needs to get hold of the name of the dict in the calling module, but functions don’t usually have access to that.&lt;/p&gt;
&lt;p&gt;In addition, it wants to get hold of the type hint (although there would be other ways to infer it without a type hint, there are advantages this way), for which we also need the name.&lt;/p&gt;
&lt;p&gt;The specific magic we need is:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;the clever function needs to get hold of the module that &lt;strong&gt;called it&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;it then looks through the module dictionary to get the name of the object that has been passed in&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;then it can find type hints, and do the checking.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, because you don’t want to write all that yourself, the code is below. It also supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;having a tuple of &lt;code class="docutils literal"&gt;Enum&lt;/code&gt; types as the key&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;allowing some items to be missing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;using &lt;code class="docutils literal"&gt;Literal&lt;/code&gt; as the key. So you can do things like this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_43dffe504c24474bae463e035898536f-1" name="rest_code_43dffe504c24474bae463e035898536f-1" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_43dffe504c24474bae463e035898536f-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;typing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_43dffe504c24474bae463e035898536f-2" name="rest_code_43dffe504c24474bae463e035898536f-2" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_43dffe504c24474bae463e035898536f-2"&gt;&lt;/a&gt;    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"negative"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_43dffe504c24474bae463e035898536f-3" name="rest_code_43dffe504c24474bae463e035898536f-3" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_43dffe504c24474bae463e035898536f-3"&gt;&lt;/a&gt;    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"zero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_43dffe504c24474bae463e035898536f-4" name="rest_code_43dffe504c24474bae463e035898536f-4" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_43dffe504c24474bae463e035898536f-4"&gt;&lt;/a&gt;    &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"positive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_43dffe504c24474bae463e035898536f-5" name="rest_code_43dffe504c24474bae463e035898536f-5" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_43dffe504c24474bae463e035898536f-5"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_43dffe504c24474bae463e035898536f-6" name="rest_code_43dffe504c24474bae463e035898536f-6" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_43dffe504c24474bae463e035898536f-6"&gt;&lt;/a&gt;&lt;span class="n"&gt;assert_complete_enumerations_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s got a ton of error checking, because once you get magical then you really don’t want to be debugging obscure messages.&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
&lt;p&gt;I hereby place the following code into the public domain - &lt;a class="reference external" href="https://creativecommons.org/publicdomain/zero/1.0/"&gt;CC0 1.0 Universal&lt;/a&gt;.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-1" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-1" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;inspect&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-2" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-2" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-2"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;itertools&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-3" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-3" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-3"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-4" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-4" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-4"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-5" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-5" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-5"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;collections.abc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-6" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-6" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-6"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-7" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-7" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-7"&gt;&lt;/a&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-8" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-8" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-8"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;frozendict&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;frozendict&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-9" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-9" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-9"&gt;&lt;/a&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-10" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-10" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-10"&gt;&lt;/a&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-11" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-11" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-11"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assert_complete_enumerations_dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;the_dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed_missing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()):&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-12" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-12" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-12"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-13" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-13" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-13"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    Magically assert that the dict in the calling module has a&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-14" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-14" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-14"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    value for every item in an enumeration.&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-15" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-15" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-15"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    The dict object must be bound to a name in the module.&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-16" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-16" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-16"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    It must be type hinted, with the key being an Enum subclass, or Literal.&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-17" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-17" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-17"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    The key may also be a tuple of Enum subclasses&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-18" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-18" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-18"&gt;&lt;/a&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-19" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-19" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-19"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    If you expect some values to be missing, pass them in `allowed_missing`&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-20" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-20" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-20"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    """&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-21" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-21" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-21"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;the_dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;the_dict&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s2"&gt; is not a dict or mapping, it is a &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;the_dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-22" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-22" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-22"&gt;&lt;/a&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-23" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-23" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-23"&gt;&lt;/a&gt;    &lt;span class="n"&gt;frame_up&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_getframe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore[reportPrivateUsage]&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-24" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-24" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-24"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;frame_up&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-25" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-25" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-25"&gt;&lt;/a&gt;    &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getmodule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame_up&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-26" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-26" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-26"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Couldn't get module for frame &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;frame_up&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-27" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-27" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-27"&gt;&lt;/a&gt;    &lt;span class="n"&gt;msg_prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"In module `&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`,"&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-28" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-28" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-28"&gt;&lt;/a&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-29" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-29" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-29"&gt;&lt;/a&gt;    &lt;span class="n"&gt;module_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frame_up&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f_locals&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-30" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-30" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-30"&gt;&lt;/a&gt;    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-31" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-31" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-31"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# Find the object:&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-32" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-32" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-32"&gt;&lt;/a&gt;    &lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;module_dict&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;the_dict&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-33" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-33" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-33"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg_prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; there is no name for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;the_dict&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, please check"&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-34" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-34" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-34"&gt;&lt;/a&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-35" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-35" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-35"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# Any name that has a type hint will do, there will usually be one.&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-36" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-36" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-36"&gt;&lt;/a&gt;    &lt;span class="n"&gt;hints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_type_hints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-37" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-37" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-37"&gt;&lt;/a&gt;    &lt;span class="n"&gt;hinted_names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hints&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-38" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-38" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-38"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-39" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-39" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-39"&gt;&lt;/a&gt;        &lt;span class="n"&gt;hinted_names&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-40" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-40" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-40"&gt;&lt;/a&gt;    &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg_prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; no type hints were found for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, they are needed to use assert_complete_enumerations_dict"&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-41" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-41" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-41"&gt;&lt;/a&gt;    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hinted_names&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-42" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-42" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-42"&gt;&lt;/a&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-43" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-43" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-43"&gt;&lt;/a&gt;    &lt;span class="n"&gt;hint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hints&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-44" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-44" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-44"&gt;&lt;/a&gt;    &lt;span class="n"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_origin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-45" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-45" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-45"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;origin&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg_prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; type hint for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; must supply arguments"&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-46" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-46" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-46"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;origin&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-47" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-47" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-47"&gt;&lt;/a&gt;        &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-48" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-48" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-48"&gt;&lt;/a&gt;        &lt;span class="n"&gt;typing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-49" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-49" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-49"&gt;&lt;/a&gt;        &lt;span class="n"&gt;Mapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-50" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-50" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-50"&gt;&lt;/a&gt;        &lt;span class="n"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-51" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-51" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-51"&gt;&lt;/a&gt;    &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg_prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; type hint for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; must be dict/frozendict/Mapping with arguments to use assert_complete_enumerations_dict, not &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-52" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-52" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-52"&gt;&lt;/a&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-53" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-53" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-53"&gt;&lt;/a&gt;    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-54" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-54" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-54"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg_prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; type hint for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; must have two args"&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-55" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-55" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-55"&gt;&lt;/a&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-56" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-56" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-56"&gt;&lt;/a&gt;    &lt;span class="n"&gt;arg0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-57" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-57" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-57"&gt;&lt;/a&gt;    &lt;span class="n"&gt;arg0_origin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_origin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-58" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-58" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-58"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;arg0_origin&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-59" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-59" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-59"&gt;&lt;/a&gt;        &lt;span class="c1"&gt;# tuple of Enums&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-60" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-60" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-60"&gt;&lt;/a&gt;        &lt;span class="n"&gt;enum_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-61" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-61" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-61"&gt;&lt;/a&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;enum_cls&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;enum_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-62" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-62" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-62"&gt;&lt;/a&gt;            &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;issubclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-63" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-63" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-63"&gt;&lt;/a&gt;                &lt;span class="n"&gt;enum_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-64" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-64" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-64"&gt;&lt;/a&gt;            &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg_prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; type hint must be an Enum to use assert_complete_enumerations_dict, not &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;enum_cls&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-65" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-65" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-65"&gt;&lt;/a&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-66" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-66" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-66"&gt;&lt;/a&gt;        &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;itertools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum_cls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;enum_cls&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;enum_list&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-67" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-67" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-67"&gt;&lt;/a&gt;    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;arg0_origin&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-68" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-68" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-68"&gt;&lt;/a&gt;        &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-69" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-69" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-69"&gt;&lt;/a&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-70" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-70" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-70"&gt;&lt;/a&gt;        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;issubclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-71" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-71" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-71"&gt;&lt;/a&gt;            &lt;span class="n"&gt;arg0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-72" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-72" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-72"&gt;&lt;/a&gt;        &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg_prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; type hint must be an Enum to use assert_complete_enumerations_dict, not &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;arg0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-73" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-73" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-73"&gt;&lt;/a&gt;        &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-74" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-74" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-74"&gt;&lt;/a&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-75" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-75" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-75"&gt;&lt;/a&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-76" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-76" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-76"&gt;&lt;/a&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allowed_missing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-77" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-77" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-77"&gt;&lt;/a&gt;            &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-78" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-78" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-78"&gt;&lt;/a&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-79" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-79" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-79"&gt;&lt;/a&gt;        &lt;span class="c1"&gt;# This is the assert we actually want to do, everything else is just error checking:&lt;/span&gt;
&lt;a id="rest_code_7efcc2725ed74da6a64e5400a05aee4c-80" name="rest_code_7efcc2725ed74da6a64e5400a05aee4c-80" href="https://lukeplant.me.uk/blog/posts/statically-checking-python-dicts-for-completeness/#rest_code_7efcc2725ed74da6a64e5400a05aee4c-80"&gt;&lt;/a&gt;        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the_dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg_prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; needs an entry for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;</content>
    <category term="python" label="Python"/>
    <category term="python-type-hints" label="Python type hints"/>
  </entry>
  <entry>
    <title>Knowledge creates technical debt</title>
    <id>https://lukeplant.me.uk/blog/posts/knowledge-creates-technical-debt/</id>
    <updated>2025-05-13T09:08:50+01:00</updated>
    <published>2025-05-13T09:08:50+01:00</published>
    <author>
      <name>Luke Plant</name>
    </author>
    <link rel="alternate" type="text/html" href="https://lukeplant.me.uk/blog/posts/knowledge-creates-technical-debt/"/>
    <summary type="html">&lt;p&gt;Some history on term “technical debt” and on better language to use when communicating about it.&lt;/p&gt;</summary>
    <content type="html">&lt;p&gt;The term &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Technical_debt"&gt;technical debt&lt;/a&gt;, now used widely in software circles, &lt;a class="reference external" href="https://www.youtube.com/watch?v=pqeJFYwnkjE"&gt;was coined to explain a deliberate process where you write software quickly to gain knowledge&lt;/a&gt;, and then you have to use that knowledge gained to improve your software.&lt;/p&gt;
&lt;p&gt;This perspective is still helpful today when people speak of technical debt as only a negative, or only as a result of bad decisions. Martin Fowler’s &lt;a class="reference external" href="https://martinfowler.com/bliki/TechnicalDebtQuadrant.html"&gt;Tech Debt Quadrant&lt;/a&gt; is a useful antidote to that.&lt;/p&gt;
&lt;p&gt;A consequence of this perspective is that technical debt can appear at any time, apparently from nowhere, if you are unfortunate enough to gain some knowledge.&lt;/p&gt;
&lt;p&gt;If you discover a better way to do things, the old way of doing it that is embedded in your code base is now “debt”:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;you can either live with the debt, “paying interest” in the form of all the ways that it makes your code harder to work with;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;or you can “pay down” the debt by fixing all the code in light of your new knowledge, which takes up front resources which could have been spent on something else, but hopefully will make sense in the long term.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This “better way” might be a different language, library, tool or pattern. In some cases, the better way has only recently been invented. It might be your own personal discovery, or something industry wide. It might be knowledge gained through the actual work of doing the current project (which was Ward Cunningham’s usage of the tem), or from somewhere else. But the end result is the same – you know more than you did, and now you have a debt.&lt;/p&gt;
&lt;p&gt;The problem is that this doesn’t sound like a good thing. You learn something, and now you have a problem you didn’t have before, and it’s difficult to put a good spin on “I discovered a debt”.&lt;/p&gt;
&lt;p&gt;But from another angle, maybe this perspective gives us different language to use when communicating with others and explaining why we need to address technical debt. Rather than say “we have a liability”, the knowledge we have gained can be framed as an opportunity. Failure to take the opportunity is an opportunity cost.&lt;/p&gt;
&lt;p&gt;The “pile of technical debt” is essentially a pile of &lt;strong&gt;knowledge&lt;/strong&gt; – everything we now think is bad about the code represents what we’ve learned about how to do software better. The gap between what it is and what it should be is the gap between what we used to know and what we now know.&lt;/p&gt;
&lt;p&gt;And fixing that code is not “a debt we have to pay off”, but an investment opportunity that will reap rewards. You can refuse to take that opportunity if you want, but it’s a tragic waste of your hard-earned knowledge – a waste of the investment you previously made in learning – and eventually you’ll be losing money, and losing out to competitors who will be making the most of their knowledge.&lt;/p&gt;
&lt;p&gt;Finally, I think phrasing it in terms of knowledge can help tame some of our more rash instincts to call everything we don’t like “tech debt”. Can I really say “we now &lt;strong&gt;know&lt;/strong&gt;” that the existing code is inferior? Is it true that fixing the code is “investing my knowledge”? If it’s just a hunch, or a personal preference, or the latest fashion, maybe I can both resist the urge for unnecessary rewrites, and feel happier about it at the same time.&lt;/p&gt;
&lt;section id="links"&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://lobste.rs/s/kexbxy/knowledge_creates_technical_debt"&gt;Discussion of this post on Lobsters&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;</content>
    <category term="python" label="Python"/>
    <category term="software-development" label="Software development"/>
  </entry>
  <entry>
    <title>Recursive project search in Emacs</title>
    <id>https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/</id>
    <updated>2024-12-30T19:34:33Z</updated>
    <published>2024-12-30T19:34:33Z</published>
    <author>
      <name>Luke Plant</name>
    </author>
    <link rel="alternate" type="text/html" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/"/>
    <summary type="html">&lt;p&gt;The workflow of recursively searching for things or dealing with a list of issues to fix without getting lost.&lt;/p&gt;</summary>
    <content type="html">&lt;p&gt;Before reading this, you might want to check it out in &lt;a class="reference external" href="https://youtu.be/1jBbVUnNbDU"&gt;video form on Youtube&lt;/a&gt;, or with the embed below:&lt;/p&gt;
&lt;div class="center"&gt;
&lt;iframe align="center" width="560" height="315" src="https://www.youtube.com/embed/1jBbVUnNbDU?si=NRO0yLkWYRRPWxEn" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;

&lt;/div&gt;&lt;p&gt;Video is probably a more helpful format for demonstrating the workflow I’m talking about. Otherwise read on. If you’re coming from the video to look at the Elisp code, it is found towards the bottom of this post.&lt;/p&gt;
&lt;p&gt;“Recursive project search” is the name I’m giving to the flow where you do some kind of search to identify things that need to be done, but each of those tasks may leads you to do another search, etc. You need to complete all the sub-searches, but without losing your place in the parent searches.&lt;/p&gt;
&lt;p&gt;This is extremely common in software development and maintenance, whether you are just trying to scope out a set of changes, or whether actually doing them. In fact just about any task can end being some form of this, and you never know when it will turn out that way.&lt;/p&gt;
&lt;p&gt;This post is about how I use Emacs to do this, which is not rocket science but includes some tips and Elisp tweaks that can help a lot. When it comes to other editors or IDEs I’ve tried, I’ve never come close to finding a decent workflow for this, so I’ll have to leave it to other people to describe their approaches with other editors.&lt;/p&gt;
&lt;nav class="contents" id="contents" role="doc-toc"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#top"&gt;Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#example-task-pyastgrep" id="toc-entry-1"&gt;Example task - pyastgrep&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#example-workflow" id="toc-entry-2"&gt;Example workflow&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#explanation" id="toc-entry-3"&gt;Explanation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#requirements" id="toc-entry-4"&gt;Requirements&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#unique-buffers-for-searches" id="toc-entry-5"&gt;Unique buffers for searches&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#other-tips" id="toc-entry-6"&gt;Other tips&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#external-tools" id="toc-entry-7"&gt;External tools&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#buffer-order-and-keeping-things-tidy" id="toc-entry-8"&gt;Buffer order and keeping things tidy&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#project-context" id="toc-entry-9"&gt;Project context&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#conclusion" id="toc-entry-10"&gt;Conclusion&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;section id="example-task-pyastgrep"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#toc-entry-1" role="doc-backlink"&gt;Example task - pyastgrep&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I’m going to take as an example my &lt;a class="reference external" href="https://github.com/spookylukey/pyastgrep/"&gt;pyastgrep&lt;/a&gt; project and a fairly simple refactoring I needed to do recently.&lt;/p&gt;
&lt;p&gt;For background, pyastgrep is a command line program and library that allows you to grep Python code at the level of Abstract Syntax Trees rather than just string. At the heart of this is a function that takes a Python path, and converts it to AST and also XML.&lt;/p&gt;
&lt;p&gt;The refactoring I want to do is make this function swappable, mostly so that users can apply different caching strategies to it. This is going to be a straightforward example of turning it into a parameter, or “dependency injection” if you want a fancy term. But that may involve modifying a number of functions in several layers of function calls.&lt;/p&gt;
&lt;p&gt;The function in question is &lt;a class="reference external" href="https://github.com/spookylukey/pyastgrep/blob/7935291013dd0aadb8cfa2ad6dc47f631bd48a5e/src/pyastgrep/files.py#L150"&gt;process_python_file&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="example-workflow"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#toc-entry-2" role="doc-backlink"&gt;Example workflow&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first step is a search, which in this case I will doing using &lt;a class="reference external" href="https://emacs-lsp.github.io/lsp-mode/"&gt;lsp-mode&lt;/a&gt;. I happen to use &lt;a class="reference external" href="https://github.com/emacs-lsp/lsp-pyright"&gt;lsp-pyright&lt;/a&gt; for Python, but there are other options.&lt;/p&gt;
&lt;p&gt;So I’ll kick off by opening the file, putting my cursor on the function &lt;code class="docutils literal"&gt;python_process_file&lt;/code&gt;, and calling &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;M-x&lt;/span&gt; &lt;span class="pre"&gt;lsp-find-references&lt;/span&gt;&lt;/code&gt;. This returns a bunch of references. I can then step through them using &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;M-x&lt;/span&gt; &lt;span class="pre"&gt;next-error&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;M-x&lt;/span&gt; &lt;span class="pre"&gt;previous-error&lt;/span&gt;&lt;/code&gt; — for which there are shortcuts defined, and I also do this so much that I have an &lt;code class="docutils literal"&gt;F&lt;/code&gt; key for it — &lt;code class="docutils literal"&gt;F2&lt;/code&gt; and &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;shift-F2&lt;/span&gt;&lt;/code&gt; respectively.&lt;/p&gt;
&lt;p&gt;Notice that in addition to the normal cursor, there is also a little triangle in the search results which shows the current result you are on.&lt;/p&gt;
&lt;figure class="align-center"&gt;
&lt;img alt="/blogmedia/emacs-recursive-search-lsp-find-references.png" class="full-bleed" src="https://lukeplant.me.uk/blogmedia/emacs-recursive-search-lsp-find-references.png"&gt;
&lt;/figure&gt;
&lt;p&gt;In this case, after the function definition itself, and an import, there is just one real usage – that last item in the search results.&lt;/p&gt;
&lt;p&gt;The details of doing this refactoring aren’t that important, but I’ll include some of the steps for completeness. The last result brings me to code like this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_c7bb4819f6de4458999236139e97dd00-1" name="rest_code_c7bb4819f6de4458999236139e97dd00-1" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c7bb4819f6de4458999236139e97dd00-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_python_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_c7bb4819f6de4458999236139e97dd00-2" name="rest_code_c7bb4819f6de4458999236139e97dd00-2" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c7bb4819f6de4458999236139e97dd00-2"&gt;&lt;/a&gt;    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;BinaryIO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_c7bb4819f6de4458999236139e97dd00-3" name="rest_code_c7bb4819f6de4458999236139e97dd00-3" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c7bb4819f6de4458999236139e97dd00-3"&gt;&lt;/a&gt;    &lt;span class="n"&gt;query_func&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;XMLQueryFunc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_c7bb4819f6de4458999236139e97dd00-4" name="rest_code_c7bb4819f6de4458999236139e97dd00-4" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c7bb4819f6de4458999236139e97dd00-4"&gt;&lt;/a&gt;    &lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_c7bb4819f6de4458999236139e97dd00-5" name="rest_code_c7bb4819f6de4458999236139e97dd00-5" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c7bb4819f6de4458999236139e97dd00-5"&gt;&lt;/a&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Iterable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Match&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ReadError&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;NonElementReturned&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;a id="rest_code_c7bb4819f6de4458999236139e97dd00-6" name="rest_code_c7bb4819f6de4458999236139e97dd00-6" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c7bb4819f6de4458999236139e97dd00-6"&gt;&lt;/a&gt;
&lt;a id="rest_code_c7bb4819f6de4458999236139e97dd00-7" name="rest_code_c7bb4819f6de4458999236139e97dd00-7" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c7bb4819f6de4458999236139e97dd00-7"&gt;&lt;/a&gt;    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;a id="rest_code_c7bb4819f6de4458999236139e97dd00-8" name="rest_code_c7bb4819f6de4458999236139e97dd00-8" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c7bb4819f6de4458999236139e97dd00-8"&gt;&lt;/a&gt;
&lt;a id="rest_code_c7bb4819f6de4458999236139e97dd00-9" name="rest_code_c7bb4819f6de4458999236139e97dd00-9" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c7bb4819f6de4458999236139e97dd00-9"&gt;&lt;/a&gt;    &lt;span class="n"&gt;processed_python&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process_python_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So I make &lt;code class="docutils literal"&gt;process_python_file&lt;/code&gt; a parameter:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-1" name="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-1" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_python_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-2" name="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-2" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-2"&gt;&lt;/a&gt;    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;BinaryIO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-3" name="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-3" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-3"&gt;&lt;/a&gt;    &lt;span class="n"&gt;query_func&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;XMLQueryFunc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-4" name="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-4" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-4"&gt;&lt;/a&gt;    &lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-5" name="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-5" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-5"&gt;&lt;/a&gt;    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-6" name="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-6" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-6"&gt;&lt;/a&gt;    &lt;span class="n"&gt;python_file_processor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ProcessedPython&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ReadError&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process_python_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-7" name="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-7" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-7"&gt;&lt;/a&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Iterable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Match&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ReadError&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;NonElementReturned&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;a id="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-8" name="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-8" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-8"&gt;&lt;/a&gt;
&lt;a id="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-9" name="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-9" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-9"&gt;&lt;/a&gt;    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;a id="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-10" name="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-10" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-10"&gt;&lt;/a&gt;
&lt;a id="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-11" name="rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-11" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_40d1c56b9eff4e6198f3d82b38ec75b2-11"&gt;&lt;/a&gt;    &lt;span class="n"&gt;processed_python&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;python_file_processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Having done this, I now need to search for all usages of the function I just modified, &lt;code class="docutils literal"&gt;search_python_file&lt;/code&gt;, so that I can pass the new parameter — another &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;M-x&lt;/span&gt; &lt;span class="pre"&gt;lsp-find-references&lt;/span&gt;&lt;/code&gt;. I won’t go into the details this time, in this case it involves the following:&lt;/p&gt;
&lt;p&gt;Wherever &lt;code class="docutils literal"&gt;search_python_file&lt;/code&gt; is used, either:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;don’t pass &lt;code class="docutils literal"&gt;python_file_processor&lt;/code&gt;, because the default is what we want.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;or do pass it, usually by similarly adding a &lt;code class="docutils literal"&gt;python_file_processor&lt;/code&gt; parameter to the calling function, and passing that parameter into &lt;code class="docutils literal"&gt;search_python_file&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This quickly gets me to &lt;code class="docutils literal"&gt;search_python_files&lt;/code&gt; (note the &lt;code class="docutils literal"&gt;s&lt;/code&gt;), and I find that it is imported in &lt;code class="docutils literal"&gt;pyastgrep.api&lt;/code&gt;. There are no usages to be fixed here, but it is exported in &lt;code class="docutils literal"&gt;__all__&lt;/code&gt;. This reminds me that the new parameter to this &lt;code class="docutils literal"&gt;search_python_files&lt;/code&gt; function is actually intended to be a part of the publicly documented API — in fact this is the whole reason I’m doing this change. This means I now need to fix the docs. Another search is needed, but this time a string-based grep in the docs folder. For this, I use ripgrep and &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;M-x&lt;/span&gt; &lt;span class="pre"&gt;rg-project-all-files&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So now I have another buffer of results to get through – I’m about 4 levels deep at this point.&lt;/p&gt;
&lt;p&gt;Now comes one of the critical points in this workflow. I’ve completed the docs fix, and I’ve reached the end of that ripgrep buffer of search results:&lt;/p&gt;
&lt;figure class="align-center"&gt;
&lt;img alt="/blogmedia/emacs-recursive-search-ripgrep-end.png" class="full-bleed" src="https://lukeplant.me.uk/blogmedia/emacs-recursive-search-ripgrep-end.png"&gt;
&lt;/figure&gt;
&lt;p&gt;So I’ve come to a “leaf” of my search. But there were a whole load of other searches that I only got half way through. What happens now?&lt;/p&gt;
&lt;p&gt;All I do is kill these finished buffers – both the file I’m done with, and the the search buffer. And that puts me back to the previous search buffer, &lt;strong&gt;at exactly the point I left off&lt;/strong&gt;, with the cursor in the search buffer in the expected place.&lt;/p&gt;
&lt;figure class="align-center"&gt;
&lt;img alt="/blogmedia/emacs-recursive-search-lsp-find-references-search_python_files_continue.png" class="full-bleed" src="https://lukeplant.me.uk/blogmedia/emacs-recursive-search-lsp-find-references-search_python_files_continue.png"&gt;
&lt;/figure&gt;
&lt;p&gt;So I just continue. This process repeats itself, with any number of additional “side quests”, such as adding tests etc., until I get to the end of last search buffer, at which point I’m done.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="explanation"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#toc-entry-3" role="doc-backlink"&gt;Explanation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What I’ve basically done here is a &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Depth-first_search"&gt;depth-first recursive search&lt;/a&gt; over “everything that needs to be done to complete the task”. I started working on one thing, which lead to another and another, and for each one I went off and completed them as they came up.&lt;/p&gt;
&lt;p&gt;Doing a search like that requires keeping track of a fair amount of state, and if I were doing that in my head, I would get lost very quickly and forget what I was doing. So what I do instead is to use Emacs buffers to maintain all of that state.&lt;/p&gt;
&lt;p&gt;The buffers themselves form a stack, and each buffer has a cursor within it which tells me how far through the list of results I am. (This is equivalent to how recursive function calls typically work in a program - there will be a stack of function calls, each with local variables stored in a frame somehow).&lt;/p&gt;
&lt;p&gt;In the above case I only went about 4 or 5 levels deep, and each level was fairly short. But you can go much deeper and not get lost, because the buffers are maintaining all the state you need. You can be 12 levels down, deep in the woods, and then just put it to one side and come back after lunch, or the next day, and just carry on, because the buffers are remembering everything for you, and the buffer that is in front of you tells you what you were doing last.&lt;/p&gt;
&lt;p&gt;It doesn’t even matter if you switch between buffers and get them a bit out of order – you just have to ensure that you get to the bottom of each one.&lt;/p&gt;
&lt;p&gt;Another important feature is that you can use &lt;strong&gt;different kinds of search&lt;/strong&gt;, and
mix and match them as you need, such as &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;lsp-find-references&lt;/span&gt;&lt;/code&gt; and the ripgrep
searches above. In addition to these two, you can insert other
“search-like” things, like linters, static type checkers, compilers and build
processes – anything that will return a list of items to check. So this is not a
feature of just one mode, it’s a feature of how buffers work together.&lt;/p&gt;
&lt;p&gt;At each step, you can also apply different kinds of fixes – e.g. instead of manually editing, you might be using &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;M-x&lt;/span&gt; &lt;span class="pre"&gt;lsp-rename&lt;/span&gt;&lt;/code&gt; on each instance, and you might be using keyboard macros etc.&lt;/p&gt;
&lt;p&gt;When I’ve attempted to use editors other than Emacs, this is one of the things I’ve missed most of all. Just getting them to do the most basic requirement of “do a search, without overwriting the previous search results” has been a headache or impossible – although I may have given up too soon.&lt;/p&gt;
&lt;p&gt;I’m guessing people manage somehow – or perhaps not: I’ve sometimes noticed that I’ve been happy to take on tasks that involved this kind of workflow which appeared to be daunting to other people, and completed them without problem, which was apparently impressive to others. I also wonder whether difficulties in using search in an editor drive a reluctance to take on basic refactoring tasks, such as manual renames, or an inability to complete them correctly. If so, it would help to explain why codebases often have many basic problems (like bad names).&lt;/p&gt;
&lt;p&gt;In any case, I don’t think I can take for granted that people can do this, so that’s why I’m bothering to post about it!&lt;/p&gt;
&lt;/section&gt;
&lt;section id="requirements"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#toc-entry-4" role="doc-backlink"&gt;Requirements&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What do you need in Emacs for this to work? Or what equivalent functionality is needed if you want to reproduce this elsewhere?&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;First, the search buffer must have the ability to remember your position. All the search modes I’ve seen in Emacs do this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Second, you need an easy way to step through results, and this is provided by the really convenient &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;M-x&lt;/span&gt; &lt;span class="pre"&gt;next-error&lt;/span&gt;&lt;/code&gt; function which does exactly what you want (see the Emacs docs for it, which describe how it works). This function is part of Compilation Mode or Compilation Minor Mode, and all the search modes I’ve seen use this correctly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Thirdly, the search modes mustn’t re-use buffers for different searches, otherwise you’ll clobber earlier search results that you hadn’t finished processing. Some modes do this automatically, others have a habit of re-using buffers — but we can fix that:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="unique-buffers-for-searches"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#toc-entry-5" role="doc-backlink"&gt;Unique buffers for searches&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If search commands re-use search buffers by default, I’ve found it’s usually pretty easy to override the behaviour so that you automatically always get a unique buffer for each new search you do.&lt;/p&gt;
&lt;p&gt;The approach you need often varies slightly for each mode, but the basic principle is similar - different searches should get different &lt;strong&gt;buffer names&lt;/strong&gt;, and you can use some &lt;a class="reference external" href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Advising-Functions.html"&gt;Elisp Advice&lt;/a&gt; to insert the behaviour you want.&lt;/p&gt;
&lt;p&gt;So here are the main ones that I override:&lt;/p&gt;
&lt;p&gt;rg.el for ripgrep searching:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code elisp"&gt;&lt;a id="rest_code_35b67af16051499dbf5ae6ea873905b9-1" name="rest_code_35b67af16051499dbf5ae6ea873905b9-1" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_35b67af16051499dbf5ae6ea873905b9-1"&gt;&lt;/a&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defadvice&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rg-run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rg-run-before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_35b67af16051499dbf5ae6ea873905b9-2" name="rest_code_35b67af16051499dbf5ae6ea873905b9-2" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_35b67af16051499dbf5ae6ea873905b9-2"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;rg-save-search&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is just using the &lt;a class="reference external" href="https://rgel.readthedocs.io/en/latest/usage.html#search-management"&gt;save feature built in to rg.el&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This goes in your &lt;code class="docutils literal"&gt;init.el&lt;/code&gt;. Since I use &lt;a class="reference external" href="https://jwiegley.github.io/use-package/"&gt;use-package&lt;/a&gt; I usually put it inside the relevant &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;(use-package&lt;/span&gt; :config)&lt;/code&gt; block.&lt;/p&gt;
&lt;p&gt;For &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;lsp-find-references&lt;/span&gt;&lt;/code&gt; I use the following which makes a unique buffer name based on the symbol being searched for:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code elisp"&gt;&lt;a id="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-1" name="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-1" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-1"&gt;&lt;/a&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;advice-add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'lsp-find-references&lt;/span&gt;
&lt;a id="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-2" name="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-2" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-2"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;:around&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nv"&gt;my/lsp-find-references-unique-buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-3" name="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-3" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-3"&gt;&lt;/a&gt;
&lt;a id="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-4" name="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-4" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-4"&gt;&lt;/a&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;my/lsp-find-references-unique-buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;orig-func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;&amp;amp;rest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-5" name="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-5" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-5"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;"Gives lsp-find-references a unique buffer name, to help with recursive search."&lt;/span&gt;
&lt;a id="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-6" name="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-6" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-6"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;
&lt;a id="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-7" name="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-7" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-7"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;xref-buffer-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"%s %s"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;xref-buffer-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;symbol-at-point&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;a id="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-8" name="rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-8" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_c015fd3fbb1f422ead9e4da7aa335ccd-8"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;orig-func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then for general &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;M-x&lt;/span&gt; compile&lt;/code&gt; or &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;projectile-compile-project&lt;/span&gt;&lt;/code&gt; commands I use the following, which gives a unique buffer name based on the compilation command used:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code elisp"&gt;&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-1" name="rest_code_249d03f491f34152bb8ec317528ee5e9-1" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-1"&gt;&lt;/a&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;advice-add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'compilation-start&lt;/span&gt;
&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-2" name="rest_code_249d03f491f34152bb8ec317528ee5e9-2" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-2"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;:around&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nv"&gt;my/compilation-unique-buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-3" name="rest_code_249d03f491f34152bb8ec317528ee5e9-3" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-3"&gt;&lt;/a&gt;
&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-4" name="rest_code_249d03f491f34152bb8ec317528ee5e9-4" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-4"&gt;&lt;/a&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;my/compilation-unique-buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;orig-func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;&amp;amp;rest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-5" name="rest_code_249d03f491f34152bb8ec317528ee5e9-5" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-5"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;"Give compilation buffers a unique name so that new compilations get new&lt;/span&gt;
&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-6" name="rest_code_249d03f491f34152bb8ec317528ee5e9-6" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-6"&gt;&lt;/a&gt;&lt;span class="s"&gt;buffers. This helps with recursive search.&lt;/span&gt;
&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-7" name="rest_code_249d03f491f34152bb8ec317528ee5e9-7" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-7"&gt;&lt;/a&gt;
&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-8" name="rest_code_249d03f491f34152bb8ec317528ee5e9-8" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-8"&gt;&lt;/a&gt;&lt;span class="s"&gt;If a compile command is run starting from the compilation buffer,&lt;/span&gt;
&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-9" name="rest_code_249d03f491f34152bb8ec317528ee5e9-9" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-9"&gt;&lt;/a&gt;&lt;span class="s"&gt;the buffer will be re-used, but if it is started from a different&lt;/span&gt;
&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-10" name="rest_code_249d03f491f34152bb8ec317528ee5e9-10" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-10"&gt;&lt;/a&gt;&lt;span class="s"&gt;buffer a new compilation buffer will be created."&lt;/span&gt;
&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-11" name="rest_code_249d03f491f34152bb8ec317528ee5e9-11" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-11"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;car&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-12" name="rest_code_249d03f491f34152bb8ec317528ee5e9-12" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-12"&gt;&lt;/a&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;compilation-buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;orig-func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-13" name="rest_code_249d03f491f34152bb8ec317528ee5e9-13" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-13"&gt;&lt;/a&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;new-buffer-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;buffer-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;compilation-buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-14" name="rest_code_249d03f491f34152bb8ec317528ee5e9-14" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-14"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;with-current-buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;compilation-buffer&lt;/span&gt;
&lt;a id="rest_code_249d03f491f34152bb8ec317528ee5e9-15" name="rest_code_249d03f491f34152bb8ec317528ee5e9-15" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_249d03f491f34152bb8ec317528ee5e9-15"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rename-buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;new-buffer-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I use this mode quite a lot via &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;M-x&lt;/span&gt; compile&lt;/code&gt; or &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;M-x&lt;/span&gt;
&lt;span class="pre"&gt;projectile-compile-project&lt;/span&gt;&lt;/code&gt; to do things other than compilation – for custom
search commands, like &lt;a class="reference external" href="https://pyastgrep.readthedocs.io/en/latest/"&gt;pyastgrep&lt;/a&gt;, for linters and static
checkers.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="other-tips"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#toc-entry-6" role="doc-backlink"&gt;Other tips&lt;/a&gt;&lt;/h2&gt;
&lt;section id="external-tools"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#toc-entry-7" role="doc-backlink"&gt;External tools&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Many of externals tools that you might run via &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;M-x&lt;/span&gt; compile&lt;/code&gt; already have output that is in exactly the format that Emacs &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;compilation-mode&lt;/span&gt;&lt;/code&gt; expects, so that search results or error messages become hyperlinked as expected inside Emacs. For example, &lt;a class="reference external" href="https://mypy.readthedocs.io/en/stable/"&gt;mypy&lt;/a&gt; has the right format by default, and most older compilers like gcc etc.&lt;/p&gt;
&lt;p&gt;For those tools that don’t, sometimes you can tweak the options of how they print. For example, if you run &lt;a class="reference external" href="https://github.com/BurntSushi/ripgrep"&gt;ripgrep&lt;/a&gt; as &lt;code class="docutils literal"&gt;rg &lt;span class="pre"&gt;--no-heading&lt;/span&gt;&lt;/code&gt; (just using normal &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;M-x&lt;/span&gt; compile&lt;/code&gt;, without a dedicated ripgrep mode), it produces the necessary format.&lt;/p&gt;
&lt;p&gt;Alternatively, you can make custom wrappers that fix the format. For example, I’ve got this one-liner bash script to wrap &lt;a class="reference external" href="https://github.com/microsoft/pyright"&gt;pyright&lt;/a&gt;:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code bash"&gt;&lt;a id="rest_code_076c38269aa84bc4bff721e5b14df257-1" name="rest_code_076c38269aa84bc4bff721e5b14df257-1" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_076c38269aa84bc4bff721e5b14df257-1"&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;
&lt;a id="rest_code_076c38269aa84bc4bff721e5b14df257-2" name="rest_code_076c38269aa84bc4bff721e5b14df257-2" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_076c38269aa84bc4bff721e5b14df257-2"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Adjust ANSI colour and codes and whitespace output from pyright to make it work nicer in Emacs&lt;/span&gt;
&lt;a id="rest_code_076c38269aa84bc4bff721e5b14df257-3" name="rest_code_076c38269aa84bc4bff721e5b14df257-3" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#rest_code_076c38269aa84bc4bff721e5b14df257-3"&gt;&lt;/a&gt;pyright&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'s/\x1b\[[0-9;]*[mGK]//g'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'{$1=$1};1'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'s/\(.*:[0-9]*:[0-9]*\) -/\1: -/g'&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="buffer-order-and-keeping-things-tidy"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#toc-entry-8" role="doc-backlink"&gt;Buffer order and keeping things tidy&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Emacs typically manages your buffers as a stack. However, it’s very easy for things to get out of order as you are jumping around files or search buffers. It doesn’t matter too much if you go through the search buffers in the “wrong” order – as long as you get to the bottom of all of them. But to be sure I have got to the bottom, I do two things:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Before starting anything that I anticipate will be more than a few levels deep, I tidy up by closing all buffers but the one I’m working on. You can do this with &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;crux-kill-other-buffers&lt;/span&gt;&lt;/code&gt; from &lt;a class="reference external" href="https://github.com/bbatsov/crux"&gt;crux&lt;/a&gt;. I have my own version that is more customised for my needs — &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;crux-kill-other-buffers&lt;/span&gt;&lt;/code&gt; only kills buffers that are files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At the end, when I think I’m done, I check the buffer list (&lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;C-x&lt;/span&gt; &lt;span class="pre"&gt;C-b&lt;/span&gt;&lt;/code&gt;) for anything else I missed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="project-context"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#toc-entry-9" role="doc-backlink"&gt;Project context&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In order to limit the scope of searches to my project, I’m typically leaning on &lt;a class="reference external" href="https://github.com/bbatsov/projectile"&gt;projectile.el&lt;/a&gt;, but there are &lt;a class="reference external" href="https://docs.projectile.mx/projectile/projectile_vs_project.html"&gt;other options&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/recursive-project-search-in-emacs/#toc-entry-10" role="doc-backlink"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I hope this post has been helpful, and if you’ve got additional tips for this kind of workflow, please leave a comment!&lt;/p&gt;
&lt;/section&gt;</content>
    <category term="emacs" label="Emacs"/>
    <category term="python" label="Python"/>
    <category term="software-development" label="Software development"/>
  </entry>
  <entry>
    <title>Check if a point is in a cylinder - geometry and code</title>
    <id>https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/</id>
    <updated>2024-12-05T16:40:24Z</updated>
    <published>2024-12-05T16:40:24Z</published>
    <author>
      <name>Luke Plant</name>
    </author>
    <link rel="alternate" type="text/html" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/"/>
    <summary type="html"></summary>
    <content type="html">&lt;p&gt;In my current project I’m doing a fair amount of geometry, and one small problem I needed to solve a while back was finding whether a point is inside a cylinder.&lt;/p&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://math.stackexchange.com/a/4864013/"&gt;accepted answer for this on math.stackexchange.com&lt;/a&gt; wasn’t ideal — part of it was very over-complicated, and also didn’t work under some circumstances. So I contributed &lt;a class="reference external" href="https://math.stackexchange.com/a/4864013/78071"&gt;my own answer&lt;/a&gt;. In this post, in addition to the maths, I’ll give an implementation in Python.&lt;/p&gt;
&lt;section id="method"&gt;
&lt;h2&gt;Method&lt;/h2&gt;
&lt;p&gt;We can solve this problem by constructing the cylinder negatively:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Start with an infinite space&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Throw out everything that isn't within the cylinder.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a classic mathematician’s approach, but it works great here, and it also works pretty well for any simply-connected solid object with only straight or concave surfaces, depending on how complex those surfaces are.&lt;/p&gt;
&lt;p&gt;First some definitions:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Our cylinder is defined by two points, A and B, at the start/end of the cylinder centre line respectively, and a radius R.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The point we want to test is P.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The vectors from the origin to points A, B and P are &lt;span class="math"&gt;\(\boldsymbol{r}_A\)&lt;/span&gt;, &lt;span class="math"&gt;\(\boldsymbol{r}_B\)&lt;/span&gt; and &lt;span class="math"&gt;\(\boldsymbol{r}_P\)&lt;/span&gt; respectively.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We start with the infinite space, that is we assume all points are within the cylinder until we show they aren’t.&lt;/p&gt;
&lt;p&gt;Then we construct 3 cuts to exclude certain values of &lt;span class="math"&gt;\(\boldsymbol{r}_P\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First&lt;/strong&gt;, a cylindrical cut of radius R about an infinite line that goes through A and B. (This was taken from John Alexiou's answer in the link above):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The vector from point A to point B is:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\boldsymbol{e} = \boldsymbol{r}_B-\boldsymbol{r}_A
\end{equation*}
&lt;/div&gt;
&lt;p&gt;This defines the direction of the line through A and B.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The distance from any point P at vector &lt;span class="math"&gt;\(\boldsymbol{r}_P\)&lt;/span&gt; to the line is:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
d = \frac{\| \boldsymbol{e}\times\left(\boldsymbol{r}_{P}-\boldsymbol{r}_{A}\right) \|}{\|\boldsymbol{e}\|}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;This is based on finding &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Another_vector_formulation"&gt;the distance of a point to a line&lt;/a&gt;,
and using point A as an arbitrary point on the line. We could equally have
used B.&lt;/p&gt;
&lt;p&gt;(You may notice that this fails if &lt;span class="math"&gt;\({\|\boldsymbol{e}\|} = 0\)&lt;/span&gt;, which will happen if A and B are exactly coincident. In this case you have a cylinder of zero volume, which contains no points. For robustness you should check for this case and return “False”)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We then simply exclude all points with &lt;span class="math"&gt;\(d &amp;gt; R\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;This can be optimised slightly by squaring both sides of the comparison to avoid two square root operations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Second&lt;/strong&gt;, a planar cut that throws away the space above the "top" of the cylinder, which I'm calling A.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The plane is defined by any point on it (A will do), and any normal pointing out of the cylinder, &lt;span class="math"&gt;\(-\boldsymbol{e}\)&lt;/span&gt; will do (i.e. in the opposite direction to &lt;span class="math"&gt;\(\boldsymbol{e}\)&lt;/span&gt; as defined above).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We can see which side a point is of this plane as per &lt;a class="reference external" href="https://math.stackexchange.com/a/1035631/78071"&gt;Relation between a point and a plane&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;The point is "above" the plane (in the direction of the normal) if:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
(\boldsymbol{r}_P - \boldsymbol{r}_A) \cdot -\boldsymbol{e} &amp;gt; 0
\end{equation*}
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We exclude points which match the above.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Third&lt;/strong&gt;, a planar cut which throws away the space below the bottom of the cylinder B.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;This is the same as the previous step, but with the other end of the cylinder and the normal vector in the other direction, so the condition is:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
(\boldsymbol{r}_P - \boldsymbol{r}_B) \cdot \boldsymbol{e} &amp;gt; 0
\end{equation*}
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="python-implementation"&gt;
&lt;h2&gt;Python implementation&lt;/h2&gt;
&lt;p&gt;Below is a minimal implementation with zero dependencies outside the standard lib, in which there are just enough classes, with just enough methods, to express the algorithm neatly, following the above steps exactly.&lt;/p&gt;
&lt;p&gt;In a real implementation:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;You might separate out a &lt;code class="docutils literal"&gt;Point&lt;/code&gt; class as being semantically different from &lt;code class="docutils literal"&gt;Vec&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You could move some functions to be methods (in my real implementation, I have a &lt;code class="docutils literal"&gt;Cylinder.contains_point()&lt;/code&gt; method, for example)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You should probably use &lt;code class="docutils literal"&gt;@dataclass(frozen=True)&lt;/code&gt; – immutable objects are a good default, I didn’t use them here because there aren’t needed and I’m focusing on clarity of the code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Conversely, if performance is more of a consideration, and you don’t have a more general need for classes like &lt;code class="docutils literal"&gt;Vec&lt;/code&gt; and &lt;code class="docutils literal"&gt;Cylinder&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;you might use a more efficient representation, such as a tuple or list, or a numpy array especially if you wanted bulk operations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;you could use more generic dot product functions etc. from numpy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;you might inline more of the algorithm into a single function.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For clarity, I also have not implemented the optimisation mentioned in which you can avoid doing some square root operations.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-1" name="rest_code_aaddb16c47434912a5c24eda54189535-1" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-2" name="rest_code_aaddb16c47434912a5c24eda54189535-2" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-3" name="rest_code_aaddb16c47434912a5c24eda54189535-3" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-3"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;math&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-4" name="rest_code_aaddb16c47434912a5c24eda54189535-4" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-4"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-5" name="rest_code_aaddb16c47434912a5c24eda54189535-5" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-5"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-6" name="rest_code_aaddb16c47434912a5c24eda54189535-6" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-6"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Implementation of https://math.stackexchange.com/questions/3518495/check-if-a-general-point-is-inside-a-given-cylinder&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-7" name="rest_code_aaddb16c47434912a5c24eda54189535-7" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-7"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-8" name="rest_code_aaddb16c47434912a5c24eda54189535-8" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-8"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# See the accompanying blog post http://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-9" name="rest_code_aaddb16c47434912a5c24eda54189535-9" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-9"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-10" name="rest_code_aaddb16c47434912a5c24eda54189535-10" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-10"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-11" name="rest_code_aaddb16c47434912a5c24eda54189535-11" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-11"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# -- Main algorithm --&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-12" name="rest_code_aaddb16c47434912a5c24eda54189535-12" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-12"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-13" name="rest_code_aaddb16c47434912a5c24eda54189535-13" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-13"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-14" name="rest_code_aaddb16c47434912a5c24eda54189535-14" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-14"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-15" name="rest_code_aaddb16c47434912a5c24eda54189535-15" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-15"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# First condition: distance from axis&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-16" name="rest_code_aaddb16c47434912a5c24eda54189535-16" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-16"&gt;&lt;/a&gt;    &lt;span class="n"&gt;cylinder_direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-17" name="rest_code_aaddb16c47434912a5c24eda54189535-17" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-17"&gt;&lt;/a&gt;    &lt;span class="n"&gt;abs_cylinder_direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder_direction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-18" name="rest_code_aaddb16c47434912a5c24eda54189535-18" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-18"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;abs_cylinder_direction&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-19" name="rest_code_aaddb16c47434912a5c24eda54189535-19" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-19"&gt;&lt;/a&gt;        &lt;span class="c1"&gt;# Empty cylinder. We also have to avoid a divide-by-zero below&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-20" name="rest_code_aaddb16c47434912a5c24eda54189535-20" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-20"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-21" name="rest_code_aaddb16c47434912a5c24eda54189535-21" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-21"&gt;&lt;/a&gt;    &lt;span class="n"&gt;point_distance_from_axis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-22" name="rest_code_aaddb16c47434912a5c24eda54189535-22" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-22"&gt;&lt;/a&gt;        &lt;span class="n"&gt;cross_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-23" name="rest_code_aaddb16c47434912a5c24eda54189535-23" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-23"&gt;&lt;/a&gt;            &lt;span class="n"&gt;cylinder_direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-24" name="rest_code_aaddb16c47434912a5c24eda54189535-24" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-24"&gt;&lt;/a&gt;            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-25" name="rest_code_aaddb16c47434912a5c24eda54189535-25" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-25"&gt;&lt;/a&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-26" name="rest_code_aaddb16c47434912a5c24eda54189535-26" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-26"&gt;&lt;/a&gt;    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder_direction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-27" name="rest_code_aaddb16c47434912a5c24eda54189535-27" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-27"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;point_distance_from_axis&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-28" name="rest_code_aaddb16c47434912a5c24eda54189535-28" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-28"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-29" name="rest_code_aaddb16c47434912a5c24eda54189535-29" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-29"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-30" name="rest_code_aaddb16c47434912a5c24eda54189535-30" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-30"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# Second condition: point must lie below the top plane.&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-31" name="rest_code_aaddb16c47434912a5c24eda54189535-31" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-31"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# Third condition: point must lie above the bottom plane&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-32" name="rest_code_aaddb16c47434912a5c24eda54189535-32" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-32"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-33" name="rest_code_aaddb16c47434912a5c24eda54189535-33" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-33"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# We construct planes with normals pointing out of the cylinder at both&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-34" name="rest_code_aaddb16c47434912a5c24eda54189535-34" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-34"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# ends, and exclude points that are outside ("above") either plane.&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-35" name="rest_code_aaddb16c47434912a5c24eda54189535-35" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-35"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-36" name="rest_code_aaddb16c47434912a5c24eda54189535-36" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-36"&gt;&lt;/a&gt;    &lt;span class="n"&gt;start_plane&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Plane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cylinder_direction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-37" name="rest_code_aaddb16c47434912a5c24eda54189535-37" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-37"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;point_is_above_plane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_plane&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-38" name="rest_code_aaddb16c47434912a5c24eda54189535-38" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-38"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-39" name="rest_code_aaddb16c47434912a5c24eda54189535-39" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-39"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-40" name="rest_code_aaddb16c47434912a5c24eda54189535-40" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-40"&gt;&lt;/a&gt;    &lt;span class="n"&gt;end_plane&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Plane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cylinder_direction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-41" name="rest_code_aaddb16c47434912a5c24eda54189535-41" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-41"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;point_is_above_plane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_plane&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-42" name="rest_code_aaddb16c47434912a5c24eda54189535-42" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-42"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-43" name="rest_code_aaddb16c47434912a5c24eda54189535-43" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-43"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-44" name="rest_code_aaddb16c47434912a5c24eda54189535-44" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-44"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-45" name="rest_code_aaddb16c47434912a5c24eda54189535-45" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-45"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-46" name="rest_code_aaddb16c47434912a5c24eda54189535-46" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-46"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-47" name="rest_code_aaddb16c47434912a5c24eda54189535-47" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-47"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# -- Supporting classes and functions --&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-48" name="rest_code_aaddb16c47434912a5c24eda54189535-48" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-48"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-49" name="rest_code_aaddb16c47434912a5c24eda54189535-49" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-49"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-50" name="rest_code_aaddb16c47434912a5c24eda54189535-50" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-50"&gt;&lt;/a&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-51" name="rest_code_aaddb16c47434912a5c24eda54189535-51" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-51"&gt;&lt;/a&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-52" name="rest_code_aaddb16c47434912a5c24eda54189535-52" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-52"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-53" name="rest_code_aaddb16c47434912a5c24eda54189535-53" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-53"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    A Vector in 3 dimensions, also used to represent points in space&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-54" name="rest_code_aaddb16c47434912a5c24eda54189535-54" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-54"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    """&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-55" name="rest_code_aaddb16c47434912a5c24eda54189535-55" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-55"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-56" name="rest_code_aaddb16c47434912a5c24eda54189535-56" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-56"&gt;&lt;/a&gt;    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-57" name="rest_code_aaddb16c47434912a5c24eda54189535-57" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-57"&gt;&lt;/a&gt;    &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-58" name="rest_code_aaddb16c47434912a5c24eda54189535-58" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-58"&gt;&lt;/a&gt;    &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-59" name="rest_code_aaddb16c47434912a5c24eda54189535-59" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-59"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-60" name="rest_code_aaddb16c47434912a5c24eda54189535-60" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-60"&gt;&lt;/a&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__add__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-61" name="rest_code_aaddb16c47434912a5c24eda54189535-61" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-61"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-62" name="rest_code_aaddb16c47434912a5c24eda54189535-62" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-62"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-63" name="rest_code_aaddb16c47434912a5c24eda54189535-63" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-63"&gt;&lt;/a&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__sub__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-64" name="rest_code_aaddb16c47434912a5c24eda54189535-64" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-64"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-65" name="rest_code_aaddb16c47434912a5c24eda54189535-65" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-65"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-66" name="rest_code_aaddb16c47434912a5c24eda54189535-66" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-66"&gt;&lt;/a&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__neg__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-67" name="rest_code_aaddb16c47434912a5c24eda54189535-67" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-67"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-68" name="rest_code_aaddb16c47434912a5c24eda54189535-68" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-68"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-69" name="rest_code_aaddb16c47434912a5c24eda54189535-69" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-69"&gt;&lt;/a&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__mul__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scalar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-70" name="rest_code_aaddb16c47434912a5c24eda54189535-70" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-70"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;scalar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;scalar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;scalar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-71" name="rest_code_aaddb16c47434912a5c24eda54189535-71" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-71"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-72" name="rest_code_aaddb16c47434912a5c24eda54189535-72" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-72"&gt;&lt;/a&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__rmul__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scalar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-73" name="rest_code_aaddb16c47434912a5c24eda54189535-73" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-73"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;scalar&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-74" name="rest_code_aaddb16c47434912a5c24eda54189535-74" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-74"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-75" name="rest_code_aaddb16c47434912a5c24eda54189535-75" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-75"&gt;&lt;/a&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__abs__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-76" name="rest_code_aaddb16c47434912a5c24eda54189535-76" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-76"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-77" name="rest_code_aaddb16c47434912a5c24eda54189535-77" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-77"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-78" name="rest_code_aaddb16c47434912a5c24eda54189535-78" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-78"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-79" name="rest_code_aaddb16c47434912a5c24eda54189535-79" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-79"&gt;&lt;/a&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-80" name="rest_code_aaddb16c47434912a5c24eda54189535-80" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-80"&gt;&lt;/a&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Plane&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-81" name="rest_code_aaddb16c47434912a5c24eda54189535-81" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-81"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-82" name="rest_code_aaddb16c47434912a5c24eda54189535-82" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-82"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    A plane defined by a point on the plane, `origin`, and&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-83" name="rest_code_aaddb16c47434912a5c24eda54189535-83" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-83"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    a `normal` vector to the plane.&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-84" name="rest_code_aaddb16c47434912a5c24eda54189535-84" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-84"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    """&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-85" name="rest_code_aaddb16c47434912a5c24eda54189535-85" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-85"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-86" name="rest_code_aaddb16c47434912a5c24eda54189535-86" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-86"&gt;&lt;/a&gt;    &lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-87" name="rest_code_aaddb16c47434912a5c24eda54189535-87" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-87"&gt;&lt;/a&gt;    &lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-88" name="rest_code_aaddb16c47434912a5c24eda54189535-88" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-88"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-89" name="rest_code_aaddb16c47434912a5c24eda54189535-89" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-89"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-90" name="rest_code_aaddb16c47434912a5c24eda54189535-90" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-90"&gt;&lt;/a&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-91" name="rest_code_aaddb16c47434912a5c24eda54189535-91" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-91"&gt;&lt;/a&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Cylinder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-92" name="rest_code_aaddb16c47434912a5c24eda54189535-92" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-92"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-93" name="rest_code_aaddb16c47434912a5c24eda54189535-93" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-93"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    A closed cylinder defined by start and end points along the center&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-94" name="rest_code_aaddb16c47434912a5c24eda54189535-94" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-94"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    line and a radius&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-95" name="rest_code_aaddb16c47434912a5c24eda54189535-95" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-95"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    """&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-96" name="rest_code_aaddb16c47434912a5c24eda54189535-96" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-96"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-97" name="rest_code_aaddb16c47434912a5c24eda54189535-97" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-97"&gt;&lt;/a&gt;    &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-98" name="rest_code_aaddb16c47434912a5c24eda54189535-98" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-98"&gt;&lt;/a&gt;    &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-99" name="rest_code_aaddb16c47434912a5c24eda54189535-99" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-99"&gt;&lt;/a&gt;    &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-100" name="rest_code_aaddb16c47434912a5c24eda54189535-100" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-100"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-101" name="rest_code_aaddb16c47434912a5c24eda54189535-101" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-101"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-102" name="rest_code_aaddb16c47434912a5c24eda54189535-102" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-102"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cross_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-103" name="rest_code_aaddb16c47434912a5c24eda54189535-103" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-103"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-104" name="rest_code_aaddb16c47434912a5c24eda54189535-104" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-104"&gt;&lt;/a&gt;        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-105" name="rest_code_aaddb16c47434912a5c24eda54189535-105" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-105"&gt;&lt;/a&gt;        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-106" name="rest_code_aaddb16c47434912a5c24eda54189535-106" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-106"&gt;&lt;/a&gt;        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-107" name="rest_code_aaddb16c47434912a5c24eda54189535-107" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-107"&gt;&lt;/a&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-108" name="rest_code_aaddb16c47434912a5c24eda54189535-108" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-108"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-109" name="rest_code_aaddb16c47434912a5c24eda54189535-109" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-109"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-110" name="rest_code_aaddb16c47434912a5c24eda54189535-110" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-110"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dot_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-111" name="rest_code_aaddb16c47434912a5c24eda54189535-111" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-111"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-112" name="rest_code_aaddb16c47434912a5c24eda54189535-112" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-112"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-113" name="rest_code_aaddb16c47434912a5c24eda54189535-113" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-113"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-114" name="rest_code_aaddb16c47434912a5c24eda54189535-114" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-114"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;point_is_above_plane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plane&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Plane&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-115" name="rest_code_aaddb16c47434912a5c24eda54189535-115" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-115"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-116" name="rest_code_aaddb16c47434912a5c24eda54189535-116" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-116"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    Returns True if `point` is above the plane — that is on the side&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-117" name="rest_code_aaddb16c47434912a5c24eda54189535-117" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-117"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    of the plane which is in the direction of the plane `normal`.&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-118" name="rest_code_aaddb16c47434912a5c24eda54189535-118" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-118"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    """&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-119" name="rest_code_aaddb16c47434912a5c24eda54189535-119" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-119"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# See https://math.stackexchange.com/a/2998886/78071&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-120" name="rest_code_aaddb16c47434912a5c24eda54189535-120" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-120"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dot_product&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;plane&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;plane&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-121" name="rest_code_aaddb16c47434912a5c24eda54189535-121" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-121"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-122" name="rest_code_aaddb16c47434912a5c24eda54189535-122" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-122"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-123" name="rest_code_aaddb16c47434912a5c24eda54189535-123" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-123"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# -- Tests --&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-124" name="rest_code_aaddb16c47434912a5c24eda54189535-124" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-124"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-125" name="rest_code_aaddb16c47434912a5c24eda54189535-125" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-125"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-126" name="rest_code_aaddb16c47434912a5c24eda54189535-126" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-126"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-127" name="rest_code_aaddb16c47434912a5c24eda54189535-127" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-127"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# Test cases constructed with help of Geogebra - https://www.geogebra.org/calculator/tnc3arfm&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-128" name="rest_code_aaddb16c47434912a5c24eda54189535-128" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-128"&gt;&lt;/a&gt;    &lt;span class="n"&gt;cylinder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Cylinder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;6.196&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-129" name="rest_code_aaddb16c47434912a5c24eda54189535-129" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-129"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-130" name="rest_code_aaddb16c47434912a5c24eda54189535-130" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-130"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# In the Z plane:&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-131" name="rest_code_aaddb16c47434912a5c24eda54189535-131" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-131"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-132" name="rest_code_aaddb16c47434912a5c24eda54189535-132" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-132"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.98&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# outside bottom plane&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-133" name="rest_code_aaddb16c47434912a5c24eda54189535-133" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-133"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-134" name="rest_code_aaddb16c47434912a5c24eda54189535-134" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-134"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-135" name="rest_code_aaddb16c47434912a5c24eda54189535-135" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-135"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# too far from center&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-136" name="rest_code_aaddb16c47434912a5c24eda54189535-136" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-136"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# outside bottom plane&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-137" name="rest_code_aaddb16c47434912a5c24eda54189535-137" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-137"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-138" name="rest_code_aaddb16c47434912a5c24eda54189535-138" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-138"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-139" name="rest_code_aaddb16c47434912a5c24eda54189535-139" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-139"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# too far from center&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-140" name="rest_code_aaddb16c47434912a5c24eda54189535-140" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-140"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-141" name="rest_code_aaddb16c47434912a5c24eda54189535-141" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-141"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;6.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-142" name="rest_code_aaddb16c47434912a5c24eda54189535-142" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-142"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;6.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# too far from center&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-143" name="rest_code_aaddb16c47434912a5c24eda54189535-143" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-143"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;6.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# outside top plane&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-144" name="rest_code_aaddb16c47434912a5c24eda54189535-144" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-144"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-145" name="rest_code_aaddb16c47434912a5c24eda54189535-145" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-145"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# Away from Z plane&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-146" name="rest_code_aaddb16c47434912a5c24eda54189535-146" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-146"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-147" name="rest_code_aaddb16c47434912a5c24eda54189535-147" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-147"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# too far from center&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-148" name="rest_code_aaddb16c47434912a5c24eda54189535-148" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-148"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# too far from center, and outside bottom plane&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-149" name="rest_code_aaddb16c47434912a5c24eda54189535-149" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-149"&gt;&lt;/a&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-150" name="rest_code_aaddb16c47434912a5c24eda54189535-150" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-150"&gt;&lt;/a&gt;    &lt;span class="n"&gt;zero_cylinder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Cylinder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_aaddb16c47434912a5c24eda54189535-151" name="rest_code_aaddb16c47434912a5c24eda54189535-151" href="https://lukeplant.me.uk/blog/posts/check-if-a-point-is-in-a-cylinder-geometry-and-code/#rest_code_aaddb16c47434912a5c24eda54189535-151"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cylinder_contains_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zero_cylinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;</content>
    <category term="geometry" label="Geometry"/>
    <category term="python" label="Python"/>
  </entry>
  <entry>
    <title>Keeping things in sync: derive vs test</title>
    <id>https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/</id>
    <updated>2024-06-28T10:15:00+01:00</updated>
    <published>2024-06-28T10:15:00+01:00</published>
    <author>
      <name>Luke Plant</name>
    </author>
    <link rel="alternate" type="text/html" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/"/>
    <summary type="html">&lt;p&gt;There are times when we need to stop trying to make everything sync automatically, and just test that it is synced. Tips for Python and web dev.&lt;/p&gt;</summary>
    <content type="html">&lt;p&gt;An extremely common problem in programming is that multiple parts of a program need to be kept in sync – they need to do exactly the same thing or behave in a consistent way. It is in response to this problem that we have mantras like “DRY” (Don’t Repeat Yourself), or, as I prefer it, &lt;a class="reference external" href="https://wiki.c2.com/?OnceAndOnlyOnce"&gt;OAOO&lt;/a&gt;, “Each and every declaration of behaviour should appear Once And Only Once”.&lt;/p&gt;
&lt;p&gt;For both of these mantras, if you are faced with possible duplication of any kind, the answer is simply “just say no”. However, since programming mantras are to be understood as &lt;a class="reference external" href="https://lukeplant.me.uk/blog/posts/programming-mantras-are-proverbs/"&gt;proverbs&lt;/a&gt;, not absolute laws, there are times that obeying this mantra can hurt more than it helps, so in this post I’m going to discuss other approaches.&lt;/p&gt;
&lt;p&gt;Most of what I say is fairly language agnostic I think, but I’ve got specific tips for Python and web development.&lt;/p&gt;
&lt;nav class="contents" id="contents" role="doc-toc"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#top"&gt;Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#the-essential-problem" id="toc-entry-1"&gt;The essential problem&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#the-ideal-solution-derive" id="toc-entry-2"&gt;The ideal solution: derive&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#alternative-solution-test" id="toc-entry-3"&gt;Alternative solution: test&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#examples" id="toc-entry-4"&gt;Examples&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#example-1-external-data-sources" id="toc-entry-5"&gt;Example 1 - external data sources&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#example-2-defining-ui-behaviour-for-domain-objects" id="toc-entry-6"&gt;Example 2 - defining UI behaviour for domain objects&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#example-3-external-polymorphism-and-static-typing" id="toc-entry-7"&gt;Example 3 - external polymorphism and static typing&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#example-4-generated-code" id="toc-entry-8"&gt;Example 4 - generated code&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#conclusion" id="toc-entry-9"&gt;Conclusion&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#links" id="toc-entry-10"&gt;Links&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;section id="the-essential-problem"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#toc-entry-1" role="doc-backlink"&gt;The essential problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To step back for a second, the essential problem that we are addressing here is that if making a change to a certain behaviour requires changing more than one place in the code, we have the risk that one will be forgotten. This results in bugs, which can be of various degrees of seriousness depending on the code in question.&lt;/p&gt;
&lt;p&gt;To pick a concrete example, suppose we have a rule that says that items in a deleted folder get stored for 30 days, then expunged. We’re going to need some code that does the actual expunging after 30 days, but we’re also going to need to tell the user about the limit somewhere in the user interface. “Once And Only Once” says that the 30 days limit needs to be defined in a single place somewhere, and then reused.&lt;/p&gt;
&lt;p&gt;There is a second kind of motivating example, which I think often crops up  when people quote “Don’t Repeat Yourself”, and it’s really about avoiding tedious things from a developer perspective. Suppose you need to add an item to a menu, and you find out that first you’ve got to edit the &lt;code class="docutils literal"&gt;MENU_ITEMS&lt;/code&gt; file to add an entry, then you’ve got to edit the &lt;code class="docutils literal"&gt;MAIN_MENU&lt;/code&gt; constant to refer to the new entry, then you’ve got to define a keyboard shortcut in the &lt;code class="docutils literal"&gt;MENU_SHORTCUTS&lt;/code&gt; file, then a menu icon somewhere else etc. All of these different places are in some way repeating things about how menus work. I think this is less important in general, but it is certainly life-draining as a developer if code is structured in this way, especially if it is difficult to discover or remember all the things that have to be done.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-ideal-solution-derive"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#toc-entry-2" role="doc-backlink"&gt;The ideal solution: derive&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OAOO and DRY say that we aim to have a single place that defines the rule or logic, and any other place should be &lt;strong&gt;derived&lt;/strong&gt; from this.&lt;/p&gt;
&lt;p&gt;Regarding the simple example of a time limit displayed in the UI and used in the backend, this might be as simple as defining a constant e.g. in Python:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_84e9834c384141c08219938e070d9aa8-1" name="rest_code_84e9834c384141c08219938e070d9aa8-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_84e9834c384141c08219938e070d9aa8-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;
&lt;a id="rest_code_84e9834c384141c08219938e070d9aa8-2" name="rest_code_84e9834c384141c08219938e070d9aa8-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_84e9834c384141c08219938e070d9aa8-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_84e9834c384141c08219938e070d9aa8-3" name="rest_code_84e9834c384141c08219938e070d9aa8-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_84e9834c384141c08219938e070d9aa8-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;EXPUNGE_TIME_LIMIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We then &lt;code class="docutils literal"&gt;import&lt;/code&gt; and use this constant in both our UI and backend.&lt;/p&gt;
&lt;p&gt;An important part of this approach is that the “deriving” process should be entirely automatic, not something that you can forget to do. In the case of a Python &lt;code class="docutils literal"&gt;import&lt;/code&gt; statement, that is very easy to achieve, and relatively hard to get wrong – if you change the constant where it is defined in one module, any other code that uses it will pick up the change the next time the Python process is restarted.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="alternative-solution-test"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#toc-entry-3" role="doc-backlink"&gt;Alternative solution: test&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By “test”, I mean ideally an automated test, but manual tests may also work if they are properly scripted. The idea is that you write a test that checks the behaviour of code is synced. Often, it may be that for one (or more) instances that need the behaviour will define it using some constant as above, let’s say the “backend” code. Then, for one instance, e.g. the UI, you would hard code “30 days” without using the constant, but have a test that uses the backend constant to build a string, and checks the UI for that string.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="examples"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#toc-entry-4" role="doc-backlink"&gt;Examples&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the example above, it might be hard to see why you want to use the fundamentally less reliable, less automatic method I’m suggesting. So I now have to show some motivating examples where the “derive” method ends up losing to the cruder, simpler alternative of “test”.&lt;/p&gt;
&lt;section id="example-1-external-data-sources"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#toc-entry-5" role="doc-backlink"&gt;Example 1 - external data sources&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;My first example comes from the project I’m currently working on, which involves
creating &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Computer-aided_manufacturing"&gt;CAM&lt;/a&gt;
files from input data. Most of the logic for that is driven using code, but
there are some dimensions that are specified as data tables by the engineers of
the physical product.&lt;/p&gt;
&lt;p&gt;These data tables look something like below. The details here aren’t important, and I’ve changed them – it’s enough to know that we’ve are creating some physical “widgets” which need to have specific dimensions specified:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th class="head" colspan="3"&gt;&lt;p&gt;Widgets have length 150mm unless specified below&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="head"&gt;&lt;p&gt;Widget id&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Location&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Length (mm)&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;A&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;start&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;100&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;A&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;end&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;120&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;F&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;start&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;105&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;F&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;end&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;110&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;These tables are supplied at design-time rather than run-time i.e. they are bundled with the software and can’t be changed after the code is shipped. But it is still convenient to read them in automatically rather than simply duplicate the tables in my code by some process. So, for the body of the table, that’s exactly what my code does on startup – it reads the bundled XLSX/CSV files.&lt;/p&gt;
&lt;p&gt;So we are obeying “derive” here — there is a single, canonical source of data, and anywhere that needs it derives it by an entirely automatic process.&lt;/p&gt;
&lt;p&gt;But what about that “150mm” default value specified in the header of that table?&lt;/p&gt;
&lt;p&gt;It would be possible to “derive” it by having a parser. Writing such a parser is not hard to do – for this kind of thing in Python I like &lt;a class="reference external" href="https://github.com/python-parsy/parsy/"&gt;parsy&lt;/a&gt;, and it is as simple as:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-1" name="rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;parsy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;P&lt;/span&gt;
&lt;a id="rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-2" name="rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-3" name="rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;default_length_parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-4" name="rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-4" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-4"&gt;&lt;/a&gt;  &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Widgets have length "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-5" name="rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-5" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-5"&gt;&lt;/a&gt;  &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;"\d+"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-6" name="rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-6" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-6"&gt;&lt;/a&gt;  &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"mm unless specified below"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-7" name="rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-7" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_f2bdec6da0c94ecb9a9ae2c5a48f60eb-7"&gt;&lt;/a&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In fact I do something similar in some cases. But in reality, the “parser” here is pretty simplistic – it can’t deal with the real variety of English text that might be put into the sentence, and to claim I’m “deriving” it from the table is a bit of a stretch – I’m just matching a specific, known pattern. In addition, it’s probably not the case that &lt;strong&gt;any&lt;/strong&gt; value for the default length would work – most likely if it was 10 times larger, there would be some other problem, and I’d want to do some manual checking.&lt;/p&gt;
&lt;p&gt;So, let’s admit that we are really just checking for something expected, using the “test” approach. You can still define a constant that you use in most of the code:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_ce0c9d63dc1e421f88efe3c93ce7a75d-1" name="rest_code_ce0c9d63dc1e421f88efe3c93ce7a75d-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_ce0c9d63dc1e421f88efe3c93ce7a75d-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;DEFAULT_LENGTH_MM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And then you test it is what you expect when you load the data file:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_8e5941c2ac244aa08829366bd12f135b-1" name="rest_code_8e5941c2ac244aa08829366bd12f135b-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_8e5941c2ac244aa08829366bd12f135b-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;worksheets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Widgets have length &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_LENGTH_MM&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;mm unless specified below"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So, I’ve achieved my aim: a guard against the original problem of having multiple sources of information that could potentially be out of sync. But I’ve done it using a simple test, rather than a more complex and fragile “derive” that wouldn’t have worked well anyway.&lt;/p&gt;
&lt;p&gt;By the way, for this specific project – &lt;a class="reference external" href="https://lukeplant.me.uk/firma-job/"&gt;we’re looking for another contract developer&lt;/a&gt;! It’s a very worthwhile project, and one I’m really enjoying – a small flexible team, with plenty of problem solving and fun challenges, so if you’re a talented developer and interested give me a shout.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="example-2-defining-ui-behaviour-for-domain-objects"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#toc-entry-6" role="doc-backlink"&gt;Example 2 - defining UI behaviour for domain objects&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Suppose you have a database that stores information about some kind of entity, like customers say, and you have different types of customer, represented using an enum of some kind, perhaps a string enum like this in Python:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-1" name="rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StrEnum&lt;/span&gt;
&lt;a id="rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-2" name="rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-3" name="rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-3"&gt;&lt;/a&gt;
&lt;a id="rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-4" name="rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-4" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-4"&gt;&lt;/a&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomerType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StrEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-5" name="rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-5" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-5"&gt;&lt;/a&gt;    &lt;span class="n"&gt;ENTERPRISE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Enterprise"&lt;/span&gt;
&lt;a id="rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-6" name="rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-6" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-6"&gt;&lt;/a&gt;    &lt;span class="n"&gt;SMALL_FRY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Small fry"&lt;/span&gt;  &lt;span class="c1"&gt;# Let’s be honest! Try not to let the name leak…&lt;/span&gt;
&lt;a id="rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-7" name="rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-7" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_f2b4a70a5c30477db19bcb664ad3ee0d-7"&gt;&lt;/a&gt;    &lt;span class="n"&gt;LEGACY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Legacy"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We need to a way edit the different customer types, and they are sufficiently different that we want quite different interfaces. So, we might have a dictionary mapping the customer type to a function or class that defines the UI. If this were a Django project, it might be a different &lt;a class="reference external" href="https://docs.djangoproject.com/en/5.0/ref/forms/api/"&gt;Form&lt;/a&gt; class for each type:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_816ef8b1cda24e749756fb7df504b1d3-1" name="rest_code_816ef8b1cda24e749756fb7df504b1d3-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_816ef8b1cda24e749756fb7df504b1d3-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;CUSTOMER_EDIT_FORMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_816ef8b1cda24e749756fb7df504b1d3-2" name="rest_code_816ef8b1cda24e749756fb7df504b1d3-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_816ef8b1cda24e749756fb7df504b1d3-2"&gt;&lt;/a&gt;    &lt;span class="n"&gt;CustomerType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ENTERPRISE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EnterpriseCustomerForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_816ef8b1cda24e749756fb7df504b1d3-3" name="rest_code_816ef8b1cda24e749756fb7df504b1d3-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_816ef8b1cda24e749756fb7df504b1d3-3"&gt;&lt;/a&gt;    &lt;span class="n"&gt;CustomerType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SMALL_FRY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SmallFryCustomerForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_816ef8b1cda24e749756fb7df504b1d3-4" name="rest_code_816ef8b1cda24e749756fb7df504b1d3-4" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_816ef8b1cda24e749756fb7df504b1d3-4"&gt;&lt;/a&gt;    &lt;span class="n"&gt;CustomerType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LEGACY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LegacyCustomerForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_816ef8b1cda24e749756fb7df504b1d3-5" name="rest_code_816ef8b1cda24e749756fb7df504b1d3-5" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_816ef8b1cda24e749756fb7df504b1d3-5"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, the DRY instinct kicks in and we notice that we now have two things we have to remember to keep in sync — any addition to the customer enum requires a corresponding addition to the UI definition dictionary. Maybe there are multiple dictionaries like this.&lt;/p&gt;
&lt;p&gt;We could attempt to solve this by “deriving”, or some “correct by construction” mechanism that puts the creation of a new customer type all in one place.&lt;/p&gt;
&lt;p&gt;For example, maybe we’ll have a base &lt;code class="docutils literal"&gt;Customer&lt;/code&gt; class with &lt;code class="docutils literal"&gt;get_edit_form_class()&lt;/code&gt; as an &lt;a class="reference external" href="https://docs.python.org/3/library/abc.html#abc.abstractmethod"&gt;abstractmethod&lt;/a&gt;, which means it is required to be implemented. If I fail to implement it in a subclass, I can’t even construct an instance of the new customer subclass – it will throw an error.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_195f4e75bfff416fbcc0a0768ce11092-1" name="rest_code_195f4e75bfff416fbcc0a0768ce11092-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_195f4e75bfff416fbcc0a0768ce11092-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;abc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;abstractmethod&lt;/span&gt;
&lt;a id="rest_code_195f4e75bfff416fbcc0a0768ce11092-2" name="rest_code_195f4e75bfff416fbcc0a0768ce11092-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_195f4e75bfff416fbcc0a0768ce11092-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_195f4e75bfff416fbcc0a0768ce11092-3" name="rest_code_195f4e75bfff416fbcc0a0768ce11092-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_195f4e75bfff416fbcc0a0768ce11092-3"&gt;&lt;/a&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_195f4e75bfff416fbcc0a0768ce11092-4" name="rest_code_195f4e75bfff416fbcc0a0768ce11092-4" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_195f4e75bfff416fbcc0a0768ce11092-4"&gt;&lt;/a&gt;    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
&lt;a id="rest_code_195f4e75bfff416fbcc0a0768ce11092-5" name="rest_code_195f4e75bfff416fbcc0a0768ce11092-5" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_195f4e75bfff416fbcc0a0768ce11092-5"&gt;&lt;/a&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_edit_form_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_195f4e75bfff416fbcc0a0768ce11092-6" name="rest_code_195f4e75bfff416fbcc0a0768ce11092-6" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_195f4e75bfff416fbcc0a0768ce11092-6"&gt;&lt;/a&gt;        &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;a id="rest_code_195f4e75bfff416fbcc0a0768ce11092-7" name="rest_code_195f4e75bfff416fbcc0a0768ce11092-7" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_195f4e75bfff416fbcc0a0768ce11092-7"&gt;&lt;/a&gt;
&lt;a id="rest_code_195f4e75bfff416fbcc0a0768ce11092-8" name="rest_code_195f4e75bfff416fbcc0a0768ce11092-8" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_195f4e75bfff416fbcc0a0768ce11092-8"&gt;&lt;/a&gt;
&lt;a id="rest_code_195f4e75bfff416fbcc0a0768ce11092-9" name="rest_code_195f4e75bfff416fbcc0a0768ce11092-9" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_195f4e75bfff416fbcc0a0768ce11092-9"&gt;&lt;/a&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EnterpriseCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_195f4e75bfff416fbcc0a0768ce11092-10" name="rest_code_195f4e75bfff416fbcc0a0768ce11092-10" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_195f4e75bfff416fbcc0a0768ce11092-10"&gt;&lt;/a&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_edit_form_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_195f4e75bfff416fbcc0a0768ce11092-11" name="rest_code_195f4e75bfff416fbcc0a0768ce11092-11" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_195f4e75bfff416fbcc0a0768ce11092-11"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;EnterpriseCustomerForm&lt;/span&gt;
&lt;a id="rest_code_195f4e75bfff416fbcc0a0768ce11092-12" name="rest_code_195f4e75bfff416fbcc0a0768ce11092-12" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_195f4e75bfff416fbcc0a0768ce11092-12"&gt;&lt;/a&gt;
&lt;a id="rest_code_195f4e75bfff416fbcc0a0768ce11092-13" name="rest_code_195f4e75bfff416fbcc0a0768ce11092-13" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_195f4e75bfff416fbcc0a0768ce11092-13"&gt;&lt;/a&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LegacyCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_195f4e75bfff416fbcc0a0768ce11092-14" name="rest_code_195f4e75bfff416fbcc0a0768ce11092-14" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_195f4e75bfff416fbcc0a0768ce11092-14"&gt;&lt;/a&gt;    &lt;span class="o"&gt;...&lt;/span&gt;  &lt;span class="c1"&gt;# etc.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I still need my enum value, or at least a list of valid values that I can use for my database field. Maybe I could derive that automatically by looking at all the sublclasses?&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_ccf993fc5f1442fab52634d62a28d8cb-1" name="rest_code_ccf993fc5f1442fab52634d62a28d8cb-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_ccf993fc5f1442fab52634d62a28d8cb-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;CUSTOMER_TYPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;a id="rest_code_ccf993fc5f1442fab52634d62a28d8cb-2" name="rest_code_ccf993fc5f1442fab52634d62a28d8cb-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_ccf993fc5f1442fab52634d62a28d8cb-2"&gt;&lt;/a&gt;    &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"CUSTOMER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_ccf993fc5f1442fab52634d62a28d8cb-3" name="rest_code_ccf993fc5f1442fab52634d62a28d8cb-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_ccf993fc5f1442fab52634d62a28d8cb-3"&gt;&lt;/a&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__subclasses__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_ccf993fc5f1442fab52634d62a28d8cb-4" name="rest_code_ccf993fc5f1442fab52634d62a28d8cb-4" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_ccf993fc5f1442fab52634d62a28d8cb-4"&gt;&lt;/a&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or maybe an &lt;code class="docutils literal"&gt;__init_subclass__&lt;/code&gt; trick, and I can perhaps also set up the various mappings I’ll need that way?&lt;/p&gt;
&lt;p&gt;It’s at this point you should stop and think. In addition to requiring you to mix UI concerns into the &lt;code class="docutils literal"&gt;Customer&lt;/code&gt; class definitions, it’s getting complex and magical.&lt;/p&gt;
&lt;p&gt;The alternative I’m suggesting is this: require manual syncing of the two parts of the code base, but add a test to ensure that you did it. All you need is a few lines after your &lt;code class="docutils literal"&gt;CUSTOMER_EDIT_FORMS&lt;/code&gt; definition:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_ddca5594de2547bba70fcb7d8ce5d642-1" name="rest_code_ddca5594de2547bba70fcb7d8ce5d642-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_ddca5594de2547bba70fcb7d8ce5d642-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;CUSTOMER_EDIT_FORMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_ddca5594de2547bba70fcb7d8ce5d642-2" name="rest_code_ddca5594de2547bba70fcb7d8ce5d642-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_ddca5594de2547bba70fcb7d8ce5d642-2"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# etc as before&lt;/span&gt;
&lt;a id="rest_code_ddca5594de2547bba70fcb7d8ce5d642-3" name="rest_code_ddca5594de2547bba70fcb7d8ce5d642-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_ddca5594de2547bba70fcb7d8ce5d642-3"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_ddca5594de2547bba70fcb7d8ce5d642-4" name="rest_code_ddca5594de2547bba70fcb7d8ce5d642-4" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_ddca5594de2547bba70fcb7d8ce5d642-4"&gt;&lt;/a&gt;
&lt;a id="rest_code_ddca5594de2547bba70fcb7d8ce5d642-5" name="rest_code_ddca5594de2547bba70fcb7d8ce5d642-5" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_ddca5594de2547bba70fcb7d8ce5d642-5"&gt;&lt;/a&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c_type&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;CustomerType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_ddca5594de2547bba70fcb7d8ce5d642-6" name="rest_code_ddca5594de2547bba70fcb7d8ce5d642-6" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_ddca5594de2547bba70fcb7d8ce5d642-6"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_ddca5594de2547bba70fcb7d8ce5d642-7" name="rest_code_ddca5594de2547bba70fcb7d8ce5d642-7" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_ddca5594de2547bba70fcb7d8ce5d642-7"&gt;&lt;/a&gt;        &lt;span class="n"&gt;c_type&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;CUSTOMER_EDIT_FORMS&lt;/span&gt;
&lt;a id="rest_code_ddca5594de2547bba70fcb7d8ce5d642-8" name="rest_code_ddca5594de2547bba70fcb7d8ce5d642-8" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_ddca5594de2547bba70fcb7d8ce5d642-8"&gt;&lt;/a&gt;    &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"You've defined a new customer type &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, you need to add an entry in CUSTOMER_EDIT_FORMS"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You could do this as a more traditional unit test in a separate file, but for simple things like this, I think an assertion right next to the code works much better. It really helps local reasoning to be able to look and immediately conclude “yes, I can see that this dictionary must be exhaustive because the assertion tells me so.” Plus you get really early failure – as soon as you import the code.&lt;/p&gt;
&lt;p&gt;This kind of thing crops up a lot – if you create a class here, you’ve got to create another one over there, or add a dictionary entry etc. In these cases, I’m finding simple tests and assertions have a ton of advantages when compared to clever architectural contortions (or other things like advanced static typing gymnastics):&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;they are massively simpler to create and understand.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;you can write your own error message in the assertion. If you make a habit of using really clear error messages, like the one above, your code base will literally tell you how to maintain it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;you can easily add things like exceptions. “Every Customer type needs an edit UI defined, except Legacy because they are read only” is an easy, small change to the above.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;This contrasts with cleverer mechanisms, which might require relaxing other constraints to the point where you defeat the whole point of the mechanism, or create more difficulties for yourself.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the rule about how the code works is very explicit, rather than implicit in some complicated code structure, and typically needs no comment other than what you write in the assertion message.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;you express and enforce the rule, with any complexities it gains, in just one place. Ironically, if you try to enforce this kind of constraint using type systems or hierarchies to eliminate repetition or the need for any kind of code syncing, you may find that when you come to change the constraint it actually requires touching far more places.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;temporarily silencing the assertion while developing is easy and doesn’t have far reaching consequences.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, there are many times when being able to automatically derive things at the code level, including some complex relationships between parts of the code, can be a win, and it’s the kind of thing you can do in Python with its many powerful techniques.&lt;/p&gt;
&lt;p&gt;But my point is that you should remember the alternative: “synchronise manually, and have a test to check you did it.” Being able to add any kind of executable code at module level – the same level as class/function/constant definitions – is a Python super-power that you should use.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="example-3-external-polymorphism-and-static-typing"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#toc-entry-7" role="doc-backlink"&gt;Example 3 - external polymorphism and static typing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A variant of the above problem is when, instead of an enum defining different types, I’ve got a set of classes that all need some behaviour defined.&lt;/p&gt;
&lt;p&gt;Often we just use polymorphism where a base class defines the methods or interfaces needed and sub-classes provide the implementation. However, as in the previous case, this can involve mixing concerns e.g. user interface code, possibly of several types, is mixed up with the base domain objects. It also imposes constraints on class hierarchies.&lt;/p&gt;
&lt;p&gt;Recently for these kind of cases, I’m more likely to prefer &lt;a class="reference external" href="https://wiki.c2.com/?ExternalPolymorphism"&gt;external polymorphism&lt;/a&gt; to avoid these problems. To give an example, in my current project I’m using the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Command_pattern"&gt;Command pattern&lt;/a&gt; or &lt;a class="reference external" href="https://mmapped.blog/posts/29-plan-execute"&gt;plan-execute pattern&lt;/a&gt; extensively, and it involves manipulating CAM objects using a series of command objects that look something like this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-1" name="rest_code_91a516cbd9754ed486115efe1a1b562f-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-1"&gt;&lt;/a&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-2" name="rest_code_91a516cbd9754ed486115efe1a1b562f-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-2"&gt;&lt;/a&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DeleteFeature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-3" name="rest_code_91a516cbd9754ed486115efe1a1b562f-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-3"&gt;&lt;/a&gt;    &lt;span class="n"&gt;feature_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-4" name="rest_code_91a516cbd9754ed486115efe1a1b562f-4" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-4"&gt;&lt;/a&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-5" name="rest_code_91a516cbd9754ed486115efe1a1b562f-5" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-5"&gt;&lt;/a&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-6" name="rest_code_91a516cbd9754ed486115efe1a1b562f-6" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-6"&gt;&lt;/a&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-7" name="rest_code_91a516cbd9754ed486115efe1a1b562f-7" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-7"&gt;&lt;/a&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SetParameter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-8" name="rest_code_91a516cbd9754ed486115efe1a1b562f-8" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-8"&gt;&lt;/a&gt;    &lt;span class="n"&gt;param_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-9" name="rest_code_91a516cbd9754ed486115efe1a1b562f-9" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-9"&gt;&lt;/a&gt;    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-10" name="rest_code_91a516cbd9754ed486115efe1a1b562f-10" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-10"&gt;&lt;/a&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-11" name="rest_code_91a516cbd9754ed486115efe1a1b562f-11" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-11"&gt;&lt;/a&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-12" name="rest_code_91a516cbd9754ed486115efe1a1b562f-12" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-12"&gt;&lt;/a&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-13" name="rest_code_91a516cbd9754ed486115efe1a1b562f-13" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-13"&gt;&lt;/a&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SetTextSegment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-14" name="rest_code_91a516cbd9754ed486115efe1a1b562f-14" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-14"&gt;&lt;/a&gt;    &lt;span class="n"&gt;text_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-15" name="rest_code_91a516cbd9754ed486115efe1a1b562f-15" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-15"&gt;&lt;/a&gt;    &lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-16" name="rest_code_91a516cbd9754ed486115efe1a1b562f-16" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-16"&gt;&lt;/a&gt;    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-17" name="rest_code_91a516cbd9754ed486115efe1a1b562f-17" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-17"&gt;&lt;/a&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-18" name="rest_code_91a516cbd9754ed486115efe1a1b562f-18" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-18"&gt;&lt;/a&gt;
&lt;a id="rest_code_91a516cbd9754ed486115efe1a1b562f-19" name="rest_code_91a516cbd9754ed486115efe1a1b562f-19" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_91a516cbd9754ed486115efe1a1b562f-19"&gt;&lt;/a&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TypeAlias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DeleteFeature&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;SetParameter&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;SetTextSegment&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that none of them share a base class, but I do have a union type that gives me the complete set.&lt;/p&gt;
&lt;p&gt;It’s much more convenient to define the behaviour associated with these separately from these definitions, and so I have multiple other places that deal with &lt;code class="docutils literal"&gt;Command&lt;/code&gt;, such as the place that executes these commands and several others. One example that requires very little code to show is where I’m generating user-presentable tables that show groups of commands. I convert each of these &lt;code class="docutils literal"&gt;Command&lt;/code&gt; objects into key-value pairs that are used for column headings and values:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_af78b50857a4473291cd264c74a94233-1" name="rest_code_af78b50857a4473291cd264c74a94233-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_af78b50857a4473291cd264c74a94233-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_command_display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;a id="rest_code_af78b50857a4473291cd264c74a94233-2" name="rest_code_af78b50857a4473291cd264c74a94233-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_af78b50857a4473291cd264c74a94233-2"&gt;&lt;/a&gt;    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_af78b50857a4473291cd264c74a94233-3" name="rest_code_af78b50857a4473291cd264c74a94233-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_af78b50857a4473291cd264c74a94233-3"&gt;&lt;/a&gt;        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;DeleteFeature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;feature_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_af78b50857a4473291cd264c74a94233-4" name="rest_code_af78b50857a4473291cd264c74a94233-4" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_af78b50857a4473291cd264c74a94233-4"&gt;&lt;/a&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Delete &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;feature_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_af78b50857a4473291cd264c74a94233-5" name="rest_code_af78b50857a4473291cd264c74a94233-5" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_af78b50857a4473291cd264c74a94233-5"&gt;&lt;/a&gt;        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;SetParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;param_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_af78b50857a4473291cd264c74a94233-6" name="rest_code_af78b50857a4473291cd264c74a94233-6" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_af78b50857a4473291cd264c74a94233-6"&gt;&lt;/a&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_af78b50857a4473291cd264c74a94233-7" name="rest_code_af78b50857a4473291cd264c74a94233-7" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_af78b50857a4473291cd264c74a94233-7"&gt;&lt;/a&gt;        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;SetTextSegment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;text_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_af78b50857a4473291cd264c74a94233-8" name="rest_code_af78b50857a4473291cd264c74a94233-8" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_af78b50857a4473291cd264c74a94233-8"&gt;&lt;/a&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is giving me a similar problem to the one I had before I had before: if I add a new &lt;code class="docutils literal"&gt;Command&lt;/code&gt;, I have to remember to add the new branch to &lt;code class="docutils literal"&gt;get_command_display&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I could split out &lt;code class="docutils literal"&gt;get_command_display&lt;/code&gt; into a dictionary of functions, and apply the same technique as in the previous example, but it’s more work, a less natural fit for the problem and potentially less flexible.&lt;/p&gt;
&lt;p&gt;Instead, all I need to do is add &lt;a class="reference external" href="https://typing.readthedocs.io/en/latest/source/unreachable.html"&gt;exhaustiveness checking&lt;/a&gt; with one more branch:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_735be2e8636045ba985a8f5df028753b-1" name="rest_code_735be2e8636045ba985a8f5df028753b-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_735be2e8636045ba985a8f5df028753b-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_735be2e8636045ba985a8f5df028753b-2" name="rest_code_735be2e8636045ba985a8f5df028753b-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_735be2e8636045ba985a8f5df028753b-2"&gt;&lt;/a&gt;    &lt;span class="o"&gt;...&lt;/span&gt;  &lt;span class="c1"&gt;# etc&lt;/span&gt;
&lt;a id="rest_code_735be2e8636045ba985a8f5df028753b-3" name="rest_code_735be2e8636045ba985a8f5df028753b-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_735be2e8636045ba985a8f5df028753b-3"&gt;&lt;/a&gt;    &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_735be2e8636045ba985a8f5df028753b-4" name="rest_code_735be2e8636045ba985a8f5df028753b-4" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_735be2e8636045ba985a8f5df028753b-4"&gt;&lt;/a&gt;        &lt;span class="n"&gt;assert_never&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, pyright will check that I didn’t forget to add branches here for any new &lt;code class="docutils literal"&gt;Command&lt;/code&gt;. The error message is not controllable, in contrast to hand-written asserts, but it is clear enough.&lt;/p&gt;
&lt;p&gt;The theme here is that additions in one part of the code require synchronised additions in other parts of the code, rather than being automatically correct “by construction”, but you have something that tests you didn’t forget.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="example-4-generated-code"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#toc-entry-8" role="doc-backlink"&gt;Example 4 - generated code&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In web development, ensuring consistent design and keeping different things in sync is a significant problem. There are many approaches, but let’s start with the simple case of using a single CSS stylesheet to define all the styles.&lt;/p&gt;
&lt;p&gt;We may want a bunch of components to have a consistent border colour, and a first attempt might look like this (ignoring the many issues of naming conventions here):&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code css"&gt;&lt;a id="rest_code_649240c51cc04892a9df3fad356954da-1" name="rest_code_649240c51cc04892a9df3fad356954da-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_649240c51cc04892a9df3fad356954da-1"&gt;&lt;/a&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;card-component&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;bordered-heading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_649240c51cc04892a9df3fad356954da-2" name="rest_code_649240c51cc04892a9df3fad356954da-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_649240c51cc04892a9df3fad356954da-2"&gt;&lt;/a&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;#800&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_649240c51cc04892a9df3fad356954da-3" name="rest_code_649240c51cc04892a9df3fad356954da-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_649240c51cc04892a9df3fad356954da-3"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This often becomes impractical when we want to organise by component, rather than by property, which introduces duplication:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code css"&gt;&lt;a id="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-1" name="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_5d97ce0ad15c486fa6f40b9b63125db0-1"&gt;&lt;/a&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;card-component&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-2" name="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_5d97ce0ad15c486fa6f40b9b63125db0-2"&gt;&lt;/a&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;#800&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-3" name="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_5d97ce0ad15c486fa6f40b9b63125db0-3"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-4" name="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-4" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_5d97ce0ad15c486fa6f40b9b63125db0-4"&gt;&lt;/a&gt;
&lt;a id="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-5" name="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-5" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_5d97ce0ad15c486fa6f40b9b63125db0-5"&gt;&lt;/a&gt;&lt;span class="c"&gt;/* somewhere far away … */&lt;/span&gt;
&lt;a id="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-6" name="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-6" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_5d97ce0ad15c486fa6f40b9b63125db0-6"&gt;&lt;/a&gt;
&lt;a id="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-7" name="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-7" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_5d97ce0ad15c486fa6f40b9b63125db0-7"&gt;&lt;/a&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;bordered-heading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-8" name="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-8" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_5d97ce0ad15c486fa6f40b9b63125db0-8"&gt;&lt;/a&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;#800&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-9" name="rest_code_5d97ce0ad15c486fa6f40b9b63125db0-9" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_5d97ce0ad15c486fa6f40b9b63125db0-9"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Thankfully, CSS has variables, so the first application of “derive” is straightforward – we define a variable which we can use in multiple places:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code css"&gt;&lt;a id="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-1" name="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b10626031d9f4ad5b26f3c49e1ff171a-1"&gt;&lt;/a&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-2" name="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b10626031d9f4ad5b26f3c49e1ff171a-2"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;--primary-border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;#800&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-3" name="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b10626031d9f4ad5b26f3c49e1ff171a-3"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-4" name="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-4" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b10626031d9f4ad5b26f3c49e1ff171a-4"&gt;&lt;/a&gt;
&lt;a id="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-5" name="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-5" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b10626031d9f4ad5b26f3c49e1ff171a-5"&gt;&lt;/a&gt;&lt;span class="c"&gt;/* elsewhere */&lt;/span&gt;
&lt;a id="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-6" name="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-6" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b10626031d9f4ad5b26f3c49e1ff171a-6"&gt;&lt;/a&gt;
&lt;a id="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-7" name="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-7" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b10626031d9f4ad5b26f3c49e1ff171a-7"&gt;&lt;/a&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;bordered-heading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-8" name="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-8" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b10626031d9f4ad5b26f3c49e1ff171a-8"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;solid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--primary-border-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-9" name="rest_code_b10626031d9f4ad5b26f3c49e1ff171a-9" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b10626031d9f4ad5b26f3c49e1ff171a-9"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;However, as the project grows, we may find that we want to use the same variables in different contexts where CSS isn’t applicable. So the next step at this point is typically to move to &lt;a class="reference external" href="https://css-tricks.com/what-are-design-tokens/"&gt;Design Tokens&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Practically speaking, this might mean that we now have our variables defined in a separate JSON file. Maybe something like this (using &lt;a class="reference external" href="https://design-tokens.github.io/community-group/format/#file-format"&gt;a W3C draft spec&lt;/a&gt;):&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code json"&gt;&lt;a id="rest_code_b60059d5fc274bea9707d93d05553095-1" name="rest_code_b60059d5fc274bea9707d93d05553095-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b60059d5fc274bea9707d93d05553095-1"&gt;&lt;/a&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_b60059d5fc274bea9707d93d05553095-2" name="rest_code_b60059d5fc274bea9707d93d05553095-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b60059d5fc274bea9707d93d05553095-2"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"primary-border-color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_b60059d5fc274bea9707d93d05553095-3" name="rest_code_b60059d5fc274bea9707d93d05553095-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b60059d5fc274bea9707d93d05553095-3"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"$value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#800000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_b60059d5fc274bea9707d93d05553095-4" name="rest_code_b60059d5fc274bea9707d93d05553095-4" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b60059d5fc274bea9707d93d05553095-4"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"$type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"color"&lt;/span&gt;
&lt;a id="rest_code_b60059d5fc274bea9707d93d05553095-5" name="rest_code_b60059d5fc274bea9707d93d05553095-5" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b60059d5fc274bea9707d93d05553095-5"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_b60059d5fc274bea9707d93d05553095-6" name="rest_code_b60059d5fc274bea9707d93d05553095-6" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b60059d5fc274bea9707d93d05553095-6"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"primary-hightlight-color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_b60059d5fc274bea9707d93d05553095-7" name="rest_code_b60059d5fc274bea9707d93d05553095-7" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b60059d5fc274bea9707d93d05553095-7"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"$value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#FBC100"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_b60059d5fc274bea9707d93d05553095-8" name="rest_code_b60059d5fc274bea9707d93d05553095-8" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b60059d5fc274bea9707d93d05553095-8"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"$type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"color"&lt;/span&gt;
&lt;a id="rest_code_b60059d5fc274bea9707d93d05553095-9" name="rest_code_b60059d5fc274bea9707d93d05553095-9" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b60059d5fc274bea9707d93d05553095-9"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_b60059d5fc274bea9707d93d05553095-10" name="rest_code_b60059d5fc274bea9707d93d05553095-10" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_b60059d5fc274bea9707d93d05553095-10"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;From this, we can automatically generate CSS fragments that contain the same variables quite easily – for simple cases, this isn’t more than a 50 line Python script.&lt;/p&gt;
&lt;p&gt;However, we’ve got some choices when it comes to how we put everything together. I think the general assumption in web development world is that a fully automatic “derive” is the only acceptable answer. This typically means you have to put your own CSS in a separate file, and then you have a build tool that watches for changes, and compiles your CSS plus the generated CSS into the final output that gets sent to the browser.&lt;/p&gt;
&lt;p&gt;In addition, once you’ve bought into these kind of tools you’ll find they want to do extensive changes to the output, and define more and more extensions to the underlying languages. For example, &lt;a class="reference external" href="https://www.npmjs.com/package/@csstools/postcss-design-tokens"&gt;postcss-design-tokens&lt;/a&gt; wants you to write things like:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code css"&gt;&lt;a id="rest_code_c177bea80fbf4f9eb4516dcefcefe9fd-1" name="rest_code_c177bea80fbf4f9eb4516dcefcefe9fd-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_c177bea80fbf4f9eb4516dcefcefe9fd-1"&gt;&lt;/a&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_c177bea80fbf4f9eb4516dcefcefe9fd-2" name="rest_code_c177bea80fbf4f9eb4516dcefcefe9fd-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_c177bea80fbf4f9eb4516dcefcefe9fd-2"&gt;&lt;/a&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;design-token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'color.background.primary'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_c177bea80fbf4f9eb4516dcefcefe9fd-3" name="rest_code_c177bea80fbf4f9eb4516dcefcefe9fd-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_c177bea80fbf4f9eb4516dcefcefe9fd-3"&gt;&lt;/a&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And instead of using CSS variables in the output, it puts the value of the token right in to every place in your code that uses it.&lt;/p&gt;
&lt;p&gt;This approach has various problems, in particular that you become more and more dependent on the build process, and the output gets further from your input. You can no longer use the &lt;a class="reference external" href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/What_are_browser_developer_tools"&gt;Dev Tools&lt;/a&gt; built in to your browser to do editing – the flow of using Dev Tools to experiment with changing a single spacing or colour CSS variable for global changes is broken, you need your build tool. And you can’t easily copy changes from Dev Tools back into the source, because of the transformation step, and debugging can be similarly difficult. And then, you’ll probably want special IDE support for the special CSS extensions, rather than being able to lean on your editor simply understanding CSS, and any other tools that want to look at your CSS now need support etc.&lt;/p&gt;
&lt;p&gt;It’s also a lot of extra infrastructure and complexity to solve this one problem, especially when our design tokens JSON file is probably not going to change that often, or is going to have long periods of high stability. There are good reasons to want to be essentially &lt;a class="reference external" href="https://world.hey.com/dhh/you-can-t-get-faster-than-no-build-7a44131c"&gt;build free&lt;/a&gt;. The current state of the art in this space is that &lt;a class="reference external" href="https://vitejs.dev/guide/features#css"&gt;to get your build tool to compile your CSS&lt;/a&gt; you add &lt;code class="docutils literal"&gt;import &lt;span class="pre"&gt;'./styles.css'&lt;/span&gt;&lt;/code&gt; &lt;strong&gt;in your entry point Javascript file!&lt;/strong&gt; What if I don’t even have a Javascript file? I think I understand how this sort of thing came about, but don’t try to tell me that it’s anything less than completely bonkers.&lt;/p&gt;
&lt;p&gt;Do we have an alternative to the fully automatic derive?&lt;/p&gt;
&lt;p&gt;Using the “test” approach, we do. We can even stick with our single CSS file – we just write it like this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code css"&gt;&lt;a id="rest_code_37c3c2a15d1a4332be2d9319390962da-1" name="rest_code_37c3c2a15d1a4332be2d9319390962da-1" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_37c3c2a15d1a4332be2d9319390962da-1"&gt;&lt;/a&gt;&lt;span class="c"&gt;/* DESIGN TOKENS START */&lt;/span&gt;
&lt;a id="rest_code_37c3c2a15d1a4332be2d9319390962da-2" name="rest_code_37c3c2a15d1a4332be2d9319390962da-2" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_37c3c2a15d1a4332be2d9319390962da-2"&gt;&lt;/a&gt;&lt;span class="c"&gt;/* auto-created block - do not edit */&lt;/span&gt;
&lt;a id="rest_code_37c3c2a15d1a4332be2d9319390962da-3" name="rest_code_37c3c2a15d1a4332be2d9319390962da-3" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_37c3c2a15d1a4332be2d9319390962da-3"&gt;&lt;/a&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_37c3c2a15d1a4332be2d9319390962da-4" name="rest_code_37c3c2a15d1a4332be2d9319390962da-4" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_37c3c2a15d1a4332be2d9319390962da-4"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;--primary-border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;#800000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_37c3c2a15d1a4332be2d9319390962da-5" name="rest_code_37c3c2a15d1a4332be2d9319390962da-5" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_37c3c2a15d1a4332be2d9319390962da-5"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;--primary-highlight-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;#FBC100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_37c3c2a15d1a4332be2d9319390962da-6" name="rest_code_37c3c2a15d1a4332be2d9319390962da-6" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_37c3c2a15d1a4332be2d9319390962da-6"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_37c3c2a15d1a4332be2d9319390962da-7" name="rest_code_37c3c2a15d1a4332be2d9319390962da-7" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_37c3c2a15d1a4332be2d9319390962da-7"&gt;&lt;/a&gt;&lt;span class="c"&gt;/* DESIGN TOKENS END */&lt;/span&gt;
&lt;a id="rest_code_37c3c2a15d1a4332be2d9319390962da-8" name="rest_code_37c3c2a15d1a4332be2d9319390962da-8" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_37c3c2a15d1a4332be2d9319390962da-8"&gt;&lt;/a&gt;
&lt;a id="rest_code_37c3c2a15d1a4332be2d9319390962da-9" name="rest_code_37c3c2a15d1a4332be2d9319390962da-9" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#rest_code_37c3c2a15d1a4332be2d9319390962da-9"&gt;&lt;/a&gt;&lt;span class="c"&gt;/* the rest of our CSS here */&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The contents of this block will be almost certainly auto-generated. We won’t have a process that fully automatically updates it, however, because this is the same file where we are putting our custom CSS, and we don’t want any possibility of lost work due to the file being overwritten as we are editing it.&lt;/p&gt;
&lt;p&gt;On the other hand we don’t want things to get out of sync, so we’ll add a test that checks whether the current &lt;code class="docutils literal"&gt;styles.css&lt;/code&gt; contains the block of design tokens that we expect to be there, based on the JSON. For actually updating the block, we’ll need some kind of manual step – maybe a script that can find and update the &lt;code class="docutils literal"&gt;DESIGN TOKEN START&lt;/code&gt; block, maybe &lt;a class="reference external" href="https://cog.readthedocs.io/en/latest/"&gt;cog&lt;/a&gt; – which is a perfect little tool for this use case — or we could just copy-paste.&lt;/p&gt;
&lt;p&gt;There are also slightly simpler solutions in this case, like using a &lt;a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/CSS/@import"&gt;CSS import&lt;/a&gt; if you don’t mind having multiple CSS files.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#toc-entry-9" role="doc-backlink"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For all the examples above, the solutions I’ve presented might not work perfectly for your context. You might also want to draw the line at different place to me. But my main point is that we don’t have to go all the way with a fully automatic derive solution to eliminate any manual syncing. Having some manual work plus a mechanism to test that two things are in sync is a perfectly legitimate solution, and it can avoid some of the large costs that come with structuring everything around “derive”.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="links"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://lukeplant.me.uk/blog/posts/keeping-things-in-sync-derive-vs-test/#toc-entry-10" role="doc-backlink"&gt;Links&lt;/a&gt;&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.jmduke.com/posts/weird-tests-tacit-knowledge.html"&gt;Use weird tests to capture tacit knowledge&lt;/a&gt;: this has a similar idea – the ideal case would be that the tacit knowledge is unnecessary because the system is correct “by construction” or automation; but failing that, you can have a test to ensure things aren’t forgotten.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://lobste.rs/s/pqhwph/keeping_things_sync_derive_vs_test"&gt;Discussion of this post on lobsters&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;</content>
    <category term="django" label="Django"/>
    <category term="python" label="Python"/>
    <category term="software-development" label="Software development"/>
    <category term="web-development" label="Web development"/>
  </entry>
</feed>
