<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Eric Normand's Newsletter]]></title><description><![CDATA[Software design, functional programming, and software engineering practices]]></description><link>https://ericnormand.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!5k3d!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90f1901b-d391-4f2c-9e2d-d760c34e239c_431x431.jpeg</url><title>Eric Normand&apos;s Newsletter</title><link>https://ericnormand.substack.com</link></image><generator>Substack</generator><lastBuildDate>Mon, 06 Apr 2026 03:12:41 GMT</lastBuildDate><atom:link href="https://ericnormand.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Eric Normand]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[ericnormand@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[ericnormand@substack.com]]></itunes:email><itunes:name><![CDATA[Eric Normand]]></itunes:name></itunes:owner><itunes:author><![CDATA[Eric Normand]]></itunes:author><googleplay:owner><![CDATA[ericnormand@substack.com]]></googleplay:owner><googleplay:email><![CDATA[ericnormand@substack.com]]></googleplay:email><googleplay:author><![CDATA[Eric Normand]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Code, Mundane and Sublime]]></title><description><![CDATA[A principle for designing systems]]></description><link>https://ericnormand.substack.com/p/code-mundane-and-sublime</link><guid isPermaLink="false">https://ericnormand.substack.com/p/code-mundane-and-sublime</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Tue, 31 Mar 2026 10:03:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5k3d!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90f1901b-d391-4f2c-9e2d-d760c34e239c_431x431.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Check out the </em><a href="https://www.youtube.com/watch?v=JJEyffSdBsk">Clojure: The Documentary</a><em><a href="https://www.youtube.com/watch?v=JJEyffSdBsk"> trailer</a>! We&#8217;re so lucky to have a documentary made about how Clojure came about and what people love about it.</em></p><div><hr></div><p>Let me tell you a story about the beautiful possibilities and the mundane realities of code. I lived this story. And it taught me that a good library doesn&#8217;t have to make code sing. It just needs to get the job done without making a mess.</p><p>I was working as a contractor at a company that was using Om Next, the sequel to the original Om. Both libraries were wrappers around React. While Om was a minimalist take on how to structure a UI, it revealed a problem: Often your UI&#8217;s hierarchy is very different from how your data is structured. Om Next solved this problem by introducing the idea of <em>parsers</em>, which let you convert a declarative expression of what a component needed into a function of the data. This indirection allowed you to shape your data and UI in different ways.</p><p>I had used the original Om, but I had not used Om Next in anger. At this job, I needed to create a few new components in the UI, so I got to writing a parser. I made a big mess. In order to express the information I needed (the <em>query</em>), I had to invent a new language. That&#8217;s not easy. Then you have to write code to interpret that new language, dig into the central data store for what the query needs, and return data in a new format that your component will consume. The code looked terrible (many nested ifs) and I just couldn&#8217;t see how to improve it. The existing components&#8217; parsers looked just as bad. It looked like all of the complexity of the data model was condensed into these parsers so that the data store could remain normalized, the components could be simple, and the query could be concise.</p><p>I couldn&#8217;t help comparing this to Reagent and Reframe, other wrappers around React, both of which I was more familiar with. Reframe also had a centralized data store, but it didn&#8217;t have parsers and queries. Instead, you built subscriptions, which were &#8220;reactive&#8221; functions of the data store. If you wanted the data for a component, you would write a subscription which gave it to you. It was more direct than writing a query (new language) and a parser (new interpreter). But it was still a layer of indirection.</p><p>I asked one of the more senior engineers at the company whether I was crazy. I had never used Om Next before, so maybe I was missing something. Was all of that gnarly code worth it? Why is this better than Reframe? His answer inspired this post. He said, &#8220;It can get gnarly, but it gives you the tools such that when it&#8217;s done right, it&#8217;s really sublime.&#8221;</p><p>Being a fan of sublime code, I wondered why his answer troubled me. And then I realized the reason: If the choice is between sublime when everything goes right and horrible if anything goes wrong, that&#8217;s not a great choice. I would much rather have mostly readable, even when I&#8217;m not making perfect decisions, with less extremes on the good and bad. Put another way, mundane but workable beats sublime with herculean effort. Reframe is mostly workable. Om Next is mostly gnarly.</p><p>Since that conversation I&#8217;ve been pondering this as a principle. It&#8217;s a tradeoff between tradeoffs. When it comes to code beauty, I would rather have a fat average of the bell curve than a lot of outliers. I&#8217;d rather have base hits than home runs, because the players swinging for home runs strike out just as often. And, listen, I&#8217;m just a working programmer trying to get the cart icon to show the count of items inside. I&#8217;m not building a Rolex.</p><p>I believe my experience also inspired me to create my Reframe course. I learned to appreciate the framework. Reframe gives you the tools to build understandable UIs of medium to high complexity. And after my experience with Om Next, I saw how Reframe&#8217;s tools made decisions straightforward. They never reach the heights of sublimity that legends tell of Om Next. But the everyday code of Reframe is fine. Messes are contained and can be split. There aren&#8217;t any difficult design decisions to make, like designing a language and interpreter.</p><p>So that&#8217;s my story. I became a fan of Reframe because of how bad the default code in Om Next was. And I decided that the promise of beautiful code if you do it right is not enough. What does the code look like when I&#8217;m in a rush? What does it look like when I don&#8217;t really care that day? What does it look like when a junior programmer writes it? What does it look like when you&#8217;ve made the wrong decision but you don&#8217;t have time to rework it? On most days, things are not at their best. Making it easy to write decent code is way more important than making it possible to write beautiful code. It&#8217;s a principle I keep in my back pocket for when I or a colleague wants to make a tool to make beautiful things at work. I pull it out to dissuade them. It&#8217;s the everyday code that matters.</p>]]></content:encoded></item><item><title><![CDATA[Denotational vs operational]]></title><description><![CDATA[It's time to retire the imperative vs declarative dichotomy]]></description><link>https://ericnormand.substack.com/p/denotational-vs-operational</link><guid isPermaLink="false">https://ericnormand.substack.com/p/denotational-vs-operational</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Tue, 16 Dec 2025 11:01:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5k3d!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90f1901b-d391-4f2c-9e2d-d760c34e239c_431x431.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>It&#8217;s time again for the annual <a href="https://www.surveymonkey.com/r/clojure2025">State of Clojure Survey</a>. I fill it out every year. And I love to read the responses. Please give the survey 15 minutes and become a statistic. This is one important way we, as a community, understand ourselves. Please <a href="https://www.surveymonkey.com/r/clojure2025">fill it out</a> by the end of the year.</em></p><div><hr></div><h1>Denotational vs operational</h1><p>I follow a number of people&#8217;s work, and one of them is Conal Elliott. I like that he&#8217;s very deliberate with his use of words. It makes me think a little harder. At some point, I heard him say that he didn&#8217;t think <em>declarative</em> was the right word. People tend to toss the word around and explain it as &#8220;saying what you want, not how to get it.&#8221; It&#8217;s the opposite of <em>imperative</em>, where you say how to get it. But Elliot is right. It&#8217;s not the word we want.</p><p>Like many problematic words, people use it in different senses. But the worst offense is that it&#8217;s not objective. It&#8217;s vibes. It&#8217;s relative. I&#8217;ve heard people say that <code>map</code> is declarative while a for loop is imperative. &#8220;With map, I just say what I want. But with a for loop, I have to say how to build the resulting array.&#8221; That may be true, but a for loop is very declarative relative to an if statement and a goto. Look at the anatomy of a for loop. You specify four things:</p><ol><li><p>Initialization</p></li><li><p>Loop condition</p></li><li><p>Loop progress</p></li><li><p>Body of loop</p></li></ol><p>And it magically executes the body of the loop until the loop condition is met. How does it do it? You never specified how. Which means it&#8217;s declarative. And I&#8217;ve implemented map many times over the years. I know how it&#8217;s working its wizardry. Which means it&#8217;s <em>imperative</em>.</p><p>These words just are not what we&#8217;re looking for. The main problem I have with the term <em>declarative</em> as we use it is that it&#8217;s all about ignorance. People say SQL is declarative because they don&#8217;t know how the query engine works. Once you learn how it works, you begin to write SQL to control <em>how</em> it runs.</p><p>Elliot suggests we instead use the term <em>denotational</em>, borrowed from the practice of Denotational Semantics. Denotational Semantics is a method for formally specifying the semantics of a programming language or construct in terms of lambda calculus. It contrasts with Operational Semantics, which is more like how a compiler might translate a language to machine code. If you squint, we want to replace <em>declarative</em> with <em>denotational</em> and replace <em>imperative</em> with <em>operational</em>.</p><p>In denotational programming, we specify how to derive the meaning of one expression by combining the meanings of the subexpressions. Even if we know what&#8217;s happening under the hood, the derivation of meaning is clear. Reasoning can be done locally. In operational semantics, we may get a good idea of how a feature can be implemented in assembly, but we have trouble simulating it in our head because of the many possible states the machine can be in. It is much harder to formalize and prove properties about it.</p><p>When we&#8217;re doing good functional programming, it&#8217;s much more on the denotational side. We&#8217;re defining one function in terms of other functions. We&#8217;re embedded in a lambda calculus, so it&#8217;s very comfortable to do.</p><p>I think there&#8217;s another correspondence, which is between operational/denotational and implementation/specification. I think one of the important skills that differentiates of senior from a junior engineer is the ability to separate implementation from specification. A junior is just trying to get something working. But a senior engineer thinks about the meanings they want to represent apart from how to represent them.</p><p>This brings me to the working title of my book, <em>Runnable Specifications.</em> The idea is one I borrowed from Alan Kay. He says the ultimate goal for a programming system is to be able to run the specification, not translate it into an implementation. You want to write down a question and run it to get the answer. He&#8217;s talking about a denotational way of programming where you write programs directly in the language of the domain.</p><p>Denotational differs from declarative in one other way. Denotational programming deals with how meanings are combined to form new meanings. Declarative can but often does not deal with combining meanings. I look at SQL again as an example of something that doesn&#8217;t make composition easy.</p><p>Well, I don&#8217;t think I really destroyed the term <em>declarative</em> as much I imagined I would at the beginning of this essay. That&#8217;s okay. I think <em>denotational</em> is a strong enough replacement that it doesn&#8217;t really need to be destroyed. The biggest downside to the word is that people don&#8217;t know what it means. But I do!</p>]]></content:encoded></item><item><title><![CDATA[What are formal systems?]]></title><description><![CDATA[A programmer-y exploration of their weird and cool nature]]></description><link>https://ericnormand.substack.com/p/what-are-formal-systems</link><guid isPermaLink="false">https://ericnormand.substack.com/p/what-are-formal-systems</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Tue, 09 Dec 2025 11:02:30 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5k3d!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90f1901b-d391-4f2c-9e2d-d760c34e239c_431x431.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>I work at Metabase, and I like my job. If you want to work at Metabase and like your job, <a href="https://www.metabase.com/jobs">come apply to work here</a>! I program in Clojure (though the frontend folks work in TypeScript), everyone is smart, and it&#8217;s 100% remote with people all over the world. The <a href="https://github.com/metabase/metabase">code is open source</a>, and it&#8217;s <a href="https://www.unesco.org/sdg4education2030/en/knowledge-hub/dashboard">used by UNESCO</a>. <a href="https://www.metabase.com/jobs">Apply now</a>.</em></p><h1>What are formal systems?</h1><p>When I&#8217;m drafting my book, I often get a glimpse of the underlying structure the ideas I&#8217;m presenting are built on. These ideas seem so important. But alas, they&#8217;re often very abstract and not directly relevant to the skills I&#8217;m trying to teach to a particular audience. This newsletter and my podcast are a way to write/talk about those ideas to a more sympathetic audience.</p><p>My book is about domain modeling. One ideal to strive for when building a domain model in code is to construct it as a <em>formal system</em>. When I first got that idea, I do what I typically do, which is to start writing about it. But soon I realized that I didn&#8217;t really know what <em>formal</em> meant in that term. I did some research. And it&#8217;s a really important idea, so I&#8217;m going to share my understanding with you.</p><p>Before my research, if you asked me what <em>formal system</em> meant, I would tell you that it&#8217;s about being rigorously defined, as in &#8220;let&#8217;s get a formal definition.&#8221; But that&#8217;s not it, really. Yes, formal system are rigorously defined, but they&#8217;re a lot more like programming languages than just &#8220;good definition.&#8221; Let&#8217;s look at an example:</p><p>In predicate logic, a kind of formal logic, we can assign variables to a few concepts:</p><ul><li><p><em>Jim</em> - the protagonist of this story</p></li><li><p><em>Pizza</em> - the plot device</p></li><li><p><em>Likes</em> - the character development</p></li><li><p><em>Eats</em> - the action</p></li></ul><p>Then we can talk about Jim, like this:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;Likes(Jim, Food) \\implies Eats(Jim, Food)&quot;,&quot;id&quot;:&quot;BVBRTVIUZZ&quot;}" data-component-name="LatexBlockToDOM"></div><p>This means that if Jim likes a food, he eats it. Now what happens if we also throw this into the mix?</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;Likes(Jim, Pizza)&quot;,&quot;id&quot;:&quot;GNJSXRULUJ&quot;}" data-component-name="LatexBlockToDOM"></div><p>Jim likes pizza. We can then imply that:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;Eats(Jim, Pizza)&quot;,&quot;id&quot;:&quot;QKTMESNUAP&quot;}" data-component-name="LatexBlockToDOM"></div><p>Jim eats pizza. Surprise ending.</p><p>Well, okay, it&#8217;s not a very interesting story. But it does give us everything we need to define <em>formal system</em>, the real plot of the story. Mathematicians will define it like this:</p><blockquote><p>A <em>formal system</em> consists of:</p><ul><li><p><em>symbols</em> (valid characters/tokens) &#8212; Jim, Likes, (, &#8658;, &#8230;</p></li><li><p><em>statements</em> (valid arrangements of symbols) &#8212; <em>Likes</em>(<em>Jim, Pizza</em>)</p></li><li><p><em>mechanistic rules</em> (for deriving new statements from existing statements) &#8212; the rule that lets us derive our ending</p></li><li><p><em>axioms</em> (statements that are true implicitly)</p></li></ul></blockquote><p>I think of it in programming terms. The first two items (symbols and statements) are the <em>syntax</em>. The last two (mechanistic rules and axioms) define the <em>semantics</em>.</p><p>There&#8217;s something about formal systems that is powerful and paradoxical. There&#8217;s a strange combination of meaning, meaninglessness, and machine-like coldness that somehow makes them more powerful. And that&#8217;s why I find them fascinating, just like programming itself. It&#8217;s just electrons moving through a circuit, yet somehow it does meaningful work. Where does the meaning come from? (Hint: It&#8217;s us.)</p><p>First, the syntax defines a structure, like an Abstract Syntax Tree. An important part of the syntax is that there are symbols that are neutral. These are the variables. We defined variables like <em>Jim</em> and <em>Pizza</em>, but as far as logic is concerned, they could be <em>A</em> and <em>B</em>. Logic doesn&#8217;t care about Jim or pizza. Likewise, your compiler doesn&#8217;t care what you name your variables, nor does the chip it runs on. And this is important. The variables are only linked to human meaning through their names and other connections to real-world things.</p><p>However, some parts of the syntax are meaningful to the system. The structure itself is meaningful (as in <em>F</em>(<em>A, B</em>) indicates a predicate called <em>F </em>with arguments <em>A</em> and <em>B</em>). And some of the symbols have meaning (&#8658; means implication). The meaning comes from their use in the mechanistic rules, somewhat how a syntax-driven interpreter would work. The structure of the statements is pattern-matched in the derivation rules, which leads to new statements. The term <em>formal</em> refers to the importance of the structure in defining meaning.</p><p>I find this mix of meaningless symbols and meaningful symbols to be a beautiful puzzle. Yes, there is a certain elegance to formal systems like propositional logic. A handful of meaningful symbols (logical connectives) and a few inference rules can ensure you only derive valid arguments. But there&#8217;s something more than just elegance at work. There&#8217;s something to their limited meaning that makes them practical. Logic wouldn&#8217;t work if you tried to include all humanly felt meaning, so you only include a tiny subset that you trust to work. Yet it can find new, if simplistic, results.</p><p>One interesting exercise is to find the minimal set of meaningful symbols that can express all the others. In propositional logic, this is the NAND connective (NOR works, too). The SK combinator calculus is Turing complete. And you can derive both S and K from the iota combinator! So iota, just one symbol (and using the lambda calculus rule of application) is Turing complete.</p><p>Similarly, Lisp is very minimal (at least the original Lisp). It is defined with three kinds of symbols:</p><ol><li><p>Atoms (alphanumeric sequences)</p></li><li><p>(</p></li><li><p>)</p></li></ol><p>The syntax also requires spaces between atoms to distinguish them. The syntax defines two kinds of values:</p><ol><li><p>Atoms</p></li><li><p>Lists</p></li></ol><p>Super minimal! The inference rules are also simple:</p><ol><li><p>An atom&#8217;s meaning is either a number (if it&#8217;s all digits) or it&#8217;s a symbol, in which case its value is looked up.</p></li><li><p>A list&#8217;s meaning is determined by the first value in it.</p><ol><li><p>There are certain symbols with special meanings, like lambda or cond.</p></li><li><p>Other symbols are looked up and applied like functions to the arguments.</p></li></ol></li></ol><p>This is such a simple formal system, yet it builds up an entire programming language. I&#8217;m leaving out that you probably have to define a lot of the functions yourself in some other language (like machine code), but even that number is small. In other words, <a href="https://ericnormand.me/article/bootstrapping-mindset">Lisps are bootstrapped</a>. Lisps today are more complicated, but they still maintain some taste of the simplicity of the original.</p><p>What I think is cool is that programming languages are essentially formal systems. They have all the ingredients. Their semantics are defined by the compiler, which defines them as a translation into another formal system, the machine code. Strangely, machine code does not have any non-meaningful parts. Anything you can express in machine code is meaningful to the semantics of the chip. The only hint of a meaningless thing is that it operates on numbers that themselves are meaningless to it, except in as much as it knows how to do arithmetic on them and fetch memory at a numeric address. They are abstract quantities that represent values meaningful to us. So we can say 1 means Jim and 2 means pizza.</p><p>I feel compelled to mention the book <em>G&#246;del, Escher, Bach</em> right now, for some reason. I have read only a few pages of it, but it&#8217;s probably a more playful and complete exploration of the strangeness of formal systems than I can possibly do. Just wait until you see what self-reference in a formal system does to it!</p><p>The challenge with using formal systems is in modeling a real-world scenario. It&#8217;s an art to translate Jim and his tastes into logical statements such that you can derive new meaningful statements from it. The derivation, once the statements are written, is quite trivial. This challenge of encoding the meaning is one reason why proofs are hard even when something seems obvious. By the way, the art of encoding a set of real-world scenario into a program is known as <em>domain modeling</em>. What are the meaningful parts you want to capture from the domain, and what parts are you leaving up to the humans for meaning?</p><p>There&#8217;s also a cool balance between minimalism of the system and human habitability. Sure, it&#8217;s very elegant that NAND is complete. But just look at the statements you have to write to do anything! They&#8217;re unreadable. It&#8217;s very hard, as a human, to wrap your intellect around them. Structure dominates. Perhaps you can eventually learn to read it, with time, but you&#8217;re mostly just counting parentheses. Check out this expression using the iota combinator:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;&#953;(&#953;(&#953;&#953;))(&#953;(&#953;(&#953;(&#953;&#953;))))&quot;,&quot;id&quot;:&quot;GNXBLGGMTT&quot;}" data-component-name="LatexBlockToDOM"></div><p>So clear! (<em>not</em>)</p><p>There&#8217;s a tradeoff between number of symbols and length/structure of statements. We want to select a set of symbols we can easily juggle in our minds (not too big) that are, at least metaphorically, meaningful to the concrete world we live in. Making the story about Jim and pizza is something we can relate to with almost no effort. Jim eats foods he likes, he likes pizza, so he eats pizza. It&#8217;s obvious (I believe) because it <a href="https://ericnormand.me/article/tap-into-your-social-brain">taps into the social parts of our brains</a>. We want symbols like &#8658;, &#8743;, and &#8744;. We can understand them. Software design is often about this tradeoff: Do we inline the code or do we extract a function and give it a meaningful name? Do we want longer code or more symbols?</p><p>The last cool thing I want to mention is that this is the key to <a href="https://ericnormand.me/podcast/lisp-a-language-for-stratified-design">stratified design and metalinguistic abstraction</a>. In stratified design, each layer defines new meaningful symbols for the layer atop it to use (relying on the underlying lambda calculus rules of inference). But in metalinguistic abstraction, you define an entirely new formal system, with new rules of inference, that hopefully lets you express your problem more easily than lambda calculus. Think a rules engine or SAT solver.</p><p>Well, it&#8217;s time to conclude. Formal systems are a neat mix of meaning and meaningless parts. The meaningless parts are like holes where we can fill in our own, human meaning. We want to find a set of meanings we can use to derive the answers to our questions. That is not easy. And finding the balance between the number of meaningful symbols and the structure is an art. These are some of the underlying questions we face every day when we program. The questions never go away, but the practice of domain modeling points you to a reliable source of answers: The domain itself.</p>]]></content:encoded></item><item><title><![CDATA[Post-Conj update]]></title><description><![CDATA[It's all about the community]]></description><link>https://ericnormand.substack.com/p/post-conj-update</link><guid isPermaLink="false">https://ericnormand.substack.com/p/post-conj-update</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Sat, 22 Nov 2025 00:53:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5k3d!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90f1901b-d391-4f2c-9e2d-d760c34e239c_431x431.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I got back from the Clojure Conj last Saturday (almost a week ago as I write this), and I&#8217;m still trying to get my life back together. Well, I should say that I always find conferences a bit disruptive to my routines. But that&#8217;s kind of the point! Conferences are a space out of time, where the quotidian responsibilities like childcare, work, and dishes are paused while you intensely spend 18 hours per day immersing yourself in a topic you&#8217;re interested in. While the cost is high, the benefits are higher. And that&#8217;s why I keep attending conferences.</p><p>It was great to go to the Conj this year. While it&#8217;s kind of a blur, I got what I was looking for, which was basically a boost in the perceived social value of Clojure. Let me put that less obtusely: Most of my friends are not programmers. They don&#8217;t know what Clojure is. I can&#8217;t really geek out with them about Clojure. Basically, my interest is private. Over time, it becomes harder and harder to maintain interest in something few people I encounter face-to-face care about. Am I crazy liking this thing? Am I out of touch with reality? Does it matter at all?</p><p>The Conj gives me that boost. Hundreds of smart people talking about Clojure, validating my interest in it, all in one small space. There are so many people that there are sub-groups. AI vs. anti-AI. Protocols vs. no protocols. Even REPL vs. no REPL (yes! they exist!). And it&#8217;s glorious.</p><p>The other benefit is that after three days together blabbing on about Clojure, it starts to get personal. You get to know people beyond their code and their jobs. It turns out that even Clojure programmers have lives.</p><p>I want to touch on a couple of things.</p><p><strong>My workshop went well.</strong> The ideal is that I blow people&#8217;s minds with skills they couldn&#8217;t have learned any other way that they can immediately apply at their work. I hit somewhat short of that. The second best is missing that but learning how to do better next time. That&#8217;s where it was. Luckily, I can translate that learning directly into my book. Thanks to all the participants who played along.</p><p><strong>People don&#8217;t get to see the magic of Rich Hickey outside of the Conj.</strong> Seriously, he&#8217;s amazing. As far as I can tell, he watches every talk (unless there are two tracks, obviously). He will ask questions during the Q&amp;A. And he&#8217;ll approach the speaker after the talk and share opinions, critiques, and encouragement. Despite not being as active online as he once was, he is committed to the community when it comes to in-person gatherings. He&#8217;s super engaged with the design of new features and is still thinking through tough problems, even after his retirement.</p><p><strong>Speaking of community, it was a big focus this year.</strong> Christophe and Jordan (and other volunteers) hosted a community-building event (where we all got to share our love within small groups guided by Clojure luminaries). Just their presence and energy made it clear that this conference is important.</p><p><strong>And I&#8217;m super excited about the Clojure documentary. </strong>Rich Hickey has already been interviewed out at his cabin. Many Clojure folks have also been filmed, including yours truly. I hope my footage doesn&#8217;t wind up on the floor. We&#8217;re very lucky that the same company that made the Python, Rails, and Node documentaries are making one for Clojure. We&#8217;re much smaller. But they&#8217;ve told me they think the influence of Clojure is greater than what&#8217;s apparent in the number of programmers. I have to agree, but it&#8217;s nice that the filmmakers can see that.</p><p>Alright, folks! Be good to each other.</p>]]></content:encoded></item><item><title><![CDATA[A functional programming course in 6 books]]></title><description><![CDATA[My recommendations for a robust foundation in the paradigm]]></description><link>https://ericnormand.substack.com/p/a-functional-programming-course-in</link><guid isPermaLink="false">https://ericnormand.substack.com/p/a-functional-programming-course-in</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Wed, 29 Oct 2025 10:02:49 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5k3d!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90f1901b-d391-4f2c-9e2d-d760c34e239c_431x431.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>I will be hosting a workshop at the <a href="https://www.2025.clojure-conj.org/schedule">Clojure/conj</a> on Wednesday, November 12 in Charlotte, North Carolina. My workshop is about domain modeling in Clojure. You can get 25% off the workshop using code <strong>DOMAINJ25OFFCONJ</strong>. And you can get 25% off tickets to the talks at the conference using code <strong>CLJ18BIRTHDAY.</strong> I&#8217;d love to see you there!</em></p><div><hr></div><p>Here&#8217;s a series of books that give an introduction to the majesty and variety of functional programming.</p><h2>Pure and higher-order functions</h2><p><em><a href="https://ericnormand.me/grokking-simplicity">Grokking Simplicity</a></em>, my own book, provides the necessary groundwork for functional programming. It begins way before most books begin by encouraging the distinction between pure and impure functions. Once that&#8217;s mastered, higher-order functions are explored. It has gotten high praise. I don&#8217;t feel weird hyping my own book because I wrote it because no book was taking up the task. However, be careful with chapter 3. Every time I open it, I dream of rewriting it for a second edition.</p><h2>Separating data and behavior</h2><p><em><a href="https://www.manning.com/books/data-oriented-programming">Data-Oriented Programming</a></em> by Yehonathan Sharvit, introduces the idea of programming with data and not objects. Since most people will be familiar with the standard object-oriented methodology of combining data with behavior, this book presents a very different perspective: Maybe data can be treated separately from behavior.</p><p>It is a decidedly Clojure-centered view of functional programming, but even a die-hard Haskeller will find some similarity to how they program, albeit using types instead of run-time schema checks. I think the idea of writing functions over data is such a foreign idea that it&#8217;s important to show how the other side lives. However, if there&#8217;s one thing from data-oriented programming that I wish was treated more in the book, it&#8217;s that much of the time, we don&#8217;t need to model our data quite so much. In other words, we&#8217;re not always building entities and defining their schemas. Sometimes, the best part of data-oriented programming is just leaving the data as it is and making a program that is data in, data out.</p><h2>Functional domain modeling</h2><p>So, I am writing <a href="https://ericnormand.me/domain-modeling">a book on this right now</a>, but it&#8217;s not done and what&#8217;s been published online will certainly change. It&#8217;s meant to be a hands-on guide to the intermediate skills of the programmer. Once you know how to write functions and for loops, where do you turn to write <em>good</em> functions and for loops? If you&#8217;re interested, go read the 200 pages that are already online for free. But I&#8217;m rewriting all of it to be more focused on learnability and objective. The premise is that software design does not need so much hand waving.</p><p>While you wait, you can read <em><a href="https://pragprog.com/titles/swdddf/domain-modeling-made-functional/">Domain-Modeling Made Functional</a></em> by Scott Wlaschin. It&#8217;s good if a little hand-waving, like most software design books. I daresay that it&#8217;s the best book I&#8217;ve read explaining Domain-Driven Design.</p><h2>Functional algorithms</h2><p><em><a href="https://www.cambridge.org/core/books/thinking-functionally-with-haskell/79F91D976F0C7229082325B41824EBBC">Thinking Functionally with Haskell</a></em> is one I enjoy. If you want something rigorous from someone who takes functional programming seriously, this is a great book. It reads a little like a textbook. But at the same time, it doesn&#8217;t have the 3-page code listings some industry books have. The algorithms are chosen to gradually show you the range of techniques you&#8217;ll need to solve any problem using functional, as opposed to imperative, style.</p><h2>Types and category theory</h2><p>I&#8217;ve read several books on these two areas, but I can&#8217;t say I&#8217;ve found a book that really captures the magic enough for me to recommend it. However, I haven&#8217;t read every book. <em><a href="https://www.manning.com/books/functional-programming-in-scala">Functional Programming in Scala</a></em>, by Paul Chiusano and Runar Bjarnason, is extremely popular. It&#8217;s so popular, it&#8217;s referred to by its color (<em>the red book</em>). Still, I doubt it&#8217;s giving me what I&#8217;m looking for. I want a book that expresses the beauty and elegance of types and Haskell-style category theory beyond &#8220;preventing errors&#8221; and reinventing imperative programming. </p><h2>Linguistic abstraction</h2><p>This topic isn&#8217;t learned quite enough today. We tend to talk about simple indirections like runtime dispatch to solve problems. Runtime dispatch, however, is just a fancy conditional. Sometimes your indirection needs a totally new semantics. My two favorite books for these are <em><a href="https://github.com/norvig/paip-lisp?tab=readme-ov-file">Principles of Artificial Intelligence Programming</a></em> (PAIP) by Peter Norvig and <em><a href="https://mitpress.mit.edu/9780262045490/software-design-for-flexibility/">Software Design for Flexibility</a></em> by Chris Hanson and Gerald Jay Sussman. </p><p>PAIP is a classic from the time when AI meant &#8220;good programming&#8221;. The examples are in Common Lisp and it shows how to build a logic programming system (similar to Prolog), rules engines, and equation simplifiers in a data-driven style.</p><p><em>Software Design for Flexibility</em> is written using Scheme and shows other techniques for developing powerful abstractions for conquering complex problems. The premise is that your semantics need to have more degrees of freedom than they problem you&#8217;re solving. Both books show techniques we rarely reach for, even when conditionals and recursion don&#8217;t cut it. If I had to pick one, it would be PAIP.</p><h2>Conclusion</h2><p>If someone read these books and tried to apply them, they&#8217;d be well on their way to mastering functional programming. I know there are a lot of books I&#8217;m not familiar with, so please don&#8217;t take this as an exhaustive list. It&#8217;s just a list of good books that fill niches in the FP journey. I&#8217;d love to hear your takes on required reading. Just hit reply and send me a message. Please share what you like about your recommendations along with the titles.</p>]]></content:encoded></item><item><title><![CDATA[My biggest fear with AI]]></title><description><![CDATA[Stagnation, not replacement]]></description><link>https://ericnormand.substack.com/p/my-biggest-fear-with-ai</link><guid isPermaLink="false">https://ericnormand.substack.com/p/my-biggest-fear-with-ai</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Tue, 05 Aug 2025 10:02:50 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5k3d!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90f1901b-d391-4f2c-9e2d-d760c34e239c_431x431.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I got drawn into the world of Alan Kay&#8217;s idea because I was going deep into functional programming. This was in 2007-2008. I had been programming in Lisp for years and was going through a radical transformation. Part of that process was understanding what I had been taught and how it worked&#8212;or whether it worked at all. (BTW, <a href="https://www.youtube.com/watch?v=wo84LFzx5nI">this recent talk</a> is amazing at tracing the history of OOP).</p><p>Smalltalk exposed a simple syntax for writing software. I won&#8217;t go into too many details, but suffice it to say that it was designed for kids. Building things for children is a constraint which, when overcome, leads to a system that could be used by adults as well. The simple syntax and semantics opened the world of programming to new people. It was democratizing. And it was powerful. <a href="https://ericnormand.me/podcast/rewrites">In 180 pages of code and 50 classes</a>, the team built an OS, a word processor, a paint application, animation studio, and more. And many of the ideas, usually oversimplified, became the dominant way people program today: with classes and methods.</p><p>The thing I&#8217;m most afraid of is that we won&#8217;t see another leap forward in programming language design. As AI generates a greater portion of code, there won&#8217;t be any need for new ways to express programs. The AI doesn&#8217;t care how it&#8217;s expressed (or at least it doesn&#8217;t care about the same things humans do). Programming won&#8217;t get easier for humans.</p><p>One counterargument to this is that we have not seen any new ways of programming in about 50 years anyway. It&#8217;s not the AI&#8217;s fault. OOP seems to have been the last major &#8220;paradigm.&#8221; Maybe there isn&#8217;t any more to do? I admit that it&#8217;s possible, but it breaks my heart to think there isn&#8217;t something better yet to be invented. And it seems so myopic and arrogant to think we&#8217;ve figured it all out. I err on the side of hope and humility.</p><p>Another argument is that AI <em>is</em> the new paradigm. Instead of typing JavaScript, you type English and the AI types JavaScript. That doesn&#8217;t quite make sense, yet, because we do still read the generated code. We have to. AI doesn&#8217;t get it right all the time, so you have to review the code, even if you get the AI to generate all of it. I accept that as part of an AI workflow, but it doesn&#8217;t support the point. It&#8217;s not a new programming paradigm, as in FP, OOP, Procedural, or Logic. Those paradigms provide basic constructs that are composed and named, allowing you to create new constructs. AI is much more like a new way to edit code. The code, though, is the same.</p><p>It&#8217;s doubtful that a new language, tailored to AI&#8217;s strengths, would be economically viable. AI is trained currently on human-written programs. Humans have written billions of lines of JavaScript and the other popular languages. These are the ones that the AI &#8220;understands&#8221; the best, so these are the ones that it will prefer to generate and make fewer errors with. But at best, it will generate code like the average industry programmer, not like the ones who invented new paradigms. So we won&#8217;t see AI being able to invent its own new paradigm.</p><p>The one hope for this is something similar to self-play that we see in AlphaGo Zero. Perhaps an AI could be given the syntax of the language and be asked to solve increasingly hard problems. It would learn how to generate good code without human-created examples. But a human would probably have to invent the language the AI was learning. What would that language look like?</p><p>Unfortunately, I don&#8217;t think it would be very friendly for humans. AIs might as well generate machine code! I&#8217;d rather have the AI learn the human thing than humans have to learn the AI thing.</p><p>But perhaps more advanced AIs will learn new human-centered languages quickly. Perhaps you could feed it the docs and some example programs, and, along with a REPL, it could figure it out. And so there&#8217;s nothing to worry about. Except the new language has bigger challenges to face than it did before. Before, if a new language was 10x more productive than JavaScript (or the most popular language), it had a shot. But if you&#8217;re already 10x faster with the AI using JavaScript, your language will have to be 10x that combination, using an AI not trained on it.</p><p>So that&#8217;s my biggest fear: The use of AI to write code is going to stunt the development of new programming paradigms. In the worst case, this is the end of paradigms as we know them, at least in industry. I hope it doesn&#8217;t happen. I still hold out for finding new and better ways to express ideas. I just don&#8217;t expect AI to help with it.</p>]]></content:encoded></item><item><title><![CDATA[LLMs' surprise teaches us about ourselves]]></title><description><![CDATA[How does a language model know, understand, and become expert?]]></description><link>https://ericnormand.substack.com/p/llms-surprise-teaches-us-about-ourselves</link><guid isPermaLink="false">https://ericnormand.substack.com/p/llms-surprise-teaches-us-about-ourselves</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Wed, 09 Jul 2025 05:24:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5k3d!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90f1901b-d391-4f2c-9e2d-d760c34e239c_431x431.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://www.youtube.com/live/gY7tFR6oeWY">Apropos with Peter Str&#246;mberg</a> was great! He showed us a lot of cool stuff that Calva can do. And also some cool tutorials he&#8217;s built into the Clojure IDE.</p><p>Apropos is now on break for the Summer. Enjoy!</p><div><hr></div><h1>LLMs&#8217; surprise teaches us about ourselves</h1><p>I studied AI in grad school. My master&#8217;s thesis was about combining Natural Language Processing (NLP) techniques with Machine Learning (ML) to answer very specific information from a large collection of text documents. That was back in 2008. When ChatGPT first came out, I was skeptical at first. But I gave it a shot and was very surprised. In this essay, I&#8217;d like to explore what was surprising to me before my memory of life before LLMs fades.</p><p>When I studied AI, NLP was a very different field than Knowledge Representation (KR) and Information Extraction (IE). NLP was about grammars, understanding sentence structure, and manipulating the structure of text, such as turning a declarative into an interrogative. Knowledge Representation was basically data modeling but for AI algorithms to be able to use. And IE was taking unstructured text and turning it into structured data.</p><p>Each of those parts had its own literature. Sure, any particular project was going to touch on multiple fields, but each had its own difficulties and hence its own literature. LLMs shatter these distinctions.</p><p>Take a basic statistical Markov Chain. It builds a statistical model of the probability distribution of the next word, given the last n words. It was clear, even back in 2008, that if you increased n and gave it more text to train on, the text that the Markov Chain generates sounds more and more human. It is more grammatically correct. And it sounds less and less like the source material, as differences of author start to average out. But also the text is meaningless. Your mind tries to make sense of it because it does sound like language. But if it does mean something, it&#8217;s more by coincidence than anything. Part of the joke was that you&#8217;d train a Markov Chain on a politician&#8217;s speeches and you could generate babble that sounds like them, often hitting the right topics, but never makes any sense. Get it? Politicians talk a lot but don&#8217;t say anything of substance.</p><p>The success of Markov Chains to produce language, but without any reliability of the content, was a kind of proof that you needed to integrate knowledge representation with the language generation. You needed a way to generate text under the constraint that it say something from the database.</p><p>LLMs ignored that problem and just kept increasing n (now called the <em>window</em>) and the size of the training set. They also moved from a statistical to a neural network model, which allows for much better generalization of learning. And there were some structural additions to classical neural nets. Transformers was one of them. It allows the network to attend to different parts of the window&#8212;which is now thousands or even millions of words long.</p><p>As I write this, LLMs can output flawless English text, meaning they don&#8217;t make grammatical mistakes. They&#8217;ve done so for a long time. In fact, they&#8217;re so good at language itself that they&#8217;re used to do fun linguistic things like converting an essay into pirate speak or a rap in the style of Shakespeare. Or even practical things like summarizing text.</p><p>What&#8217;s most surprising is that the models have improved their factual correctness. I know it may sound odd given how famous LLMs are for hallucination, but it is the most prominent improvement over classical Markov Chains. Whereas statistical Markov Chains said something factually true 1% of the time, LLMs are correct close to 100% of the time. They&#8217;ve gone from babbling, senile uncle to overconfident but smart uncle. Or maybe it&#8217;s more like a dancing dog. When a dog dances for a few seconds, we clap. But the dance isn&#8217;t very good by human standards. We clap for the fact that it danced at all. But a human dancing, that&#8217;s a different matter. We start to judge its faults. It missed a beat. There&#8217;s some awkward movements. LLMs have crossed over into the level where we notice its hallucinations more than the fact that it works at all.</p><p>LLMs began as NLP and mastered it, but in order to keep improving, had to get good at Knowledge Representation and Information Extraction. That&#8217;s the surprise. And, like all AI surprises, teaches us something about ourselves.</p><p>The lines between language and knowledge and reason have always been hard to draw. If you draw up a simple English grammar, then generate sentences using it, most of the sentences will be meaningless. But some will be awkward. It will say things like &#8220;Colorless green ideas sleep furiously.&#8221; That one is classically nonsensical. But it will also say things like &#8220;I ate three rices.&#8221; The trouble is that <em>rice</em> is a mass noun, not a collective noun. It can&#8217;t take a number as a modifier. It&#8217;s a grammatical distinction that we can incorporate into the simple grammar. But it&#8217;s also a very simple aspect of a model of the world. As we add more and more of these aspects to the grammar, it becomes more and more an understanding of the world. And there are countless fine distinctions in grammar that linguists have teased apart in English. LLMs, in my opinion, have creeped into knowledge representation through the kind of world knowledge embedded in grammar.</p><p>And they&#8217;ve moved far beyond it. However, grammar doesn&#8217;t know what the capital cities of countries are. They&#8217;re historical accidents. And that&#8217;s why it might hallucinate those kinds of facts. It&#8217;s still a wonder that they can know any of them at all.</p><p>But here&#8217;s where the information extraction comes in. In my thesis, I built a system to learn from text. I gave it all of the country names and all of the capital city names. Then I gave it all of the sentences in Wikipedia that contained both a country name and a city name. I parsed the sentences into a graph, then plucked out the path between the city and country names. Then I used good, old-fashioned graph matching to figure out which ones were correlated with capital cities.</p><p>The software broke about 80% accuracy with thousands of sentences. But LLMs are different. You can give it one sentence, like &#8220;Paris is the capital of France&#8221;, and then ask it: &#8220;What is the capital of France?&#8221; It will tell you. Or you can ask the other way, &#8220;What country is Paris the capital of?&#8221; Even if it doesn&#8217;t know what the capital of France is without you telling it (it probably does), it can use the information in the sentence you gave it to answer questions. What&#8217;s more is that you can give it a 10 page document full of information (including the sentence &#8220;Paris is the capital of France.&#8221;) and ask it those questions. It will still find the answer. Again, it&#8217;s approaching Information Extraction from an NLP doorway.</p><p>And this also tells us something about ourselves. The knowledge of capital cities is declarative knowledge. It&#8217;s the kind of knowledge that is easy to quiz for. I say UK, you say London. But imagine all of the knowledge you have to have to even begin answering the question. What is a country? What is a capital? What if I ask it more subtly, like &#8220;Where is the seat of government in the largest country by area?&#8221; How could it get that right without a deeper model of the world? Capitals are typically the seat of government. Cities are places. What is the largest country? What does area mean? In my opinion, this has been the key to unlocking Information Extraction&#8212;a critical mass of facts filling in an adequate model. And I don&#8217;t know how to argue for this, but I believe it: What we call <em>understanding</em> is largely linguistic, captured in our feelings of correct usage. It&#8217;s what Wittgenstein was on about. So as the LLM gets better at correct usage, it gets better at interpreting the meaning from text.</p><p>There&#8217;s one more thing that&#8217;s perhaps more subtle. When I first used ChatGPT, it was version 3.5. It hallucinated a lot. But more than that, you&#8217;d ask it a difficult question and it would pick a side. For instance, what is the best way to learn to a new language?It might say immersion. Or it might say study from books. Or take a class. All plausible. But when 4 came out, the same question was answered qualitatively differently. What is the best way to learn a new language? &#8220;There are differing opinions about what is the most efficient way to learn a language. Some believe immersion is best. Others believe personal study from books. But it depends largely on your goals with learning the language&#8230;&#8221; In short, it had a theory of knowledge, a kind of epistemology. It didn&#8217;t just know facts. It knew that different groups of people believed different facts. In fact, a better way to understand a topic is to understand the controversies of the field&#8212;and to be able to argue all sides.</p><p>Again, this is surprising. Simply by making the LLM bigger (bigger window, bigger network, bigger training) it has transcended basic models of truth and found a higher version of expertise. This also tells us about ourselves. As we become experts, we transcend any particular point of view and take on a meta point of view. We can hold multiple, often conflicting models in mind at once, and choose between them as we please, never really believing in them any more than we have to. Expertise is built on conflicting models.</p><p>Well, thanks for coming on this tour with me. I think the main point is that language is a doorway into our intelligence. It&#8217;s hard to know where language ends and understanding begins. And this has allowed LLMs to do many skillful things. Not everything, mind you. LLMs are not able to walk or drive. But they can do a lot. And like all interesting AI, they teach us about ourselves.</p>]]></content:encoded></item><item><title><![CDATA[The economic inevitability of AI]]></title><description><![CDATA[Renting shovels during the AI goldrush]]></description><link>https://ericnormand.substack.com/p/the-economic-inevitability-of-ai</link><guid isPermaLink="false">https://ericnormand.substack.com/p/the-economic-inevitability-of-ai</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Tue, 01 Jul 2025 10:03:33 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/29811b54-cf7e-4bb2-a8a9-28ce0227db7b_1024x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This week, our <a href="https://www.youtube.com/live/gY7tFR6oeWY">Apropos episode</a> is on Thursday. We invite Peter Str&#246;mberg to the show!</p><p>The Clojure/conj <a href="https://clojure.eventsair.com/2025-clojureconj/speakers">CFP</a> is open! If you&#8217;re looking to speak, go apply! I&#8217;ll be there giving a workshop on domain modeling in Clojure.</p><div><hr></div><h1>The economic inevitability of AI</h1><p>The current hype around AI triggers our cognitive biases and makes it hard to take an objective look. It&#8217;s just hard to be certain about anything. On what timeline will AI take our jobs? What threats does AI pose to culture and civilization? Will AI help us manage and mitigate climate change or will it exacerbate it? There are a lot of difficult questions. But there&#8217;s one thing I&#8217;m fairly confident about, and I think it&#8217;s worth taking the time to clear the air about it. I&#8217;m certain of the economic inevitability of the AI industry.</p><p>The future is clear. It has replayed itself many times in the history of business, and certainly in the history of the computing industry. The future is this: Some company (probably OpenAI but we can&#8217;t be sure) will capture a significant percentage of the revenue of white-collar labor. Just like Amazon now captures a significant portion of online commerce, just like AWS takes a cut of most startup&#8217;s revenue, the winner of the AI race will extract a fee from most every white collar worker. Like licenses for Microsoft Office or Windows, companies will pay for access to AIs they believe will make their employees more productive.</p><p>Companies will gladly ask their employees to use AI if it speeds up their work. Businesses want to be competitive, so as other companies gain efficiencies from their use of AI, they&#8217;ll need to do it, too, to keep up. It&#8217;s a rational choice. I&#8217;d ask my company to do it, too, if I were the CTO. Eventually, most employees doing knowledge work will be using AI. And the employers will be paying licenses to OpenAI (or whoever wins).</p><p>We live in a world where winner-takes-all is the natural course of things. Slight advantages compound. The big company can acquire, undercut, or outlast the small company. Eventually, the market sorts out the winner with ~80% market share. The rest of the companies fight over the remaining 20%.</p><p>In short, one company will have 80% market share, and the market is AI accelerators for knowledge work. Microsoft had &#8220;A computer on every desk.&#8221; OpenAI will have &#8220;An AI on every desk.&#8221;</p><p>Once the 80% winner is found, the <a href="https://www.merriam-webster.com/slang/enshittification">enshittification</a> can begin. These companies are fueled by massive investments. They can sell services well below cost for years. OpenAI forecasts <a href="https://finance.yahoo.com/news/report-reveals-openais-44-billion-145334935.html?guccounter=1&amp;guce_referrer=aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8&amp;guce_referrer_sig=AQAAAILcYR1EGuG5KFsBWgW4zPo-7nDtc4_dFdTtnXScHPK7EhZn0aUV60MXmCEcWUl15qZ66OXQs-uk9H_EoAVIPU0iYrQwvLxgPARc5TnCKq8IMEoLrQY8cDgZClxwUXr7G9NzLAVb2MCRTblIPmbSaag_gwH3--tJR_hgySnhUWQs">losing $44 billion</a> in the next four years. But in 2029, they can start experimenting with how to turn all those customers, now dependent on their services, into profitable clients, likely by raising prices, degrading service, or inserting ads. OpenAI will create a few trillionaires, many billionaires, and uncountable millionaires on their ascent.</p><p>If you&#8217;re looking to make money, hitch yourself to the rise of these AI giants. You could be one of the millionaires by being one of the companies renting the shovels during this gold rush.</p><p>History is repeating. Personal computers were supposed to liberate us. But what we didn&#8217;t realize when the digital dream of personal freedom was still alive was that the companies who amassed more computers and more data would be more free than others. The computer giants donated computers to school.</p><p>Similarly, AI is currently sold as a liberating force. It is &#8220;democratizing&#8221; programming. It&#8217;s giving everyone a cheap therapist. They are more reliable companions. But we know that the productivity gains and the huge profits will accrue to the employers and to the AI companies most of all. OpenAI and the others are making products that are sure to become the next essential tool for thought work. Employers will demand that we use them. OpenAI will take rent on all of the work we do. And it will become enshittified. With that inevitability out of the way, we can talk about the more nuanced ideas in the next emails, including some positive ones!</p>]]></content:encoded></item><item><title><![CDATA[AI, Lisp, and Programming]]></title><description><![CDATA[Intertwined since the beginning]]></description><link>https://ericnormand.substack.com/p/ai-lisp-and-programming</link><guid isPermaLink="false">https://ericnormand.substack.com/p/ai-lisp-and-programming</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Tue, 24 Jun 2025 10:02:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5k3d!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90f1901b-d391-4f2c-9e2d-d760c34e239c_431x431.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>On the last Apropos, <a href="https://www.youtube.com/live/kGOB9IPKHfo">we welcomed Christoph Neumann</a> to talk about his new role as the Clojure Developer Evangelist at Nubank. It&#8217;s very exciting that the role is in such great hands.</p><p>Our <a href="https://youtube.com/live/gY7tFR6oeWY?feature=share">next guest is Peter Str&#246;mberg</a>. Peter is known as PEZ online. He is the creator of Calva, the Clojure plugin for VS Code. He&#8217;s going to demo some REPL-Driven Development with Calva.</p><div><hr></div><h1>AI, Lisp, and Programming</h1><p>I have a Masters of Science in Computer Science, with a specialty in Artificial Intelligence. With the way AI salaries are these days, you&#8217;d think I pull seven figures. Alas, my degree is from 2008. At that time, people wondered why I wanted to go into a field that had been in a sorry state since the 1980s. Wouldn&#8217;t I enjoy something more lucrative like security or databases?</p><p>But I liked the project of AI. AI, to me, was an exploration of our own intelligence&#8212;how we thought, solved problems, and perceived the world&#8212;to better understand how we can get a machine to do it. It was a kind of reverse-engineering of the human mind. By building our own minds, we could understand ourselves.</p><p>It&#8217;s clear that Lisp and AI have been linked since very early in the two fields&#8217; existences. John McCarthy, the inventor of Lisp, also coined and defined the term <em>artificial intelligence</em>. Lisp was traditionally closely associated with the study of AI. Lisp has been a generator of programming language ideas that might seem normal now, but were considered weird or costly at the time. Here&#8217;s a partial list:</p><ol><li><p>Garbage collection</p></li><li><p>Structured conditionals</p></li><li><p>First-class and higher-order functions</p></li><li><p>Lexical scoping</p></li></ol><p>While it&#8217;s clear that Lisp has been influential on programming languages (even after being ridiculed for the same features languages borrow), what is not so clear is how much AI has been an influence on programming practice. Until recently, it was kind of a joke that AI had been around since the 1950s but hadn&#8217;t produced any real results. The other side of the joke is that once something works, it&#8217;s just considered <em>programming</em> and not <em>artificial intelligence</em>.</p><p>Artificial intelligence has produced so many riches in the programming world. Here is a partial list:</p><ol><li><p>Compilers (which used to be called &#8220;automatic programming&#8221;)</p></li><li><p>Tree search</p></li><li><p>Hash tables</p></li><li><p>Constraint satisfaction</p></li><li><p>Rules engines</p></li><li><p>Priority queues</p></li></ol><p>Let me put it directly: Seeking to understand human thought has been fruitful for software engineering. My interest in Lisp is interlinked with my interest in AI. AI has always been synonymous with &#8220;powerful programming techniques.&#8221; And I have always seen myself as part of that thread, however small my contribution might be.</p><p>The link between AI, Lisp, and programming was so strong 30 years ago that Peter Norvig started the preface of <em>Paradigms of Artificial Intelligence Programming</em> with these words:</p><blockquote><pre><code>This book is concerned with three related topics: the field of artificial intelligence, or AI; the skill of computer programming; and the programming language Common Lisp.

<a href="https://github.com/norvig/paip-lisp/blob/main/docs/preface.md">source</a></code></pre></blockquote><p>In 2008, when I graduated, Google could barely categorize images. Identifying a cat in a photo was still considered a hard problem. Many attempts had been made, but none could get close to human-level. In 2014, I heard about a breakthrough called &#8220;deep learning&#8221;. It was using the scale of the internet and the vast parallelism of GPUs to make huge neural networks trained on millions of images to break accuracy records. It was working. And it was completely uninteresting to me.</p><p>Okay, not really <em>completely</em> uninteresting. It tickled my interest in building new things. I could see how being able to identify cats (or other objects) reliably could be useful. But I saw in this nothing of the project for understanding ourselves. Instead, it was much like what happened at Intel.</p><p>Nobody really likes the Intel architecture. It&#8217;s not that great. But once Intel got a slight lead in marketshare, it could ride Moore&#8217;s Law. Instead of looking for a better architecture, invest your time instead in scaling the transistor down and scaling the number of transistors up. Even the worst architecture will get faster. And Intel can cement their lead by investing in better manufacturing processes. Their dominance wound up lasting decades. But computer architecture has languished relative to the growth of the demand for computing.</p><p>The same effect is at play in neural networks: Instead of investing in understanding of how thought works, just throw more processing and more training at bigger networks. With enough money to fund the project, your existing architectures, scaled up, will do better.</p><p>These are oversimplifications. There were undoubtedly many minor and some major architectural breakthroughs that helped Intel keep pace with Moore&#8217;s Law. Likewise, there have been similar architectural breakthroughs in neural networks, including convolutions and transformers. But the neural network strategy is dominated by scale&#8212;more training data, more neurons, more FLOPS.</p><p>My whole point is that my research into the history of the field of AI has somewhat inoculated me against the current hype. I don&#8217;t think AI will &#8220;replace all humans&#8221;. And I don&#8217;t think AGI (artificial general intelligence) is defined well enough to be a real goal. So where does that leave us? How is AI going to transform programming? Where will all of this end up?</p><p>Artificial intelligence has <em>always</em> been a significant part of the leading edge of programming. And its promise has always been far ahead of its ability. In the next few issues, I want to explore what this current hype wave of AI means for us. I don&#8217;t like where I see AI going. But I also want to give apply some optimism to it because I think a lot of the consequences are inevitable. The world is being changed, and we will have to live in that new world.</p>]]></content:encoded></item><item><title><![CDATA[Work and meta-work]]></title><description><![CDATA[At what point does meta-work become work?]]></description><link>https://ericnormand.substack.com/p/work-and-meta-work</link><guid isPermaLink="false">https://ericnormand.substack.com/p/work-and-meta-work</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Tue, 17 Jun 2025 10:02:42 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c456ca84-7055-4fa1-840d-9463f9111a5a_1024x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Part of the work I did when I ran my own business was standardizing procedures. I got fascinated by checklists and instructions. Sometimes you only do something once in a while. You want to remember all of those little details, all the little problems solved, for next time. Because you will forget. Having them written down means you can pick it up whenever you need to. And, when you&#8217;re running a business, there&#8217;s enough to keep your mind busy. It&#8217;s nice to have a list of instructions to follow when you&#8217;re running out of decision-making power.</p><p>I started perfecting the process of writing processes. At one point, even the meta-work was included in the process itself. Think of the process like a recipe. The recipe says you need 4 cups of flour, 1 cup of oil, etc. Then it has steps like mix the flour and oil in the bottom of a heavy pan, mix some other ingredients in a medium bowl. It seems complete. But it&#8217;s not. At some point I started even writing the little steps like gather a spoon, a heavy pan, and a medium bowl. All of the implied steps got written down.</p><p>It might seem like those steps are obvious and don&#8217;t need to be explicit. To people who believe that, I say that they just haven&#8217;t experienced the calming effect of a good list of steps. The steps become the artifact that records all of your learning. You can rearrange the steps and otherwise optimize the whole process in writing.</p><p>It&#8217;s such a relief when you start to do that. It&#8217;s very relaxing to know that every little detail is handled in the instructions. For example, you might not know you need a medium bowl until you read step three. But at that point, you might have your pan on the stove and need to stir constantly. Having all of the steps, including the meta-steps, listed out and in an order that works is freeing. Your mind can attend to the present step.</p><p>At work I&#8217;ve been working through this same thing with project planning. We&#8217;ve got a feature we want to build. We discussed the details of it. Now it&#8217;s time to make a project in Linear to track it. There are two questions that I&#8217;m wrestling with: How much granularity do you want? And how much of the meta-work needs to be written down?</p><p>Too much granularity and things get a little too prescriptive. In work like programming, breaking the work down into smaller chunks and writing a description of those chunks is actually part of the work. And if your chunks are too small, you&#8217;re not taking into account the realistic uncertainty every programming project has about exactly how it should work. However, don&#8217;t break it down enough and you&#8217;ve got a single task: Implement the feature. Finding that middle path is part of the art of project planning.</p><p>But should planning the project be written down as the first step in the project? Sometimes I think yes. If the planning is not obvious (where you can just do it then and there), yes, you can write the first step is &#8220;Break the project into steps.&#8221; And if your planning process is more structured with multiple steps (like getting approval, etc.), write those down, too.</p><p>The default setup in Linear is to have multiple statuses for each task. Backlog&#8594;In-progress&#8594;In review&#8594;Done. But I find that code review where I work is hefty enough to merit its own step. So I put it. Meta-work is work when it requires effort. For instance, I wouldn&#8217;t put a step in the project saying &#8220;change status of step 2 to Done.&#8221; That seems weird and a little too meta. But asking for a review, following up until they do it, addressing the comments, then following up again until it&#8217;s approved is real work. That gets its own step.</p><p>It reminds me very much of the Getting Things Done methodology where they recommend writing down the &#8220;next action&#8221; to take. It should be precise enough to actually accomplish. For instance, if you need to call a plumber, but you don&#8217;t know a plumber, the next action might be &#8220;ask your friends for plumber recommendations.&#8221; Better yet would be to list the friends.</p><p>In the same way, we want each step to be clear. If the project requires research before you know what steps to take, write &#8220;Research x.&#8221; You can add more steps as things become clear. The point is to capture the steps you do know and feel a sense of progress when you check them off.</p><p>It&#8217;s all work, meta or not. The real trick is that planning is a way of manipulating the future. You write down steps, you change them, you rearrange them, you erase them, all before you take the first step. Some steps are known and fixed. Merging to main comes last. It&#8217;s pegged at the end, while some are fluid and can be rearranged, done in parallel, or even eliminated if they&#8217;re not necessary. The question is whether the meta-task warrants a place in the future among all the other tasks. I say we should default to saying yes more often. It helps us take the meta-tasks too seriously. For example, some teams may consider testing as a meta-task. By making it a task and writing it down, we honor its place among other tasks. And we honor ourselves when we complete it, giving us the satisfaction of a job done.</p>]]></content:encoded></item><item><title><![CDATA[Too many degrees of freedom]]></title><description><![CDATA[Error throwing and catching is ironically full of errors]]></description><link>https://ericnormand.substack.com/p/too-many-degrees-of-freedom</link><guid isPermaLink="false">https://ericnormand.substack.com/p/too-many-degrees-of-freedom</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Tue, 03 Jun 2025 10:01:35 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/6c45e9a3-406d-450f-b323-fbb9a7947d2c_1024x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I love conference talks. I believe that YouTube has made me a better programmer over the last 17 years. I&#8217;ll often turn one on while I&#8217;m doing chores. I&#8217;ll learn something and sometimes even be inspired to write about it. Like this one.</p><p>The talk I was watching was <a href="https://www.youtube.com/watch?v=oWvX-hdIAQo">You&#8217;re Doing Exceptions Wrong</a> by Matt Burke. In it, Burke presents some rather compelling advice on how to design your exception throwing and handling to make your code more robust and easier to work with. I quite agree with a lot of what he said, though I think there&#8217;s plenty of room for subtleties and context-dependent decisions. You can&#8217;t cover everything in a one-hour talk.</p><p>What struck me, though, was how easy exceptions are to get wrong. He goes over many examples where poor use of exceptions led to problems. Sometimes people caught exceptions and ignored them. Sometimes they threw the wrong exception. Etc., etc. There was a lot of advice to digest and apply. And you could still get it wrong.</p><p>It got me thinking about how many degrees of freedom there are when dealing with exceptions. What class of exception you choose? Should you throw? How much code you wrap your try/catch in. Do you catch at all? How specific do you make your catch statement&#8217;s selector? How do you rethrow? Do you need a finally? All of these were subtle and non-mechanical decisions. That&#8217;s a lot of cognitive work going on.</p><p>It reminds me a lot of the advice in <em><a href="https://jcip.net/">Java Concurrency in Practice</a></em>. It&#8217;s a great book. The advice is solid. I learned a ton about Java when reading that book. But it&#8217;s impossible to apply correctly all the time. It is 432 pages of dos and don&#8217;ts. Java&#8217;s design defaults to a sequential model of computation&#8212;one thread. To switch to a concurrent model, you have to start using a whole set of new conventions. You need to properly use the volatile and synchronized keywords, choose &#8220;thread safe&#8221; classes, and master a whole set of concurrency primitives. There are too many degrees of freedom.</p><p>Clojure solved this by reversing the situation: Make concurrency the default. What does that look like? Immutable data structures and concurrency primitives with simple contracts. You can still do sequential programming for those inner loops where you need it. But it&#8217;s more cumbersome. In short, Clojure chose the more general case (multiple threads) and constrained the degrees of freedom by choosing the right defaults.</p><p>I wonder if there isn&#8217;t something like that for exceptions. What would that look like? It would be about choosing the right, general-case default, reduce the number of decisions that have to be made, and making the few decisions left easy to get right. (Aside: This is something Clojure does particularly well.)</p><p>If we assume the default is the general case (every function is broken :), then we </p><p>The first thing I would do is to come up with a short list of scenarios that the exception can capture. Here&#8217;s a first stab:</p><ul><li><p><code>IllegalArgumentException</code> &#8212; somebody passed me an invalid argument</p></li><li><p><code>UnexpectedReturnValueException</code> &#8212; I called a function and got something back I wasn&#8217;t ready for</p></li><li><p><code>NoPossibleAnswerException</code> &#8212; I was called with valid arguments but I can&#8217;t fulfill the contract</p></li></ul><p>The main idea behind the first two is try to distinguish the source of bad values. The ubiquitous <code>NullPointerException</code> tells you you got a null, but not whether it was from an argument or a return value. I&#8217;m not as concerned about whether it was a null or some other invalid value (such as 0 for division) as much as where the bad value came from. These exceptions would require you to provide the value.</p><p><code>NoPossibleAnswerException</code> is for situations where you want to throw an exception because you can&#8217;t answer. For example, if I ask for the configuration key &#8220;HTTP_PORT&#8221; but it&#8217;s not set, I may want to throw an exception instead of returning null. This is the exception for that case.</p><p>There are still other exceptions that might come up. I&#8217;m thinking about <code>FileNotFound</code> or <code>TimeoutExceptions</code>. I wonder where those go.</p><p>There&#8217;s another dimension by which we should slice the exceptions: Whether or not they should be retried. For example, if there&#8217;s a timeout on an http get request, go ahead and retry. But if you divide by zero, retrying is useless. If there&#8217;s one piece of information I&#8217;d love to have available programmatically, it&#8217;s whether the thrower thinks retrying is a good idea.</p><p>With these few pieces, you can build some primitives that help localize the errors:</p><pre><code>(defn divide [num denom]
  (assert-argument denom (not (zero? denom)))
  (/ num denom))</code></pre><p><code>assert-argument</code> is a macro that throws <code>IllegalArgumentExceptions</code> if the condition is not true. It can use the form to report good error messages, including the name of the argument, its value, and the condition that failed. It helps document your assumptions about the arguments you will receive.</p><p>Similarly, you can document the assumptions about the return values you will get with <code>assert-return</code>.</p><p>Unfortunately, most existing libraries (certainly those in Java) do not follow these conventions. What has worked best for me is to build something like an <a href="https://ddd-practitioners.com/home/glossary/bounded-context/bounded-context-relationship/anticorruption-layer/">Anti-Corruption Layer</a> (as in Domain Driven Design). The anti-corruption layer wraps the library and converts the library&#8217;s conventions into the conventions of my codebase. It&#8217;s a place to say &#8220;this is how we use this library&#8221; and &#8220;this is what we expect this library to do&#8221;. It seems redundant at first. Why wrap each library function you call in another function that just calls the library function? Well, it&#8217;s to constrain and centralize the assumptions you&#8217;re making about the library. Instead of having workarounds for the library spread throughout the codebase, you centralize them and standardize them to the anti-corruption layer.</p><p>Before I conclude, I want to mention a different yet similar approach. Erlang is famous for its &#8220;let it crash and retry&#8221; strategy for handling errors. It&#8217;s similar in that Erlang assumes anything can fail (the default is something will go wrong). And the default strategy is to reset the state and try again. This often works, especially with stateful systems. I think it&#8217;s a great default.</p><p>The problem I have encountered with it is that you do need a certain level of correctness for retries to be effective. If your code doesn&#8217;t handle the null you got, retrying it is not going to fix that. You would like to surface bugs early (hopefully during development) so you can fix them. In those cases, you don&#8217;t want to hide the problem with a retry. However, we all know that bugs are inevitable. So being protective and having a decent default strategy is prudent. Retry seems to be pretty good to me as a default. So it seems we may want different behavior in production from during development. </p><p>In development, crash early so we can surface the errors. In production, do your best to continue without errors. For instance, if the product recommendations don&#8217;t load on your product page for a new tea kettle, you should still load the page, just without recommendations. That way, the user can still buy their tea kettle. But don&#8217;t do that during development because if you break the recommendation engine, you want to know.</p><p>The tension between these two behaviors shows that there are still significant decision points a programmer will need to make. The key is to make the decision easy to get right. That means reducing the number of options and making each option easier to do right. We want to standardize and reduce boilerplate.</p><p>Let&#8217;s conclude. Exception throwing and handling has a lot of degrees of freedom&#8212;too many to get right all the time. It&#8217;s ironic because the error reporting and handling will have errors! We can apply Clojure&#8217;s design principles of using the general case as the default. In this case, it&#8217;s that the function you&#8217;re calling can&#8217;t be trusted fully, and the caller of the current function can&#8217;t be trusted. You have to check your arguments and the return values. But checking them is a lot of work and easy to get wrong. We need better primitives to make it easy to get right. I proposed a systematic evaluation of the kinds of errors you want to throw, and making those easy to throw. I talked about an anti-corruption layer to enforce conventions when using third-party libraries. And I talked about wanting different behavior in development and production. I don&#8217;t think I got it all right. So please let me know what your policies are in your software. And I&#8217;m also interested in knowing in what other areas (besides exceptions) you apply the policy of &#8220;general case is the default&#8221;.</p>]]></content:encoded></item><item><title><![CDATA[REPL-Driven Development and Learning Velocity]]></title><description><![CDATA[The last Lisp advantage]]></description><link>https://ericnormand.substack.com/p/repl-driven-development-and-learning</link><guid isPermaLink="false">https://ericnormand.substack.com/p/repl-driven-development-and-learning</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Tue, 13 May 2025 10:01:21 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/6c6ac7d0-6dc4-4a75-8f2a-aa4efaac47db_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our next <a href="https://youtube.com/live/h8cdxBnW8Ic?feature=share">Apropos will feature Nathan Marz</a> on May 20. Be sure to subscribe!</p><div><hr></div><h1>REPL-Driven Development and Learning Velocity</h1><p>The main advantage of Lisps (including Clojure) over other languages is the REPL (Read-Eval-Print Loop). Lisp used to have a bunch of advantages (if statements, garbage collection, built-in data structures, first-class closures, etc.), but these are common now. The last holdout is the REPL.</p><p>The term <em>REPL</em> has diluted, so I should define it: A REPL is a way to interactively inspect and modify running, partially correct software. My typical workflow is to open my editor, start the REPL, and start the application server from within it. I can make requests from my browser (to the running server), recompile functions, run functions, add new libraries, and inspect variables.</p><p>The REPL accelerates learning by increasing the speed and information richness of feedback. While programming, you learn about:</p><ul><li><p>Your problem domain</p></li><li><p>The languages and libraries you&#8217;re using</p></li><li><p>The existing codebase and its behavior</p></li></ul><p>The REPL improves the latency and bandwidth of feedback. Faster and richer feedback helps you learn. It lets you ask more questions, check your assumptions, and learn from and correct your mistakes. Fast, rich feedback is essential to achieving a flow state.</p><p>The obvious contrast with REPLs is with the mainstream edit-compile-run (ECR) loop that most languages enable. You edit your source code, run the compiler, and run the code. Let&#8217;s look at the main differences between REPL and ECR:</p><ul><li><p>In ECR, your state starts from scratch. In REPL, your state is maintained throughout. All of the variables you set up are still there. Web sessions are still open.</p></li><li><p>In ECR, your compiler may reject your program, forcing you back into the Edit phase. Nothing is running, so you must fix it before continuing. In REPL, when the compiler rejects your change, the system is still running with the old code, so you can use runtime information.</p></li><li><p>In ECR, if you want to try something out, you have to write an entire program to compile and run. In REPL, trying something out means typing the expression and hitting a keystroke.</p></li></ul><p>The end result is that the ECR loop is much slower than the REPL. One of the benefits of modern incremental testing practices (like TDD) is that it approximates the fast feedback you get from the REPL:</p><ul><li><p>With testing, you do not maintain your state. However, you write the code to initialize the state for each test.</p></li><li><p>With testing, you make small changes to the code before rerunning the tests so you are usually not far from a running program.</p></li><li><p>With testing, it is easy to add a new test and run just that.</p></li></ul><p>The advantage of testing is that you have a regression suite, which you don&#8217;t get from the REPL. But feedback in testing is poorer. I haven&#8217;t heard of anyone writing a test just to see what the result of an expression is. And, by the way, doing incremental testing with the REPL is a breeze. It&#8217;s like the best of both worlds.</p><p>The REPL gives you fast, rich feedback in three main ways:</p><ul><li><p>Maintaining state &#8212; Your running system is still running, with all in-memory stuff still loaded. This means that after editing a function and hitting a keystroke, you can inspect the result of your change with a delay that is below human perception.</p></li><li><p>Running small expressions &#8212; Within your running system, you can understand the return value from any expression you can write, including expressions calling your code or libraries you use. The cost of running these expressions is below a psychological threshold, so they feel almost free compared to having to scaffold a test or a <code>public static void main(String[] argv)</code>.</p></li><li><p>Ad hoc inspection of the running system &#8212; This is a big one you gain skill with over time. You can do anything you can imagine, from running your partially completed function (just to make sure it does what you expect) to printing out the value of a global variable (that you saved values to from your last web request). The flexibility makes tools like debuggers feel rigid.</p></li></ul><p>However, other languages have chipped away at the advantages of REPL-Driven Development. I already talked about incremental testing approaches (like TDD) and how they approximate the feedback of the REPL. But there are more technologies that provide good feedback in the mainstream ECR (Edit-Compile-Run) paradigm:</p><ul><li><p>Static analysis &#8212; You can get feedback on problems without leaving the edit phase with tools like LSP and squiggles under your code.</p></li><li><p>Static types &#8212; If you&#8217;ve got good types and you know how to use them, the Edit-Compile cycle can also give you rich feedback. The question is whether your compiler is fast enough to keep up.</p></li><li><p>IDEs with run buttons &#8212; Many IDEs for compiled languages use their own, incremental compiler. The code is constantly being compiled as you edit. When you hit the Run button, you&#8217;re essentially cutting out the Compile phase (which can often be very lengthy). If you can set it up to run a small expression at a keystroke, you&#8217;re very close.</p></li><li><p>Autocomplete &#8212; Autocomplete speeds up the Edit phase. Autocomplete with the REPL is a cinch. You can inspect what variables are available in the environment dynamically. However, modern IDEs can use static analysis to aid autocomplete.</p></li><li><p>Incremental testing &#8212; Incremental testing (like TDD) speeds up the Edit and Run phases. Added here just for completeness.</p></li></ul><p>But don&#8217;t tell anyone: we can use these in addition to the REPL. In fact, Clojure has an excellent LSP and a great testing story. The only thing we don&#8217;t have is a great story about static typing.</p><p>Many languages claim that they have a REPL. But what they really have is an interactive prompt where you can type in expressions and get the result. This is great! But it doesn&#8217;t fully capture the full possibility of the REPL. </p><p>People often ask what&#8217;s missing, especially from a very dynamic language like JavaScript. I&#8217;ve tried to set up a REPL-Driven Development workflow in JavaScript, but I encountered these roadblocks. There are probably more:</p><ul><li><p>Redefining <code>const</code>: In Clojure, we pride ourselves on immutability. However, global variables are mutable so that we can redefine them during development. Unfortunately, JavaScript engines are strict about global variables defined with <code>const</code>. I found no way to redefine them once they were defined.</p></li><li><p>Reloading code: It&#8217;s not clear how to reload code after it has changed. Let&#8217;s say I have a module called <code>catfarm.js</code> and I modify one of the functions in it. My other module <code>old-macdonald.js</code> imports <code>catfarm</code>. How do I get <code>old-macdonald</code> to run the new code? The engines I tried did not re-read the imported modules, instead opting for a cached version. In addition, even if they did, the <code>old-macdonald</code> code needs to be recompiled. Clojure&#8217;s global definitions have a mechanism to allow them to be redefined, and any accesses to them after redefinition are immediately available. If I recompile a function <code>a</code> in Clojure, the next time I call <code>b</code> (which calls <code>a</code>), it calls the new version of <code>a</code>.</p></li><li><p>Calling functions from other modules: When you <code>import</code> a module, it&#8217;s basically a compiler directive where you say what you&#8217;re importing. But how do you call things from that aren&#8217;t imported? Why would you do that? Because you want to try them out! This, combined with not being able to re-import them, makes it really hard to incrementally work on your code. In Clojure, <code>require</code> is a function that loads new modules. And <code>in-ns</code> is a function that lets you navigate through the modules like in a directory structure from your terminal. <code>require()</code> in JavaScript (the old way of doing modules) worked more like that.</p></li></ul><p>Hot module reloading is an attempt to address these limitations. It also seems like a major project with a lot of pitfalls. And it still doesn&#8217;t maintain state. Maybe it will get good enough one day to be close to REPL-Driven Development. Or maybe Node should have a &#8220;REPL&#8221; mode where <code>const</code> variables can be redefined and modules can be reloaded and navigated.</p><p>The biggest problem with REPLs is that they require an enormous amount of skill to operate effectively. To know what you need to recompile, you must understand how Clojure loads code and how Vars work. You need to navigate namespaces. Most importantly, you need to develop the habit of using the REPL, which is not built-in. It&#8217;s common for someone in a beginner&#8217;s chat to ask &#8220;what happens when I call <code>foo</code> on a <code>nil</code>?&#8221; My first reaction is: &#8220;Why are you asking me? Instead of typing it here, type it in the REPL!&#8221; People need to be indoctrinated.</p><p>Here are some ways to improve your use of the Clojure REPL:</p><ul><li><p>Next time you&#8217;re writing a function, use a rich comment block ( <code>(comment &#8230;)</code> ) to call that function. After the tiniest of modifications, call the function to inspect the return value. This is useful when writing long chains of functions.</p></li><li><p>The next time you&#8217;re tempted to look up documentation for a function, do a little experiment to see what happens at the REPL. Some common questions to answer:</p><ul><li><p>What type does a function return?</p></li><li><p>Can I call that with an empty value?</p></li><li><p>What does the zero-argument version do?</p></li></ul></li><li><p>Learn the keystrokes in your editor to evaluate:</p><ul><li><p>The whole file</p></li><li><p>The current top-level form (often referred to as <code>defn</code>)</p></li><li><p>The expression just before the cursor</p></li></ul></li><li><p>Learn the keystrokes to run the tests from your editor. Run your tests. Edit and compile functions (see previous bullet). Run the tests again. This combines the best of incremental testing and RDD.</p></li></ul><p>I love the REPL. I miss it when I go to other languages. The REPL is also forgiving. It can make up for a lot of missing tooling (like autocomplete and debuggers). The REPL also requires a lot of skill. Squiggles and LSP can immediately give you hints for making your code better. But the REPL requires a deep understanding of the language and lots of practice. This is ultimately the biggest barrier to its adoption. People who haven&#8217;t learned how to use the REPL don&#8217;t even know what they are missing.</p><p>PS: You can learn the art of REPL-Driven Development in <a href="https://ericnormand.podia.com/beginning-clojure">my Beginner Clojure signature course</a>.</p>]]></content:encoded></item><item><title><![CDATA[Crafting your environment]]></title><description><![CDATA[Managing the real bottleneck of your programming productivity]]></description><link>https://ericnormand.substack.com/p/crafting-your-environment</link><guid isPermaLink="false">https://ericnormand.substack.com/p/crafting-your-environment</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Wed, 07 May 2025 03:24:03 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a51e9109-5272-470a-b48b-88eb023946d9_1024x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This week we have <a href="https://youtube.com/live/a-PrBjlBdw8?feature=share">JP Monetta on Apropos</a>. He&#8217;s the creator of <a href="https://www.flow-storm.org/">FlowStorm Debugger</a>, which is a time traveling debugger for Clojure. Check it out! It is quite amazing.</p><p><em><a href="https://ericnormand.podia.com/beginning-clojure">Beginner Clojure</a>, </em>my video course, is better than ever. I recently completely rebuilt the Introduction to Clojure module. It&#8217;s the fastest way to get from zero to a deep, functional programming and data-driven programming experience. <a href="https://ericnormand.podia.com/view/courses/introduction-to-clojure-v3/2738781-day-1-syntax-and-repl/8829820-read-eval-print-loop-repl">Go check it out </a>to see why hundreds of people have enjoyed it. If you buy, you get lifetime access plus all updates. Planned future updates include <em>VS Code with Calva</em>, <em>The Clojure Command-Line, </em>and <em>Shell Scripting with Babashka</em>. The course already contains eight great modules, including JVM, Repl-Driven Development, and functional programming. <a href="https://ericnormand.podia.com/beginning-clojure">Buy it today</a>.</p><div><hr></div><h1>Crafting your environment</h1><p>My Emacs setup is boring. So is my shell prompt. I&#8217;ve usually left things vanilla. Sometimes I look at other programmers and their awesome tooling setup and their colorful zsh prompts and feel superior. I tell myself that I am serious. I don&#8217;t waste time with silly things. Instead, I get to work. I&#8217;m very conservative.</p><p>But over time, I&#8217;ve heard of enough arguments for extreme configuration and seen enough examples of powerful IDE setups and missed enough tools that are now standard that I wonder if I&#8217;m wrong. Is it a waste of time? Or is it time invested? I&#8217;m starting to like the other side, though I&#8217;m still very bad at it.</p><p>Let&#8217;s start with the argument for why it&#8217;s not really worth your time to improve your tools.</p><h2>Being slightly faster does not pay off</h2><p>Sometimes I see a programmer obsessing over their setup. They&#8217;re spending hours reading forums and getting their Emacs config just the way they like it. They get into building their own keyboard. They figure out the best chair to sit in. It&#8217;s not so much that it doesn&#8217;t matter at all, it&#8217;s that it seems like a huge distraction. You want to be better at programming, so you spend hours 3D printing your keycaps? It seems to me like a big waste of time.</p><p>Let&#8217;s calculate it. If you spend 10 hours making a keyboard (which is very conservative), and it makes you 1% faster at typing, and we assume you spend 1 hour per day typing, you will need to type for 1,000 hours before you&#8217;ve made up the difference. That&#8217;s 2.7 years. That doesn&#8217;t seem worthwhile to me.</p><h2>Typing speed is not the bottleneck</h2><p>One of my friends put it this way: Typing speed is not the bottleneck in programming. They&#8217;re evoking a principle from the Theory of Constraints which is very popular in manufacturing circles. The principle says that every system has a bottleneck, and improvements to anything besides the bottleneck will not improve the overall productivity of the system. The bottleneck is the limiting factor. According to my friend, typing speed is not the the limiting factor to your effectiveness as a programmer.</p><p>All that time spent learning another vim command won&#8217;t really make your programming faster. You&#8217;re still limited by the bottleneck, which I agree is not typing speed.</p><h2>Customization is not shareable</h2><p>And just one more issue with ultra-customization. One time I was pairing with my coworker on his laptop. I was having trouble communicating something he should try, so he handed me the laptop. I put my hands on the keys. And every key I pressed surprised me. I was skilled at Emacs. And this was Emacs. But this was not the Emacs I knew.</p><p>He had customized the basic Emacs commands to different keys. And the official keys were remapped to other commands. They made sense to him, but I was useless on his keyboard. That really made me double down on leaving keys standard. With non-standard keys, sure, you may be faster, but nobody else can use your computer.</p><p>Do you agree with these? Are there other arguments?</p><p>I once agreed with these, but not any more. These three arguments are missing something important.</p><h2>Being slightly faster is not the point</h2><p>The reason the &#8220;efficiency argument&#8221; doesn&#8217;t hold water is that it&#8217;s not really about being faster. If all you&#8217;re looking at is productivity such as lines of code per second, you&#8217;re missing the main point. I talk about this a lot in <a href="https://ericnormand.me/article/automation">The Surprising Benefits of Automation</a>. We improve our tools and skills for agency and efficient use of cognitive resources, which we&#8217;ll talk about soon.</p><h2>Typing is on the critical path</h2><p>My friend evoked the concept of the bottleneck from manufacturing (note the efficiency mindset). Another concept from manufacturing and project management is the <em>critical path.</em> This is the path of sequential steps that affect the timeline of the project if they take more or less time. Typing might not be the bottleneck, but it is on the critical path, especially of iterative development practices like REPL-Driven Development (RDD).</p><p>I think what my friend meant when he said that typing is not the bottleneck is that programming is mostly about thinking. You might spend two hours thinking and ten minutes typing it in. Taking eleven minutes to type it in isn&#8217;t going to seriously change the timeline.</p><p>However, when I&#8217;m doing RDD, the speed I can type a piece of code, eval it, and understand its effect is part of the thinking process. With RDD, we extend our mental capacity with the REPL, just like a geometer extends their mental capacity with a compass and straightedge. The faster you can go through the loop, the better integrated your thinking and the REPL can be. Slowing down your typing can significantly affect that integration. And speeding it up with better configuration can help it, too.</p><p>And typing speed becomes more important the more iterative and incremental your process is. If you&#8217;re doing waterfall, your process is dominated by thinking, planning, design, and redesign. But if you&#8217;re working iteratively, you are typing a lot and learning from the result. The faster you type, the faster you learn.</p><h2>Customization is not shareable</h2><p>Well, this one I think is still huge. But as Juan Monetta opined on our <a href="https://youtube.com/live/a-PrBjlBdw8?feature=share">recent episode of Apropos</a>, that is much less important when we&#8217;re all programming on our own machines. We share a lot of development tooling: Linter configs, formatter configs, and tests. But do we need to share editor configurations? I&#8217;ve heard it both ways. I&#8217;ve heard people who love pairing and process argue that the team should be discovering as a group the best way to work as a team&#8212;meaning they should use the same editor configuration. But I&#8217;ve also worked at places that took an individualistic approach, so much so that the variance of productivity and understanding was huge. Some people knew how to run the code at the REPL, and others thought it was impossible. More on this later.</p><p>Now that we see that it&#8217;s not about speed but about cognitive resources, what are some arguments for configuration?</p><h2>Energy and joy</h2><p>Let&#8217;s say you open up a drawer in your kitchen. It&#8217;s filled with kitchen tools, but it&#8217;s a cluttered mess. It&#8217;s frustrating. You dig around, find the spoon you need, and close it. You get on with your cooking task. Or you could stop what you&#8217;re doing and organize the drawer. Toss out broken tools. Create sections and sort the tools that you still use. How do you feel afterwards? I bet you feel energized. The frustration has turned to joy.</p><p>The fact is, feeling control over your environment is important psychologically. That frustration is a signal that you want something to change. Ignoring that signal takes energy. Resigning yourself to live with frustration is a sure path to despair and burnout.</p><p>Energy is important. You can&#8217;t measure everything based on time efficiency. Time efficiency could be important, but it&#8217;s not everything. A programmer&#8217;s good energy improves their agency and their productivity. If a programmer feels like they can&#8217;t improve their environment, they&#8217;re more likely to zone out at work. You might as well use that zone-out time to make some improvements.</p><h2>Cognitive resources are a bottleneck</h2><p>My friend was correct that thinking is the bottleneck. Each of us is endowed with a certain amount of &#8220;cognitive resources&#8221; to work with. Research shows that this is a limited and shared resource&#8212;there&#8217;s only one pool of it. So if your roof is leaking and you&#8217;re worried about it, that will affect your programming at work. The bottleneck is the cognitive resource pool.</p><p>Your resources are also drained by tasks that take effort. Imagine if you had to search for the <code>{</code> key on the keyboard each time you needed to type it. Sure, it would slow you down. But worse than the slowdown is that you would use up precious cognitive resources. You would have fewer resources to think about the problem. If it took you long enough, you might even forget what you were trying to do. In short, it&#8217;s not slow typing that is the problem, but typing that takes effort.</p><p>How many of your subskills take effort because you haven&#8217;t mastered them yet? Maybe you can type <code>{</code> without thinking about it. But what about figuring out which braces to close? An easy solution is to use paredit&#8212;but that requires configuration and learning new keystrokes. Well worth it in my opinion. But what other packages could unlock similar improvements?</p><h2>Experimentation and waste</h2><p>I try to read a lot of books on programming. Learning from others is a great way to get better. Right now, I&#8217;m focused on books on software design. Unfortunately, most of the books on software design have bad advice. Does that mean it&#8217;s a waste of time to even look? Hour-for-hour, I don&#8217;t think it&#8217;s worth it. My software design skills could not have improved by that much. I have spent thousands of hours over my career reading programming advice. But it probably has not led me to ship everything thousands of hours faster (even in aggregate).</p><p>But how can we know? I certainly feel like the time spent reading was worth it, even considering that a lot of the advice was bad. When I look at my skill vs someone who doesn&#8217;t read, I can see the difference. When I look at my skill vs myself 10 years ago, having read fewer books, I can see the difference. But I can&#8217;t attribute any improvement to any particular piece of advice.</p><p>While you&#8217;re reading, you are absorbing other people&#8217;s experience. You&#8217;re organizing your own experiences. Even when you read something you don&#8217;t agree with, it could give you a new perspective on that idea. Reading alongside direct experience helps you learn.</p><p>Likewise, when I see someone building their fifth custom keyboard, it feels like a waste of time. Do you really think you&#8217;re going to get that much better this time? But they think it&#8217;s possible. And even if the keyboard doesn&#8217;t make them better, there&#8217;s something else going on: Engagement in the process. Building your own keyboard is a physical way to engage your brain. What keystrokes are important to my process? How do I want that key to feel when I press it? What fingers do I want to use? The learning transfers to configuration and typing.</p><h2>You cannot program 100% of the time</h2><p>Another misconception is that configuration time is eating away at programming time. If you&#8217;d give up that time spent configuring, you&#8217;d have more time to program. But that&#8217;s not how people work. We need breaks from the productive work. The productive work makes a mess, and we need to clean up. The productive work gives us ideas for doing our work better, and we can use those ideas to improve our work. </p><h2>Permission to make small improvements</h2><p>If you want to engender resentment and passivity in your programmers, tell them they can&#8217;t configure their tools. They&#8217;ll probably say they agree with you that it&#8217;s a waste of time. But you&#8217;ve also tapped into the efficiency-seeking part of the brain. That efficiency-seeking part of the brain isn&#8217;t looking to get more done with less. No, it&#8217;s about doing as little as they can get away with.</p><p>If you want to activate someone&#8217;s brain to look for ways to improve, encourage (or require!) them to make small changes daily. Some companies require each employee to make a change that could save them 2 seconds. Setting the bar low makes it achievable. But it also gets the mental balls rolling. With each small change, they develop a sense of ownership over their process and that engenders agency&#8212;which is probably what you want in an employee.</p><h2>Improvements compound</h2><p>Some tweak to your process might seem small. But over time, the work with each other to improve non-linearly.</p><h2>Working at the right cognitive level</h2><p>Again, typing speed is not the bottleneck, but thought is. If you are moving a cursor around, backspacing, and typing, you&#8217;re thinking about the cursor and characters. Another person, however, could be issuing higher-level commands, like rename symbol, if they had the right configuration and practice with the keystrokes. There is less distance between what they want done and the command to do it. It&#8217;s not about speed. It&#8217;s about the cognitive processing it takes to translate the desired action into editor commands.</p><h2>Improvements reveal underlying problems</h2><p>As workers at Toyota eliminated inventory between steps, the assembly line would start to break. They would say: &#8220;We told you that we need that inventory. Otherwise the process doesn&#8217;t run smoothly.&#8221;</p><p>Taiichi Ohno didn&#8217;t agree: &#8220;We are lowering the level of the water in the river (the inventory) to reveal the stones (the problems). The stones were always there, we just couldn&#8217;t see them.&#8221;</p><p>Taiichi Ohno knew that inventory tends to pile up before the bottleneck. Since the bottleneck is the slowest step in the process, the fast steps before it produce more than the bottleneck can handle. But when he looked around, there was inventory everywhere. By eliminating the easy piles of inventory, he could see where the real problems were.</p><p>We can do that in our tools, too. Our tools are full of frustration. We can start anywhere, fixing problems as we see them. This will make the process smoother up to a point. But the better our configuration is, the easier it is to see the parts that still frustrate us. Focusing on the bottleneck is good in theory, but it&#8217;s hard to recognize the bottleneck when everything feels frustrating.</p><h2>Fixing our frustrations leads to new projects</h2><p>If we don&#8217;t fix our frustrations, we have a tendency to become blind to them. We get used to them and work around them. But these frustrations can be very valuable. Frustration with existing debugging approaches (like <code>println</code>) fueled the development of FlowStorm. And Tailwind CSS&#8217;s creator Adam Wathan credits his sensitivity to frustration for the development of Tailwind. If you&#8217;re feeling frustrated, chances are, other programmers do, too. Your solution could be valuable to them.</p><h2>Discontinuous improvement</h2><p>There&#8217;s a phrase &#8220;continuous improvement&#8221;, which sounds great. You&#8217;re making improvements all the time. But I also think there&#8217;s something like discontinuous improvement, too. You see, if our cognitive resources are the bottleneck, there&#8217;s a discontinuity at the edge of our cognitive capacity. Jobs just bigger than our capacity need some kind of process and externalization (like a todo list) to manage it. But jobs small enough to fit in our minds don&#8217;t. If you can cross that boundary with better skills and tooling, you&#8217;ve got a stepwise improvement.</p><h2>Aesthetics are important</h2><p>When I see people geeking out over their keyboards, it&#8217;s not just about function. People choose colors and lights and other design elements that please them. The same for a lovely shell prompt or a nice font in their editor. The aesthetics of your environment plays a noticeable role in your productivity. If your office is a dump, you&#8217;ll probably make trash software. But if you surround yourself with beautiful things, you&#8217;ll be inspired to do beautiful work.</p><p>Now let&#8217;s talk about some principles that can help make the most of this learning.</p><h2>Manage resources</h2><p>The most important principle is that we are trying to manage limited cognitive resources. You can:</p><ul><li><p>Make a tool more pleasant so that it gives you more resources (e.g., make it have nice UX)</p></li><li><p>Use tools that operate with the right concepts (e.g., paredit)</p></li><li><p>Learn a skill better so that it takes fewer resources to perform (e.g., practicing paredit commands so they are muscle memory)</p></li></ul><h2>Choose your battles</h2><p>Alan Kay talks about the problem with a good engineer is they are so dissatisfied with everything that they want to build it all themselves. It takes great discernment to choose what you build on top of. Likewise, improvements do have a cost. Are you really going to hand-craft your keyboard? Might you want to try one of the existing commercial ones first? It might get you 99% of the way there.</p><h2>Configure away, but use standard keystrokes</h2><p>Emacs has a set of keystrokes that come standard out of the box. They have been the same for decades. <code>C-a</code> goes to the beginning of the line. <code>C-e</code> goes to the end. <code>C-x-f</code> opens a file. And, believe it or not, there are many packages for opening files. So while you can change the way you open files, most packages keep the same keystroke. Even though the behavior is different, someone familiar with the keystrokes can still use it. I recommend that approach.</p><h2>Share when you can</h2><p>I&#8217;m not as extreme as my friend who wanted everyone at the company to program with the same tools. But I also think most companies don&#8217;t encourage enough sharing of tooling and process. Pairing and watching people work can help spread the great tools and skills that people have set up. But you can also just talk about it. Have a monthly meeting where someone shows off their editor.</p><h2>AI can help you configure</h2><p>I don&#8217;t use AI to generate production code. But I&#8217;m leaning on AI now to help me configure my tools. Even though I&#8217;ve used Emacs for years, I&#8217;m terrible at programming it. I have to admit that it&#8217;s one of the reasons I haven&#8217;t done more configuration. Likewise for packages. There are just so many out there. I don&#8217;t have the patience to try them all. But with AI, I can ask for package recommendations and for tips to configure it how I like it. It&#8217;s really helping.</p>]]></content:encoded></item><item><title><![CDATA[Models are messy]]></title><description><![CDATA[And that's how they should be]]></description><link>https://ericnormand.substack.com/p/models-are-messy</link><guid isPermaLink="false">https://ericnormand.substack.com/p/models-are-messy</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Thu, 01 May 2025 22:30:23 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d1717c1c-11a5-422a-8245-488a22fbccb2_1024x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our <a href="https://youtube.com/live/_g69GKN6lAM?feature=share">last episode</a> was with Fogus. It was a great episode where we learn from his experience as a core contributor. The <a href="https://www.youtube.com/live/a-PrBjlBdw8">next episode</a> is on Tuesday, May 6 with special guest JP Monetta. Please watch us live so you can ask questions in the chat.</p><p>If you want to learn Clojure, there&#8217;s no better way than <em><a href="https://ericnormand.podia.com/beginning-clojure">Beginner Clojure</a></em>, my signature course for those new to the language. I&#8217;ve recently rebuilt the introduction module to teach better, so there&#8217;s never been a better time to buy. The point of that module is to give you a deep experience with Clojure (and FP+Data-Driven Programming) as quickly as possible.</p><div><hr></div><h1>Models are messy</h1><p>I am not a Platonic Idealist. I do not believe that the round table I&#8217;m working at is some pale shadow of the ideal circle. At best, circle is a mathematical abstraction that&#8217;s easy to define and analyze which happens to approximate the real table well enough to be useful. It is the abstract circle that is the pale shadow of the real table. Circles are just ideas.</p><p>So I guess that makes me a Materialist. But as I write this, I realize how much of a process it was for me to arrive at that conclusion. And even describing it took some work. And by learning that about myself, I realized a profound truth: Many people are idealists unconsciously.</p><p>One problem is that materialism is often conflated with anti-intellectualism. If the material world is primary, maybe it calls into question studying the idealized world of geometry and algebra. Maybe math isn&#8217;t important. But, no, it doesn&#8217;t. Materialism acknowledges that geometry and algebra are simply useful mental tools&#8212;not the ultimate essence or the source of all truth.</p><p>I sometimes wonder, as one does, about the marriage between empirical experiment and mathematical abstraction that has led to the recent explosion of scientific understanding. Why is it that mathematics has been so fruitful at describing the world we discover through experiments? Mustn&#8217;t mathematics be a fundamental part of the universe? Is mathematics discovered? Or is it invented by us?</p><p>Those are fun questions, but I&#8217;m not going to explore them deeply here. I will simply say that it was Buckminster Fuller who got me thinking about all of this stuff. He said there are no zero-dimensional points in the real-world. There are no one-dimensional lines. There are no two-dimensional planes. Everything we can experience is three-dimensional. So he didn&#8217;t base his geometry on points and lines and planes. Instead, he always used real objects to explore the nature of space. I still think about that. He went back to first principles and built up an understanding based on empirical exploration instead of abstractions.</p><p>The world is messy. Everything is complicated. When I talk about creating a domain model, one of the common pushbacks I get is from people who worry I&#8217;m advocating for creating an over-idealized model. One that sounds good but misses all the rich detail of the messy world.</p><p>Nothing is further from the truth. In fact, your domain model should enable all of the messy details that you need to capture. The level of abstraction most mathematics achieves is too clean. However, abstraction is the basis for how software works. Software for managing a farm does not operate on real sheep. Yet the sheep are represented.</p><p>No, what I&#8217;m suggesting is that we must represent the structures we encounter in the world in the software. The structure is often messy. But there is often a deeper, underlying order to it, as in my example of <a href="https://ericnormand.substack.com/p/messy-domains-have-a-core">combining simple Boolean predicates</a> to build up a representation of the messy voting laws.</p><p>Many real world &#8220;laws&#8221; are actually rules that apply most of the time but they have a number of exceptions. That itself is the structure we must capture&#8212;general rules with exceptions. Many real-world processes are <em>mostly</em> continuous&#8212;they&#8217;re continuous with a known discontinuity. That&#8217;s fine. Those discontinuities can often be handled at a higher level. It&#8217;s kind of the complement of finding order at a lower level. Move the messiness to a higher level.</p><p>It reminds me a lot of the way I recommend people deal with algebraic properties in property-based testing. People often get caught up looking for extremely regular properties, like the commutativity of addition. </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;a+b=b+a&quot;,&quot;id&quot;:&quot;ZKBFVFLIAZ&quot;}" data-component-name="LatexBlockToDOM"></div><p>What I recommend is using the formula of commutativity as a starting point&#8212;a kind of inspiration. </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;f(x,y)=f(y,x)&quot;,&quot;id&quot;:&quot;CFNWMBBOMO&quot;}" data-component-name="LatexBlockToDOM"></div><p>Ask about whether the order of arguments matter, how they matter, when they matter. You could find some rich patterns there that are worth capturing as properties for your domain.</p><p>For instance, <code>merge</code> for maps is commutative <em>if</em> you know the two maps don&#8217;t share any keys. So you start with the basic formula and you adorn it with the real-world detail you&#8217;re looking for.</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;keys(a)\\cap keys(b) = \\emptyset \\rightarrow \nmerge(a, b) = merge(b, a)&quot;,&quot;id&quot;:&quot;VWBAQUTZYF&quot;}" data-component-name="LatexBlockToDOM"></div><p>Likewise, the order of checks arriving at the bank doesn&#8217;t matter&#8212;as long as you have enough balance for all checks to clear:</p><p>let <em>bill</em> be the $x check to for the power bill (debit to your account)<br>let <em>paycheck</em> be the $y weekly paycheck (credit to your account)</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;account.balance \\ge bill \\rightarrow\naccount.deposit(paycheck).withdraw(bill) = account.withdraw(bill).deposit(paycheck)&quot;,&quot;id&quot;:&quot;OXRLJSZZSK&quot;}" data-component-name="LatexBlockToDOM"></div><p>That is, if you&#8217;ve got the money to pay the bill, it doesn&#8217;t matter which order the checks are cleared. But if you don&#8217;t have the money and the bill clears first, you might get an overdraft fee.</p><p>Remember, commutativity is just a nice idea that corresponds to some things we see in the world. We can adapt it, like any mathematical notion, to make it useful to our purposes. Putting the simpler definition of commutativity ahead of what we encounter in the real world is latent Platonic idealism. We have to reject that idealism. The real world is where the action happens. And its rich detail is what makes it interesting enough to model in software. When you&#8217;re building a model, avoid the temptation to make a perfect gem. Instead, aim to honor the imperfections. But don&#8217;t be an anti-intellectual and reject modeling outright. Modeling is what enables the magic we wield. So are you an idealist of a materialist&#8212;or an anti-intellectual?</p>]]></content:encoded></item><item><title><![CDATA[Small modular parts]]></title><description><![CDATA[The key to habitability?]]></description><link>https://ericnormand.substack.com/p/small-modular-parts</link><guid isPermaLink="false">https://ericnormand.substack.com/p/small-modular-parts</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Tue, 22 Apr 2025 10:01:06 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3b98aaeb-4d93-47e6-9f11-bcb67905bf4e_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our <a href="https://www.youtube.com/watch?v=52lGEPWA2WU">last episode</a> was with David Nolen. We talk about his development process, his origin, and his philosophy. The <a href="https://youtube.com/live/_g69GKN6lAM?feature=share">next episode</a> is on Tuesday, April 22 with special guest Fogus. Please watch us live so you can ask questions.</p><p>I have finally released the new version of <em>Introduction to Clojure</em>, my flagship module in <a href="https://ericnormand.podia.com/">Beginner Clojure</a>, my signature video course. This update is long overdue, but it makes up for its tardiness with fully updated content, modernized for current idioms and better teaching.</p><p>If you have the previous edition, you can already find the new edition in <a href="https://ericnormand.podia.com/">your dashboard</a>. You get the upgrade for free as my thanks for being a part of this crazy journey. </p><p>If you buy <a href="https://ericnormand.podia.com/">Beginner Clojure</a> now, you&#8217;ll also get the new version. Because it&#8217;s such a major upgrade, I&#8217;m going to raise the prices soon. If you want it, now is the time to buy. It will never be this cheap again.</p><div><hr></div><h1>Small modular parts</h1><p>I&#8217;ve been seeping in the rich conceptual stews of <em>Patterns of Software</em>. In it, Richard Gabriel explores how the ideas of Christopher Alexander apply to software engineering (long before the GoF <em>Design Patterns</em> book). One of the early ideas in the book is that of <em>habitability</em>, the characteristic of a building to support human life. Architecture needs to provide the kinds of spaces humans need, and also be adaptable to changing life circumstances. A house must support time together, time alone, and time alone together (sharing a space but doing different things). But it also must allow adding an extra bedroom as your family grows.</p><p>Habitable software is analogous. Programmers live in the code. They must feel comfortable navigating around, finding what they need, and making changes as requirements change. Christopher Alexander says that it is impossible to create truly living structures out of modular parts. They simply don&#8217;t adapt enough to the circumstances.</p><p>However, we know that&#8217;s not entirely true. Bricks are modular parts, and many of the living examples he gives are buildings made of bricks. It must be that the modules need to be small enough to permit habitability. You can&#8217;t adjust the size of a wall if the wall is prefabricated. But you can adjust the size of the wall if the bricks are prefabricated to a resolution that is just right.</p><p>This is true in software as well. Large modules are not as reusable as small ones. Take classical Java. I think the language gets the size of the abstractions wrong. The affordances of the language are the for/if/&#8230; statements, arithmetic expressions, and method calls, plus a way to compose those up into a new class. It goes from the lowest level (basically C) to a very high level, with very little in-between.</p><p>Contrast that with Clojure, which gives you many general-purpose abstractions at a higher level than Java (map/filter/reduce, first-class functions, generic data structures), and then almost nothing above it. Just the humble function to parameterize and name a thing. Lambda calculus (basically first-class functions) goes a long way. Java&#8217;s methods and classes give you a way to build procedural abstractions over data storage and algorithms, but the language offers torturous facilities for control flow abstractions. First-class functions can abstract control flow. I think Clojure got the level right.</p><p>Except maybe Clojure&#8217;s standard library overdoes it. I&#8217;m a big fan of map/filter/reduce. You can do a lot with them. But then there are others. For instance, there&#8217;s <code>keep</code>, which is like `map` but it rejects `nil`s. And there&#8217;s `remove`, which is the opposite of `filter`. Any call to keep could be rewritten:</p><pre><code>(keep {:a 1 :b 2 :c 3} [:a :b :c :d])

(filter some? (map {:a 1 :b 2 :c 3} [:a :b :c :d]))</code></pre><p>Those two are equivalent. `remove` can also be rewritten:</p><pre><code>(remove #{:a :b :c} [:a :b :c :d])

(filter (comp #{:a :b :c}) [:a :b :c :d])</code></pre><p>I do use keep and remove sometimes, when I think about them. But how much do they really add? Is the cost of learning these worth it? How often do you have to switch it back to map and filter anyway to make the change you want?</p><p>Here&#8217;s what I think: keep is just slightly too big. It&#8217;s a modular part that does just a tad too much. map is like a standard brick. keep is like an L-shaped brick that&#8217;s only useful at the end of a wall or on a corner. Useful but not that useful, and certainly not necessary. The same is true of remove. It&#8217;s not useful enough.</p><p>I think Clojure did a remarkably good job of finding the right size of module. They feel human-scale, ready for composition in an understandable way. It makes programs of medium to large size feel more habitable. I see this about what little Smalltalk code I&#8217;ve read: Smalltalk&#8217;s classes are small, highly general modular units, like Point and Rectangle, not UserPictureDrawingManager.</p><p>One aspect of habitability is <em>maintainability</em>&#8212;the de moda holy grail of software design. Back in 1996, when Patterns of Software was published, Gabriel felt the need to argue against <em>efficiency</em> as the reason for software design. Somewhere between <em>efficiency</em>&#8217;s reign and today&#8217;s <em>maintainability</em>, <em>code size</em> and then <em>complexity</em> ruled.</p><p>Long-time readers may guess where I&#8217;m going: These characteristics all focus on the code. Abstraction gets talked about in terms of its (excuse me) abstract qualities. An abstraction is too big or small, too high- or low-level, too shallow or deep. At best, we&#8217;re talking about something measurable in the code, at worst, some mental structures only in the mind of the guru designer who talks about it.</p><p>I want to posit <em>domain fit</em> as a better measure&#8212;one that leads to habitability&#8212;and that is also objective. Domain fit asks: &#8220;How good is the mapping between what your code represents and the meanings available in the domain?&#8221; That mapping goes both ways. We ask both &#8220;How easily can I <em>express</em> a domain situation in the code?&#8221; and &#8220;How easily does the code <em>express</em> the domain situation it represents?&#8221; Fit covers both directions of expressivity.</p><p>I believe that domain <em>mis</em>fit causes the most difficulties for code habitability. If your code doesn&#8217;t fit well with the domain, you&#8217;ll need many workarounds. Using reusable modules is only a problem because they don&#8217;t adapt well to the needs of your domain&#8212;not because they&#8217;re too big. It just so happens that bigger modules are harder to adapt. It&#8217;s not that a wall module is bad, per se, just that it&#8217;s almost never exactly the right size, and so you make compromises.</p><p>It&#8217;s not that the components in Clojure are the right size. It&#8217;s that Clojure&#8217;s domain&#8212;data-oriented programming&#8212;is the right size for many problems. It allows you, the programmer, to compose a solution out of parts&#8212;like bricks in a wall. And Clojure&#8217;s code fits the domain very well. Tangentially: It makes me wonder what the domain of Java is. I guess what I&#8217;m saying is that using a vector graphics API to do raster graphics is going to feel uninhabitable. But you can&#8217;t say it&#8217;s because vector graphics is a bigger abstraction than raster. It&#8217;s more about having the right model.</p><p>Now, Alexander might disagree that a pre-fab wall of exactly the right size is okay. He believes that there&#8217;s something in the handmadeness of things, too. It&#8217;s not just that the wall is the wrong or right size. Even if it were perfect, the perfection itself doesn&#8217;t lend itself to beauty. Geometrically precisely laid tiles cross some threshold where you don&#8217;t feel comfortable anymore. Ragged symmetry is better. We want bricks but they shouldn&#8217;t be platonic prisms.</p><p>So this is where I conclude and tease the next issue. I started this essay thinking size was important. I thought that Clojure got it right by finding a size of composable module that was a sweet spot. But now, I think it&#8217;s not about size. I don&#8217;t even know what size means anymore. It&#8217;s more about domain fit than ever. Perhaps I&#8217;m digging in deeper to my own biases (and please, I&#8217;m relying on you the reader to help me realize if I am). But this is what my reading is leading me to&#8212;the importance of building a domain model. When we talk about domain models, we often think of these jewel-like abstractions with perfect geometry. But this is a pipe dream. Our domains are too messy for that. In the next issue, I want to explore the dichotomy of geometric and organic adaptation.</p>]]></content:encoded></item><item><title><![CDATA[Skyscrapers or mud huts]]></title><description><![CDATA[On habitability and the Curse of Lisp]]></description><link>https://ericnormand.substack.com/p/skyscrapers-or-mud-huts</link><guid isPermaLink="false">https://ericnormand.substack.com/p/skyscrapers-or-mud-huts</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Wed, 16 Apr 2025 17:17:20 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/605b42af-6521-4871-ac0b-c46f138fc9d6_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our <a href="https://www.youtube.com/watch?v=52lGEPWA2WU">last episode</a> was with David Nolen. We talk about his development process, his origin, and his philosophy. The <a href="https://youtube.com/live/_g69GKN6lAM?feature=share">next episode</a> is on Tuesday, April 22 with special guest Fogus. Please watch us live so you can ask questions.</p><p>Have you seen <em><a href="https://ericnormand.me/gs">Grokking Simplicity</a></em>, my book for beginners to functional programming? Please check it out or recommend it to a friend. You can also <a href="https://ericnormand.me/gsm">get it from Manning</a>. Use coupon code TSSIMPLICITY for 50% off.</p><div><hr></div><h1>Skyscrapers or mud huts</h1><p>From 2005-2007, I lived in the West African country of Guinea. I loved the people, but their country had a sad story. After rejecting their French colonizers, they made a series of bad deals with other countries. While they started out as the hope of West Africa due to their wealth of natural resources, after 45 years of kleptocracy, the infrastructure had decayed. While Guinea&#8217;s neighbors saw increasing prosperity, theirs was declining.</p><p>One of the symptoms of their decline was that they were de-urbanizing. In other countries in the region, people were leaving the village, looking for a better life in the cities. But in Guinea, apparently the better life was in the village.</p><p>I would often ponder this on my infrequent trips to the capital, Conakry. Large portions of the streets were broken or missing, revealing mud that could swallow a small car during the rainy season. Utility poles were a rats-nest of spliced wires. Probably most of the wires were dead. Roofs leaked. Many inhabited ten-story highrises had no electricity or running water.</p><p>Contrast this to the village where I lived. Yes, they were poor, but I never saw squalor. What I came to discover was that the village was simply easier to maintain by people with few resources. You could make a house out of mud bricks and thatch it with straw you harvested from your field. If the roof leaked, you could climb up and patch it. If a wall crumbled, you could repair it.</p><p>In the city, the tall buildings require a huge number of resources to maintain. You need cranes and bulldozers and other diesel machines. You need cement and iron forges and other high-input materials. You need a functional supply chain to get the diesel and other inputs to you. And you need the highly skilled labor to do the repairs. Don&#8217;t forget a functioning economy to pay for those workers. In short, modern urban life, which is very attractive when it works, is hard to maintain.</p><p>Even if you aren&#8217;t talking about repairs, the cost of keeping the building working is expensive. You need to pump water up to the top floors. If the building is tall enough, you need to pump clean air up there as well. And you need to deal with all of the waste products of life. Things have to keep working or the top floors aren&#8217;t usable anymore.</p><p>When programming, sometimes I wonder if our high-power tools, like Clojure, cause us a similar issue. One person can create a wondrous indirection mechanism that solves the problem at hand. It&#8217;s like erecting a skyscraper. Sure, it might be easy to write the code&#8212;easier than construction. But there is a certain mental cost. The complexity of understanding it requires the mental equivalent of cranes.</p><blockquote><p>Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it? &#8212; Brian Kernighan</p></blockquote><p>This quote from Kernighan (though I believe the sentiment comes from before his time). It expresses succinctly the idea I&#8217;m trying to convey. Clojure gives us the power to build things that are too clever to debug, just like a surge of political will can create a skyscraper that you don&#8217;t have the ability to maintain. Is maintaining a skyscraper twice as hard as building it?</p><p>I&#8217;ve worked on codebases where the cleverness was too much to maintain. The history of the project was a super smart programmer being brought onto the team to solve a tough problem. They built a brilliant solution. Then they left to use their brain somewhere more worthy of their mental capacity. The team enjoyed their skyscraper while it slowly decayed.</p><p>Richard Gabriel wrote a wonderful essay in <em><a href="https://www.dreamsongs.com/Files/PatternsOfSoftware.pdf">Patterns of Software</a></em> about the habitability of our code. In it, he argues that <em>habitability</em> is the important quality of good software. Programmers work inside the code, making adjustments, improvements, and expansions. We need to feel at home there&#8212;and like we can take care of it ourselves.</p><blockquote><p>What is important is that it be easy for programmers to come up to speed with the code, to be able to navigate through it effectively, to be able to understand what changes to make, and to be able to make them safely and correctly. If the beauty of the code gets in the way, the program is not well written, just as an office building designed to win design awards is not well designed when the building later must undergo changes but those changes are too hard to make. &#8212; Richard Gabriel</p></blockquote><p>A skyscraper has low habitability. It&#8217;s hard to adapt it to new purposes. It&#8217;s hard to adapt it as your needs change. But a mud hut is easy to change and expand. Our software needs that because we can&#8217;t plan it out ahead of time.</p><p>The genius&#8217;s plan for the software might have been good. But it was a master plan that only they could understand. They were the smartest programmer at the company and they used all of their capacity. How could anyone else keep up? Further, you can never fully communicate the &#8220;theory&#8221; behind the code. Just like Peter Naur recounted in <em><a href="https://pages.cs.wisc.edu/~remzi/Naur.pdf">Programming as Theory Building</a></em>, it&#8217;s not just the raw intelligence. There&#8217;s also quite a lot of knowledge for how to build and extend the program:</p><blockquote><p>In several major cases it turned out that the solutions suggested by group B [the extenders of the software] were found by group A [the original builders] to make no use of the facilities that were not only inherent in the structure of the existing compiler but were discussed at length in its documentation, and to be based instead on additions to that structure in the form of patches that effectively destroyed its power and simplicity. The members of group A were able to spot these cases instantly and could propose simple and effective solutions, framed entirely within the ex1 isting structure. This is an example of how the full program text and additional documentation is insufficient in conveying to even the highly motivated group B the deeper insight into the design, that theory which is immediately present to the members of group A. &#8212; Peter Naur</p></blockquote><p>You have to learn to live in a skyscraper. And you have to learn to extend the software.</p><p>So we&#8217;ve got this triple whammy:</p><ol><li><p>The mental capacity of the original &#8220;hero&#8221; developer.</p></li><li><p>The knowledge and plan they build while developing it (the &#8220;theory&#8221;).</p></li><li><p>The high-leverage Clojure programming language.</p></li></ol><p>But here&#8217;s the thing: We can&#8217;t consider the power of Clojure to be a bad thing. That can&#8217;t make sense. If it were true, we&#8217;d want to move toward a much poorer programming language, maybe all the way back to assembly. I can&#8217;t believe that. Neither can we consider intelligence bad. Where would that lead us?</p><p>It seems to me that we have a problem of balancing power with wisdom. What I mean by <em>wisdom</em> is the skill of using half of one&#8217;s mental capacity to build the original software so that you have enough intelligence to debug it. It&#8217;s the ability to see that our plan is too ambitious, we need to grow the software more organically. It&#8217;s the ability to keep the theory simple so you can teach it to the rest of the team. Software can get complicated. The only solution seems to be humility.</p>]]></content:encoded></item><item><title><![CDATA[Anti-entropic functions]]></title><description><![CDATA[Postel's Law for programming]]></description><link>https://ericnormand.substack.com/p/anti-entropic-functions</link><guid isPermaLink="false">https://ericnormand.substack.com/p/anti-entropic-functions</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Tue, 08 Apr 2025 10:02:26 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a23cf570-d23f-430a-b0d0-3d560d733bbc_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our last Apropos was with <a href="https://www.youtube.com/watch?v=cDfmpe_w_3I">Bobbi</a>. Check it out. Our <a href="https://www.youtube.com/watch?v=52lGEPWA2WU">next episode</a> is with David Nolen on Tuesday April 8. Please watch us live so you can ask questions.</p><p>Have you seen <em><a href="https://ericnormand.me/gs">Grokking Simplicity</a></em>, my book for beginners to functional programming? Please check it out or recommend it to a friend. You can also <a href="https://ericnormand.me/gsm">get it from Manning</a>. Use coupon code TSSIMPLICITY for 50% off.</p><div><hr></div><h1>Anti-entropic functions</h1><p>There&#8217;s a rule in internet protocols called <a href="https://en.wikipedia.org/wiki/Robustness_principle">Postel&#8217;s Law</a>. It states that you should accept input liberally (doing your best to decipher faulty messages you receive) but you should be very strict in your output. If every node in a network follows this principle, then we would expect the network to operate more robustly than one that did not. The receiver&#8217;s flexibility allows even buggy implementations of the sender&#8217;s protocol to operate effectively.</p><p>There is an analogous principle in electronics. In electronic components, noise is a major concern. So you build modules to tolerate some noise on the input but to do their best to minimize noise on the output. If every component does this, the system will work better. Noise is a fact of life, but if every component aims to reduce it, then the system will work better.</p><p>I&#8217;m not a huge fan of Postel&#8217;s Law. I&#8217;ve had to work with some horrendous HTML as input to my software. It was only horrendous because the browser is so tolerant that it rendered fine visually. But if you tried to parse it with the intent of making sense of it, good luck. In short, tolerance of input allows all sorts of deviant behavior with no consequences.</p><p>But Postel&#8217;s Law does work for the user of the browser. Instead of showing just an error message, the browser shows something. The page is readable. The links are clickable. And the user is happy. It&#8217;s not nothing. If you imagine the world where browsers weren&#8217;t tolerant, one stray &lt; character could mess up the parse, which would cause the browser to crash, which would make the user lose their job, then they lose their house, then they&#8217;re living on the street. All because of a &lt;.</p><p>If Postel&#8217;s Law is about protocol errors and the electronics version is about noise, programmers need a law about complexity. Our systems are so complex. We complain about it all the time as the main source of difficulty. So I thought we could formulate a principle like this:</p><blockquote><p>Aim to reduce complexity at every step.</p></blockquote><p>We already do this a little bit. We receive text from an HTTP request and parse it as JSON. This vastly reduces the space of input by structuring it. Then we normalize the JSON into a form that we want to work with, often translating it to a different, less complex data structure, like say a <code>Document</code>. Then we call &#8220;accessors&#8221; on these data structures; for example, <code>getStatus()</code>. The accessors boil down the complex data structure we pass it to a small set of possible states.</p><p>If you combine it with the idea I discussed in <a href="https://ericnormand.substack.com/p/total-functions-and-beyond">my last issue</a>, that of extending the spectrum from partial function through total function to a function that is very forgiving of the input but manages to reduce the output to a small set of states.</p><p>In other words, they accept more complexity than needed and output as little complexity as needed. <code>getStatus()</code> takes a whole <code>Document</code>, which includes all the information relevant to status plus all the non-relevant information, and it spits out one of just four valid values for status. That&#8217;s a big reduction in complexity. A word for this might be <em>anti-entropic</em>.</p><p>When you&#8217;re writing a function, ask yourself: Does this function reduce complexity or add complexity? Does it make the problem easier to solve? Does it introduce other problems that will need to be solved?</p><p>For example, in <a href="https://ericnormand.me/podcast/how-variants-can-reduce-complexity">How variants can reduce complexity</a>, I talk about how returning different types from a function and attaching meaning to those types actually makes the problem harder. It&#8217;s too common in Clojure that we return a string or an integer from a function, with the idea that if it&#8217;s a string, it&#8217;s the URL of an external resource, but if it&#8217;s an integer, it&#8217;s the id of an entity from our database.</p><p>Returning different types forces any code that receives that value to interpret it. That means the code:</p><ol><li><p>Is coupled to the meanings of the types given elsewhere. Action at a distance.</p></li><li><p>Needs to add an if statement to do the interpretation. Added code complexity.</p></li></ol><p>Instead, aim to return the smallest structure that solves the problem. There are many options that are super dependent on context. But here are several distinct approaches. Here&#8217;s our original function:</p><pre><code>function getDocument(e: Person): string | number;</code></pre><p>One improvement would be to use more precise types:</p><pre><code>function getDocument(e: Person): URL | DocumentID</code></pre><p>That already looks much better. But we can improve it:</p><pre><code>type DocumentLocator = {
  locatorType: "url";
  url: URL;
} | {
  locatorType: "document-id";
  url: DocumentID;
};

function getDocument(e: Person): DocumentLocator;</code></pre><p>In this code, we&#8217;re defining a new type which captures the meaning we intend. You still have the if, but you don&#8217;t have the same kind of coupling.</p><p>But, more and more, I&#8217;m seeing the value of separating these two functions:</p><pre><code>function getURLDocument(e: Person): URL | null;
function getDBDocument(e: Person): DocumentID | null;</code></pre><p>You need an if statement to determine between them anyway. You might as well just ask for the one you want. I&#8217;ve added the <code>null</code> in there because in my imaginary domain, you have none or one or both.</p><p>A final approach is to forget the data altogether. Instead of returning a way to get a resource (by URL or by DB id), we return a function you call to get it:</p><pre><code>function getDocumentFetcher(e: Person): () =&gt; (Document | null);</code></pre><p>This one requires no ifs where it is used.</p><p>On the other hand, if you&#8217;re looking at an existing function and it&#8217;s a mess, the way to clean it up is to look for opportunities to introduce steps that reduce complexity. For instance, I once helped someone wrangle some really gnarly code. It was a big nested if statement, where each test was itself a complex boolean expression. We could have sat there and tried to detangle it, looking for some better way to express it.</p><p>Instead, I asked the person: What does this code do? After some back-and-forth, it was clear that the if statements were managing the states and transitions of a state machine. They were ushering a complex entity through a process. So we introduced a function that named what state the entity was in. While the if statements were still there, it detangled choosing what state you were in from choosing what to do, and that helped. The code had fewer nested ifs and the logic was clearer.</p><p>We could have attacked the if statements themselves head-on. I do that, too, sometimes. I do simple refactorings like eliminating double negatives, rearranging the tests to make them less nested, extracting a function, etc. That would be addressing the style. But in this case, we didn&#8217;t. The purpose was to understand what it was doing&#8212;to understand the substance. I believe that the substance (the domain model) of our code is a much richer source for finding and eliminating code complexity.</p><p>So when I&#8217;m talking about <a href="https://ericnormand.me/podcast/reduce-complexity-at-every-step">reducing complexity at every step</a>, I&#8217;m actually referring to model complexity. Reducing model complexity will reduce your code complexity. Code complexity is the biggest thing we complain about as programmers. We must look beyond the surface to bring clarity to the model&#8212;make the <a href="https://ericnormand.substack.com/p/expressivity-a-fourth-definition">code more expressive</a> of the model. And with this &#8220;principle&#8221; of reducing complexity at every step, we can ask ourselves not if the code in the body of a function is simple, but if the function returns something that is less complex than what it receives as arguments&#8212;and how perhaps it could reduce complexity even more. I want to believe that this could be a principle, but I&#8217;m jaded so I don&#8217;t trust it. What do you think? Do you see any holes in this idea? Is it yet another principle that only applies sometimes?</p>]]></content:encoded></item><item><title><![CDATA[Total functions and beyond]]></title><description><![CDATA[On continuous benefits and extending spectra]]></description><link>https://ericnormand.substack.com/p/total-functions-and-beyond</link><guid isPermaLink="false">https://ericnormand.substack.com/p/total-functions-and-beyond</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Tue, 25 Mar 2025 10:00:34 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/fe08e3a1-6592-4295-a8cd-b5d6cd011fd2_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our last Apropos was with <a href="https://www.youtube.com/live/vfYB-5Z7gts">Sean Corfield</a>. Check it out. Our <a href="https://youtube.com/live/vfYB-5Z7gts?feature=share">next episode</a> is with <a href="https://youtube.com/live/cDfmpe_w_3I?feature=share">Bobbi</a> on Tuesday March 25. Please watch us live so you can ask questions.</p><p>Have you seen <em><a href="https://ericnormand.me/gs">Grokking Simplicity</a></em>, my book for beginners to functional programming? Please check it out or recommend it to a friend. You can also <a href="https://ericnormand.me/gsm">get it from Manning</a>. Use coupon code TSSIMPLICITY for 50% off.</p><div><hr></div><h1>Total functions and beyond</h1><h2>What is a total function?</h2><p>A <em><a href="https://ericnormand.me/podcast/what-is-a-total-function">total function</a></em> returns a valid value for all valid argument values. The opposite is a <em>partial function</em>. These terms come from mathematics.</p><p>A classic example of a partial function is division. Let&#8217;s look at it from a programming perspective:</p><pre><code><code>function divide(a: number, b: number): number;</code></code></pre><p>This says that for any two numbers <code>a</code> and <code>b</code>, <code>divide()</code> will return a number. But this is a lie: If <code>b</code> is zero, there is no answer. It&#8217;s undefined in mathematical division.</p><p>In JavaScript (and TypeScript), division by zero is possible and well-defined:</p><pre><code><code> 1/0 =&gt; Infinity
-1/0 =&gt; -Infinity
 0/0 =&gt; NaN</code></code></pre><p>All these values are valid in the type <code>number</code>&#8212;but in my experience, they&#8217;re useless, so I don&#8217;t count them as valid return values. Some other languages don&#8217;t return such values. Instead, they throw an error:</p><pre><code><code>(/ 1 0) ; throws ArithmeticError "Divide by zero"</code></code></pre><p>Division is a partial function due to one lousy value that breaks the type signature, whether the function throws an exception or returns a useless value.</p><h2>Why are total functions important?</h2><p>I think total functions are the next important concept from functional programming that needs to escape to the mainstream. Total functions make our code more reliable and easier to reason about. With a total function, we don&#8217;t need to worry about edge cases or unexpected behavior. Every input has a well-defined output, so we won&#8217;t accidentally send weird inputs as we build on them.</p><h2>What if we saw the binary (partial/total) as a spectrum?</h2><p>I often try to turn a binary into a spectrum. I did that for pure functions in <em>Grokking Simplicity</em>. To make an impure function pure, convert the implicit inputs to parameters and include the implicit outputs in the return value. Zero implicit inputs and outputs means it&#8217;s a pure function.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rI9n!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe603ada-2661-4d4e-ba9d-3c43b3d0310c_357x88.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rI9n!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe603ada-2661-4d4e-ba9d-3c43b3d0310c_357x88.png 424w, https://substackcdn.com/image/fetch/$s_!rI9n!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe603ada-2661-4d4e-ba9d-3c43b3d0310c_357x88.png 848w, https://substackcdn.com/image/fetch/$s_!rI9n!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe603ada-2661-4d4e-ba9d-3c43b3d0310c_357x88.png 1272w, https://substackcdn.com/image/fetch/$s_!rI9n!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe603ada-2661-4d4e-ba9d-3c43b3d0310c_357x88.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rI9n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe603ada-2661-4d4e-ba9d-3c43b3d0310c_357x88.png" width="357" height="88" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/be603ada-2661-4d4e-ba9d-3c43b3d0310c_357x88.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:88,&quot;width&quot;:357,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:9351,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ericnormand.substack.com/i/159703945?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe603ada-2661-4d4e-ba9d-3c43b3d0310c_357x88.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rI9n!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe603ada-2661-4d4e-ba9d-3c43b3d0310c_357x88.png 424w, https://substackcdn.com/image/fetch/$s_!rI9n!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe603ada-2661-4d4e-ba9d-3c43b3d0310c_357x88.png 848w, https://substackcdn.com/image/fetch/$s_!rI9n!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe603ada-2661-4d4e-ba9d-3c43b3d0310c_357x88.png 1272w, https://substackcdn.com/image/fetch/$s_!rI9n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe603ada-2661-4d4e-ba9d-3c43b3d0310c_357x88.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Just because you can&#8217;t get the implicit inputs and outputs to zero, it doesn&#8217;t mean reducing them isn&#8217;t beneficial. If your function reads from 3 global mutable variables and you reduce that to 2 or 1, your function is better off, even without all the benefits of it being pure.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6cuZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e2cbfbf-68bd-4856-9840-58c69cf9df7f_742x152.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6cuZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e2cbfbf-68bd-4856-9840-58c69cf9df7f_742x152.png 424w, https://substackcdn.com/image/fetch/$s_!6cuZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e2cbfbf-68bd-4856-9840-58c69cf9df7f_742x152.png 848w, https://substackcdn.com/image/fetch/$s_!6cuZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e2cbfbf-68bd-4856-9840-58c69cf9df7f_742x152.png 1272w, https://substackcdn.com/image/fetch/$s_!6cuZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e2cbfbf-68bd-4856-9840-58c69cf9df7f_742x152.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6cuZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e2cbfbf-68bd-4856-9840-58c69cf9df7f_742x152.png" width="742" height="152" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5e2cbfbf-68bd-4856-9840-58c69cf9df7f_742x152.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:152,&quot;width&quot;:742,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:12639,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ericnormand.substack.com/i/159703945?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e2cbfbf-68bd-4856-9840-58c69cf9df7f_742x152.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6cuZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e2cbfbf-68bd-4856-9840-58c69cf9df7f_742x152.png 424w, https://substackcdn.com/image/fetch/$s_!6cuZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e2cbfbf-68bd-4856-9840-58c69cf9df7f_742x152.png 848w, https://substackcdn.com/image/fetch/$s_!6cuZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e2cbfbf-68bd-4856-9840-58c69cf9df7f_742x152.png 1272w, https://substackcdn.com/image/fetch/$s_!6cuZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e2cbfbf-68bd-4856-9840-58c69cf9df7f_742x152.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The benefits of being pure are important. A pure function can be tested easily. It can be easily combined, moved, and erased by the compiler. These benefits are discontinuous since you only get them when you become fully pure. But we often forget that the other benefits, like how easy it is to reason about the function, are continuous. Getting rid of each implicit input makes your function easier to understand.</p><p>To<a href="https://ericnormand.me/podcast/how-do-you-make-a-function-total"> make a partial function total</a>, we must return a correct value for all valid parameter combinations. But making one more valid parameter return a valid output helps. So let&#8217;s consider it a continuous spectrum.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!M8pY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0696b8cd-f633-4684-b788-d55a5a45970e_742x157.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!M8pY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0696b8cd-f633-4684-b788-d55a5a45970e_742x157.png 424w, https://substackcdn.com/image/fetch/$s_!M8pY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0696b8cd-f633-4684-b788-d55a5a45970e_742x157.png 848w, https://substackcdn.com/image/fetch/$s_!M8pY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0696b8cd-f633-4684-b788-d55a5a45970e_742x157.png 1272w, https://substackcdn.com/image/fetch/$s_!M8pY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0696b8cd-f633-4684-b788-d55a5a45970e_742x157.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!M8pY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0696b8cd-f633-4684-b788-d55a5a45970e_742x157.png" width="742" height="157" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0696b8cd-f633-4684-b788-d55a5a45970e_742x157.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:157,&quot;width&quot;:742,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:13750,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ericnormand.substack.com/i/159703945?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0696b8cd-f633-4684-b788-d55a5a45970e_742x157.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!M8pY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0696b8cd-f633-4684-b788-d55a5a45970e_742x157.png 424w, https://substackcdn.com/image/fetch/$s_!M8pY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0696b8cd-f633-4684-b788-d55a5a45970e_742x157.png 848w, https://substackcdn.com/image/fetch/$s_!M8pY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0696b8cd-f633-4684-b788-d55a5a45970e_742x157.png 1272w, https://substackcdn.com/image/fetch/$s_!M8pY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0696b8cd-f633-4684-b788-d55a5a45970e_742x157.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h2>What lies beyond total functions?</h2><p>Another question I ask is &#8220;What if I extend the trend?&#8221; Consider total function an interesting point on the line beyond which there are other interesting points. To the left of &#8220;total function&#8221;, there are various degrees of partiality, with more valid inputs causing errors. To the right, we could accept more &#8220;invalid&#8221; inputs (as long as we know how to handle them).</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HVg3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eeb385b-03e3-4465-9b5a-b3f82ec3567c_742x154.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HVg3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eeb385b-03e3-4465-9b5a-b3f82ec3567c_742x154.png 424w, https://substackcdn.com/image/fetch/$s_!HVg3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eeb385b-03e3-4465-9b5a-b3f82ec3567c_742x154.png 848w, https://substackcdn.com/image/fetch/$s_!HVg3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eeb385b-03e3-4465-9b5a-b3f82ec3567c_742x154.png 1272w, https://substackcdn.com/image/fetch/$s_!HVg3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eeb385b-03e3-4465-9b5a-b3f82ec3567c_742x154.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HVg3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eeb385b-03e3-4465-9b5a-b3f82ec3567c_742x154.png" width="742" height="154" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7eeb385b-03e3-4465-9b5a-b3f82ec3567c_742x154.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:154,&quot;width&quot;:742,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:15234,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ericnormand.substack.com/i/159703945?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eeb385b-03e3-4465-9b5a-b3f82ec3567c_742x154.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HVg3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eeb385b-03e3-4465-9b5a-b3f82ec3567c_742x154.png 424w, https://substackcdn.com/image/fetch/$s_!HVg3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eeb385b-03e3-4465-9b5a-b3f82ec3567c_742x154.png 848w, https://substackcdn.com/image/fetch/$s_!HVg3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eeb385b-03e3-4465-9b5a-b3f82ec3567c_742x154.png 1272w, https://substackcdn.com/image/fetch/$s_!HVg3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eeb385b-03e3-4465-9b5a-b3f82ec3567c_742x154.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>For example, let&#8217;s say we had a function called <code>parse-int</code>. </p><pre><code><code>;; String -&gt; Long (throws if can't parse)
(defn parse-int [s]
  (Long/parseLong s))</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!o-r8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11e1b87e-f639-4be4-bc77-ec0ebc882a95_742x183.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!o-r8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11e1b87e-f639-4be4-bc77-ec0ebc882a95_742x183.png 424w, https://substackcdn.com/image/fetch/$s_!o-r8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11e1b87e-f639-4be4-bc77-ec0ebc882a95_742x183.png 848w, https://substackcdn.com/image/fetch/$s_!o-r8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11e1b87e-f639-4be4-bc77-ec0ebc882a95_742x183.png 1272w, https://substackcdn.com/image/fetch/$s_!o-r8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11e1b87e-f639-4be4-bc77-ec0ebc882a95_742x183.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!o-r8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11e1b87e-f639-4be4-bc77-ec0ebc882a95_742x183.png" width="742" height="183" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/11e1b87e-f639-4be4-bc77-ec0ebc882a95_742x183.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:183,&quot;width&quot;:742,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:19498,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ericnormand.substack.com/i/159703945?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11e1b87e-f639-4be4-bc77-ec0ebc882a95_742x183.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!o-r8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11e1b87e-f639-4be4-bc77-ec0ebc882a95_742x183.png 424w, https://substackcdn.com/image/fetch/$s_!o-r8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11e1b87e-f639-4be4-bc77-ec0ebc882a95_742x183.png 848w, https://substackcdn.com/image/fetch/$s_!o-r8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11e1b87e-f639-4be4-bc77-ec0ebc882a95_742x183.png 1272w, https://substackcdn.com/image/fetch/$s_!o-r8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11e1b87e-f639-4be4-bc77-ec0ebc882a95_742x183.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>It&#8217;s partial. Not all strings can be parsed. We make it total by returning <code>nil</code> if there&#8217;s any trouble parsing:</p><pre><code><code>;; String -&gt; Long | nil
(defn parse-int [s]
  (try
    (Long/parseLong s)
    (catch Exception e 
      nil)))</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!t1Av!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92e6609e-1a5f-4f1e-b636-26c312e0dfa2_742x191.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!t1Av!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92e6609e-1a5f-4f1e-b636-26c312e0dfa2_742x191.png 424w, https://substackcdn.com/image/fetch/$s_!t1Av!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92e6609e-1a5f-4f1e-b636-26c312e0dfa2_742x191.png 848w, https://substackcdn.com/image/fetch/$s_!t1Av!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92e6609e-1a5f-4f1e-b636-26c312e0dfa2_742x191.png 1272w, https://substackcdn.com/image/fetch/$s_!t1Av!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92e6609e-1a5f-4f1e-b636-26c312e0dfa2_742x191.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!t1Av!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92e6609e-1a5f-4f1e-b636-26c312e0dfa2_742x191.png" width="742" height="191" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/92e6609e-1a5f-4f1e-b636-26c312e0dfa2_742x191.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:191,&quot;width&quot;:742,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:18649,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ericnormand.substack.com/i/159703945?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92e6609e-1a5f-4f1e-b636-26c312e0dfa2_742x191.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!t1Av!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92e6609e-1a5f-4f1e-b636-26c312e0dfa2_742x191.png 424w, https://substackcdn.com/image/fetch/$s_!t1Av!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92e6609e-1a5f-4f1e-b636-26c312e0dfa2_742x191.png 848w, https://substackcdn.com/image/fetch/$s_!t1Av!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92e6609e-1a5f-4f1e-b636-26c312e0dfa2_742x191.png 1272w, https://substackcdn.com/image/fetch/$s_!t1Av!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92e6609e-1a5f-4f1e-b636-26c312e0dfa2_742x191.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Great! It&#8217;s now total. But there&#8217;s more we can do. What about a string that&#8217;s almost parseable, like it has some whitespace at the beginning? Could we handle that? I can imagine software wanting that, so let&#8217;s pretend ours does and add it:</p><pre><code><code>;; String -&gt; Long | nil
(defn parse-int [s]
  (try
    (Long/parseLong (clojure.string/trim s))
    (catch Exception e 
      nil)))</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QnlU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe71d7d77-fe30-4ee0-b913-00a9dd68ccee_742x177.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QnlU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe71d7d77-fe30-4ee0-b913-00a9dd68ccee_742x177.png 424w, https://substackcdn.com/image/fetch/$s_!QnlU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe71d7d77-fe30-4ee0-b913-00a9dd68ccee_742x177.png 848w, https://substackcdn.com/image/fetch/$s_!QnlU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe71d7d77-fe30-4ee0-b913-00a9dd68ccee_742x177.png 1272w, https://substackcdn.com/image/fetch/$s_!QnlU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe71d7d77-fe30-4ee0-b913-00a9dd68ccee_742x177.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QnlU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe71d7d77-fe30-4ee0-b913-00a9dd68ccee_742x177.png" width="742" height="177" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e71d7d77-fe30-4ee0-b913-00a9dd68ccee_742x177.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:177,&quot;width&quot;:742,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:18626,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ericnormand.substack.com/i/159703945?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe71d7d77-fe30-4ee0-b913-00a9dd68ccee_742x177.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QnlU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe71d7d77-fe30-4ee0-b913-00a9dd68ccee_742x177.png 424w, https://substackcdn.com/image/fetch/$s_!QnlU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe71d7d77-fe30-4ee0-b913-00a9dd68ccee_742x177.png 848w, https://substackcdn.com/image/fetch/$s_!QnlU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe71d7d77-fe30-4ee0-b913-00a9dd68ccee_742x177.png 1272w, https://substackcdn.com/image/fetch/$s_!QnlU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe71d7d77-fe30-4ee0-b913-00a9dd68ccee_742x177.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>We could handle more &#8220;invalid&#8221; inputs to this function. We could call it <code>to-int</code> instead of <code>parse-int</code>. It would convert a value to a long. What values could we convert to a long?</p><ul><li><p>Strings</p></li><li><p>A Long itself (idempotent)</p></li><li><p>A Double</p></li></ul><pre><code><code>;; String | Long | Double -&gt; Long | nil
(defn to-int [v]
  (cond 
    (string? v)
    (try
      (Long/parseLong (clojure.string/trim s))
      (catch Exception e 
        nil))

    (int? v)
    v

    (double? v)
    (long v)))</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZVfC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb362758f-c733-4a32-9634-7370c324fa7e_742x177.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZVfC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb362758f-c733-4a32-9634-7370c324fa7e_742x177.png 424w, https://substackcdn.com/image/fetch/$s_!ZVfC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb362758f-c733-4a32-9634-7370c324fa7e_742x177.png 848w, https://substackcdn.com/image/fetch/$s_!ZVfC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb362758f-c733-4a32-9634-7370c324fa7e_742x177.png 1272w, https://substackcdn.com/image/fetch/$s_!ZVfC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb362758f-c733-4a32-9634-7370c324fa7e_742x177.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZVfC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb362758f-c733-4a32-9634-7370c324fa7e_742x177.png" width="742" height="177" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b362758f-c733-4a32-9634-7370c324fa7e_742x177.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:177,&quot;width&quot;:742,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:18592,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ericnormand.substack.com/i/159703945?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb362758f-c733-4a32-9634-7370c324fa7e_742x177.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZVfC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb362758f-c733-4a32-9634-7370c324fa7e_742x177.png 424w, https://substackcdn.com/image/fetch/$s_!ZVfC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb362758f-c733-4a32-9634-7370c324fa7e_742x177.png 848w, https://substackcdn.com/image/fetch/$s_!ZVfC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb362758f-c733-4a32-9634-7370c324fa7e_742x177.png 1272w, https://substackcdn.com/image/fetch/$s_!ZVfC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb362758f-c733-4a32-9634-7370c324fa7e_742x177.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>We could go further with it.</p><p>As an example of a real function that does this, check out <code>clojure.core/slurp.</code> Here&#8217;s a list of the kinds of inputs it will take:</p><ul><li><p>Reader</p></li><li><p>BufferedReader</p></li><li><p>InputStream</p></li><li><p>File</p></li><li><p>URI</p><ul><li><p>If it&#8217;s a <code>file://</code> URL, convert it to a File first</p></li></ul></li><li><p>URL</p></li><li><p>Socket</p></li><li><p>byte arrays</p></li><li><p>character arrays</p></li><li><p>String</p><ul><li><p>Try to parse it as a URL</p></li><li><p>Failing that, try to parse it as a local filename</p></li></ul></li></ul><p>I usually think this kind of function, which does a lot of magic to &#8220;do the right thing,&#8221; doesn&#8217;t have much place. I prefer to know the type I&#8217;ve got and call the right function to convert it to what I want. But sometimes I think they feel right&#8212;and this is one of those times. I&#8217;m trying to figure out when they feel right and when they don&#8217;t. In this case, I believe it feels right because the function:</p><ul><li><p>reduces the system complexity by converging several different (potentially unknown) types down to one known type</p></li><li><p>remains a total function</p></li></ul><p>Do you have ideas?</p><h2>The other side of the coin: Avoiding invalid answers</h2><p>I want to discuss the other side of the coin. In JavaScript, even with TypeScript&#8217;s protection, you still run into issues:</p><pre><code><code>function divide(a: number, b: number): number {
  return a/b;
}</code></code></pre><p>That could return a &#8220;real&#8221; number, or it could return <code>NaN</code>, <code>Infinity</code>, or <code>-Infinity</code>. Those last three values suck because they throw a monkey wrench in other arithmetic operations. They&#8217;re just waiting to cause trouble and this function will silently return them.</p><p>Let&#8217;s prevent that!</p><pre><code><code>function divide(a: number, b: number): number {
  if(b === 0) throw new Error("Divide by zero");
  return a/b;
}</code></code></pre><p>Although this function is now partial, it does a better job at protecting our code from <code>NaN</code> and other pesky values. I think we do similar things in Clojure.</p><p>I see this behavior as the flip side of total functions. Total functions ask you to work for all valid inputs. This technique guarantees that a function will only return valid outputs. Do you have a name for this?</p><p>By the way, I think we need to use these in type-safe languages a lot as well, just perhaps not for the types of cases I&#8217;ve shown. For instance, there are values of <code>numberOfMegabytes</code> that will result in problems and you probably want to limit it:</p><pre><code><code>function allocate(numberOfMegabytes: number): Array {
  if(numberOfMegabytes &gt; 1000) throw new Error("Can't allocate more than one gig");
...
}</code></code></pre><p>You could, of course, return <code>undefined</code> instead of throwing an error to keep it total.</p><h2>Review and conclusion</h2><p>I like the concept of total and partial functions. I hope that, just like pure/impure functions, it escapes the functional programming niche and enters the mainstream. I explored how we could turn the binary into a spectrum because a partial function being closer to total is better than not. But that opened up a world of possible inputs we could make valid past the special point on the spectrum called &#8220;total&#8221;. We saw what that could look like. I also brought up the idea that we often want to protect the return value. It&#8217;s a dual concern to the idea of total functions.</p><p>What techniques do you use to avoid functions returning bad values? How do you ensure that you only pass good values to your functions?</p><p>I speculated about why I thought that <code>to-int</code> was an okay function, even though it borders on &#8220;magical&#8221; type conversion. I think it has to do with its output being less complex than its input&#8212;and also being a total function. In the next issue, I want to expand on this idea, bring in &#8220;Postel&#8217;s Law&#8221;, complexity, and understandability.</p>]]></content:encoded></item><item><title><![CDATA[Messy domains have a core]]></title><description><![CDATA[There's plenty of structure at the bottom]]></description><link>https://ericnormand.substack.com/p/messy-domains-have-a-core</link><guid isPermaLink="false">https://ericnormand.substack.com/p/messy-domains-have-a-core</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Tue, 18 Mar 2025 14:38:32 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/01fe1baf-736d-43c4-ad7a-ec718b906d33_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our last Apropos was with <a href="https://www.youtube.com/live/vfYB-5Z7gts">Sean Corfield</a>. Check it out. Our <a href="https://youtube.com/live/vfYB-5Z7gts?feature=share">next episode</a> is with <a href="https://youtube.com/live/cDfmpe_w_3I?feature=share">Bobbi</a> on March 25. Please watch us live so you can ask questions.</p><p>Have you seen <em><a href="https://ericnormand.me/gs">Grokking Simplicity</a></em>, my book for beginners to functional programming? Please check it out or recommend it to a friend. You can also <a href="https://ericnormand.me/gsm">get it from Manning</a>. Use coupon code TSSIMPLICITY for 50% off.</p><div><hr></div><p>The presentation was chaotic. My slides weren&#8217;t working. My daughter was sitting on my lap. And, what&#8217;s worse, the audience did not believe what I had to say. My main point was that you didn&#8217;t have to live in a nest of if statements. You could find a model in any domain. But they weren&#8217;t buying it.</p><p>That presentation was my first step toward the idea of domain modeling as the heart of software design. That was years ago. And it seems obvious now (so obvious that it seems weird to need to say it), but here&#8217;s where I eventually arrived: <strong>A domain model is judged by how well it corresponds to the domain.</strong> The model and the domain must have similar (ideally: identical) structure. The closeness of correspondence between model and domain is where the power of abstraction comes from. How do we get computers to do useful work? Correspondence.</p><p>But correspondence doesn&#8217;t sound great when you&#8217;re looking at a messy domain. In the intimate audience of that presentation, someone mentioned their domain had lots of government regulation. He believed there was no way to find a clean model. The only choice was building up spaghetti code to match the spaghetti of laws that made up their domain. And here I was trying to sell an answer to complexity by finding a better model.</p><p>I empathized with him. I&#8217;ve faced similar domains. But I&#8217;ve also been surprised. I came away from that presentation with a new awareness of the difficulty of the challenge I had taken on. You see, many times in my career, when we were struggling to find a simple model, we&#8217;ve eventually found a one (though sometimes too late). Well, not just a simple model&#8212;a much better model than what we currently had. The new model was so good, in fact, that when we did find them in time to build them into our code, they drastically reduced the complexity of our codebase. At the same time, they added dramatically to the business value.</p><p>I&#8217;ll tell a story that I&#8217;ve told before. But it&#8217;s very relevant. I worked at a company that helped Americans register to vote. In the US, each state determines its own voting registration requirements. A co-founder of the company had studied all of the laws of all the states, and she presented us with a big mess of overlapping categories, showing how crazy the logic was. It was clear she&#8217;d tried to organize the chaos but came up short.</p><p>We all struggled with the rules. Some states let you register the day of. Some you didn&#8217;t need to register. Some you had to register two weeks before. Some required you to live in the state for two years, unless the state you moved from didn&#8217;t allow you to vote there. Some just wanted you to vote where you lived. Ugh. It really was a mess. There were no clear seams that we could pull apart&#8212;which, in retrospect, makes sense when each state is acting mostly independently.</p><p>Eventually, I gave up. Let&#8217;s just do a big if statement! Why not? The first level of nesting will be by state. If you&#8217;re in Nebraska, then follow this logic. If you&#8217;re in New Jersey, follow this logic. We divide it up, and the task is 50x easier. We just have 50 new if statements to write, one for each state.</p><p>It&#8217;s sometimes useful to look at that worst case scenario. The worst case is that you have 50 small messes instead of one giant mess. Ask yourself: How would you do it in a straightforward way? It doesn&#8217;t mean you have to commit to going that way, but it helps jog the mind. And jog my mind it did.</p><p>Once it was a set of conditionals, I saw something we had overlooked: The question we were trying to answer was really simple: Can person x register in state y? That can be written in a nice function signature:</p><pre><code>function canRegister(x: Person, y: State): boolean;</code></pre><p>And each of those 50 branches of the conditional was then a question of this signature:</p><pre><code>function canRegisterInNebraska(x: Person): boolean;</code></pre><p>One of those functions for each state. That&#8217;s just a simple predicate on <code>Person</code>. And when you look at the rules of Nebraska (or any state), they&#8217;re of a similar form:</p><pre><code>function isMajorityAge(x: Person): boolean;
function isResident(x: Person): boolean;
function isCitizen(x: Person): boolean;</code></pre><p>We can easily combine those smaller, easy to implement questions into one bigger question with some boolean operations:</p><pre><code>function and(f1: (x:Person)=&gt;boolean, 
             f2: (x:Person)=&gt;boolean): (x:Person)=&gt;boolean;
function or (f1: (x:Person)=&gt;boolean, 
             f2: (x:Person)=&gt;boolean): (x:Person)=&gt;boolean;</code></pre><p>It turns out that <code>isMajorityAge() </code>is very reusable. Most states have that rule. So we were getting plenty of reuse. And we were able to build up rules that had a very similar structure to how the laws worked in each state. Each state was custom, but it was built out of modular, reusable parts.</p><p>In essence, we looked beyond the mess to find structure underneath. The structure was at a level of higher generality than we were looking at. We wanted to find a hierarchy to fit the states into. Instead, we needed to accept that each state was complicated and build a platform of atomized decisions that could be composed to mirror the structure of the state&#8217;s laws. We build a platform on which to model the states&#8217; laws. I sometimes call such a platform a <em>core abstraction</em>.</p><p>I think of that story every time someone claims their domain has no structure. There&#8217;s always a structure. Humans build things with structure, even if it&#8217;s very messy and complicated. It&#8217;s never random. You can find the structure. </p><p>However, I don&#8217;t think it&#8217;s merely a mindset shift. I don&#8217;t really believe mindset is enough. Over the years, I&#8217;ve realized that it takes a very trained eye to spot that structure. How did we see that there was Boolean logic at the base? Lots of practice? How did I come up with that function signature? Years of working in Haskell. A part of my brain is always asking &#8220;what is the type?&#8221; And the composing functions? Luckily, this was a Clojure company, so we were used to thinking about higher-order functions. Would the average Java programmer have thought of that? I would guess not because they&#8217;re not used to thinking that way.</p><p>We should judge a model by how well it corresponds to the domain. The better it corresponds, the simpler our code that uses that model. But many domains are messy. Their structure is entangled. Does that mean that our model should be messy as well? I say no. It needn&#8217;t be messy, but you have to look for structure at a different level of generality than you&#8217;re currently looking.</p><p>But this a bittersweet synthesis. On the one hand, it&#8217;s hopeful. There is a core model that structures the mess. I&#8217;ve always found one, eventually. On the other hand, it&#8217;s not easy. I think of a passage from <em><a href="https://worrydream.com/EarlyHistoryOfSmalltalk/#smalltalkAndChildren">The Early History of Smalltalk</a> </em>(emphasis mine)<em>:</em></p><blockquote><p>It started to hit home in the Spring of '74 after I taught Smalltalk to 20 PARC nonprogrammer adults. They were able to get through the initial material faster than the children, but just as it looked like an overwhelming success was at hand, they started to crash on problems that didn't look to me to be much harder than the ones they had just been doing well on. One of them was a project thought up by one of the adults, which was to make a little database system that could act like a card file or rolodex. <strong>They couldn't even come close to programming it.</strong> I was very surprised because I "knew" that <strong>such a project was well below the mythical "two pages"</strong> for end-users we were working within. That night I wrote it out, and the next day I showed all of them how to do it. Still, none of them were able to do it by themselves. Later, I sat in the room pondering the board from my talk. <strong>Finally, I counted the number of nonobvious ideas in this little program. They came to 17.</strong> And some of them were like the concept of the arch in building design: very hard to discover, if you don't already know them.<br><br>The connection to literacy was painfully clear. It isn't enough to just learn to read and write. There is also a <em>literature</em> that renders <em>ideas.</em> Language is used to read and write about them, but at some point the organization of ideas starts to dominate mere language abilities. <strong>And it helps greatly to have some powerful ideas under one's belt to better acquire more powerful ideas.</strong></p></blockquote><p>The challenge I&#8217;ve taken on in my book is to give people a handful of powerful ideas that help people model. I think the biggest idea is to judge code complexity not by how hard it is to read, but on how well it expresses the model. Then there are others, like to use total functions. And total functions is what we&#8217;ll talk about next week.</p><p>Do you have examples of finding core abstractions in an otherwise intractable domain? Is there a domain you gave up on and resorted to spaghetti code?</p>]]></content:encoded></item><item><title><![CDATA[Bad data models lead to code complexity]]></title><description><![CDATA[Extra ifs are the cost of poor data models]]></description><link>https://ericnormand.substack.com/p/bad-data-models-lead-to-code-complexity</link><guid isPermaLink="false">https://ericnormand.substack.com/p/bad-data-models-lead-to-code-complexity</guid><dc:creator><![CDATA[Eric Normand]]></dc:creator><pubDate>Tue, 11 Mar 2025 22:55:57 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ab0a069e-3175-496d-a549-ebfb075dbbf0_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our last Apropos was with <a href="https://www.youtube.com/watch?v=8IeXoC2Lo7A">Alex Engelberg</a>. Check it out. Our <a href="https://youtube.com/live/vfYB-5Z7gts?feature=share">next episode</a> is with Sean Corfield on March 11 (Paula had to cancel). Please watch us live so you can ask questions.</p><p>Have you seen <em><a href="https://ericnormand.me/gs">Grokking Simplicity</a></em>, my book for beginners to functional programming? Please check it out or recommend it to a friend. You can also <a href="https://ericnormand.me/gsm">get it from Manning</a>. Use coupon code TSSIMPLICITY for 50% off.</p><div><hr></div><h1>Bad data models lead to code complexity</h1><p>There I was, staring at a complex, nested if statement with hard-to-follow logic in each condition. What&#8217;s worse, similar logic was repeated in several places in the codebase. That was when I realized how much complexity a bad data model can cause.</p><h2>An example of a bad domain model</h2><p>The domain was simple. I&#8217;ll describe a similar one that is an easier example so I don&#8217;t have to explain the whole domain. Imagine a content management system for a magazine. Articles go through several stages as they are prepared for publication:</p><ul><li><p>Drafting</p></li><li><p>Editing</p></li><li><p>Approval</p></li><li><p>Publication</p></li></ul><p>We modeled it with several <code>Maybe&lt;DateTime&gt;</code> type. As we finished a stage, we added the time. If the time didn&#8217;t exist, it meant that we had not finished that stage yet. </p><pre><code><code>type Document = {
  drafted?  : Date;
  edited?   : Date;
  approved? : Date;
  published?: Date;
};</code></code></pre><p>This worked for a while, but eventually we found problems.</p><p>The first problem was that our logic was complicated and duplicated. If we wanted to know if something was in the approval stage, we had logic like this:</p><pre><code>if(document.drafted &amp;&amp; document.edited &amp;&amp; !document.approved)</code></pre><p>The second problem was that as we increased the number of documents in the database, with many releases to our software (often with bugs), we eventually got data that had impossible states. For instance, it was edited without being drafted:</p><pre><code>{
  drafted: null,
  edited: new Date("2010-01-02")
}</code></pre><p>How could this happen? Well, despite our checks, something slipped through. Maybe it was a bug in a prior release. Maybe it was user error. Whatever the case, it existed, and our code needed to make sense of it. That meant more if statements.</p><p>This is a bad data model. I mean that in an objective way. We can actually measure how bad it is. We can count the number of states in the domain and the number of states in the model, and see how far off they are from each other. Here is the timeline of a document workflow:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jaLp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jaLp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 424w, https://substackcdn.com/image/fetch/$s_!jaLp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 848w, https://substackcdn.com/image/fetch/$s_!jaLp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 1272w, https://substackcdn.com/image/fetch/$s_!jaLp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jaLp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png" width="252" height="477" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:477,&quot;width&quot;:252,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:35993,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ericnormand.substack.com/i/158857006?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jaLp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 424w, https://substackcdn.com/image/fetch/$s_!jaLp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 848w, https://substackcdn.com/image/fetch/$s_!jaLp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 1272w, https://substackcdn.com/image/fetch/$s_!jaLp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The horizontal lines represent events that divide the timeline into sections. Each section is a distinct state that the document can be in. We see clearly that there are five states a document can be in in this domain.</p><p>Our model is different. It contains for optional values, where the presence of the value is meaningful. The date is also meaningful, but in a different way. I&#8217;m going to ignore it for this analysis, even though it also could present difficulties such as if a document is published before it is drafted. We&#8217;ll just look at the presence or absence of the value.</p><p>In that situation, there are four values with two options each (present/absent), which is <em>2^4 = 16. </em>So our data can represent 16 states. That means that 11 of our states are redundant, ambiguous, or invalid, because we only needed 5.</p><p>That sucks. Those unneeded states caused a lot of code complexity because our code had to map them to the domain concept. It had to deal with the ambiguity of the situation. That was the second problem I talked about (finding impossible data in the database). We could have seen this coming with some simple math.</p><p>The first problem was still there. Remember, the first problem was about the complex, duplicated logic we found everywhere. It wasn&#8217;t really because there were ambiguous states. It was more that what we stored didn&#8217;t correspond well to what we needed. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jaLp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jaLp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 424w, https://substackcdn.com/image/fetch/$s_!jaLp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 848w, https://substackcdn.com/image/fetch/$s_!jaLp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 1272w, https://substackcdn.com/image/fetch/$s_!jaLp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jaLp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png" width="252" height="477" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:477,&quot;width&quot;:252,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:35993,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ericnormand.substack.com/i/158857006?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!jaLp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 424w, https://substackcdn.com/image/fetch/$s_!jaLp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 848w, https://substackcdn.com/image/fetch/$s_!jaLp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 1272w, https://substackcdn.com/image/fetch/$s_!jaLp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f28d32-9eff-491c-9565-d6421b527ba1_252x477.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Remember, we were storing the time that we crossed each of the horizontal lines. But what we wanted to know was which green box we were in. We had to write compound conditionals to convert the lines into boxes.</p><h2>Adapting the existing data model to a better one</h2><p>Luckily, we can adapt this model and solve both problems with in one stroke. Remember, domain modeling is about mapping. So let&#8217;s create a function that maps the data model we have to the data model we wish we had:</p><pre><code>function documentStatus(document) {
  if(document.published) return "published";
  if(document.approved) return "ready";
  if(document.edited) return "approval";
  if(document.drafted) return "editing";
  else return "drafting";
}</code></pre><p>We did double-check to make sure that this does correctly map the ambiguous states to the correct states. Luckily, it was simple. We could imagine it being much more complex than this. For instance, we might have a check somewhere in the code that a document with a published date but no content is actually in the drafting stage. I have a hunch that this is where the really hairy complexity comes from&#8212;different behavior in different places in the codebase because of inconsistent logic. Either way, having a single place that defines the mapping to our desired model is a good thing. The biggest trouble with it is making sure to use it consistently.</p><p>We could then use the human-readable string representing the status in place of a bunch of booleans anded together:</p><pre><code>if(documentStatus(document) === "approval")</code></pre><p>instead of:</p><pre><code><code>if(document.drafted &amp;&amp; document.edited &amp;&amp; !document.approved)</code></code></pre><p>Here are the things we achieved:</p><ul><li><p>Centralized the logic for determining the status</p></li><li><p>Eliminated inconsistencies in status logic across the codebase</p></li><li><p>Made status checks more human-readable</p></li><li><p>Eliminated complex conditional logic</p></li></ul><p>I&#8217;d say it was a major win.</p><h2>Is this a major win? Or a waste of time?</h2><p>Even still, I have encountered people who think this is a waste of time. To put a fine point on it, this change was both small (in the number of lines of code it saved) and extensive (in the number of lines of code and the number of files it touched). Further, it was risky. Changing so many lines of working code could introduce bugs we couldn&#8217;t anticipate. In short, it&#8217;s high risk and it doesn&#8217;t eliminate much complexity.</p><p>When I ask them about where code complexity comes from, they say it&#8217;s from other sources&#8212;mainly business logic, user requirements, and system interactions. Bad domain models do introduce complexity, but it&#8217;s a rounding error compared to those sources.</p><p>My response is always the same: Those sources of complexity can be modeled, too. Nesting if statements is one simple tool we have to address a corner case. I&#8217;ve done it myself thousands of times. It&#8217;s extremely useful when I don&#8217;t have the time to fully understand what is happening in the codebase. Surgically adding an if where I&#8217;m certain it will be useful is a versatile tool.</p><p>But nesting ifs is a tool that breeds more complexity. The next time I or someone else needs to handle a new case, it becomes that much harder to understand, so that much more likely that we&#8217;ll add yet another if.</p><h2>Modeling is the only way to rein in complexity</h2><p>Look, I&#8217;m getting deep into the weeds here, and I need to bring this thread back to my intended point: There&#8217;s no way to clean up those ifs without understanding the domain. All a domain model is is an understanding of the domain expressed in code. When you express it in code, its an understand that is available to anyone who cares to read it. Domain modeling is software design. Trying to eliminate complexity without modeling is a fool&#8217;s errand. Domain modeling is a necessary skill for designing software. So we might as well get good at it.</p><p>When we talk about modeling business logic, often people hear &#8220;oversimplifying&#8221;. That is not what I&#8217;m talking about at all. Domain modeling means embracing the complexity, but identifying the structure of it, then writing that structure down in useful code. Maybe I&#8217;m preaching to the choir, so I&#8217;ll just leave it at that. If you&#8217;ve got an understanding, you can write it down. I also hope to show that there are objective measures of data model quality in my upcoming book. Modeling is a skill, and you can get better at it.</p><h2>Conclusion</h2><p>Code complexity is a major cost of software development. Bad data models add significantly to code complexity&#8212;when models don&#8217;t correspond to the domain, we require extra code to handle the redundant and missing cases. Cleaning up the data model can significantly contribute to reducing complexity. Even if you think that data models aren&#8217;t a major source of complexity, the sources of complexity can also be modeled. Chances are, the current models of things like business logic, user requirements, and system interactions are poor models. They&#8217;re adding way more complexity than they actually need. You can clean those up, too.</p><p>I hope to bring this awareness of poor models to the industry. Currently we&#8217;re stuck thinking about code in isolation instead of how it relates to the domain. I&#8217;d like to help fix that, which is why it&#8217;s a major theme of my book. I don&#8217;t want domain modeling to be just another abstract skill. Instead, it&#8217;s fundamental to software design. The more I research it, the more I see that it&#8217;s the real key to explaining a lot of software design advice.</p><p>Do you have any interesting stories involving domain modeling? How do you apply domain modeling in your work?</p>]]></content:encoded></item></channel></rss>