<?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[Stateless Machine]]></title><description><![CDATA[Some software-related thoughts]]></description><link>https://statelessmachine.com</link><image><url>https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg</url><title>Stateless Machine</title><link>https://statelessmachine.com</link></image><generator>Substack</generator><lastBuildDate>Sun, 19 Apr 2026 01:20:23 GMT</lastBuildDate><atom:link href="https://statelessmachine.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Hugo Sousa]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[statelessmachine@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[statelessmachine@substack.com]]></itunes:email><itunes:name><![CDATA[Hugo Sousa]]></itunes:name></itunes:owner><itunes:author><![CDATA[Hugo Sousa]]></itunes:author><googleplay:owner><![CDATA[statelessmachine@substack.com]]></googleplay:owner><googleplay:email><![CDATA[statelessmachine@substack.com]]></googleplay:email><googleplay:author><![CDATA[Hugo Sousa]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[New dev requirements all over the place]]></title><description><![CDATA[Because of developers making more use of AI and wanting to get the most out of it, there&#8217;s new assessment criteria popping up in discussions of what technology to use.]]></description><link>https://statelessmachine.com/p/new-dev-requirements-all-over-the</link><guid isPermaLink="false">https://statelessmachine.com/p/new-dev-requirements-all-over-the</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Fri, 25 Apr 2025 15:12:48 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Because of developers making more use of AI and wanting to get the most out of it, there&#8217;s new assessment criteria popping up in discussions of what technology to use. </p><p>I was recently roped into a discussion of what programming language should be used on a new backend project. There was quite the struggle there.</p><p>Some things they didn&#8217;t care about:</p><ul><li><p>Experience using the language - They believed they&#8217;d pick anything up quickly enough.</p></li><li><p>Good ecosystem of libraries - As long as some basic things were there, they didn&#8217;t mind building the rest. They speculated not much would be necessary. </p></li><li><p>Learning curve - Everyone had over 5 years experience and experience in multiple paradigms.</p></li></ul><p>With this stuff not being a requirement, I thought we were setup for a wide range of options. Most of the things on their wishlist also didn&#8217;t seem hard to fulfil:</p><ul><li><p>Garbage collection - They didn&#8217;t want to care about memory management.</p></li><li><p>Static types with nullability included.</p></li><li><p>Quickly go from uncompiled code to up and running (fast compilation and startup)</p></li><li><p>Basic IDE support (syntax highlighting and auto complete)</p></li></ul><p>Given they weren&#8217;t very demanding on the compilation speed, I thought a bunch of options were still available. But then, the more interesting requirement came in:</p><ul><li><p>Unit tests in the same file as the tested code - They believe this improves using AI to code.</p></li></ul><p>I can totally relate to this requirement. I&#8217;ve felt a lot of joy and been quite productive using AI to write code in my programming language, which does support tests in the same file. But my programming language is not something I can recommend yet and I was also not sure what fits the bill. </p><p>I didn&#8217;t have anything up my sleeve. After some thinking I arrived at Typescript as maybe the way to go if a new testing framework is created. My curiosity got the best of me and I had to try. They also wanted to use <a href="https://effect.website/">Effect</a>. This is what a hello world endpoint handler file looks like:</p><pre><code>import { Effect } from "effect"
import { test } from "../testing/testing";

const handler: () =&gt; Effect.Effect&lt;string, Error&gt; = () =&gt; {
    return Effect.succeed("hello world");
}

test("hello world", (assert) =&gt; {
    const result = Effect.runSync(handler());
    assert.isEqual(result, "hello world");
});

export default handler;</code></pre><p>And the funny thing is I didn&#8217;t even write that test. I wrote the testing framework (more of a hack than an actual framework, mind you) and AI generated the test for me. I didn&#8217;t even write a prompt. It was automatically suggested in the editor.</p><p>This was a lot of fun. I wonder what other interesting requirements people have for new projects outside of my bubble.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[My golang guilty pleasure: ADTs]]></title><description><![CDATA[Golang is designed as a quite open language.]]></description><link>https://statelessmachine.com/p/my-golang-guilty-pleasure-adts</link><guid isPermaLink="false">https://statelessmachine.com/p/my-golang-guilty-pleasure-adts</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Fri, 18 Apr 2025 09:39:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Golang is designed as a quite open language. It&#8217;s not designed for you to express a lot of constraints on types. It doesn&#8217;t have the facilities for doing so. A notable omission from the language is enums. While enum support might come at some point, ADT support seems highly unlikely.</p><p>If you haven&#8217;t heard of ADTs, they are similar to enums but apply to types instead. The ADT abbreviation stands for Algebraic Data Type, but I won&#8217;t explain that mouthful today.</p><p><a href="https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html">Rust has this concept but just calls it enum</a> and there&#8217;s an example from the standard library in the official documentation:</p><pre><code>enum Option&lt;T&gt; {
    None,
    Some(T),
}</code></pre><p>It means that whenever you have a variable of type `Option`, it&#8217;s guaranteed to be a `None` or a `Some` value. You can&#8217;t create other variants elsewhere in the code. If you wish, somewhere where you use it, to break it down into the possible cases, you can rely on the compiler to force you to handle all possibilities. This is usually referred to as exhaustiveness checking. </p><p>But this is far from a Rust-specific feature. You can find direct support for it or an equivalently powerful construct in a lot of other programming languages.</p><p>The golang codebase where I&#8217;ve worked the most lately is a compiler and compilers are a domain where this kind of closed universe domain modelling comes up often. </p><p>Even though solutions like <a href="https://github.com/alecthomas/go-check-sumtype">go-check-sumtype</a> exist, I&#8217;ve settled for a slightly different flavour. Instead of additional tooling, I rely on, or perhaps abuse, language features to achieve it. </p><p>Let&#8217;s start with a sample Rust enum, taken from <a href="https://doc.rust-lang.org/rust-by-example/custom_types/enum.html">Rust by Example</a>:</p><pre><code>enum WebEvent {
    PageLoad,
    Paste(String),
    Click { x: i64, y: i64 },
}</code></pre><p>And, without further ado, convert it to golang (explanation follows):</p><pre><code>type WebEvent interface {
&#9;sealedWebEvent()
&#9;WebEventCases() (*PageLoad, *Paste, *Click)
}

type PageLoad struct{}

func (p *PageLoad) sealedWebEvent() {}
func (p *PageLoad) WebEventCases() (*PageLoad, *Paste, *Click) {
&#9;return p, nil, nil
}

type Paste struct {
&#9;Content string
}

func (p *Paste) sealedWebEvent() {}
func (p *Paste) WebEventCases() (*PageLoad, *Paste, *Click) {
&#9;return nil, p, nil
}

type Click struct {
&#9;X int
&#9;Y int
}

func (c *Click) sealedWebEvent() {}
func (c *Click) WebEventCases() (*PageLoad, *Paste, *Click) {
&#9;return nil, nil, c
}</code></pre><p>The non-exported `sealedWebEvent` method makes it so no implementations of the interface can be defined in other packages. This is how you can enforce a closed universe of types implementing the interface. </p><p>The `WebEventCases` function is the (fisher-price grade) pattern matching. If any cases are added or removed from the return of the function, golang compiler forces us to update all call-sites where they are used (either by being returned or assigned to variables). The other key feature is that golang compiler doesn&#8217;t allow you to have unused variables within a method/function body. This means that as long as you assigned it, you&#8217;ll use it. </p><p>On top of all of that, if you decide to return within the handling of each case, the compiler will also make sure none are missed.</p><p>How all of this looks in practice:</p><pre><code>func webEventDescription(webEvent WebEvent) string {
&#9;casePageLoad, casePaste, caseClick := webEvent.WebEventCases()
&#9;if casePageLoad != nil {
&#9;&#9;return"page load"
&#9;} else if casePaste != nil {
&#9;&#9;return "paste"
&#9;} else if caseClick != nil {
&#9;&#9;return "click"
&#9;} else {
&#9;&#9;panic("WebEventCases nil")
&#9;}
}</code></pre><p>Don&#8217;t worry. I wouldn&#8217;t call it idiomatic code. No need to bring out the pitchforks. It&#8217;s a very handy trick, though.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[The Amazing Peter Parser]]></title><description><![CDATA[This post is meant as an introduction to parser combinators.]]></description><link>https://statelessmachine.com/p/the-amazing-peter-parser</link><guid isPermaLink="false">https://statelessmachine.com/p/the-amazing-peter-parser</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Wed, 16 Apr 2025 18:07:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This post is meant as an introduction to parser combinators. I&#8217;ve recently built a parser combinator library for <a href="https://statelessmachine.com/p/the-origin-story-of-my-programming">tenecs (my programming language)</a>, called <a href="https://github.com/xplosunn/peter_parser">peter_parser</a>. By the end of this article hopefully you&#8217;ll also be understand the concepts enough to build such a library yourself.</p><p>A parser, conceptually, allows us to convert something into something else, if possible. </p><p>Too abstract? Let&#8217;s get into examples:</p><ul><li><p>Parsing a Date from a String</p></li><li><p>Parsing a data structure from a JSON</p></li><li><p>Parsing a number from a String</p></li></ul><p>If we restrict our input to Strings, we can define our parser type as:</p><pre><code>struct Parser&lt;ThingWeWant&gt;(
  parse: (input: String) ~&gt; ThingWeWant | ParseError
)
struct ParseError(message: String)</code></pre><p>It&#8217;s a structure that contains a `parse` function. The `parse` function allows us to take a String and either get the ThingWeWant or a ParseError, when that&#8217;s not possible. </p><p>But we don&#8217;t have to restrict our input to a String. Some JSON libraries parse the JSON String into a data structure representing the JSON values and then allow you to parse from that data structure into your intended target data structure. For that we need to generalise the parser&#8217;s input. </p><pre><code>struct Parser&lt;In, T&gt;(
  parse: (input: In) ~&gt; T | ParseError
)
struct ParseError(message: String)</code></pre><p>Now we&#8217;d be able to write that code.</p><pre><code>struct Color(red: Int, green: Int, blue: Int)

jsonParser: Parser&lt;String, Json&gt; = functionYouWouldImportFromALibrary()

colorFromJsonParser: Parser&lt;Json, Color&gt; = functionYouWouldImplement()

colorFromStringParser: Parser&lt;String, Color&gt; = Parser(
  parse = (input: String): Color | ParseError =&gt; {
    json: Json ?= jsonParser.parse(input)
    colorFromJsonParser.parse(json)
  }
)</code></pre><p>So parsers can be sequentially combined. They are just a function, after all. But that&#8217;s not what parser combinators are about. Let&#8217;s get into that.</p><p>When you&#8217;re trying to parse something, there&#8217;s often smaller parts you isolate and tackle in order to build it up. For instance, when parsing a date &#8220;2025-12-25&#8221;, you need to parse a number three times. So perhaps it&#8217;s possible to build a parser out of smaller parsers. </p><p>For that, we need a slightly more sophisticated notion of a parser. We need to know the remaining of the input, so we can feed it into the next parser.</p><pre><code>struct Parser&lt;In, T&gt;(
  parse: (input: In) ~&gt; ParseSuccess&lt;In, T&gt; | ParseError
)
struct ParseSuccess&lt;In, T&gt;(parsed: T, remaining: In)
struct ParseError(message: String)</code></pre><p>And now, if we implement a parser for a number and a parser for the dash, all we&#8217;re missing is the parser combinators to turn it into a date parser. Assuming they exist, you can do something like:</p><pre><code>struct Date(year: Int, month: Int, day: Int)

intParser: Parser&lt;String, Int&gt; = assumeThisIsImplemented()
dashParser: Parser&lt;String, Void&gt; = thisOneIsTrivialToImplement()
emptyStringParser: Parser&lt;String, Void&gt; = alsoTrivialToImplement()

dateParser: Parser&lt;String, Date&gt; = 
  intParser
    -&gt;andThenIgnore(dashParser)
    -&gt;andThen(intParser, (year, month) =&gt; {
      Date(year, month, 0)
    })
    -&gt;andThenIgnore(dashParser)
    -&gt;andThen(intParser, (date, day) =&gt; {
      Date(date.year, date.month, day)
    })
    -&gt;andThenIgnore(emptyStringParser) /// make sure no remaining chars</code></pre><p>The validation is missing for what constitutes a valid month number, for example, but that&#8217;s not a lot of work to add. </p><p>And this is what parser combinators are all about. Building smaller parsers that are easy to reason about and then combining them into something more complex. Some examples of combinators my library supports:</p><ul><li><p>`andThen`: Combine two parsers sequentially</p></li><li><p>`ignoreAndThen`: Run two parsers but ignore the first result</p></li><li><p>`andThenIgnore`: Run two parsers but ignore the second result</p></li><li><p>`or`: Try one parser, fall back to another</p></li><li><p>`zeroOrMoreTimes`: Parse zero or more occurrences</p></li><li><p>`oneOrMoreTimes`: Parse one or more occurrences</p></li><li><p>`optional`: Make a parser optional</p></li><li><p>`separatedBy`: Parse items separated by a delimiter</p></li></ul><p>You can use these to build more complex parsers, such as a CSV parser or a JSON parser. Break down your problem into really small ones and find the appropriate combinators. You might have fun treating parsers like legos.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Thinking more about JSON than you would like to]]></title><description><![CDATA[JSON is used as a representation for a lot of data going across programs and systems.]]></description><link>https://statelessmachine.com/p/thinking-more-about-json-than-you</link><guid isPermaLink="false">https://statelessmachine.com/p/thinking-more-about-json-than-you</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Wed, 26 Mar 2025 16:01:01 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>JSON is used as a representation for a lot of data going across programs and systems. And as those systems evolve, we&#8217;re forced to think all the time about backwards compatibility of their communication protocols. </p><p>The most common case I&#8217;ve faced that starts such discussions is communication between a backend web server and the clients (frontend &amp; mobile apps). We need to make changes to the backend that are compatible with the oldest client still in use. And different teams usually arrive at the convention. There&#8217;s some sort of agreed-upon specification, in which the rules are as such:</p><p>On any value:</p><ul><li><p>it has a specified type (boolean, string, number, array, object)</p><ul><li><p>changing the type is not backwards-compatible</p></li></ul></li><li><p>whether it can be &#8220;null&#8221; or not is specified</p><ul><li><p>changing from nullable to non-null is backwards-compatible</p></li><li><p>changing from non-null to nullable is not backwards-compatible</p></li></ul></li></ul><p>On a JSON object:</p><ul><li><p>adding a field is backwards-compatible</p></li><li><p>removing a non-null field is not backwards-compatible</p></li><li><p>removing a nullable field is not backwards-compatible</p></li></ul><p>On a JSON array:</p><ul><li><p>every element of an array is of the same type</p><ul><li><p>changing the type is not backwards-compatible</p></li></ul></li><li><p>whether the array can contain &#8220;null&#8221; values is specified</p><ul><li><p>changing from nullable to non-null is backwards-compatible</p></li><li><p>changing from non-null to nullable is not backwards-compatible</p></li></ul></li><li><p>adding or removing elements is backwards-compatible</p></li></ul><p></p><p>Even if you haven&#8217;t explicitly thought about these guidelines, you&#8217;ve probably seen them in practice before, even if they&#8217;re not exactly what you&#8217;re using right now. And even though these rules are wide-spread, automation around it isn&#8217;t nearly as popular. We can find tools and standards around the idea of JSON schemas and API contracts, such as <a href="https://www.openapis.org/">OpenAPI</a> and <a href="https://json-schema.org/">JSON Schema</a>, which have existed for a while. But the idea that those schemas need to evolve is also far from novel. Confluent&#8217;s <a href="https://docs.confluent.io/platform/current/schema-registry/index.html">Schema Registry</a> exists to tackle this problem. </p><p>The ideas of JSON backwards-compatibility don&#8217;t require running systems to reason about. There&#8217;s no sequence of instructions. There are schemas, which dictate what values are allowed and there are backwards-compatibility rules. There&#8217;s nothing stopping us from building developer tooling around these ideas.</p><p>It should be possible to build a JSON library that has the concept of the schema as a first-class citizen. As you would expect of a JSON library, it should allow reading/write JSON (conforming to the schema). But it should also have the capability to:</p><ul><li><p>read / write the schema onto a text representation</p></li><li><p>compare different schemas for compatibility</p></li></ul><p>Having the schema also means we&#8217;re able to generate documentation or client code automatically.</p><p></p><p>Imagine the team workflow we can then empower:</p><ul><li><p>Every time you make a new backend release, you dump your JSON schema into a file and check it in onto git.</p></li><li><p>On the CI for each backend PRs you check that you haven&#8217;t broken compatibility with your check-in versions.</p></li><li><p>Whenever a client version is no longer in use, the backend can now delete the check-in version files that no longer apply.</p></li></ul><p>Bonus points if the client code is auto-generated from the schema and updating means updating a dependency of their program and can do most of work by updating the lines where the Typescript compiler complains.</p><p></p><p>Fuelled by all of this dreaming I started a while ago working on my own JSON library for Scala. Scala is a good candidate because JSON libraries already usually have a type that&#8217;s separate from the data type. Something like `JsonWriter&lt;T&gt;` and `JsonReader&lt;T&gt;` for any `T` you want to convert to / from JSON. So I just had to conceptually upgrade this type to be a `JsonDescriptor&lt;T&gt;`. </p><p>I did mention that none of the ideas here are specific to some programming language. You can build all of this in your language of choice. And so I recently ported the Scala library into tenecs (my programming language). You can find the <a href="https://github.com/xplosunn/transporter">code here</a>. As a showcase of the functionality, here&#8217;s two unit tests:</p><pre><code>_ := UnitTest(
  "canRead fails for object with non-optional new field",
  (testkit) =&gt; {
    hasExpectedIssue := (issues: List&lt;CompatibilityIssue&gt;): Void =&gt; {
      testkit.assert.equal(issues-&gt;length(), 1)
    }
    newSchema := JsonObject(&lt;JsonObjectField&gt;[
      JsonObjectField("field1", JsonBoolean()),
      JsonObjectField("field2", JsonBoolean())
    ])
    oldSchema := JsonObject(&lt;JsonObjectField&gt;[
      JsonObjectField("field1", JsonBoolean())
    ])
    hasExpectedIssue(newSchema-&gt;canRead(oldSchema))
  }
)

_ := UnitTest(
  "canRead object with missing field as optional",
  (testkit) =&gt; {
    noExpectedIssues := (issues: List&lt;CompatibilityIssue&gt;): Void =&gt; {
      testkit.assert.equal(issues, &lt;CompatibilityIssue&gt;[])
    }
    newSchema := JsonObject(&lt;JsonObjectField&gt;[
      JsonObjectField("field1", JsonBoolean()),
      JsonObjectField("field2", JsonOneOf(&lt;JsonSchema&gt;[JsonBoolean(), JsonNull()]))
    ])
    oldSchema := JsonObject(&lt;JsonObjectField&gt;[
      JsonObjectField("field1", JsonBoolean())
    ])
    noExpectedIssues(newSchema-&gt;canRead(oldSchema))
  }
)

</code></pre><p>The library also supports enums (specific string values), which have their own compatibility rules with strings. What I have on my radar to dive into at some point:</p><ul><li><p>Generating documentation</p></li><li><p>Supporting regex</p></li><li><p>Generating client code</p></li></ul><p>But that&#8217;s enough of thinking about JSON for today.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[From long shot to one-shot]]></title><description><![CDATA[There&#8217;s a part of Twitter that&#8217;s been on-fire lately, talking about AI.]]></description><link>https://statelessmachine.com/p/from-long-shot-to-one-shot</link><guid isPermaLink="false">https://statelessmachine.com/p/from-long-shot-to-one-shot</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Sun, 16 Mar 2025 20:10:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!VXOW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff38e564a-5795-4f09-ade1-9206ba019999_2170x1494.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There&#8217;s a part of Twitter that&#8217;s been on-fire lately, talking about AI. It feels very vibrant. People are building things, discussing the best models, the best setups, completely new tooling is emerging. There&#8217;s  a lot of excitement. Some in-group lingo is emerging. Let me give a couple of examples:</p><ul><li><p>&#8220;One-shotting&#8221;: getting from zero work done to having the whole thing you wanted to accomplish completed after a single prompt. </p></li><li><p>&#8220;Vibe-coding&#8221;: interfacing with AI as the means of creating and iterating on a programming project and never interacting with the source code directly. </p></li></ul><p>And in the same way you can find a <a href="https://x.com/levelsio/status/1893350391158292550">proof-of-concept game created in the 30 minutes</a> that is now a <a href="https://fly.pieter.com/">multiplayer game</a> or even <a href="https://x.com/shl/status/1900534124097495454">funding for vibe-coders</a>, there&#8217;s an equal or greater amount of people reporting that AI feels useless to them. A lot of them feel like they are getting gaslit. It all looks like marketing-geared demos with no applicability in the real world of professional software engineering.</p><p>At some point I became increasingly intrigued by the discrepancy in experiences and thought it was time to give it a go and start using AI. And I had enough success from the get go to keep me going and believe it&#8217;s a very useful tool. So if you&#8217;re looking to get started with AI, I have two pieces of advice.</p><h4>Advice #1: Treat it as a new skill</h4><p>It took you some time to learn to write code the way you do right now. You&#8217;re probably still honing your craft. And you know your way to do things is far from the only way. You&#8217;ve met other practitioners that have ways of working so different to yours that it&#8217;s almost a miracle that you&#8217;re able to collaborate. It&#8217;s a bit of a similar problem when it comes to AI. Your old dog needs to learn new tricks. Poke it with curiosity about what will happen.</p><h4>Advice #2: Set yourself up for being rewarded</h4><p>There&#8217;s a lot of demos of apps being created from scratch with AI. And for good reason. AI is easier to use in new projects or small projects than otherwise. Start small. You can, for instance, start a new library in a programming language you&#8217;re not an expert in. Or you can take something as small as a single simple function alongside unit tests and ask chatgpt to write a similar one. Increase the complexity as your results get better. Play with how you&#8217;re asking the questions and the existing code that you&#8217;re feeding it. Build up an intuition as you see increasing success. And ride that joy.</p><p></p><p>As I&#8217;m not aspiring to be an inspirational guru, let&#8217;s get practical. I attempted to one-shot a syntax highlighting plugin for VS Code for my programming language, called tenecs. I&#8217;ve never built a VS Code plugin. I&#8217;m not even an avid VS Code user. I&#8217;ll report it in the present tense, as it is a transcription of my notes as I went through the process. You can skip to the end to see where I ended up if you don&#8217;t want to tag along on the ride.</p><p></p><p>Let&#8217;s start by feeding it a sample file of a parser combinators library I started building (<a href="https://gist.github.com/xplosunn/477e21e43bc444b9b9d2916f20f95521">gist</a>) as I think it has a good coverage over the language syntax. I&#8217;ll use the prompt: &#8220;<em>This is a file of a new programming language I made, called tenecs. Please create a vscode syntax highlighting plugin.</em>&#8221;. It suggested some files that I need to create and some steps to follow to create the plugin project. Looking at the files I can see some issues:</p><ul><li><p>Wrong file extension. It should be &#8220;.10x&#8221; rather than &#8220;.tenecs&#8221;.</p></li><li><p>Support for single-quote syntax, that the language doesn&#8217;t have.</p></li><li><p>The pattern for number literals does not include the potential minus at the start, used for negative numbers.</p></li><li><p>The list of keywords contains a bunch of keywords the language doesn&#8217;t have.</p></li></ul><p>Clearly a failed first attempt. I do happen to have a <a href="https://gist.github.com/xplosunn/65e93c33cf27d386b62a6c958a91318b">grammar definition</a> I can give as context. Let&#8217;s try giving it just the grammar and the prompt &#8220;<em>This is the grammar of a new programming language I made, called tenecs. The file extension is &#8220;.10x&#8221;. Please create a vscode syntax highlighting plugin.</em>&#8221; Looking at the output of this prompt I don&#8217;t quickly find any problems except that it missed the syntax for block comments. It is only supporting single-line comments. This made me realise that I don&#8217;t have the definitions for comments on the grammar I provided. So it speculated correctly, to some extent. There&#8217;s probably other problems there I haven&#8217;t spotted yet.</p><p>For my next attempt I&#8217;ll provide the grammar again but the prompt is &#8220;<em>This is the grammar of a new programming language I made, called tenecs. Besides that grammar, there is syntax for line comments and block comments. The file extension is &#8220;.10x&#8221;. Please create a vscode syntax highlighting plugin.</em>&#8221;. I don&#8217;t even state what the syntax is, as it is the most commonly used by other languages.</p><p>Now that I couldn&#8217;t find issues with the generated files (but they probably exist), when I tried to follow the suggested steps to create the plugin I&#8217;m starting to run into problems. Apparently it didn&#8217;t generate an entry-point for the vscode plugin and therefore the recommended tool to package the plugin fails. So my next attempt will be the same prompt, but followed by &#8220;<em>Don&#8217;t forget to create the index.js file.</em>&#8220;. </p><p>It went in a completely different direction. Bunch of problems:</p><ul><li><p>There&#8217;s no &#8220;index.js&#8221; file. </p></li><li><p>It&#8217;s missing some language constructs it had correctly done before. </p></li><li><p>It suggests very different steps and those don&#8217;t include the same plugin packaging tool. </p></li></ul><p>I&#8217;ve clearly hit a barrier here of what I can easily get it to do. Time to switch the approach. Instead of asking it to make the whole plugin for me, I&#8217;ll follow the official <a href="https://code.visualstudio.com/api/language-extensions/syntax-highlight-guide">Syntax Highlighting Guide</a>, because after that all I really need is the content for two files that describe a language and those it has already generated. I&#8217;ll repeat the previous prompt and attempt to just use those two files. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VXOW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff38e564a-5795-4f09-ade1-9206ba019999_2170x1494.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VXOW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff38e564a-5795-4f09-ade1-9206ba019999_2170x1494.png 424w, https://substackcdn.com/image/fetch/$s_!VXOW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff38e564a-5795-4f09-ade1-9206ba019999_2170x1494.png 848w, https://substackcdn.com/image/fetch/$s_!VXOW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff38e564a-5795-4f09-ade1-9206ba019999_2170x1494.png 1272w, https://substackcdn.com/image/fetch/$s_!VXOW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff38e564a-5795-4f09-ade1-9206ba019999_2170x1494.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VXOW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff38e564a-5795-4f09-ade1-9206ba019999_2170x1494.png" width="1456" height="1002" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f38e564a-5795-4f09-ade1-9206ba019999_2170x1494.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1002,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:419585,&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://statelessmachine.com/i/159128069?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff38e564a-5795-4f09-ade1-9206ba019999_2170x1494.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_!VXOW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff38e564a-5795-4f09-ade1-9206ba019999_2170x1494.png 424w, https://substackcdn.com/image/fetch/$s_!VXOW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff38e564a-5795-4f09-ade1-9206ba019999_2170x1494.png 848w, https://substackcdn.com/image/fetch/$s_!VXOW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff38e564a-5795-4f09-ade1-9206ba019999_2170x1494.png 1272w, https://substackcdn.com/image/fetch/$s_!VXOW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff38e564a-5795-4f09-ade1-9206ba019999_2170x1494.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>And we have a working plugin! I&#8217;m using a theme I wouldn&#8217;t normally use to code but that seems nice to quickly spot syntax highlighting issues. The syntax highlighting isn&#8217;t quite right, though:</p><ul><li><p>A declaration should have &#8220;:&#8221; and &#8220;=&#8221; as the same color.</p></li><li><p>&#8220;~&gt;&#8221; is part of a type definition. </p></li></ul><p>Let&#8217;s try to fix those two things with a new prompt: &#8220;<em>This is the grammar of a new programming language I made, called tenecs. Besides that grammar, there is syntax for line comments and block comments. Please create a vscode syntax highlighting plugin. Make sure the "&lt;", "&gt;", "|" and "~&gt;" are part of the type but all other symbols are bundled together.</em>&#8221;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XMhy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F794eb237-444c-4472-bb5f-a112f4ab5852_2226x1574.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XMhy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F794eb237-444c-4472-bb5f-a112f4ab5852_2226x1574.png 424w, https://substackcdn.com/image/fetch/$s_!XMhy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F794eb237-444c-4472-bb5f-a112f4ab5852_2226x1574.png 848w, https://substackcdn.com/image/fetch/$s_!XMhy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F794eb237-444c-4472-bb5f-a112f4ab5852_2226x1574.png 1272w, https://substackcdn.com/image/fetch/$s_!XMhy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F794eb237-444c-4472-bb5f-a112f4ab5852_2226x1574.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XMhy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F794eb237-444c-4472-bb5f-a112f4ab5852_2226x1574.png" width="1456" height="1030" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/794eb237-444c-4472-bb5f-a112f4ab5852_2226x1574.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1030,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:389292,&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://statelessmachine.com/i/159128069?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F794eb237-444c-4472-bb5f-a112f4ab5852_2226x1574.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_!XMhy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F794eb237-444c-4472-bb5f-a112f4ab5852_2226x1574.png 424w, https://substackcdn.com/image/fetch/$s_!XMhy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F794eb237-444c-4472-bb5f-a112f4ab5852_2226x1574.png 848w, https://substackcdn.com/image/fetch/$s_!XMhy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F794eb237-444c-4472-bb5f-a112f4ab5852_2226x1574.png 1272w, https://substackcdn.com/image/fetch/$s_!XMhy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F794eb237-444c-4472-bb5f-a112f4ab5852_2226x1574.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>It really seems to have focused on bundling all symbols together and things are overall a bit worse. Perhaps if we remove that part of the prompt and go with &#8220;<em>This is the grammar of a new programming language I made, called tenecs. Besides that grammar, there is syntax for line comments and block comments. Please create a vscode syntax highlighting plugin. Make sure the "&lt;", "&gt;", "|" and "~&gt;" are part of the type</em>&#8221;. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!16UN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1c9cd46-0d50-4913-9786-644bd83dcedb_2230x1566.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!16UN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1c9cd46-0d50-4913-9786-644bd83dcedb_2230x1566.png 424w, https://substackcdn.com/image/fetch/$s_!16UN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1c9cd46-0d50-4913-9786-644bd83dcedb_2230x1566.png 848w, https://substackcdn.com/image/fetch/$s_!16UN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1c9cd46-0d50-4913-9786-644bd83dcedb_2230x1566.png 1272w, https://substackcdn.com/image/fetch/$s_!16UN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1c9cd46-0d50-4913-9786-644bd83dcedb_2230x1566.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!16UN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1c9cd46-0d50-4913-9786-644bd83dcedb_2230x1566.png" width="1456" height="1022" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e1c9cd46-0d50-4913-9786-644bd83dcedb_2230x1566.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1022,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:391531,&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://statelessmachine.com/i/159128069?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1c9cd46-0d50-4913-9786-644bd83dcedb_2230x1566.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_!16UN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1c9cd46-0d50-4913-9786-644bd83dcedb_2230x1566.png 424w, https://substackcdn.com/image/fetch/$s_!16UN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1c9cd46-0d50-4913-9786-644bd83dcedb_2230x1566.png 848w, https://substackcdn.com/image/fetch/$s_!16UN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1c9cd46-0d50-4913-9786-644bd83dcedb_2230x1566.png 1272w, https://substackcdn.com/image/fetch/$s_!16UN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1c9cd46-0d50-4913-9786-644bd83dcedb_2230x1566.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>And it&#8217;s still far from the goal. Perhaps listing the symbols is not the way to go. Let&#8217;s try <em>&#8220;This is the grammar of a new programming language I made, called tenecs. Besides that grammar, there is syntax for line comments and block comments. Please create a vscode syntax highlighting plugin. Make sure the function types and sum types share the same highlight</em>&#8221;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Mpeq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b1531c-ea46-47a9-8d46-cbcfb6893abf_2202x1446.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Mpeq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b1531c-ea46-47a9-8d46-cbcfb6893abf_2202x1446.png 424w, https://substackcdn.com/image/fetch/$s_!Mpeq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b1531c-ea46-47a9-8d46-cbcfb6893abf_2202x1446.png 848w, https://substackcdn.com/image/fetch/$s_!Mpeq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b1531c-ea46-47a9-8d46-cbcfb6893abf_2202x1446.png 1272w, https://substackcdn.com/image/fetch/$s_!Mpeq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b1531c-ea46-47a9-8d46-cbcfb6893abf_2202x1446.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Mpeq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b1531c-ea46-47a9-8d46-cbcfb6893abf_2202x1446.png" width="1456" height="956" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4b1531c-ea46-47a9-8d46-cbcfb6893abf_2202x1446.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:956,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:381978,&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://statelessmachine.com/i/159128069?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b1531c-ea46-47a9-8d46-cbcfb6893abf_2202x1446.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_!Mpeq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b1531c-ea46-47a9-8d46-cbcfb6893abf_2202x1446.png 424w, https://substackcdn.com/image/fetch/$s_!Mpeq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b1531c-ea46-47a9-8d46-cbcfb6893abf_2202x1446.png 848w, https://substackcdn.com/image/fetch/$s_!Mpeq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b1531c-ea46-47a9-8d46-cbcfb6893abf_2202x1446.png 1272w, https://substackcdn.com/image/fetch/$s_!Mpeq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b1531c-ea46-47a9-8d46-cbcfb6893abf_2202x1446.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>Now it&#8217;s gotten close. But &#8220;:=&#8221;, &#8220;:?&#8221; and &#8220;=&gt;&#8221; definitely shouldn&#8217;t be be half-highlighted. Let&#8217;s go with &#8220;<em>This is the grammar of a new programming language I made, called tenecs. Besides that grammar, there is syntax for line comments and block comments. Please create a vscode syntax highlighting plugin. Make sure the function types and sum types share the same highlight. Make sure =&gt; and :? and := are highlighted.</em>&#8221;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8OoV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d22be6d-93f6-4cca-bb7b-632663dfa6c2_2636x1602.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8OoV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d22be6d-93f6-4cca-bb7b-632663dfa6c2_2636x1602.png 424w, https://substackcdn.com/image/fetch/$s_!8OoV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d22be6d-93f6-4cca-bb7b-632663dfa6c2_2636x1602.png 848w, https://substackcdn.com/image/fetch/$s_!8OoV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d22be6d-93f6-4cca-bb7b-632663dfa6c2_2636x1602.png 1272w, https://substackcdn.com/image/fetch/$s_!8OoV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d22be6d-93f6-4cca-bb7b-632663dfa6c2_2636x1602.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8OoV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d22be6d-93f6-4cca-bb7b-632663dfa6c2_2636x1602.png" width="1456" height="885" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5d22be6d-93f6-4cca-bb7b-632663dfa6c2_2636x1602.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:885,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:426605,&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://statelessmachine.com/i/159128069?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d22be6d-93f6-4cca-bb7b-632663dfa6c2_2636x1602.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_!8OoV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d22be6d-93f6-4cca-bb7b-632663dfa6c2_2636x1602.png 424w, https://substackcdn.com/image/fetch/$s_!8OoV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d22be6d-93f6-4cca-bb7b-632663dfa6c2_2636x1602.png 848w, https://substackcdn.com/image/fetch/$s_!8OoV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d22be6d-93f6-4cca-bb7b-632663dfa6c2_2636x1602.png 1272w, https://substackcdn.com/image/fetch/$s_!8OoV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d22be6d-93f6-4cca-bb7b-632663dfa6c2_2636x1602.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>Now we fixed that problem but &#8220;~&gt;&#8221; is no longer highlighted. Let&#8217;s go with &#8220;<em>This is the grammar of a new programming language I made, called tenecs. Besides that grammar, there is syntax for line comments and block comments. Please create a vscode syntax highlighting plugin. Make sure | and ~&gt; and =&gt; and :? and := are highlighted.</em>&#8221;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bHDu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6633e036-eb1f-464a-b5e2-8aac8cc7bb19_2660x1608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bHDu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6633e036-eb1f-464a-b5e2-8aac8cc7bb19_2660x1608.png 424w, https://substackcdn.com/image/fetch/$s_!bHDu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6633e036-eb1f-464a-b5e2-8aac8cc7bb19_2660x1608.png 848w, https://substackcdn.com/image/fetch/$s_!bHDu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6633e036-eb1f-464a-b5e2-8aac8cc7bb19_2660x1608.png 1272w, https://substackcdn.com/image/fetch/$s_!bHDu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6633e036-eb1f-464a-b5e2-8aac8cc7bb19_2660x1608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bHDu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6633e036-eb1f-464a-b5e2-8aac8cc7bb19_2660x1608.png" width="1456" height="880" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6633e036-eb1f-464a-b5e2-8aac8cc7bb19_2660x1608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:880,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:430011,&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://statelessmachine.com/i/159128069?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6633e036-eb1f-464a-b5e2-8aac8cc7bb19_2660x1608.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_!bHDu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6633e036-eb1f-464a-b5e2-8aac8cc7bb19_2660x1608.png 424w, https://substackcdn.com/image/fetch/$s_!bHDu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6633e036-eb1f-464a-b5e2-8aac8cc7bb19_2660x1608.png 848w, https://substackcdn.com/image/fetch/$s_!bHDu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6633e036-eb1f-464a-b5e2-8aac8cc7bb19_2660x1608.png 1272w, https://substackcdn.com/image/fetch/$s_!bHDu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6633e036-eb1f-464a-b5e2-8aac8cc7bb19_2660x1608.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>I&#8217;m going to consider this one a success. It&#8217;s a working plugin. It has questionable taste in the details but that&#8217;s what the human touch is for. The last sentence of my prompt where I specify some symbols is for sure not something I would do on a first prompt right now, but that goes to show that either the work needs to be iterative and/or there&#8217;s a lot of intuition for me to build up.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Big Short-circuit]]></title><description><![CDATA[Error handling in any given programming language is something that influences every program written in that language.]]></description><link>https://statelessmachine.com/p/the-big-short-circuit</link><guid isPermaLink="false">https://statelessmachine.com/p/the-big-short-circuit</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Mon, 10 Mar 2025 19:57:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!x6Gp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f8d7504-9fcd-4739-b0fa-74ac1bf2ac93_720x511.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Error handling in any given programming language is something that influences every program written in that language. Programmers are usually writing the happy path of their business logic and have to grapple with possible failures along the way. When faced with this problem designing my own language, tenecs, I took a look at what&#8217;s out there.</p><p>To have an example we can compare over different languages, let&#8217;s say you have two functions you want to put together. The scenario would be you&#8217;re building some service where ads are shown to free tier users but not premium users. You have &#8220;getSubscriptionByUserId&#8221;, which gives you the type of subscription a user has. You also have &#8220;shouldPlayAdvertisement&#8221;, which tells you whether to play the advertisement given the subscription type. Now you have to have &#8220;shouldPlayAdvertisementToUser&#8221;. </p><p>Example in javascript:</p><pre><code>function getSubscriptionByUserId(userId) {
  throw new Error('failed to connect to database');
}

function shouldPlayAdvertisement(subscriptionType) {
  return subscriptionType != 'premium';
}

function shouldPlayAdvertisementToUser(userId) {
  const subscription = getSubscriptionByUserId(userId);
  return shouldPlayAdvertisement(subscription);
}</code></pre><p>Since errors are thrown, in the &#8220;shouldPlayAdvertisementToUser&#8221; function you can only read the happy path. You don&#8217;t know which of the functions invoked might fail.</p><p>In wanted to take a nearly opposite approach with tenecs. I didn&#8217;t want some short-circuiting that&#8217;s invisible at any point in the code. Something more similar to golang, which uses early returns.</p><p>Example in golang:</p><pre><code><code>func getSubscriptionByUserId(userId string) (string, error) {
&#9;return "", errors.New("failed to connect to database")
}

func shouldPlayAdvertisement(subscriptionType string) bool {
&#9;return subscriptionType != "premium"
}

func shouldPlayAdvertisementToUser(userId string) (bool, error) {
&#9;subscription, err := getSubscriptionByUserId(userId)
&#9;if err != nil {
&#9;&#9;return false, err
&#9;}
&#9;return shouldPlayAdvertisement(subscription), nil
}
</code></code></pre><p>Example in tenecs:</p><pre><code>getSubscriptionByUserId := (userId: String): String | Error =&gt; {
  Error("failed to connect to database")
}

shouldPlayAdvertisement := (subscriptionType: String): Boolean =&gt; {
  not(subscriptionType-&gt;eq("premium"))
}

shouldPlayAdvertisementToUser := (userId: String): Boolean | Error =&gt; {
  when getSubscriptionByUserId(userId) {
    is err: Error =&gt; {
      err
    }
    is userId: String =&gt; {
      shouldPlayAdvertisement(userId)
    }
  }
}</code></pre><p>Now we have something very explicit, but due to the lack of early returns, it came with a cost. Nesting. Code with lots of errors will be super nested. It&#8217;s a big loss in readability. Wouldn&#8217;t want to be the next iteration of the hadouken meme (<a href="https://www.reddit.com/r/ProgrammerHumor/comments/3hbov9/hadouken/">source</a>):</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!x6Gp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f8d7504-9fcd-4739-b0fa-74ac1bf2ac93_720x511.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!x6Gp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f8d7504-9fcd-4739-b0fa-74ac1bf2ac93_720x511.webp 424w, https://substackcdn.com/image/fetch/$s_!x6Gp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f8d7504-9fcd-4739-b0fa-74ac1bf2ac93_720x511.webp 848w, https://substackcdn.com/image/fetch/$s_!x6Gp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f8d7504-9fcd-4739-b0fa-74ac1bf2ac93_720x511.webp 1272w, https://substackcdn.com/image/fetch/$s_!x6Gp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f8d7504-9fcd-4739-b0fa-74ac1bf2ac93_720x511.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!x6Gp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f8d7504-9fcd-4739-b0fa-74ac1bf2ac93_720x511.webp" width="720" height="511" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5f8d7504-9fcd-4739-b0fa-74ac1bf2ac93_720x511.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:511,&quot;width&quot;:720,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!x6Gp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f8d7504-9fcd-4739-b0fa-74ac1bf2ac93_720x511.webp 424w, https://substackcdn.com/image/fetch/$s_!x6Gp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f8d7504-9fcd-4739-b0fa-74ac1bf2ac93_720x511.webp 848w, https://substackcdn.com/image/fetch/$s_!x6Gp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f8d7504-9fcd-4739-b0fa-74ac1bf2ac93_720x511.webp 1272w, https://substackcdn.com/image/fetch/$s_!x6Gp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f8d7504-9fcd-4739-b0fa-74ac1bf2ac93_720x511.webp 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>This is probably not a problem you&#8217;ve ever faced because all major languages have solutions for it. I&#8217;ve mentioned throwing and early returns but there&#8217;s another solution worth looking into. Languages that have some sort of &#8220;Result&#8221; type, that encapsulates the possibility of success or failure, have some additional syntax. We usually call this &#8220;syntactic sugar&#8221;, because it reads nicer, but is redundant syntax, as it doesn&#8217;t enable something new. It&#8217;s still not exactly the same, as in tenecs you return directly something like &#8220;Boolean | Error&#8221; instead of a &#8220;Result&#8221; type, but it might get us closer.</p><p>Scala has <a href="https://docs.scala-lang.org/tour/for-comprehensions.html">for comprehensions</a>, probably inspired by Haskell&#8217;s <a href="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/monad_comprehensions.html">similar solution</a>. It doesn&#8217;t fully get rid of the nesting, but you can have only one level of nesting even when calling multiple functions that might error.</p><p>Example in Scala:</p><pre><code><code>def getSubscriptionByUserId(userId: String): Try[String] = {
  Failure(new RuntimeException("failed to connect to database"))
}

def shouldPlayAdvertisement(subscriptionType: String): Boolean = {
  subscriptionType != "premium"
}

def shouldPlayAdvertisementToUser(userId: String): Try[Boolean] = {
  for {
    subscription &lt;- getSubscriptionByUserId(userId)
  } yield shouldPlayAdvertisement(subscription)
}</code></code></pre><p>Gleam has a quite clever solution to this problem. You can use their <a href="https://tour.gleam.run/advanced-features/use/">use keyword</a> to turn the rest of your function into a lambda parameter.</p><p>Example in Gleam:</p><pre><code>fn get_subscription_by_user_id(user_id: String) -&gt; Result(String, String) {
  Error("failed to connect to database")
}

fn should_play_advertisement(subscription_type: String) -&gt; Bool {
  subscription_type != "premium"
}

fn should_play_advertisement_to_user(user_id: String) -&gt; Result(Bool, String) {
  use subscription &lt;- result.map(get_subscription_by_user_id(user_id))
  should_play_advertisement(subscription)
}</code></pre><p>Rust has a different way of addressing this by using their <a href="https://doc.rust-lang.org/rust-by-example/std/result/question_mark.html">&#8220;?&#8221; operator</a> to short-circuit on the error case.</p><p>Example in Rust:</p><pre><code><code>fn get_subscription_by_user_id(user_id: &amp;str) -&gt; Result&lt;String, String&gt; {
    Err("failed to connect to database".to_string())
}

fn should_play_advertisement(subscription_type: &amp;str) -&gt; bool {
    subscription_type != "premium"
}

fn should_play_advertisement_to_user(user_id: &amp;str) -&gt; Result&lt;bool, String&gt; {
    let subscription = get_subscription_by_user_id(user_id)?;
    Ok(should_play_advertisement(&amp;subscription))
}</code></code></pre><p>So Rust seems to be really close to something I could use. Since I don&#8217;t have a Result type and don&#8217;t want to constrain the short-circuiting to a specific Error type, I&#8217;ll have to make the user annotate either the type to be returned or the type to use. Inspired by Gleam, I&#8217;ll handle this on the left-hand side of variable declaration. Let&#8217;s start with an example having both.</p><p>Example in tenecs:</p><pre><code><code>shouldPlayAdvertisementToUser := (userId: String): Boolean | Error =&gt; {
  userId: String ? Error = getSubscriptionByUserId(userId)
  shouldPlayAdvertisement(userId)
}</code></code></pre><p>Now I can decide to omit one of the types and let the compiler infer it.</p><p>Example in tenecs with variable type inferred:</p><pre><code><code>shouldPlayAdvertisementToUser := (userId: String): Boolean | Error =&gt; {
  userId :? Error = getSubscriptionByUserId(userId)
  shouldPlayAdvertisement(userId)
}</code></code></pre><p>Example in tenecs with returned short-circuit type inferred:</p><pre><code><code>shouldPlayAdvertisementToUser := (userId: String): Boolean | Error =&gt; {
  userId: String ?= getSubscriptionByUserId(userId)
  shouldPlayAdvertisement(userId)
}</code></code></pre><p>It definitely doesn&#8217;t feel like a silver bullet. It doesn&#8217;t allow chaining of expressions. It doesn&#8217;t feel familiar to people coming from any language whatsoever. So I&#8217;m not sure if I arrived at a jumbled version or a greatest hits remix, but it seems to be very effective at what it&#8217;s supposed to do. So far when writing tenecs code I&#8217;m trying to reach for it on every opportunity. Time will tell if I keep growing fond of it or learn to dislike it. </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The origin story of my programming language]]></title><description><![CDATA[Whenever the programming language I am building comes up in a conversation, the most asked question is &#8220;why?&#8221;.]]></description><link>https://statelessmachine.com/p/the-origin-story-of-my-programming</link><guid isPermaLink="false">https://statelessmachine.com/p/the-origin-story-of-my-programming</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Sun, 02 Mar 2025 13:24:17 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Whenever the programming language I am building comes up in a conversation, the most asked question is &#8220;why?&#8221;.  I found myself the other day getting asked about it again and figured it&#8217;s time to write it. </p><p>At some point I read somewhere, either in a tweet or blog post (can&#8217;t find the source right now), that your product being marginally better doesn&#8217;t beat the inertia people have against switching over. Your product needs to be ten times better.</p><p>And so I thought&#8230; What does it take for a programming language to be ten times better?</p><p>That thought propelled the work on the programming language I later named &#8220;tenecs&#8221;. The name is a word play on &#8220;10 x&#8221;, where the &#8220;x&#8221; means mathematical &#8220;times&#8221;. The file extension is &#8220;.10x&#8221;.</p><p>I didn&#8217;t necessarily have an answer for what ten times better could look like, but I thought it started with testing. I have professionally written way more web service backend code than anything else, so that&#8217;s where my focus started. Not discarding the idea of it being a general purpose programming language, but it&#8217;s easier for me to identify the pain points and potential improvements in that area.</p><p>Whenever I&#8217;m changing existing code, to fix a bug or add a new functionality, it&#8217;s way more pleasant and productive when I can easily change and/or add new tests related to the change and be confident I&#8217;m done with it. The &#8220;being done with it&#8221; is fuzzier topic to attempt to tackle, but making it easy to add and/or change tests is something that I think could be tackled.</p><p>So my first idea was: why do I write unit tests at all? If I have a function that I can unit test, I could have a command that goes through code and gives me unit tests for each code path. In order for the function to be unit testable I realised I can have <a href="https://statelessmachine.com/p/forcefully-deterministic-unit-testing">forcefully deterministic unit tests</a> by making sure side-effecting functions always come from arguments. So, knowing it can be built, came the question: would these generated tests be good? Would I want to keep them? Maybe they give me an accurate overview of how the system behaves technically but are too detached from what the system is meant to achieve? I tried exploring this idea without building a prototype by talking with some people I consider to be very smart and have vast experience on this topic, but we didn&#8217;t come to much of a conclusion. Would have to build to see.</p><p>I started working on it. Let me give you a concrete example of my early efforts, taken out of a unit test of the test generator:</p><pre><code><code>package example_package

import tenecs.test.UnitTest

// the function we feed to the test generator
logPrefix := (isError: Boolean): String =&gt; {
  if isError {
    "[error]"
  } else {
    "[info]"
  }
}

//
// The output of the test generator: two tests
//

// 1. Test named "[error]", that checks that output
_ := UnitTest("[error]", (testkit) =&gt; {
  result := logPrefix(true)

  expected := "[error]"
  testkit.assert.equal(result, expected)
})

// 2. Test named "[info]", that checks that output
_ := UnitTest("[info]", (testkit) =&gt; {
  result := logPrefix(false)

  expected := "[info]"
  testkit.assert.equal(result, expected)
})</code></code></pre><p>In the process of building this I kept thinking about the potential of tooling enabled by the constraint of having side-effecting functions always come from arguments. So, even though I have built some stuff in the original testing direction, I am now investing on building the language also with other tools in mind. </p><p>I don&#8217;t want to turn this post into a list of everything I&#8217;d like to achieve, and the test generator is definitely still on the list, but I&#8217;ll let you the testing idea that currently excites me the most.</p><p></p><h4>Test generation out of a trace</h4><p>Generating tests out of the implementation won&#8217;t necessarily produce realistic test scenarios. But what if we could record real code runs and turn them into tests? You can&#8217;t have more realistic than that. We could have a tracing tool, somewhat like jaegar or honeycomb, but with a button that turns a trace into a unit test. With this tool I&#8217;m imagining you could:</p><ul><li><p>Take an error out of production and as soon as you find it on the tracing tool, you can have a unit test that reproduces it.</p></li><li><p>Have another person (maybe QA or PO) do some business-oriented testing and you can take those to be unit test cases you want to maintain.</p></li><li><p>As soon as you finish writing your new feature and run the code to check that it behaves as intended, you can press the button to get that exact scenario as a unit test. Easier than having to write the code for that yourself afterwards.</p></li></ul><p></p><p>I still have a lot of work to do on this hobby project, but hopefully after reading this you&#8217;re also a bit excited by the idea or at least understand my excitement around it.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Dividing ponies]]></title><description><![CDATA[A lot of UX considerations can go into a programming language.]]></description><link>https://statelessmachine.com/p/dividing-ponies</link><guid isPermaLink="false">https://statelessmachine.com/p/dividing-ponies</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Sat, 15 Feb 2025 10:35:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A lot of UX considerations can go into a programming language. Every constraint you put in place, due to your design goals, will have vast repercussions.</p><p>I decided that in my programming language, currently named &#8220;tenecs&#8221; (more on that some other day), I don&#8217;t want to have things that shortcircuit the call stack. What I mean is I don&#8217;t want to have the control flow that is usually enabled by keywords such as &#8220;panic&#8221; in Golang or &#8220;throw&#8221; in Java, Javascript and many others. </p><p>If you can&#8217;t throw, how should division by zero work? </p><p>Mathematically it&#8217;s not defined. So it shouldn&#8217;t be a supported operation. But I don&#8217;t have a non-zero integer type. Division is defined over the `Int` type the same way `plus` and `times` are. </p><p>What do we do when a function can technically take a value as input that is not a valid input? In a language where errors aren&#8217;t thrown, the error is returned. But in the case of division, maybe it will feel clunky and unnecessary? When doing math operations in programming we expect to be able to chain them and not have to deal with intermediate error handling. </p><p>Gladly, I&#8217;m definitely not the first one to stumble upon this problem!</p><p>The Pony programming language had a similar problem and has a section on their tutorial dedicated to <a href="https://tutorial.ponylang.io/gotchas/divide-by-zero.html">Divide by Zero</a>: </p><blockquote><p>In Pony, <em>integer division by zero results in zero</em></p></blockquote><p>Inspired by their approach, I decided to support both normal &#8220;div&#8221; and &#8220;ponyDiv&#8221;:</p><pre><code>package example

import tenecs.error.Error
import tenecs.list.fold
import tenecs.list.length
import tenecs.int.div
import tenecs.int.ponyDiv
import tenecs.int.sum

// Average that returns an error when you pass an empty List&lt;Int&gt;
avg := (values: List&lt;Int&gt;): Int | Error =&gt; {
  val total = fold(values, 0, (acc, elem) =&gt; { acc + elem })
  div(total, length(values))
}

// Average that returns zero when you pass an empty List&lt;Int&gt;
ponyAvg := (values: List&lt;Int&gt;): Int =&gt; {
  val total = fold(values, 0, (acc, elem) =&gt; { acc + elem })
  ponyDiv(total, length(values))
}</code></pre><p>If you prefer reading code with pipe syntax, the language supports it using &#8220;-&gt;&#8221;. Here&#8217;s the same code using that syntax:</p><pre><code>package example

import tenecs.error.Error
import tenecs.list.fold
import tenecs.list.length
import tenecs.int.div
import tenecs.int.ponyDiv
import tenecs.int.sum

// Average that returns an error when you pass an empty List&lt;Int&gt;
avg := (values: List&lt;Int&gt;): Int | Error =&gt; {
  val total = values-&gt;fold(0, (acc, elem) =&gt; { acc + elem })
  total-&gt;div(values-&gt;length())
}

// Average that returns zero when you pass an empty List&lt;Int&gt;
ponyAvg := (values: List&lt;Int&gt;): Int =&gt; {
  val total = values-&gt;fold(0, (acc, elem) =&gt; { acc + elem })
  total-&gt;ponyDiv(values-&gt;length())
}</code></pre><p>Given either div or ponyDiv it's possible for users to implement the other, so I might drop one of them at some point. But for now, users have the choice.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Nailing the live coding interview]]></title><description><![CDATA[The objective behind a live coding interview, from the interviewer&#8217;s point of view, is usually to get a feeling of what having the interviewee on your team.]]></description><link>https://statelessmachine.com/p/nailing-the-live-coding-interview</link><guid isPermaLink="false">https://statelessmachine.com/p/nailing-the-live-coding-interview</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Thu, 28 Nov 2024 06:40:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The objective behind a live coding interview, from the interviewer&#8217;s point of view, is usually to get a feeling of what having the interviewee on your team. That is done in the live interview by trying to get a feeling of their coding abilities and what it's like to talk to them about code. The devil is in the details, as usual. How much it is split between those two objectives? What tools are allowed? In which parts does the interviewer help solve the problem or gives more food for thought? All those vary, but don't lose hope just yet. There's a common pattern. </p><p></p><p>You have to talk.</p><p></p><p>Talk.</p><p></p><p>Talk. Talk. Talk.</p><p></p><p>If you improve on that axis, you might increase your chances of a successful interview. </p><p>By talking you give the interviewer a chance to see how you think. </p><p>By talking you give the interviewer a chance to help you. </p><p>By talking you give the interviewer a chance to talk with you. </p><p></p><p>So how do we turn you into a radio star? Ready to talk non-stop but also ready to be interrupted with questions, without stopping the show. Talking about your thoughts is definitely a different skill from having them in the first place. </p><p>If you were making a presentation about a train of thought, you would probably not structure it the way it actually happened. You would change the narrative such that it tells a story, because stories are engaging. </p><p>But in a live setting you don't have the luxury of going through things and later turning it into a story. So we have to do the other way around. You adapt your way of thinking to let the story unfold. </p><p>The story of your interview is how it goes from zero to the challenge solved, with a nice conversation had along the way. This will feel more natural once we lay out the story arc:</p><ol><li><p>The problem is presented</p></li><li><p>Understand the problem and the relevant concerns</p></li><li><p>Decide on the approach to tackle the problem </p></li><li><p>Start solving</p></li></ol><p></p><p><strong>1. The problem is presented</strong></p><p>The interviewer will tell / show you what the problem is about or present you with a problem statement for you to read. Pay attention to what they are saying. Interrupt with questions when you are two steps behind. Otherwise, let them continue.</p><p>When a problem is presented, a lot of times it goes like &#8220;and <em>Blob</em> is made out of <em>Foo</em>&#8221;, when they have explained <em>Blob</em> but not <em>Foo</em>. Give the interviewer a chance to explain <em>Foo</em> before you jump in asking what it is. Allow yourself to be one step behind. If you are two steps behind, then you are at risk of losing the train. Interrupt then.</p><p>You can phrase your question like: &#8220;I&#8217;m sorry, can we please take a step back? I&#8217;m not sure if I fully understood <em>Foo</em>.&#8221; Then repeat in your own words what you understood about it. That will help the interviewer help you.</p><p></p><p><strong>2. Understand the problem and the relevant concerns</strong></p><p>At this stage you&#8217;ve been given the goal of the game you&#8217;re about to play, but not the rules of the game. You should clarify the scope of concerns to take into account. </p><p>Example questions:</p><ul><li><p>Do I need to think of concurrency?</p></li><li><p>Is storage part of the scope of the problem</p></li><li><p>Is the API meant to be optimised for read or write?</p></li></ul><p>A couple of questions to test the waters should help you get closer to the mental model of the interviewer. </p><p></p><p><strong>3. Decide on the approach to tackle the problem</strong> </p><p>If you had the goal of helping a fellow developer tackle a task, it&#8217;s likely that before letting that person write code you would think of the easiest way to tackle the problem. Do the same thing here, double checking with the interviewer. </p><p>Example questions:</p><ul><li><p>The existing tests seem to be in increasing order of complexity. I&#8217;ll turn them from red to green, going top to bottom. Is that ok?</p></li><li><p>There seem to be specific concerns we should have when the API is designed. Could I start from there?</p></li></ul><p></p><p><strong>4. Start solving</strong></p><p>At this point you&#8217;ve set the stage for what&#8217;s happening next. You are now ready to take the first step and start talking through the code you are writing. You have all of the context you previously built, ready to be leveraged in the conversation. It&#8217;s okay to not literally talk and write at the same time. You can talk between lines of code. You can also write code comments with notes to come back to. It&#8217;s easier to talk while writing a comment, because you&#8217;re writing in plain English, so you can say the sentence as you write.</p><p></p><p>Follow the steps you created for yourself and unfold your narrative into your happy ever after.</p><p></p><p><strong>Bonus tip:</strong></p><p>You can practice by yourself by writing code while talking about what you are doing. This helps you get into the headspace of narrating as you go.</p><p></p><p>Interviews are hard. Good luck out there!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Built something so complex I no longer want to deliver it]]></title><description><![CDATA[A friend reached out to me the other day with a question:]]></description><link>https://statelessmachine.com/p/built-something-so-complex-i-no-longer</link><guid isPermaLink="false">https://statelessmachine.com/p/built-something-so-complex-i-no-longer</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Mon, 30 Sep 2024 15:42:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A friend reached out to me the other day with a question:<br></p><blockquote><p>How do I tell my team that the project I've been working on has become so complex I no longer want to deliver it? I think it would actually be detrimental to the codebase.</p></blockquote><p></p><p>What popped in my mind was: this looks like accidental complexity. That&#8217;s something that can be tackled. If you haven&#8217;t heard &#8220;essential&#8221; vs &#8220;accidental&#8221; complexity before, I bring you a brief explanation.</p><p>Essential complexity is the complexity of the problem you are solving. Any solution you deliver cannot be less complex than that complexity, as the solution can&#8217;t be less complex than the problem itself. Otherwise, the problem is not fully addressed. </p><p>Accidental complexity is the complexity you add to your solution, while building it. There's always some of it. Usually there&#8217;s a fair share of it. And also, not so rarely, there&#8217;s a ton of it.</p><p></p><p>So my actual answer to my friend, after explaining &#8220;essential&#8221; vs &#8220;accidental&#8221; complexity was:</p><blockquote><p>It sounds like you landed on something that has a lot of accidental complexity, which is common, when you're solving a problem for the first time without having good examples or experience on it. </p><p>So now the question is how much would the team like to invest on bringing it down.</p><p>You can tell them you have something, but it feels a bit overblown for the problem it is solving, as you were exploring and adapting as you were figuring things out. If someone would pair with you, where you explain the code and both of you work together on making it clean, you would probably arrive at something maintainable. Because as it stands, it's a bit rough</p><p>That gives the team an option to double down or back down.</p></blockquote><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The magical dot]]></title><description><![CDATA[Programming language syntax]]></description><link>https://statelessmachine.com/p/the-magical-dot</link><guid isPermaLink="false">https://statelessmachine.com/p/the-magical-dot</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Thu, 19 Sep 2024 17:10:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As I&#8217;m working on my programming language, syntax is sometimes a struggle. There&#8217;s a lot of inspiration one can get from existing languages, but things from different languages a lot of times don&#8217;t match. Sometimes it even feels like things from the same language don&#8217;t match. And it&#8217;s super hard to discuss this as it comes down to familiarity and taste. So I know my mental models are all biased. Please excuse any generalisations that don&#8217;t apply to you.</p><p></p><p>When working with some code in some IDE, something I see developers do frequently, is to write some variable name, press &#8220;.&#8221; and then see what auto-complete suggests. I do it myself as well. This is a big deal in exploring what once can do with a variable of some type. </p><p>I also like the end result. Out of the following two examples I prefer the first one:</p><pre><code>var1.concat(var2)</code></pre><pre><code>concat(var1, var2)</code></pre><p>The problem in most languages is that you can only do the first one when `concat` is defined in the same place as the type for `var1`. There&#8217;s two approaches for enabling this feature:</p><ul><li><p>Extensions, like <a href="https://kotlinlang.org/docs/extensions.html">Kotlin</a> and <a href="https://docs.scala-lang.org/scala3/reference/contextual/extension-methods.html">Scala</a> have</p></li><li><p>Piping, like <a href="https://rescript-lang.org/docs/manual/latest/pipe">rescript</a> and <a href="https://tour.gleam.run/functions/pipelines/">Gleam</a> have</p></li></ul><p>Extensions don&#8217;t allow the reader of the code to know if it&#8217;s an extension or not without checking the definition and/or imports. I think I would prefer something that acts more like syntactic sugar. </p><p></p><p>The other approach seems fine. And while I don&#8217;t like to read `var1|&gt;concat(var2)` (with a space it becomes better), I do like `var1-&gt;concat(var2)`. </p><p>So I can just adopt what rescript has, right? If a user of my language wants to see what functions take the type of `var1` as an argument, they can write `var1-&gt;` and let the IDE do its magic! And if they want to look at the fields, they use &#8220;.&#8221; instead of &#8220;-&gt;&#8221;. </p><p></p><p>Problem solved?</p><p></p><p>Unfortunately not yet. </p><p></p><p>In order to add new syntax, it needs to match what is already there. Let me show you some example code, so you get an idea of the existing syntax:</p><pre><code>import tenecs.list.map

struct Todo(
  title: String,
  done: Boolean
)

struct TodoFilter(
  predicateFunction: (Todo) -&gt; Boolean
)

exampleFunctionUsingFilter := (): Void =&gt; {
  todoList := [](
    Todo("finish blog post", false),
    Todo("publish blog post", false)
  )

  filterThatRejectsAll := TodoFilter((todo) =&gt; false)

  emptyList := map(todoList, filterThatRejectsAll.predicateFunction)
}</code></pre><p>You can see in that example that `-&gt;` is being used to declare a function type. I searched for another symbol that would also work, so I don&#8217;t have to mess with existing syntax, but haven&#8217;t found anything as satisfying as `-&gt;`. So, if I want to use that, then I need another symbol for the functions. I can use `=&gt;`. But I was using that as part of the syntax for declaring a function. And that one I can drop without replacement, if I no longer allow defining single-expression functions without the brackets around them. </p><p>Let&#8217;s try those changes:</p><pre><code>import tenecs.list.map

struct Todo(
  title: String,
  done: Boolean
)

struct TodoFilter(
  predicateFunction: (Todo) =&gt; Boolean // changed -&gt; to =&gt;
)

exampleFunctionUsingFilter := (): Void { // dropped =&gt;
  todoList := [](
    Todo("finish blog post", false),
    Todo("publish blog post", false)
  )

  filterThatRejectsAll := TodoFilter((todo) { false }) // dropped =&gt;

  emptyList := map(todoList, filterThatRejectsAll.predicateFunction)
}</code></pre><p>And, for completeness&#8217; sake, let&#8217;s apply our new syntactic sugar:</p><pre><code>import tenecs.list.map

struct Todo(
  title: String,
  done: Boolean
)

struct TodoFilter(
  predicateFunction: (Todo) =&gt; Boolean
)

exampleFunctionUsingFilter := (): Void {
  todoList := [](
    Todo("finish blog post", false),
    Todo("publish blog post", false)
  )

  filterThatRejectsAll := TodoFilter((todo) { false })

  // doesn't look too bad, I think?
  emptyList := todoList-&gt;filterThatRejectsAll.predicateFunction()
}</code></pre><p>I&#8217;m nearly happy. I don&#8217;t mind too much that we lost the `=&gt;` on exampleFunctionUsingFilter` but on `filterThatRejectsAll`&#8230; There I&#8217;m not a fan at all. It&#8217;s missing something. I might end up having to introduce a third, weirder arrow, like `+&gt;` or `~&gt;` to not have ambiguity there. </p><p>I suspect there&#8217;s a best case scenario here that I just haven&#8217;t yet puzzled together. I&#8217;ll think about it some more.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Does your team have momentum?]]></title><description><![CDATA[Imagine a developer in your team, unprompted, did:]]></description><link>https://statelessmachine.com/p/does-your-team-have-momentum</link><guid isPermaLink="false">https://statelessmachine.com/p/does-your-team-have-momentum</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Mon, 16 Sep 2024 15:15:10 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Imagine a developer in your team, unprompted, did:</p><ul><li><p>a refactor that nobody asked for but others liked</p></li><li><p>a bug fix for a bug that wasn&#8217;t reported</p></li><li><p>an improvement to a test suite</p></li></ul><p></p><p>Assume this change took half a day.</p><p></p><p>Are you happy or upset with it? Is that a good or a bad thing? Did the developer go rogue or showcase initiative?</p><p></p><p>I think the answer lies in the perceived momentum of your team. If it&#8217;s perceived that there&#8217;s no time to waste (low momentum), such time spent feels misaligned with the absolute most critical mission important goals. And that is upsetting. If it&#8217;s perceived that the team is a powerhouse of delivering the goods, then improvements are great, as they are aligned with preserving what makes the team so good.</p><p></p><p>It&#8217;s important for you to know where your team sits and adapt to it. If you hear &#8220;we don&#8217;t fix things just because we can&#8221; you should avoid taking initiative by yourself. Initiatives require synchronisation and prioritisation, because time is of the essence. That might feel like a waste of time to you, but you have to consider that you taking actions that are perceived as you going rogue means people will perceive you as less reliable. It&#8217;s a net negative overall. </p><p></p><p>Team momentum is a hell of a drug and makes a lot of actions that seem bad become suddenly welcome. People are suddenly less concerned if you are running slightly in the wrong direction, because direction is in such an environment easy to correct. It&#8217;s much easier to correct course than it is to get things going. </p><p></p><p>So, does your team have momentum? And are you acting accordingly?</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Are we all library developers?]]></title><description><![CDATA[I&#8217;m not sure exactly what prompted it, but I found myself a couple of times debating the topic:]]></description><link>https://statelessmachine.com/p/are-we-all-library-developers</link><guid isPermaLink="false">https://statelessmachine.com/p/are-we-all-library-developers</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Sun, 08 Sep 2024 18:31:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;m not sure exactly what prompted it, but I found myself a couple of times debating the topic:<br><br>Is application development the same as library development?</p><p></p><p>I&#8217;ve mostly been on the camp that it&#8217;s not. I think mostly because it felt different to me. But quite a few times I had a hard time putting my finger on why. It&#8217;s still writing code. It still has goals of what that code is meant to support / achieve. Even if when doing open source those goals are quite different, when doing closed source library they overlap more.</p><p></p><p>So is it different?</p><p></p><p>It still feels different to me.</p><p></p><p>Why, though?</p><p></p><p>The most fundamental difference that I have in mind right now is how other developers will interact with the code that I write. </p><p>When I write library code, I expect most of the interaction with my code to be done using the APIs and mostly treating the internals as a blackbox. They&#8217;re there for you to dive into, but you&#8217;ll avoid it unless really necessary.</p><p>When I write application code, I expect most of the interaction with my code will be trying to read it, in order to iterate on it or debug it. Instead of shying away from reading and understanding it, you&#8217;ll take the default stance of using it for gathering context related to what you&#8217;re trying to achieve.</p><p></p><p>But then again, I still write my code to achieve its purpose and taking into account the preferences of those working with me. So the distinction might just be in my head and not quite real. Go figure.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[TrAgIc software development]]></title><description><![CDATA[I was having a conversation the other day about how agile / scrum can look so different in different companies.]]></description><link>https://statelessmachine.com/p/tragic-software-development</link><guid isPermaLink="false">https://statelessmachine.com/p/tragic-software-development</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Wed, 28 Aug 2024 16:19:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I was having a conversation the other day about how agile / scrum can look so different in different companies. I think most people haven&#8217;t experienced an extreme opposite of corporate agile environment. So let me introduce you to a real team workflow that I experienced at some point. It was used in a startup developing a web app. In this post I will name it &#8220;Truly Agile Icarus&#8221;, or &#8220;TrAgIc&#8221;, for short. </p><p>In TrAgIc, there is only one recurring meeting with a fixed schedule: the retrospective.</p><p>There is also only one recurring meeting without a fixed schedule: feature introduction.</p><p>How did it work at all? With one golden rule:</p><blockquote><p>Whenever you are doing some work, you make sure you empower whoever comes after you to also get their job done. When in doubt, go talk to people.</p></blockquote><p></p><p>Let&#8217;s go a bit more concrete on how a feature might go:</p><ol><li><p>Product Owner presents it at the feature introduction meeting. This includes a document describing things from a user point of view, that people can refer back to. Frontend and backend team ask questions and create tickets on the issue tracker of choice, which dependencies between them.</p></li><li><p>Backend dev picks up backend ticket. Things are implemented in a backwards compatible manner, so backend will have a code review and be merged before any frontend work starts. As part of doing the ticket, they also provide all the required documentation such that the frontend dev can do their work.</p></li><li><p>Frontend dev picks up frontend ticket. Code is reviewed and merged.</p></li><li><p>Product owner tests the feature.</p></li><li><p>At some point, product owner thinks there are enough new features to make a production release and asks someone to do so. </p></li></ol><p></p><p>This all looks really simple and radically optimistic, but I have seen it work first-hand. As long as the golden rule is respected. Because if you look at each of those points, a long of things can go outside of the happy path. And you&#8217;re responsible for communicating so. Some examples might also help here:</p><ol><li><p>Feature introduction meeting</p><ul><li><p>After questions from the devs, Product Owner realises they need to re-work the feature. There will be later in time another Feature introduction meeting for the same feature.</p></li><li><p>Backend devs believe they should discuss how the new feature should be approached, because it impacts the architecture. They schedule a meeting to do so. Somebody is appointed to prepare and run that meeting.</p></li><li><p>Frontend devs notice that the feature is not testable unless some non-user-facing work is done is done in the backend. It goes into the backend ticket(s) to do that work.</p></li></ul></li><li><p>Backend work</p><ul><li><p>Backend dev is not sure what would be the correct API changes to make frontend implementation easier. They go talk to one or more frontend devs.</p></li></ul></li><li><p>Frontend work</p><ul><li><p>The documentation provided by backend was insufficient for the feature implementation. They request for what is missing to be added.</p></li></ul></li><li><p>Testing</p><ul><li><p>Found a bug but not sure if it&#8217;s a backend or frontend issue? Get a developer to look at it with you and help determine the root cause.</p></li></ul></li></ol><p>At any stage: is this same problem happening a lot of times? Discuss it in the retrospective meeting! </p><p></p><p>Does this mean this way of working is purely a feature factory without time for refactors? It might surprise you, but it does not. If it&#8217;s a small refactor, you just do it. If it&#8217;s a bigger one, you discuss with others how to tackle it and create the tickets. Because refactors are a part of normal work and you trust people to be reasonable about their split between feature work and other necessary work, things will be picked up accordingly. And if you think the current split is unbalanced? </p><p>I hope you guessed it by now&#8230;</p><p>You talk to people!</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[That's a nice id]]></title><description><![CDATA[Another day where a bug was because the wrong id was being passed somewhere.]]></description><link>https://statelessmachine.com/p/thats-a-nice-id</link><guid isPermaLink="false">https://statelessmachine.com/p/thats-a-nice-id</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Wed, 14 Aug 2024 15:22:04 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Another day where a bug was because the wrong id was being passed somewhere. This time it didn't make it onto production. I caught it due to a unit test I wrote. But it did take unnecessarily long to find out. </p><p>Fuelled by that distaste, comes today's post where I give you tips that I think improve the developer experience when working with ids. </p><p></p><p><strong>#1: prefix the entity type</strong></p><p>Ever had an id in front of you and you weren't necessarily sure which database table it belonged to? That won't happen anymore if you prefix them. If user ids start with &#8220;user_&#8221; and profile ids with &#8220;profile_&#8221; you&#8217;ll thank yourself over and over again in the future. </p><p></p><p><strong>#2: use a time-sorteable string</strong></p><p>It's not meaningful most of the time, but you can have a time-sorteable string without centralizarion or delegating it to the database. You can use ksuid or uuid v7. For any of them, if you sort the strings, you also get them time-ordered. </p><p></p><p><strong>#3: let the type-system help you</strong></p><p>If you are using a statically typed language, then you can have different types (classes, interfaces, etc) for different ids. Then your functions receive and return `UserId` and the compiler complains if you try to give it a `CatId`. It's not bulletproof but most of these errors happen at the layers with business logic and not the ones concerning serialization. </p><p>If the boilerplate of creating a bunch of types bothers you there's always the option of creating a single generic one, `EntityId&lt;T&gt;`. Then your functions use `EntityId&lt;User&gt;` or `EntityId&lt;Cat&gt;`. It achieves the same type-safety. </p><p></p><p>Obviously we&#8217;re not creating new systems all the time and migrations can be painful, but hopefully you have a chance of using one of these in the future and save yourself some precious debugging time. </p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The pitfall of Kotlin - Java interoperability]]></title><description><![CDATA[And why it exists]]></description><link>https://statelessmachine.com/p/the-pitfall-of-kotlin-java-interoperability</link><guid isPermaLink="false">https://statelessmachine.com/p/the-pitfall-of-kotlin-java-interoperability</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Thu, 25 Jul 2024 17:07:05 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It&#8217;s quite easy to trigger a NullPointerException in Kotlin. Let&#8217;s look at a small snippet:<br></p><pre><code>import java.util.concurrent.atomic.AtomicReference

func main() {
  val foo: AtomicReference&lt;Int&gt; = AtomicReference(42)
  foo.set(null) // it compiles!!!
  println("The answer is " + foo.get().toDouble())
}</code></pre><p>Credit to <a href="https://x.com/ramtop">Uberto Barbini</a> for <a href="https://x.com/ramtop/status/1816011337425936778">the code snippet and tweet illustrating this problem</a>.</p><p></p><p>So what&#8217;s happening here? Shouldn&#8217;t the Kotlin type system prevent this issue?</p><p>As soon as you interact with Java code, null restrictions don't have the same rules. We can see that if we wrap the Java class in Kotlin, we won&#8217;t have the same issue.</p><pre><code>import java.util.concurrent.atomic.AtomicReference

class MyAtomicReference&lt;T&gt;(initialValue: T) {
    private val javaAtomicRef = AtomicReference(initialValue)

    fun get(): T = javaAtomicRef.get()
    fun set(newValue: T) = javaAtomicRef.set(newValue)
}

val foo: MyAtomicReference&lt;Int&gt; = MyAtomicReference(42)

foo.set(null) // does not compile!!!
println("The answer is " + foo.get().toDouble())</code></pre><p>Why do we face this problem in the first place? In Java everything can be null, but why do we need that to bleed over? </p><p>The first problem is that Kotlin can&#8217;t know if a value coming from Java code can be null. If we assume that all of them can be null then you&#8217;d be forced to deal with that possibility on cases where it doesn&#8217;t happen and it&#8217;d be annoying.</p><p>Can we assume the opposite, that no values coming from Java are null? We can, because Kotlin won&#8217;t stop you from doing null-checks or anything else you would do to safeguard your code.</p><p></p><p>But do we really have to face this problem when a Java class / interface has a generic? Come on, there we get to specify if we want a `String` or a `String?`&#8230;</p><p>Unfortunately that doesn&#8217;t work for all cases.</p><p>Let&#8217;s go over a concrete Java interface I just made up:</p><pre><code>import java.util.function.Predicate

interface Filtered&lt;T&gt; {
    void set(T t); // T should never be null
    void filter(Predicate&lt;T&gt; f);
    T get() // T can be null
}</code></pre><p>If I instantiated that interface with `String?` as `T`, then I can pass null to the `set` function, which is a lie.</p><p>If I instantiated that interface with `String` as `T`, then we&#8217;re thinking that what is returned from `get` is never null, which is also a lie.</p><p></p><p>No way to be honest this time! Because we lack information to do so. </p><p>So the language designers stuck with the ones that give the user the most flexibility.</p><p></p><p>In any case make sure to browse the official Kotlin documentation. It tends to be pretty good.</p><p></p><p>And my final advice to you is:</p><ul><li><p>wrap Java code</p></li><li><p>make sure it&#8217;s properly tested</p></li></ul><p>Hopefully that allows avoiding those nasty NullPointerExceptions in production.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Caching integration test results]]></title><description><![CDATA[Can we?]]></description><link>https://statelessmachine.com/p/caching-integration-test-results</link><guid isPermaLink="false">https://statelessmachine.com/p/caching-integration-test-results</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Mon, 22 Jul 2024 16:33:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the things in software development that usually has a long feedback loop is integration testing. So many developers are pushing their code and then later check if the tests pass. What if we could cache the test results? Then, at least for most small changes, most tests wouldn&#8217;t run. We can only dream of the productivity and morale gains by pushing the code and seeing CI green within seconds. </p><p></p><p>As part of my building my own programming language, I&#8217;ve been thinking about what it would take to achieve this dream. The compiler can determine what code changed and what tests depend on that code, in order to invalidate the cache. The missing piece to make it safe for unit tests is to <a href="https://open.substack.com/pub/statelessmachine/p/forcefully-deterministic-unit-testing?r=2o3ap3&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">make them forcefully deterministic</a>. But we don&#8217;t have the same luxury for integration tests. If I use a database and upgrade the database version, the compiler of my programming language has no way of knowing that the tests related to that integration should run again. </p><p></p><p>The Go programming language suffers from this exact problem. There&#8217;s no (automatic; without programmer intervention/discipline) way to distinguish between tests that rely on outside state and ones that don&#8217;t. So, while there is test caching, most people I know professionally working with the language always run all of their tests. They don&#8217;t leverage the cache at all.</p><p></p><p>So what would it take to cache integration test results and not think it&#8217;s eroding software quality? As you might&#8217;ve guessed, I don&#8217;t have a silver bullet. But if we look at the individual problems, we might get somewhere. </p><p></p><p><strong>Problem 1: depending on external systems</strong></p><p>Your code might be exactly the same, but system you&#8217;re integrating with has changed. The example above of updating database version falls into this category. A similar problem, which is way more common and way more likely to break your tests, is changing your database schema. </p><p></p><p>How about we add those in a configuration file?</p><pre><code>[integration.dependencies]
postgres = "16.0"
latest_db_update_file_name = "321.up.sql"</code></pre><p>Then we could have specific tests depend on specific dependencies and the programming language test runner would check the version on the file against the version the tests used. We could have special CLI commands to set the version, to make it easy to use.</p><p></p><p>This dependency on configuration might require some discipline and/or CI work, but it might easy to use enough that it becomes enjoyable to use. </p><p></p><p>But what about when a vendor updates their software? Well, if a third party service you depend on, that you&#8217;re integrating through calls to a REST API, makes changes to their production system&#8230; Integration tests wouldn&#8217;t save you there. But we can always keep a date for the version and re-run all the relevant tests.</p><pre><code>[integration.dependencies]
last_known_stripe_api_update = "2024-05-03"</code></pre><p></p><p>Not perfect. But, perhaps, good enough?</p><p></p><p><strong>Problem 2: a test might be flaky</strong></p><p>This is a bit more tricky to have a holistic view of. Why is the test flaky? Is it because test order matters? Is it because two tests running concurrently interfere with each other? Is it because it waits for something with a timeout and the timeout is seldom enough?</p><p></p><p>I think we definitely need control over concurrency. Some integrations have flows that only work if a certain action is preceded by another action. We can only guarantee that if we&#8217;re not running other tests that might interfere with that. So, like the dependency versions, we need another sort of tag, that has a concurrency limit, and have the test runner know how to handle it.</p><pre><code><code>[integration.concurrency]
message_queue_push_and_pull = 1</code></code></pre><p></p><p>That covers the concurrency scenario, but much more can be happening. We don&#8217;t want to run all of the tests all of the time, but maybe we can add some randomness here and run some? We could add a couple of mechanisms, leaving their configuration to the language user:</p><ul><li><p>Pick 1/X tests and invalidate their cached result</p></li><li><p>Pick 1/X tests to be ran twice in the same test run</p></li></ul><pre><code>[integration.run]
randomly_invalidate = 2
randomly_run_twice_one_in = 100</code></pre><p>With this example configuration, on every test run:</p><ul><li><p>two tests would have their previously cached results invalidated (thus re-ran) </p></li><li><p>there&#8217;s a 1% chance that a test runs twice during a test run</p></li></ul><p>A bit of chaos helps everyone to sleep better at night.</p><p></p><p>When these mechanisms are actually in use then I&#8217;ll probably come across things that don&#8217;t feel as good in practice as one hoped and obviously iterate from there. As of today, this sounds good enough to me for an MVP. Tomorrow I might have changed my mind.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Forcefully deterministic unit testing]]></title><description><![CDATA[Programming language design]]></description><link>https://statelessmachine.com/p/forcefully-deterministic-unit-testing</link><guid isPermaLink="false">https://statelessmachine.com/p/forcefully-deterministic-unit-testing</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Thu, 18 Jul 2024 17:47:48 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;m working a general-purpose programming language on my free time. I&#8217;m writing about the design of unit tests. Any mentions of &#8220;test&#8221; in the article refer to unit tests. </p><p>We&#8217;ll consider a unit test any test which does not interact with anything outside of the program. Reading a file? Forbidden. Writing to the console? Not allowed. No changes can be observed by running it.</p><p></p><p>It&#8217;d be unproductive to talk about testing without providing examples. Since the ideas I want to talk about are not specific to my language, we can imagine I&#8217;m building a new Typescript runtime, for the sake of the examples. </p><p>Hello world!</p><pre><code>import { console } from "imaginary-standard-library";

function main() {
  console.log("Hello world!")
}</code></pre><p>The example above assumes a couple of rules are in place:</p><ul><li><p>There&#8217;s no implicitly available global variables. We need to import `console`.</p></li><li><p>When running a program, the runtime looks for the function named &#8220;main&#8221; and starts there.</p></li></ul><p></p><p>Because we love tidy code, we&#8217;re going to move the business logic of our application into a separate function.</p><pre><code>import { console } from "imaginary-standard-library";

function main() {
  greet()
}

function greet() {
  console.log("Hello world!")
}</code></pre><p></p><p>Now we want to create a test for our business logic. Let&#8217;s introduce more rules to our imaginary runtime:</p><ul><li><p>Tests are all functions whose name starts with &#8220;test_&#8221;.</p></li><li><p>Tests can be in any file. They can be in the same file as the functions being tested.</p></li></ul><p>That should be enough to add our test:</p><pre><code>import { console } from "imaginary-standard-library";

function main() {
  greet()
}

function greet() {
  console.log("Hello world!")
}

function test_greet() {
  greet()
}</code></pre><p>Our test doesn&#8217;t test anything, besides the fact that function doesn&#8217;t crash! &#128561;</p><p>Even if it was testable, we haven&#8217;t introduced assertions yet. Because we don&#8217;t want `assert` being used outside of test code, it&#8217;ll be introduced within a function parameter, which we&#8217;ll call testkit.</p><pre><code>import { console } from "imaginary-standard-library";
import { TestKit } from "imaginary-standard-library/test";

function main() {
  greet()
}

function greet() {
  console.log("Hello world!")
}

function test_greet(testkit: TestKit) {
  greet()
  testkit.assert.equals(true, true)
}</code></pre><p></p><p>Now let&#8217;s get back to making our code testable. In order to do so, we need to introduce some interface to abstract away the console. </p><pre><code>interface Console {
  log(input: string): void;
}</code></pre><p>Now we can make our function testable:</p><pre><code>import { console } from "imaginary-standard-library";
import { TestKit } from "imaginary-standard-library/test";

interface Console {
  log(input: string): void;
}

function main() {
  greet({ log: console.log })
}

function greet(console: Console) {
  console.log("Hello world!")
}

function test_greet(testkit: TestKit) {
  let fakeStdOut: String[] = []
  let fakeConsole = {
    log(input: String) {
      fakeStdOut.push(input)
    }
  }
  greet(fakeConsole)
  testkit.assert.equals(["Hello world!"], fakeStdOut)
}</code></pre><p>We&#8217;ve achieved our goal of having our function testable. All it took was having interactions with the outside world abstracted in an interface. This is a pattern that can be used in nearly any programming language.</p><p>The success of this is still hinging on your discipline, of not having any outside world interactions being directly used. But we could enforce that responsibility, if it&#8217;s up to the runtime to pass it as a function parameter to main. So we move all of the functionality to interact with the outside world into an OutsideWorld instance and remove the ability to import things like console directly.</p><pre><code>import { OutsideWorld, Console } from "imaginary-standard-library";
import { TestKit } from "imaginary-standard-library/test";

function main(outsideWorld: OutsideWorld) {
  greet(outsideWorld.console)
}

function greet(console: Console) {
  console.log("Hello world!")
}

function test_greet(testkit: TestKit) {
  let fakeStdOut: String[] = []
  let fakeConsole = {
    log(input: String) {
      fakeStdOut.push(input)
    }
  }
  greet(fakeConsole)
  testkit.assert.equals(["Hello world!"], fakeStdOut)
}</code></pre><p>Now that the only place where the OutsideWorld instance comes from is the main function argument, we can no longer pass it to tests. This means all tests no longer have the ability to do anything related to the outside world.  </p><p>As long as the programming language author keeps all of the functions that can interact with the outside world in the OutsideWorld instance, no discipline is required from the programmer anymore.</p><p>Until concurrency primitives are introduced, the tests are deterministic. Forcefully so. By design. That means I can reliably cache the test results and users of my language only run the relevant tests when code changes.</p><p></p><p>As a side-note, it would be annoying to write the fake instances all the time. I provide a fake OutsideWorld instance in the tests alongside other functions to make most things easy to test.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://statelessmachine.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Stateless Machine! Subscribe to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[A good boss is the hardest to find]]></title><description><![CDATA[Have you noticed how it&#8217;s harder to have a good relationship with your boss than it is to have a good relationship with your colleagues?]]></description><link>https://statelessmachine.com/p/a-good-boss-is-the-hardest-to-find</link><guid isPermaLink="false">https://statelessmachine.com/p/a-good-boss-is-the-hardest-to-find</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Thu, 11 Jul 2024 06:01:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you noticed how it&#8217;s harder to have a good relationship with your boss than it is to have a good relationship with your colleagues? I think there&#8217;s a few things at play here.</p><p></p><p><strong>You have a few (or a lot of) colleagues, but only one boss.</strong></p><p>You&#8217;ll keep a range of relationships. Some of them will be good. You can keep the bad ones at bay by leveraging group dynamics.</p><p></p><p><strong>The relationship with your peers feels more quid-pro-quo.</strong></p><p>Today I have your back, tomorrow you have my back. </p><p></p><p><strong>Your boss has no incentive to be a great boss.</strong></p><p>When you&#8217;re frustrated that your boss is bad, you most likely are not putting yourself enough in their shoes. From their position, how should they behave?</p><p>What are they evaluated / promoted on? Certainly not your good relationship.</p><p>They don&#8217;t need to have your back. You need to not be a problem for them.</p><p>They don&#8217;t have resources to tangibly reward your good performance.</p><p></p><p>If you would replace your boss with a robot that acts purely on incentives, do you think the robot would be a good boss? Probably not. Then, why do you expect your boss to be different? Human behaviour is heavily influenced by incentives.</p><p></p><p>So you either have to be lucky to be in an organization that has wildly different incentives or your boss has to go against their incentives to be stellar, from your point of view. It&#8217;s a rare find.</p>]]></content:encoded></item><item><title><![CDATA[ORMs do not ORM]]></title><description><![CDATA[The discourse of whether using an ORM is good is making the rounds again on Twitter.]]></description><link>https://statelessmachine.com/p/orms-do-not-orm</link><guid isPermaLink="false">https://statelessmachine.com/p/orms-do-not-orm</guid><dc:creator><![CDATA[Hugo Sousa]]></dc:creator><pubDate>Wed, 10 Jul 2024 10:09:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hcmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d395e47-34d4-45b2-8135-d0f17c48f68f_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The discourse of whether using an ORM is good is making the rounds again on Twitter. Let me introduce our contestants:</p><p>&#8220;You don't need an ORM. Just write SQL.&#8221;</p><p>VS</p><p>&#8220;You still need the code that goes around the SQL. You&#8217;re just building your own ORM.&#8221;</p><p></p><p>I think the debate comes from people not having the same mental model of what an ORM is. ORM stands for &#8220;Object Relational Mapper&#8221;. </p><p>Let&#8217;s look at a snippet in a made-up language:</p><p><code>class Person(id: Int, name: String)</code></p><p><code>let person = new Person(-1,&#8220;Mr Reader&#8221;)</code></p><p><code>person.save()</code></p><p></p><p>Now let's compare that to another snippet, writing SQL in the same made-up language:</p><p><code>sql.query(&#8220;INSERT INTO person (name) VALUES ($)").args(&#8220;Mr Reader&#8221;).run()</code></p><p></p><p>You can think of the two snippets as similar. They are definitely in the same category, in the sense that they are addressing the same problem. </p><p></p><p>If we come back to the &#8220;Object Relational Mapping&#8221;, none of them do it. A &#8220;mapping&#8221; would imply a conversion between the two worlds and you have both sides of it. And none of the two snippets do that, do they? </p><p></p><p>In the ORM snippet you don't have the sql side of things. What's the query that runs under the hood? When is it run? Are queries running in the order I write them? I wish this last question was a joke, but I have worked with an ORM that reordered queries sometimes if you didn't <code>forceFlush</code>. So it tries to hide the database from you. </p><p></p><p>In the SQL snippet there's no mapping. It's up to you to take the values and put them onto the SQL. If you add a new field to your class, you won't have the compiler telling you you&#8217;re missing an argument to your query. You don't have more type-safety than in the other snippet. </p><p></p><p>So next time you engage in a debate or ORM vs no ORM, you can now be aware of what the other side is valuing. </p><p></p><p>A middle ground are query builders, that just provide a DSL for building SQL queries and code to help do the mapping. Some of them make people from both sides less annoyed and they can finally work together to bring world peace. </p>]]></content:encoded></item></channel></rss>